codehere 0.8.0 → 1.0.1

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 (134) hide show
  1. package/README.md +22 -13
  2. package/dist/application/services/verdict-service.js +154 -0
  3. package/dist/application/use-cases/assess-cross-vendor.js +126 -0
  4. package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
  5. package/dist/application/use-cases/export-aat.js +107 -0
  6. package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
  7. package/dist/application/use-cases/generate-final-report.js +140 -15
  8. package/dist/application/use-cases/generate-narration.js +5 -3
  9. package/dist/application/use-cases/generate-thread-title.js +5 -2
  10. package/dist/application/use-cases/plan-auto-review.js +57 -0
  11. package/dist/application/use-cases/reconcile-audit.js +100 -0
  12. package/dist/application/use-cases/rerun-tests.js +226 -0
  13. package/dist/application/use-cases/search-sessions.js +70 -0
  14. package/dist/application/use-cases/should-auto-review.js +71 -0
  15. package/dist/application/use-cases/thread-export.js +90 -0
  16. package/dist/application/use-cases/verify-claim.js +187 -5
  17. package/dist/domain/audit.js +11 -0
  18. package/dist/domain/services/codebase-detector.js +0 -6
  19. package/dist/domain/task.js +91 -0
  20. package/dist/error-handler.js +3 -115
  21. package/dist/index.js +269 -25
  22. package/dist/infrastructure/completion/bash-completion.js +7 -3
  23. package/dist/infrastructure/completion/fish-completion.js +7 -12
  24. package/dist/infrastructure/completion/zsh-completion.js +7 -12
  25. package/dist/infrastructure/config/config-manager.js +4 -3
  26. package/dist/infrastructure/config/product-context.js +1 -19
  27. package/dist/infrastructure/context/adapters/figma-source.js +37 -2
  28. package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
  29. package/dist/infrastructure/context/github-api.js +110 -0
  30. package/dist/infrastructure/context/github-device-flow.js +65 -0
  31. package/dist/infrastructure/context/oauth-store.js +5 -2
  32. package/dist/infrastructure/context/registry-bootstrap.js +0 -4
  33. package/dist/infrastructure/cost/budget-config.js +3 -2
  34. package/dist/infrastructure/cost/cost-router.js +16 -23
  35. package/dist/infrastructure/cost/spend-aggregator.js +90 -15
  36. package/dist/infrastructure/cost/team-ledger.js +355 -0
  37. package/dist/infrastructure/discovery/discovery-log.js +85 -0
  38. package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
  39. package/dist/infrastructure/hooks/claude-sdk-hook.js +133 -3
  40. package/dist/infrastructure/hooks/hook-mode.js +20 -4
  41. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
  42. package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
  43. package/dist/infrastructure/observability/execution-tracer.js +1 -1
  44. package/dist/infrastructure/orchestration/agents-config-seed.js +22 -10
  45. package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
  46. package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
  47. package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
  48. package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
  49. package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
  50. package/dist/infrastructure/orchestration/context-handoff.js +32 -14
  51. package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
  52. package/dist/infrastructure/orchestration/failover-config.js +0 -10
  53. package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
  54. package/dist/infrastructure/orchestration/generic-cli-adapter.js +132 -1
  55. package/dist/infrastructure/orchestration/graphify-context.js +14 -59
  56. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +164 -9
  57. package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
  58. package/dist/infrastructure/orchestration/progress-aggregator.js +238 -21
  59. package/dist/infrastructure/orchestration/session-manager.js +593 -94
  60. package/dist/infrastructure/orchestration/session-persistence.js +177 -41
  61. package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
  62. package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
  63. package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
  64. package/dist/infrastructure/protocols/mcp-server-impl.js +1462 -380
  65. package/dist/infrastructure/protocols/mcp-tools.js +401 -0
  66. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
  67. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
  68. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
  69. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
  70. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
  71. package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
  72. package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
  73. package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
  74. package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
  75. package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
  76. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
  77. package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
  78. package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
  79. package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
  80. package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
  81. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
  82. package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
  83. package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
  84. package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
  85. package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
  86. package/dist/infrastructure/protocols/ui/styles.js +2147 -0
  87. package/dist/infrastructure/protocols/ui/tokens.js +96 -0
  88. package/dist/infrastructure/protocols/ui-page-v2.js +116 -4214
  89. package/dist/infrastructure/protocols/verify-page.js +849 -155
  90. package/dist/infrastructure/security/fix-code.js +144 -0
  91. package/dist/infrastructure/security/fix-secret.js +89 -0
  92. package/dist/infrastructure/security/permissions-config.js +3 -2
  93. package/dist/infrastructure/security/scan-types.js +10 -0
  94. package/dist/infrastructure/security/ship-scan.js +195 -0
  95. package/dist/infrastructure/security/trust-scorer.js +70 -3
  96. package/dist/infrastructure/security/vibe-checks.js +99 -0
  97. package/dist/infrastructure/storage/atomic-write.js +86 -0
  98. package/dist/infrastructure/storage/project-thread-mirror.js +150 -3
  99. package/dist/infrastructure/storage/project-thread-reader.js +261 -24
  100. package/dist/monitoring.js +2 -2
  101. package/dist/presentation/cli/commands/audit-command.js +79 -4
  102. package/dist/presentation/cli/commands/budget-command.js +2 -2
  103. package/dist/presentation/cli/commands/check-command.js +96 -0
  104. package/dist/presentation/cli/commands/context-source-command.js +20 -1
  105. package/dist/presentation/cli/commands/help-command.js +5 -5
  106. package/dist/presentation/cli/commands/hook-command.js +5 -5
  107. package/dist/presentation/cli/commands/init-command.js +26 -14
  108. package/dist/presentation/cli/commands/memory-command.js +28 -28
  109. package/dist/presentation/cli/commands/permission-command.js +1 -12
  110. package/dist/presentation/cli/commands/protocols-builder.js +2 -2
  111. package/dist/presentation/cli/commands/protocols-command.js +4 -4
  112. package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
  113. package/dist/presentation/cli/commands/report-command.js +48 -32
  114. package/dist/presentation/cli/commands/scan-command.js +52 -0
  115. package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
  116. package/dist/presentation/cli/commands/search-command.js +51 -0
  117. package/dist/presentation/cli/commands/setup-command.js +31 -8
  118. package/dist/presentation/cli/commands/status-command.js +39 -86
  119. package/dist/presentation/cli/commands/team-command.js +256 -0
  120. package/dist/presentation/cli/commands/thread-command.js +87 -5
  121. package/dist/presentation/cli/commands/trust-command.js +53 -31
  122. package/dist/presentation/cli/commands/verify-command.js +74 -9
  123. package/dist/presentation/cli/error-display.js +0 -8
  124. package/dist/presentation/cli/repl.js +15 -7
  125. package/dist/presentation/cli/task-deliverable.js +36 -10
  126. package/dist/testing/replay-adapter.js +0 -19
  127. package/dist/ui.js +51 -239
  128. package/dist/utils/version.js +0 -6
  129. package/package.json +30 -10
  130. package/dist/domain/entities/task.js +0 -61
  131. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  132. package/dist/infrastructure/security/input-validator.js +0 -152
  133. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  134. package/dist/infrastructure/storage/task-repository.js +0 -206
