codehere 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +36 -192
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
package/README.md CHANGED
@@ -1,223 +1,67 @@
1
1
  # codehere
2
2
 
3
- > **One owned, signed record of what your AI coding agents did, and what they cost.**
4
- > For a team running more than one coding agent: an independent, cross-vendor record on your own machine. One `npm install`, no account.
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-1371%2F1371-brightgreen)]()
9
- [![Honest status](https://img.shields.io/badge/honest--status-PRODUCT__STATE-blue)](../docs/PRODUCT_STATE.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 what 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** — 1371 internal tests verify codehere's code against codehere's own assumptions about what each adapter emits, plus an end-to-end suite (`npm run e2e`) that drives the real product in a browser. The current verified / planned / blocked state of every surface lives in [`docs/PRODUCT_STATE.md`](../docs/PRODUCT_STATE.md) and the capability scoreboard [`docs/CAPABILITIES.md`](../docs/CAPABILITIES.md).
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
- 5 vendors supported, 2 proven on a real billed run. Your record, your machine, nobody's vendor.
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
-
29
- ### Setup (once)
30
-
31
- | | | |
32
- |---|---|---|
33
- | 1 | **Install** | `npm install -g codehere` (Node 20+) — v0.8.0 is the current published stable (`@latest`) |
34
- | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
35
- | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
36
- | 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) |
37
- | 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) |
38
- | 6 | **Fill in AGENTS.md** | 5 minutes. The agent will follow what's written there. |
39
- | 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. |
40
-
41
- You're done. `codehere start` opens the web UI at `:3773`; you delegate from there.
42
-
43
- ### The prompt formula
10
+ ## Install
44
11
 
45
12
  ```
46
- Context + End State + Constraints
13
+ npx codehere check
47
14
  ```
48
15
 
49
- - **Context** — *"Read .codehere/CONTEXT.md"* + any specific files
50
- - **End State** — the user-visible change, the test that passes, the file that exists
51
- - **Constraints** — files not to touch, dependencies not to add, patterns not to use
52
-
53
- ### Folder convention (what `codehere init` writes)
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`.
54
17
 
55
- The v0.6 layout — F3 project memory:
18
+ ## The four commands
56
19
 
57
- | Path | Purpose | Committed? |
58
- |---|---|---|
59
- | `.codehere/AGENTS.md` *(or legacy `CONTEXT.md`)* | What this repo is. Conventions. Style. Constraints. | yes |
60
- | `.codehere/memory/working/session.md` *(or legacy `HANDOVER.md`)* | What's in flight in the current session. | **no** |
61
- | `.codehere/memory/episodic/AGENT_LEARNINGS.jsonl` | Symlink to your full session history at `~/.codehere/sessions.jsonl`. | (symlink) |
62
- | `.codehere/memory/episodic/repo_map.json` | Ranked symbol map. Built by `codehere memory repo-map`. | yes |
63
- | `.codehere/memory/semantic/lessons.jsonl` | Project-specific lessons promoted from episodic patterns. | yes |
64
- | `.codehere/memory/personal/preferences.json` | Per-developer prefs. | **no** |
65
- | `.codehere/skills/<name>/SKILL.md` *(or legacy flat `PROMPTS.md`)* | Prompt templates — one folder per skill. | yes |
66
- | `.codehere/protocols/permissions.md` + `hook_patterns.json` | Safety policy for the PreToolUse hook. | yes |
67
-
68
- 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.
69
-
70
- ---
71
-
72
- ## What you see when you delegate a task
73
-
74
- `codehere start` opens `http://127.0.0.1:3773`. Pick an agent, type a task, hit **Assign**. codehere:
75
-
76
- 1. Spawns the agent with the right headless flags
77
- 2. Streams the steps back as readable progress · *"Read src/auth.ts · Edit src/auth.ts · Run npm test"*
78
- 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)
79
- 4. On completion, hands you a **Final report**: files changed with `+/-` lines, trust score per file, test pass/fail, token usage
80
-
81
- 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+.
82
-
83
- ### Just the safety hook (no agents required)
84
-
85
- If you only want the audit-and-block layer for AI tools you already use:
86
-
87
- ```bash
88
- codehere hook install # activates the PreToolUse hook in your global ~/.claude/settings.json
89
- codehere trust . # static trust scan of the current directory
90
- codehere report # generates a shareable markdown audit report
91
- codehere status # quick dashboard · sessions, blocks, trust trends
92
20
  ```
93
-
94
- 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.
95
-
96
- ## Memory · F3 (v0.6)
97
-
98
- 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.
99
-
100
- **What gets injected, in priority order:**
101
- 1. **`.codehere/AGENTS.md`** — your project briefing (conventions, style, constraints)
102
- 2. **`.codehere/memory/working/session.md`** — what's in flight right now (per-machine, gitignored)
103
- 3. **`.codehere/memory/semantic/lessons.jsonl`** — graduated project-wide lessons
104
- 4. **`.codehere/memory/episodic/repo_map.json`** — top-N most-referenced symbols (run `codehere memory repo-map` to build / refresh)
105
-
106
- 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.
107
-
108
- **Inspect what would inject** before spawning anything:
109
- ```bash
110
- codehere memory show injection
111
- ```
112
-
113
- **Add a lesson** so future spawns learn from it:
114
- ```bash
115
- 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
116
25
  ```
117
26
 
