codehere 1.0.1 → 1.0.3

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 +35 -193
  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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codehere",
3
- "version": "1.0.1",
4
- "description": "See what your AI coding agents cost, and prove what they actually did. Runs Claude Code, Codex, Aider, Gemini and OpenCode on your machine, records every action in a hash-chained log, and re-runs your tests instead of taking an agent's word for it.",
3
+ "version": "1.0.3",
4
+ "description": "Re-runs the tests an AI says passed, and tells you whether they did. Flags weakened tests and test files that do not exist.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -25,8 +25,6 @@
25
25
  "prebuild": "rm -rf dist",
26
26
  "build": "tsc",
27
27
  "postbuild": "chmod +x dist/index.js",
28
- "repomap": "node scripts/gen-repo-map.mjs",
29
- "pattern-counts": "node scripts/print-pattern-counts.mjs",
30
28
  "prepublishOnly": "npm run build && find dist -name '*.map' -delete && find dist -name '*.d.ts' -delete && find dist -name '*.d.ts.map' -delete",
31
29
  "start": "node dist/index.js",
32
30
  "dev": "tsx src/index.ts",
@@ -35,34 +33,19 @@
35
33
  "lint": "eslint src/ --ext .ts",
36
34
  "knip": "knip",
37
35
  "knip:gate": "node scripts/knip-gate.mjs",
38
- "lint:tokens": "node scripts/ui-token-gate.mjs",
39
- "arch:gate": "tsc --noEmit && eslint src/ --ext .ts && node scripts/knip-gate.mjs && node scripts/ui-token-gate.mjs",
36
+ "arch:gate": "tsc --noEmit && eslint src/ --ext .ts && node scripts/knip-gate.mjs",
40
37
  "format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
41
38
  "format:check": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
42
39
  "sbom": "npm sbom --sbom-format=spdx --omit=dev",
43
- "pretag:check": "../scripts/pretag-npm-check.sh",
44
- "cassette:record": "../scripts/capture-cassette.sh",
45
- "e2e": "node test/e2e/run.mjs",
46
- "e2e:live": "CODEHERE_E2E_LIVE=1 node test/e2e/run.mjs",
47
- "degraded": "node scripts/degraded-matrix.mjs",
48
- "inventory": "node scripts/inventory-drift.mjs"
40
+ "pretag:check": "../scripts/pretag-npm-check.sh"
49
41
  },
50
42
  "keywords": [
51
43
  "ai-coding-agents",
52
- "verification",
53
- "agent-audit",
54
- "llm-cost",
55
- "ai-cost-tracking",
56
- "audit-trail",
57
- "trust-scoring",
58
- "ai-safety",
59
- "ai-governance",
44
+ "test-verification",
45
+ "ci",
46
+ "github-action",
60
47
  "claude-code",
61
48
  "codex",
62
- "aider",
63
- "opencode",
64
- "hook-system",
65
- "sast",
66
49
  "cli-tool",
67
50
  "typescript"
68
51
  ],
@@ -75,13 +58,10 @@
75
58
  "bugs": {
76
59
  "url": "https://github.com/muhammadegaa/codehere/issues"
77
60
  },
78
- "homepage": "https://codehere.uk",
61
+ "homepage": "https://github.com/muhammadegaa/codehere",
79
62
  "dependencies": {
80
63
  "chalk": "^5.3.0",
81
- "cli-table3": "^0.6.5",
82
- "commander": "^11.1.0",
83
- "dotenv": "^16.3.1",
84
- "yaml": "^2.8.4"
64
+ "commander": "^11.1.0"
85
65
  },
86
66
  "devDependencies": {
87
67
  "@eslint/js": "^10.0.1",
@@ -90,19 +70,10 @@
90
70
  "eslint-import-resolver-typescript": "^4.4.5",
91
71
  "eslint-plugin-import-x": "^4.16.2",
92
72
  "knip": "^6.16.1",
93
- "playwright": "^1.60.0",
94
73
  "prettier": "^3.8.3",
95
74
  "tsx": "^4.21.0",
96
75
  "typescript": "^5.9.3",
97
76
  "typescript-eslint": "^8.58.2",
98
77
  "vitest": "^4.1.4"
99
- },
100
- "peerDependencies": {
101
- "@mohammednagy/graphify-ts": "^0.22.3"
102
- },
103
- "peerDependenciesMeta": {
104
- "@mohammednagy/graphify-ts": {
105
- "optional": true
106
- }
107
78
  }
108
79
  }