package/README.md CHANGED
@@ -1,37 +1,36 @@
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
+ > **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.
5
5
 
6
6
  [![npm](https://img.shields.io/npm/v/codehere)](https://www.npmjs.com/package/codehere)
7
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)
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)
10
10
 
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.
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.
12
12
 
13
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
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.
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
16
 
17
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
18
 
19
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
20
 
21
- One layer, every agent, your work stays portable.
21
+ 5 vendors supported, 2 proven on a real billed run. Your record, your machine, nobody's vendor.
22
22
 
23
23
  ---
24
24
 
25
25
  ## Recipe — setup to first audit in 30 minutes
26
26
 
27
27
  > One folder convention, one prompt formula, four ready-to-use prompts.
28
- > Full version: **[docs/RECIPE.md](../docs/RECIPE.md)**.
29
28
 
30
29
  ### Setup (once)
31
30
 
32
31
  | | | |
33
32
  |---|---|---|
34
- | 1 | **Install** | `npm install -g codehere` (Node 20+) — v0.6.0 is the current stable |
33
+ | 1 | **Install** | `npm install -g codehere` (Node 20+) — v0.8.0 is the current published stable (`@latest`) |
35
34
  | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
36
35
  | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
37
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) |
@@ -135,7 +134,7 @@ For technical readers — the living documents that describe codehere as a syste
135
134
 
136
135
  | Doc | What it is |
137
136
  |---|---|
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 |
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 |
139
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 |
140
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 |
141
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 |
@@ -181,12 +180,22 @@ Every blocked call is logged with the reason; every allowed call is logged with
181
180
  codehere serve
182
181
  ```
183
182
 
184
- Exposes 4 read-only tools to any MCP client (Claude Desktop, Cursor, Zed, Continue, Cline, etc.):
183
+ Exposes 6 read-only tools to any MCP client (Claude Desktop, Cursor, Zed, Continue, Cline, etc.):
185
184
 
186
185
  - `codehere_trust` · score a file or code snippet
187
186
  - `codehere_scan` · scan text for injection / threat patterns
188
187
  - `codehere_review` · scan a directory and return riskiest files
189
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
191
+
192
+ Connect it to **Claude Code** in one command (the daemon must be running):
193
+
194
+ ```
195
+ claude mcp add --transport http codehere http://127.0.0.1:3773/mcp
196
+ ```
197
+
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`.
190
199
 