118
- **See everything codehere has accumulated:**
119
- ```bash
120
- codehere memory show # one-line summary per layer
121
- codehere memory show semantic # numbered lesson list
122
- codehere memory show episodic # session count + top symbols
123
- codehere memory show protocols # safety patterns by category
124
- ```
125
-
126
- **Other memory commands:**
127
- - `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)
128
- - `codehere memory repo-map [--rebuild]` — build the symbol map; currently TS-native MVP
129
- - `codehere protocols export [--rebuild]` — write the trust-scorer pattern bank to `.codehere/protocols/hook_patterns.json` for inspection / cross-tool ingestion
130
-
131
- ## Going deeper
132
-
133
- For technical readers — the living documents that describe codehere as a system, not just a CLI:
134
-
135
- | Doc | What it is |
136
- |---|---|
137
- | [`docs/BLUEPRINT.md`](../docs/BLUEPRINT.md) | The HOW — system overview, module map, data flow, adapter contracts, audit chain, failover, F3 memory, cost-bounded execution |
138
- | [`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 |
139
- | [`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 |
140
- | [`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 |
27
+ `codehere help check` lists the options. `--json` adds a `verdict` (`pass`, `fail`, `unverified` or `skipped`) and the receipt text.
141
28
 
142
- ## Commands
29
+ Exit codes:
143
30
 
144
- ```
145
- codehere start · launch the task intake web UI at :3773
146
- codehere task <prompt> · spawn a task from the CLI (requires `start` running)
147
- codehere kill <sessionId> · kill a running task
148
- codehere serve · start as MCP server for Claude Desktop, Cursor, etc.
149
- codehere status · dashboard · sessions, blocked calls, audit trends
150
- codehere trust [path] · static trust scan · score 0-100, grade A-F
151
- codehere report · generate a shareable markdown audit report
152
- codehere hook · install/uninstall/inspect the PreToolUse safety hook
153
- codehere permission · manage this folder's allow-list (skip false-positive blocks)
154
- codehere budget · daily-spend ceiling · `set <usd>` blocks paid spawns at the cap
155
- codehere narrator · customize the PM narrator prompt (~/.codehere/narrator-prompt.md)
156
- codehere setup · interactive first-run setup wizard
157
- codehere config · manage configuration and preferences
158
- codehere health · system health check
159
- codehere help · show help (use --all for hidden commands)
160
- codehere completion <shell> · generate bash/zsh/fish completion script
161
- codehere scan-tool · internal · used by the hook system, not by users
162
- ```
163
-
164
- `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.
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).
165
34
 
166
- ## What it catches
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.
167
36
 
168
- The trust scorer + safety hook detect ~36 patterns across:
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.
169
38
 
170
- - SQL injection · XSS · command injection · path traversal
171
- - Hardcoded secrets · API keys · weak crypto · JWT bypass
172
- - Eval injection · prompt injection · credential theft
173
- - Destructive shell operations · supply-chain patterns
39
+ ## GitHub Action
174
40
 
175
- 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`.
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.
176
42
 
177
- ## MCP server
43
+ ## What it does not catch
178
44
 
179
- ```bash
180
- codehere serve
181
- ```
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`.
182
49
 
183
- Exposes 6 read-only tools to any MCP client (Claude Desktop, Cursor, Zed, Continue, Cline, etc.):
50
+ ## What has been measured
184
51
 
185
- - `codehere_trust` · score a file or code snippet
186
- - `codehere_scan` · scan text for injection / threat patterns
187
- - `codehere_review` · scan a directory and return riskiest files
188
- - `codehere_audit` · read the local hook audit log
189
- - `codehere_spend` · today's estimated spend by agent and project, vs your budget
190
- - `codehere_team` · the team's merged cost and record across machines
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).
191
54
 
192
- Connect it to **Claude Code** in one command (the daemon must be running):
55
+ ## Development
193
56
 
194
57
  ```
195
- claude mcp add --transport http codehere http://127.0.0.1:3773/mcp
58
+ cd agent
59
+ npm ci
60
+ npm test # build, then vitest
61
+ npm run lint
62
+ node eval/false-done.mjs
196
63
  ```
197
64
 
198
- Then from inside a Claude Code session you can ask things like "what did we spend today?" or "what did each agent do on this repo?" and it answers inline, no switching to the browser. Remove with `claude mcp remove codehere`.
199
-
200
- `codehere serve --show-config` prints the JSON block to paste into Claude Desktop's MCP settings.
201
-
202
- ## Architecture, story, and contribution
203
-
204
- For depth beyond this README:
205
-
206
- - **[docs/STRATEGIC_VISION.md](../docs/STRATEGIC_VISION.md)** · 20-year invariants + v4 positioning (individual-tier vendor-neutral) · figures in `docs/figures/`
207
- - **[docs/PRD.md](../docs/PRD.md)** · master decision document · features, architecture, roadmap
208
- - **[docs/THREAT_MODEL.md](../docs/THREAT_MODEL.md)** · security posture · OWASP Top 10 + STRIDE · trust boundaries · hook-bypass analysis
209
- - **[docs/STRATEGY.md](../docs/STRATEGY.md)** · current strategy · competitive analysis (Conductor / Whim / Hermes), positioning, the wedge
210
- - **[CONTRIBUTING.md](../CONTRIBUTING.md)** · how to contribute · setup, commit discipline, testing philosophy
211
- - **[SECURITY.md](../SECURITY.md)** · responsible disclosure · `security@codehere.uk`
212
- - **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** · community standards
213
- - **[CHANGELOG.md](../CHANGELOG.md)** · per-version release notes
214
-
215
- ## Requirements
216
-
217
- - **Node.js 20+** (pinned in `.nvmrc` and `engines.node`)
218
- - **macOS or Linux** · Windows via WSL works for development
219
- - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, OpenCode, or Gemini) installed before running `codehere start`
220
-
221
65
  ## License
222
66
 
223
- 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