@@ -1,154 +0,0 @@
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
@@ -1,126 +0,0 @@
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
@@ -1,118 +0,0 @@
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
@@ -1,110 +0,0 @@
1
- /**
2
- * Per-domain trust grade for W-PASS write targets · v0.9 must-ship.
3
- *
4
- * Demo v12 + locked v1 tier scope: "linear.app A · *.atlassian.net B ·
5
- * unknown F · first-write confirmation per domain."
6
- *
7
- * Two pieces of state:
8
- * 1. WELL_KNOWN_GRADES · hardcoded · the platforms we've vetted ourselves
9
- * ship with grade A. Subdomain wildcards get B. Everything else
10
- * defaults to F (forces explicit user confirmation on first write).
11
- * 2. USER_TRUSTED_DOMAINS · ~/.codehere/keys/trusted-domains.json ·
12
- * domains the user has explicitly trusted after a first-write prompt.
13
- * Domains here promote from F → B.
14
- *
15
- * Pure logic. CLI + HTTP wrap it.
16
- */
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
18
- import { homedir } from 'os';
19
- import { dirname, join } from 'path';
20
- const WELL_KNOWN_GRADES = [
21
- // Grade A · platforms codehere has shipped+tested adapters for
22
- { pattern: /^api\.linear\.app$/i, grade: 'A', reason: 'official Linear API · codehere ships a vetted adapter' },
23
- { pattern: /^api\.github\.com$/i, grade: 'A', reason: 'official GitHub API · codehere ships a vetted adapter' },
24
- { pattern: /^api\.figma\.com$/i, grade: 'A', reason: 'official Figma API · codehere ships a vetted adapter' },
25
- // Grade B · same vendor, surrounding subdomains
26
- { pattern: /^[a-z0-9-]+\.linear\.app$/i, grade: 'B', reason: 'Linear subdomain · adjacent to the vetted API' },
27
- { pattern: /^[a-z0-9-]+\.github\.com$/i, grade: 'B', reason: 'GitHub subdomain · adjacent to the vetted API' },
28
- { pattern: /^[a-z0-9-]+\.figma\.com$/i, grade: 'B', reason: 'Figma subdomain · adjacent to the vetted API' },
29
- {
30
- pattern: /^[a-z0-9-]+\.atlassian\.net$/i,
31
- grade: 'B',
32
- reason: 'Atlassian-hosted instance · vendor known but instance not vetted',
33
- },
34
- // Grade C · public hosts known to be commonly used
35
- { pattern: /^raw\.githubusercontent\.com$/i, grade: 'C', reason: 'GitHub-hosted raw content · static, read-mostly' },
36
- ];
37
- function trustedDomainsPath() {
38
- const home = process.env.CODEHERE_HOME || homedir();
39
- return join(home, '.codehere', 'keys', 'trusted-domains.json');
40
- }
41
- export function loadTrustedDomains() {
42
- const path = trustedDomainsPath();
43
- if (!existsSync(path))
44
- return { schema: 1, domains: {} };
45
- try {
46
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
47
- if (raw && raw.schema === 1 && typeof raw.domains === 'object')
48
- return raw;
49
- }
50
- catch {
51
- /* fall through · empty trust store */
52
- }
53
- return { schema: 1, domains: {} };
54
- }
55
- export function trustDomain(host, note) {
56
- const file = loadTrustedDomains();
57
- file.domains[host.toLowerCase()] = { addedAt: Date.now(), note };
58
- mkdirSync(dirname(trustedDomainsPath()), { recursive: true });
59
- writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
60
- }
61
- export function untrustDomain(host) {
62
- const file = loadTrustedDomains();
63
- const key = host.toLowerCase();
64
- if (!file.domains[key])
65
- return false;
66
- delete file.domains[key];
67
- writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
68
- return true;
69
- }
70
- export function gradeDomain(host) {
71
- const userFile = loadTrustedDomains();
72
- const userTrusted = !!userFile.domains[host.toLowerCase()];
73
- for (const rule of WELL_KNOWN_GRADES) {
74
- if (rule.pattern.test(host)) {
75
- return {
76
- host,
77
- grade: rule.grade,
78
- reason: rule.reason,
79
- userTrusted,
80
- requiresFirstWriteConfirmation: false,
81
- };
82
- }
83
- }
84
- if (userTrusted) {
85
- return {
86
- host,
87
- grade: 'B',
88
- reason: 'previously trusted by you · stored in trusted-domains.json',
89
- userTrusted: true,
90
- requiresFirstWriteConfirmation: false,
91
- };
92
- }
93
- return {
94
- host,
95
- grade: 'F',
96
- reason: 'unknown domain · not in well-known list · not previously trusted',
97
- userTrusted: false,
98
- requiresFirstWriteConfirmation: true,
99
- };
100
- }
101
- /** Extract the host from a fully-resolved HTTPS URL. */
102
- export function hostOf(url) {
103
- try {
104
- return new URL(url).host;
105
- }
106
- catch {
107
- return '';
108
- }
109
- }
110
- //# sourceMappingURL=domain-trust.js.map