191
200
  `codehere serve --show-config` prints the JSON block to paste into Claude Desktop's MCP settings.
192
201
 
@@ -196,8 +205,8 @@ For depth beyond this README:
196
205
 
197
206
  - **[docs/STRATEGIC_VISION.md](../docs/STRATEGIC_VISION.md)** · 20-year invariants + v4 positioning (individual-tier vendor-neutral) · figures in `docs/figures/`
198
207
  - **[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
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
201
210
  - **[CONTRIBUTING.md](../CONTRIBUTING.md)** · how to contribute · setup, commit discipline, testing philosophy
202
211
  - **[SECURITY.md](../SECURITY.md)** · responsible disclosure · `security@codehere.uk`
203
212
  - **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** · community standards
@@ -0,0 +1,154 @@
1
+ /**
2
+ * VerdictService — the single owner of how a session's honesty verdict is
3
+ * ASSESSED from its parts (R11 / ARCHITECTURE.md M2).
4
+ *
5
+ * Before this, every surface re-derived the verdict inputs inline: the HTTP
6
+ * `/api/tasks` route did it twice (spawned + persisted records) and the
7
+ * `codehere verify` CLI did it a third time, each independently mapping a session
8
+ * to `verifyClaim({ summary, artifacts, steps })`. A change to what feeds the
9
+ * verdict meant editing 3+ sites in 2 layers — the "verdict has no owner" finding.
10
+ *
11
+ * This is the one entry point. It COMPOSES the pure use-cases (today `verifyClaim`;
12
+ * `reconcileAudit`, composed into the single `SessionVerdict`) — it does
13
+ * NOT re-implement their logic. Surfaces render what this returns; they never
14
+ * recompute a verdict rule.
15
+ */
16
+ import { verifyClaim } from '../use-cases/verify-claim.js';
17
+ /**
18
+ * Assess a session's claim-vs-reality verdict. The single owner of the
19
+ * session → `verifyClaim` input mapping. Pure (delegates to the pure use-case).
20
+ */
21
+ export function assessSession(session) {
22
+ return verifyClaim({
23
+ summary: session.finalReport?.summary,
24
+ artifacts: session.artifacts,
25
+ steps: session.steps,
26
+ });
27
+ }
28
+ /** Compose the single verdict object every surface consumes. */
29
+ export function composeVerdict(session, audit, crossVendor) {
30
+ const verdict = {
31
+ claim: assessSession(session),
32
+ ...(audit !== undefined ? { audit } : {}),
33
+ ...(crossVendor !== undefined ? { crossVendor } : {}),
34
+ };
35
+ // The one owner attaches the grade so no surface re-derives "is this green".
36
+ verdict.grade = gradeComposite(verdict);
37
+ return verdict;
38
+ }
39
+ /**
40
+ * The one canonical verdict vocabulary. Every surface (web app + CLI) reads its
41
+ * verdict word from here, so the same state reads the same word everywhere
42
+ * instead of each surface re-deriving its own ("Proven" vs "Verified" vs
43
+ * "verified" for the flagship cross-vendor case was a real three-way split).
44
+ * Wording locked with Ega 2026-06-23, refined 2026-07-17: a green composite grade
45
+ * reads "Verified" on EVERY surface — a single other-vendor review CORROBORATES,
46
+ * it doesn't prove, so the seal expands it as "Corroborated by <vendor>" and no
47
+ * headline says "Proven" for a lone reviewer. "Proven" stays in the vocab
48
+ * (reserved) but is not rendered today. "Caught" = a contradicted claim.
49
+ *
50
+ * The first seven are the canonical headline words. `partial` is a residual the
51
+ * web still emits for two narrow "couldn't fully verify" branches (not-a-git-repo
52
+ * and the catch-all); it is kept here so nothing is silently dropped, pending a
53
+ * decision to fold it into `unverified`.
54
+ */
55
+ export const VERDICT_WORDS = {
56
+ proven: 'Proven',
57
+ verified: 'Verified',
58
+ reported: 'Reported',
59
+ stopped: 'Stopped',
60
+ caught: 'Caught',
61
+ unverified: 'Unverified',
62
+ practice: 'Practice',
63
+ partial: 'Partial',
64
+ };
65
+ /**
66
+ * The green-verdict corroboration clause, scoped to the ACTUAL independent
67
+ * signal(s) that earned the green. A LONE cross-vendor pass is one reviewer's
68
+ * corroboration — "corroborated by one other vendor" — never "independently
69
+ * corroborated", which would overstate a single reviewer. Disk-observed writes
70
+ * and multi-signal greens keep the "independently corroborated" wording. The
71
+ * web mirrors this inline (app-script `indepClause`) so both surfaces name the
72
+ * same signal for the same state.
73
+ */
74
+ export function corroborationClause(grade) {
75
+ const pass = grade.signals.filter((s) => s.provenance !== 'self-reported' && s.state === 'pass');
76
+ if (pass.length === 1 && pass[0].provenance === 'adversarial')
77
+ return 'corroborated by one other vendor.';
78
+ const hasDisk = pass.some((s) => s.provenance === 'observed');
79
+ return hasDisk ? 'independently corroborated · file writes confirmed on disk.' : 'independently corroborated.';
80
+ }
81
+ /**
82
+ * Grade a composed `SessionVerdict` into the headline the surface may show.
83
+ * Pure — no I/O. The single owner of "what is the ✓ allowed to say."
84
+ */
85
+ export function gradeComposite(verdict) {
86
+ const signals = [];
87
+ // 1 · self-reported — the claim vs its own artifacts.
88
+ const cs = verdict.claim.status;
89
+ const claimState = cs === 'verified' ? 'pass' : cs === 'broken' ? 'fail' : cs === 'no-report' ? 'absent' : 'neutral';
90
+ signals.push({
91
+ name: 'self-report',
92
+ provenance: 'self-reported',
93
+ state: claimState,
94
+ detail: `the agent's report ${cs === 'verified' ? 'matches' : cs === 'broken' ? 'contradicts' : 'makes no checkable claim against'} its own recorded work`,
95
+ });
96
+ // 2 · observed — the audit's declared writes vs the real git diff.
97
+ const av = verdict.audit?.verdict;
98
+ if (av !== undefined) {
99
+ const auditState = av === 'consistent' ? 'pass' : 'neutral'; // 'unconfirmed' is honest under-count, never a fail
100
+ signals.push({
101
+ name: 'audit-on-disk',
102
+ provenance: 'observed',
103
+ state: auditState,
104
+ detail: av === 'consistent'
105
+ ? 'declared file writes confirmed on disk'
106
+ : av === 'no-writes'
107
+ ? 'no file writes to confirm'
108
+ : av === 'unconfirmed'
109
+ ? "couldn't confirm every declared write"
110
+ : 'on-disk effect unobservable',
111
+ });
112
+ }
113
+ // 3 · adversarial — a different vendor independently reviewed it.
114
+ const cv = verdict.crossVendor;
115
+ if (cv !== undefined) {
116
+ const cvState = cv.proven ? 'pass' : 'neutral'; // not-proven ⇒ shown, never a fail (codehere doesn't adjudicate)
117
+ signals.push({
118
+ name: 'cross-vendor',
119
+ provenance: 'adversarial',
120
+ state: cvState,
121
+ detail: cv.proven
122
+ ? `${cv.reviewer.agent} independently reviewed ${cv.writer.agent}'s work, integrity-checked`
123
+ : cv.crossVendor
124
+ ? `cross-vendor review ran but isn't a clean proof — ${cv.caveats[0] ?? 'see caveats'}`
125
+ : 'not a cross-vendor review',
126
+ });
127
+ }
128
+ const independentPass = signals.some((s) => s.provenance !== 'self-reported' && s.state === 'pass');
129
+ const selfFailed = claimState === 'fail';
130
+ let headline;
131
+ if (cs === 'no-report')
132
+ headline = 'no-report';
133
+ else if (selfFailed)
134
+ headline = 'broken';
135
+ else if (cs === 'nothing-to-verify' && !independentPass)
136
+ headline = 'nothing-to-verify';
137
+ else if (independentPass)
138
+ headline = 'proven';
139
+ else
140
+ headline = 'reported';
141
+ const green = headline === 'proven';
142
+ const clause = corroborationClause({ signals });
143
+ const summary = headline === 'proven'
144
+ ? clause.charAt(0).toUpperCase() + clause.slice(1)
145
+ : headline === 'broken'
146
+ ? 'The report contradicts its own recorded work.'
147
+ : headline === 'reported'
148
+ ? 'Self-reported only — no independent signal has corroborated it yet.'
149
+ : headline === 'nothing-to-verify'
150
+ ? 'No checkable claim to verify.'
151
+ : 'No report to grade.';
152
+ return { headline, green, signals, summary };
153
+ }
154
+ //# sourceMappingURL=verdict-service.js.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Cross-vendor verdict — the receipt a model lab structurally cannot sell.
3
+ *
4
+ * When one vendor wrote code and a DIFFERENT vendor reviewed it in the same
5
+ * room, this produces the proof-of-process: who wrote, who reviewed, what the
6
+ * audit chain attributes to each, and the combined cost. It is the output that
7
+ * turns the wedge from "two sessions happened to share a room" into a stated,
8
+ * checkable claim.
9
+ *
10
+ * HONESTY (the trap the wedge test already flagged, refuter-pinned):
11
+ * codehere does NOT adjudicate whether the reviewer found a REAL bug — it can't
12
+ * judge code correctness, and a generic "found a flaw" tripwire would read green
13
+ * on a review that found nothing. So this proves the PROCESS (a different vendor
14
+ * independently reviewed and recorded findings, integrity-checked) and surfaces
15
+ * the reviewer's findings verbatim for the human to judge. `proven` is true ONLY
16
+ * when it really is a cross-vendor review, the chain verifies, both vendors are
17
+ * attributed in it, and the review actually produced output. Anything short of
18
+ * that is stated plainly, never laundered into a green check.
19
+ *
20
+ * Wording stays "integrity-checked", never "tamper-evident" (domain/audit.ts:
21
+ * the unkeyed SHA-256 chain detects naive edits, not a determined re-walk).
22
+ */
23
+ import { reconcileAudit } from './reconcile-audit.js';
24
+ import { estimateCostUsd } from '../../infrastructure/cost/spend-aggregator.js';
25
+ /** Read-ish tool names across adapters — used to report "reviewer read N files".
26
+ * Conservative: only names that are unambiguously a read. A reviewer SHOULD
27
+ * have only reads; any write is surfaced as a caveat, not hidden. */
28
+ const READ_TOOLS = new Set(['read', 'glob', 'grep', 'ls', 'cat', 'view', 'search_file', 'list_files'].map((t) => t.toLowerCase()));
29
+ /** Same WRITE_TOOLS spirit as reconcile-audit, kept local + minimal: we only
30
+ * need "did the reviewer mutate anything" (it shouldn't). */
31
+ const WRITE_TOOLS = new Set([
32
+ 'write',
33
+ 'edit',
34
+ 'multiedit',
35
+ 'notebookedit',
36
+ 'delete',
37
+ 'write_file',
38
+ 'edit_file',
39
+ 'create_file',
40
+ 'str_replace',
41
+ 'apply_patch',
42
+ ].map((t) => t.toLowerCase()));
43
+ function countTools(entries, set) {
44
+ return entries.filter((e) => set.has((e.toolName || '').toLowerCase())).length;
45
+ }
46
+ function fmtUsd(n) {
47
+ return n === 0 ? '$0' : `$${n.toFixed(2)}`;
48
+ }
49
+ export function assessCrossVendor(input) {
50
+ const { writer, reviewer, entries, chain } = input;
51
+ const crossVendor = writer.agent !== reviewer.agent;
52
+ // Attribute by the sessionId the tool-call entries carry. For a direct run
53
+ // that's the run's own sessionId; for a conductor-routed run the audit hook
54
+ // keyed the entries by the SPECIALIST's runtime id (delegateSessionId), not
55
+ // the conductor task's id — so match EITHER (1.3). Without the delegate leg,
56
+ // every Auto-routed task reads "couldn't attribute" though its writes are in
57
+ // the chain. pre_prompt scans use a separate scan id and carry no write/read
58
+ // tool, so they never inflate counts; sessionId scoping stays precise and
59
+ // immune to mixing other rooms' same-vendor sessions.
60
+ const matchesRun = (e, run) => e.sessionId === run.sessionId || (run.delegateSessionId !== undefined && e.sessionId === run.delegateSessionId);
61
+ const writerEntries = entries.filter((e) => matchesRun(e, writer));
62
+ const reviewerEntries = entries.filter((e) => matchesRun(e, reviewer));
63
+ const writerRecon = reconcileAudit(writerEntries, writer.artifacts);
64
+ const reviewerRead = countTools(reviewerEntries, READ_TOOLS);
65
+ const reviewerWrote = countTools(reviewerEntries, WRITE_TOOLS);
66
+ const reviewHasFindings = !!(reviewer.reviewText && reviewer.reviewText.trim().length > 0);
67
+ const writerAttributed = writerEntries.length > 0;
68
+ const reviewerAttributed = reviewerEntries.length > 0;
69
+ const totalCostUsd = estimateCostUsd(writer.agent, writer.artifacts?.usage) + estimateCostUsd(reviewer.agent, reviewer.artifacts?.usage);
70
+ const caveats = [];
71
+ if (!crossVendor)
72
+ caveats.push(`Same vendor on both sides (${writer.agent}) — not a cross-vendor proof.`);
73
+ if (!chain.valid)
74
+ caveats.push("Audit chain didn't verify — the proof is withheld until it does.");
75
+ if (crossVendor && !writerAttributed)
76
+ caveats.push(`Couldn't attribute ${writer.agent}'s work in the audit chain.`);
77
+ if (crossVendor && !reviewerAttributed)
78
+ caveats.push(`Couldn't attribute ${reviewer.agent}'s review in the audit chain.`);
79
+ if (reviewerWrote > 0)
80
+ caveats.push(`${reviewer.agent} also changed ${reviewerWrote} file(s) — this wasn't a read-only review.`);
81
+ if (!reviewHasFindings)
82
+ caveats.push(`${reviewer.agent}'s review recorded no findings text.`);
83
+ // A clean cross-vendor REVIEW proof is read-only — if the reviewer also wrote,
84
+ // the receipt can't honestly say "changed nothing", so it isn't `proven`
85
+ // (the reviewerWrote caveat above explains why). Refuter 2026-06-13.
86
+ const proven = crossVendor && chain.valid && writerAttributed && reviewerAttributed && reviewHasFindings && reviewerWrote === 0;
87
+ const summary = proven
88
+ ? `${reviewer.agent} reviewed ${writer.agent}'s work in this room. ` +
89
+ `${writer.agent} ${writerRecon.verdict === 'consistent' ? `wrote ${writerRecon.filesObserved} file(s), confirmed on disk` : `recorded ${writerRecon.writeToolCalls} write(s)`}; ` +
90
+ `${reviewer.agent} read ${reviewerRead}, changed nothing. ` +
91
+ `Audit chain: ${chain.chainedEntries}/${chain.totalEntries} integrity-checked, both vendors attributed. ` +
92
+ `Combined cost: ${fmtUsd(totalCostUsd)}.`
93
+ : crossVendor
94
+ ? `${reviewer.agent} reviewed ${writer.agent}'s work, but this isn't a clean cross-vendor proof yet — ${caveats[0]}`
95
+ : caveats[0];
96
+ return {
97
+ crossVendor,
98
+ proven,
99
+ writer: {
100
+ agent: writer.agent,
101
+ sessionId: writer.sessionId,
102
+ wrote: writerRecon.writeToolCalls,
103
+ reconciliation: writerRecon.verdict,
104
+ reconNote: writerRecon.note,
105
+ },
106
+ reviewer: {
107
+ agent: reviewer.agent,
108
+ sessionId: reviewer.sessionId,
109
+ read: reviewerRead,
110
+ wrote: reviewerWrote,
111
+ hasFindings: reviewHasFindings,
112
+ },
113
+ chain: {
114
+ totalEntries: chain.totalEntries,
115
+ chainedEntries: chain.chainedEntries,
116
+ valid: chain.valid,
117
+ writerAttributed,
118
+ reviewerAttributed,
119
+ },
120
+ totalCostUsd,
121
+ summary,
122
+ caveats,
123
+ reviewText: reviewer.reviewText,
124
+ };
125
+ }
126
+ //# sourceMappingURL=assess-cross-vendor.js.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Gather the live data a cross-vendor verdict needs (room sessions, audit
3
+ * entries, chain facts) and run the pure `assessCrossVendor`. Shared by the CLI
4
+ * (`codehere verify`) and the daemon (GET /api/task/:id/cross-vendor) so both
5
+ * surfaces compute the receipt the same way — one path, no drift.
6
+ *
7
+ * The pure judgment lives in assess-cross-vendor.ts; this is the (disk-reading)
8
+ * glue around it.
9
+ *
10
+ * PAIRING is OBSERVED, not inferred: a reviewer records the writer it reviewed
11
+ * (parentSessionId, stamped at spawn). Older / manual reviews that predate that
12
+ * ground truth fall back to a STRICT inference — a candidate is only treated as
13
+ * a reviewer if it changed NO files (a read-only review). A session that wrote
14
+ * files is a writer, never a reviewer; inferring otherwise mispaired in a
15
+ * multi-run room (a later writer read as a reviewer of an earlier one).
16
+ */
17
+ import { existsSync, readFileSync } from 'fs';
18
+ import { loadRecent, loadSessionById, } from '../../infrastructure/orchestration/session-persistence.js';
19
+ import { AUDIT_FILE, verifyAuditChain } from '../../infrastructure/hooks/claude-sdk-hook.js';
20
+ import { assessCrossVendor } from './assess-cross-vendor.js';
21
+ /** Assemble the verdict for a KNOWN writer↔reviewer pair (the role decision is
22
+ * the caller's; this just reads the chain + runs the pure assessment). */
23
+ function assembleVerdict(writer, reviewer) {
24
+ let entries = [];
25
+ if (existsSync(AUDIT_FILE)) {
26
+ entries = readFileSync(AUDIT_FILE, 'utf-8')
27
+ .split('\n')
28
+ .filter(Boolean)
29
+ .map((l) => {
30
+ try {
31
+ return JSON.parse(l);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ })
37
+ .filter((e) => !!e);
38
+ }
39
+ const chain = verifyAuditChain(AUDIT_FILE);
40
+ const reviewText = reviewer.finalReport?.summary?.trim() ||
41
+ (reviewer.steps ?? [])
42
+ .filter((s) => s.kind === 'say')
43
+ .map((s) => s.label)
44
+ .join('\n')
45
+ .trim() ||
46
+ undefined;
47
+ return assessCrossVendor({
48
+ writer: {
49
+ agent: writer.agent,
50
+ sessionId: writer.sessionId,
51
+ delegateSessionId: writer.delegateSessionId,
52
+ artifacts: writer.artifacts,
53
+ },
54
+ reviewer: {
55
+ agent: reviewer.agent,
56
+ sessionId: reviewer.sessionId,
57
+ delegateSessionId: reviewer.delegateSessionId,
58
+ artifacts: reviewer.artifacts,
59
+ reviewText,
60
+ },
61
+ entries,
62
+ chain: { totalEntries: chain.totalEntries, chainedEntries: chain.chainedEntries, valid: chain.valid },
63
+ });
64
+ }
65
+ /** Build the cross-vendor verdict for a session that REVIEWED an earlier writer
66
+ * in the same room. Returns undefined when there's no such pair (the session
67
+ * isn't a cross-vendor review). */
68
+ export function buildCrossVendorVerdict(reviewer) {
69
+ if (!reviewer.cwd)
70
+ return undefined;
71
+ // OBSERVED link: an auto-review records the exact writer it reviewed. Gate on
72
+ // isReview, NOT parentSessionId alone — parentSessionId is overloaded (the room
73
+ // composer also sets it when a goal is HANDED OFF to a different vendor to
74
+ // CONTINUE the work, which is not a review). Only isReview means "this session
75
+ // reviewed its parent." Without this gate a continuation would be falsely
76
+ // labeled "X reviewed Y's work."
77
+ if (reviewer.isReview && reviewer.parentSessionId) {
78
+ const writer = loadSessionById(reviewer.parentSessionId);
79
+ if (!writer || writer.agent === reviewer.agent)
80
+ return undefined;
81
+ return assembleVerdict(writer, reviewer);
82
+ }
83
+ // LEGACY / manual: no recorded link. Only a READ-ONLY session (changed no
84
+ // files) can be a reviewer — a session that wrote files is a writer. This is
85
+ // the guard that stops a later writer being mispaired as a reviewer.
86
+ if ((reviewer.artifacts?.files?.length ?? 0) > 0)
87
+ return undefined;
88
+ const writer = loadRecent(50)
89
+ .filter((s) => s.cwd === reviewer.cwd &&
90
+ s.sessionId !== reviewer.sessionId &&
91
+ s.startedAt < reviewer.startedAt &&
92
+ s.agent !== reviewer.agent &&
93
+ (s.artifacts?.files?.length ?? 0) > 0)
94
+ .sort((a, b) => b.startedAt - a.startedAt)[0];
95
+ if (!writer)
96
+ return undefined;
97
+ return assembleVerdict(writer, reviewer);
98
+ }
99
+ /** Convenience for the daemon route: load the session by id, then build. */
100
+ export function buildCrossVendorVerdictById(sessionId) {
101
+ const reviewer = loadSessionById(sessionId);
102
+ return reviewer ? buildCrossVendorVerdict(reviewer) : undefined;
103
+ }
104
+ /** The WRITER-keyed mirror: given a writer, find the reviewer that reviewed IT
105
+ * and build the verdict. A user opens the WRITER's run and wants to see "a
106
+ * different vendor reviewed this"; the reviewer recorded the link, so we read
107
+ * it directly (parentSessionId === this writer + isReview) — no inference, no
108
+ * mispairing. Returns undefined when no review of this writer exists. */
109
+ export function buildCrossVendorVerdictForWriter(writerId) {
110
+ const writer = loadSessionById(writerId);
111
+ if (!writer || !writer.cwd)
112
+ return undefined;
113
+ const reviewer = loadRecent(50).find((s) => s.isReview === true && s.parentSessionId === writerId);
114
+ if (!reviewer || reviewer.agent === writer.agent)
115
+ return undefined;
116
+ return assembleVerdict(writer, reviewer);
117
+ }
118
+ //# sourceMappingURL=build-cross-vendor-verdict.js.map
@@ -0,0 +1,107 @@
1
+ /**
2
+ * export-aat — map codehere's hash-chained audit log to the IETF
3
+ * draft-sharif-agent-audit-trail-00 (AAT) record structure, JSONL (the
4
+ * draft's RECOMMENDED primary export format).
5
+ *
6
+ * Why: the 2026 wave-4 swarm discourse converged on audit-trail
7
+ * standardization (EU AI Act Art. 12 logging lands Aug 2026; the AAT draft
8
+ * maps to SOC 2 / ISO 42001). codehere's chain already carries the
9
+ * substance — agent identity, action, outcome, risk, hash-chained
10
+ * SHA-256 linking — so speaking the emerging standard is a mapping, not a
11
+ * rebuild.
12
+ *
13
+ * HONEST MAPPING NOTES (each also stated in the export header record):
14
+ * - trust_level is "L0" for every record: AAT's levels describe the
15
+ * AGENT's cryptographic identity, and codehere agents carry none (the
16
+ * DAEMON's keystore signs the chain tip — that is recorder identity,
17
+ * not agent identity; claiming L1 would overstate).
18
+ * - prev_hash/record hashing is codehere's sorted-key JSON canon over the
19
+ * original entries — same SHA-256 chain idea as the draft, but NOT
20
+ * RFC 8785 JCS, and the hashes commit to the ORIGINAL codehere entries,
21
+ * not to the AAT projection. Verify against the source chain.
22
+ * - input_hash is SHA-256 of the recorded toolInput; the raw input stays
23
+ * local (the draft's privacy-by-design posture).
24
+ * - record_id is the entry's own content hash formatted as a UUID-shaped id —
25
+ * deterministic, no invented identity (UUID-shaped, not an RFC 4122 UUID).
26
+ */
27
+ import { createHash } from 'crypto';
28
+ import { existsSync, readFileSync } from 'fs';
29
+ function hashToUuid(hexHash) {
30
+ const h = hexHash.padEnd(32, '0').slice(0, 32);
31
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
32
+ }
33
+ function agentOf(entry) {
34
+ try {
35
+ const ti = JSON.parse(entry.toolInput);
36
+ if (typeof ti.agent === 'string' && ti.agent)
37
+ return ti.agent;
38
+ }
39
+ catch {
40
+ /* non-JSON toolInput */
41
+ }
42
+ return 'unrecorded';
43
+ }
44
+ export function exportAat(auditFile, now = new Date().toISOString()) {
45
+ const out = {
46
+ header: {
47
+ format: 'codehere-aat-export',
48
+ source_format: 'draft-sharif-agent-audit-trail-00',
49
+ exported_at: now,
50
+ record_count: 0,
51
+ notes: [
52
+ 'trust_level is L0 for all records: AAT trust levels describe the AGENT’s cryptographic identity and codehere agents carry none — the daemon’s keystore signs the chain tip (recorder identity, not agent identity).',
53
+ 'prev_hash values are codehere’s SHA-256 chain over the ORIGINAL entries (sorted-key JSON canonicalization, not RFC 8785 JCS); verify against the source chain, not this projection.',
54
+ 'input_hash is SHA-256 of the recorded tool input; raw inputs stay local (privacy by design).',
55
+ 'record_id is the source entry’s content hash formatted as a UUID-shaped id — deterministic, not an invented identity.',
56
+ ],
57
+ },
58
+ records: [],
59
+ skippedLegacy: 0,
60
+ };
61
+ if (!existsSync(auditFile))
62
+ return out;
63
+ for (const line of readFileSync(auditFile, 'utf-8').split('\n')) {
64
+ const raw = line.trim();
65
+ if (!raw)
66
+ continue;
67
+ let e;
68
+ try {
69
+ e = JSON.parse(raw);
70
+ }
71
+ catch {
72
+ out.skippedLegacy++;
73
+ continue;
74
+ }
75
+ if (typeof e.seq !== 'number' || typeof e.entryHash !== 'string' || typeof e.prevHash !== 'string') {
76
+ out.skippedLegacy++; // pre-chain legacy — no hash-chained position to export
77
+ continue;
78
+ }
79
+ const rec = {
80
+ record_id: hashToUuid(e.entryHash),
81
+ timestamp: e.timestamp,
82
+ agent_id: `urn:agent:${agentOf(e)}.codehere.local`,
83
+ session_id: e.sessionId,
84
+ action_type: e.toolName === 'pre_prompt' ? 'decision' : 'tool_call',
85
+ action_detail: {
86
+ tool_name: e.toolName,
87
+ input_hash: createHash('sha256')
88
+ .update(e.toolInput ?? '')
89
+ .digest('hex'),
90
+ ...(e.writer ? { writer: e.writer } : {}),
91
+ },
92
+ outcome: e.blocked ? 'blocked' : 'success',
93
+ trust_level: 'L0',
94
+ prev_hash: e.prevHash,
95
+ ...(typeof e.riskScore === 'number' && e.riskScore > 0 ? { risk_score: Math.min(1, e.riskScore / 100) } : {}),
96
+ };
97
+ out.records.push(rec);
98
+ }
99
+ out.header.record_count = out.records.length;
100
+ return out;
101
+ }
102
+ /** Serialize per the draft's recommended JSONL: header line first, then one
103
+ * record per line. */
104
+ export function aatToJsonl(exp) {
105
+ return [JSON.stringify(exp.header), ...exp.records.map((r) => JSON.stringify(r))].join('\n') + '\n';
106
+ }
107
+ //# sourceMappingURL=export-aat.js.map