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
@@ -1,456 +0,0 @@
1
- /**
2
- * v0.7 · verify-claim use case
3
- *
4
- * Pure function that compares what the Final Report TEXT claims against
5
- * the structured reality (artifacts + steps) captured during the session.
6
- *
7
- * Codehere's #1 differentiator (per claim_market_position v4 + landing copy):
8
- * agents round to optimism, the verifier catches the rounding.
9
- *
10
- * Examples this catches today:
11
- * - Report says "18 tests passed" but artifacts.tests.passed = 17 → MISMATCH
12
- * - Report says "all tests green" but artifacts.tests.failed > 0 → MISMATCH
13
- * - Report says "no files changed" but artifacts.files has entries → MISMATCH
14
- * - Report says "5 files modified" but artifacts.files.length === 3 → MISMATCH
15
- * - Report says "Added subtract() to calc.js" and calc.js IS in the recorded
16
- * changes → VERIFIED (named-file claim, the dominant real-run shape)
17
- * - Report names a changed file the recorded changes don't include → WARN
18
- *
19
- * NOT a substitute for human review · this is a tripwire for the easy lies.
20
- * The verifier is intentionally conservative: only flag claims it can
21
- * compare against structured data. Unverifiable claims (prose-only,
22
- * intent-only) pass through silently.
23
- *
24
- * I/O is deferred to the CLI/UI surface. This file is pure: same input,
25
- * same output, no side-effects, no clock, no filesystem.
26
- */
27
- /* ──────────────────────────────────────────────────────────────────────── */
28
- /* Main verifier */
29
- /* ──────────────────────────────────────────────────────────────────────── */
30
- export function verifyClaim(input) {
31
- if (!input.summary || !input.summary.trim()) {
32
- return {
33
- status: 'no-report',
34
- findings: [
35
- {
36
- severity: 'warn',
37
- kind: 'no-report',
38
- claim: '(no Final Report generated)',
39
- reality: 'nothing to verify',
40
- message: 'No Final Report available for this session. Nothing to verify against.',
41
- },
42
- ],
43
- summary: 'No Final Report to verify.',
44
- };
45
- }
46
- const findings = [
47
- ...verifyTestsClaim(input.summary, input.artifacts),
48
- ...verifyFilesClaim(input.summary, input.artifacts),
49
- ...verifyNamedFileClaims(input.summary, input.artifacts),
50
- ];
51
- // Tier 1.1 · codehere RE-RAN the suite (observed) and it did NOT pass. This is
52
- // a WARN, NOT a hard `broken` — a re-run can fail for reasons the agent's run
53
- // didn't have (a monorepo where the root `npm test` is the wrong suite, a test
54
- // needing a server/DB, a flaky test). A verifier that fails honest work is worse
55
- // than no verifier. We ADD the env-caveat warn but do NOT touch any `fail` the
56
- // scrape-based verifyTestsClaim already produced — a prose-vs-own-stdout
57
- // self-contradiction stays `broken` (a real lie), the re-run failure is a
58
- // separate, softer signal (self-audit 2026-06-09, fixing the over-correction
59
- // that had laundered every re-run failure — including caught lies — to warn).
60
- const obs = input.artifacts?.tests;
61
- if (obs?.observed &&
62
- ((obs.exitCode != null && obs.exitCode !== 0) || (obs.failed ?? 0) > 0) &&
63
- !findings.some((f) => f.kind === 'tests' && f.severity === 'fail')) {
64
- findings.push({
65
- severity: 'warn',
66
- kind: 'tests',
67
- claim: 'the agent reported its run as done',
68
- reality: `codehere re-ran ${obs.command || 'the test suite'} · exit ${obs.exitCode ?? '?'}, ${obs.failed ?? 0} failing`,
69
- message: "codehere re-ran the suite and it did NOT pass — but a re-run can differ from the agent's environment (a monorepo's root suite, a test needing a server/DB). Worth a look, not a confirmed failure.",
70
- });
71
- }
72
- const hasFail = findings.some((f) => f.severity === 'fail');
73
- const hasNon = findings.length > 0;
74
- // user-#1 dogfood 2026-06-07 (session 4b6bc481): an aider run that did
75
- // NOTHING produced a Final Report that was just the startup banner. The
76
- // banner makes no test/file claim, so the verifier found zero findings and
77
- // returned 'verified' — laundering a no-op into "claims match reality". When
78
- // there are no findings AND the report made no checkable claim at all, there
79
- // is nothing to verify; say so honestly instead of vacuously passing.
80
- const status = hasFail
81
- ? 'broken'
82
- : hasNon
83
- ? 'partial'
84
- : hasCheckableClaim(input.summary, input.artifacts)
85
- ? 'verified'
86
- : 'nothing-to-verify';
87
- // Was the green earned purely by codehere's own re-run (no agent prose claim)?
88
- // If so, say that honestly instead of "Claims match reality" (there was no claim).
89
- const observedOnly = status === 'verified' && findings.length === 0 && !proseMakesCheckableClaim(input.summary, input.artifacts);
90
- const summary = buildVerdictSummary(status, findings, observedOnly);
91
- return { status, findings, summary };
92
- }
93
- /**
94
- * True when the report makes at least one assertion the verifier can check
95
- * against structured reality (a test claim or a file claim). If nothing is
96
- * checkable, a green "verified" verdict would be vacuous — see status
97
- * 'nothing-to-verify'.
98
- */
99
- function hasCheckableClaim(summary, artifacts) {
100
- // Tier 1.1 · a clean OBSERVED re-run is itself a fact codehere confirmed, so it
101
- // earns 'verified' even if the prose phrased no claim (the green ✓ here means
102
- // "codehere ran the suite and it passed" — observed, not the agent's word).
103
- const ot = artifacts?.tests;
104
- if (ot?.observed && (ot.exitCode == null || ot.exitCode === 0) && (ot.failed ?? 0) === 0)
105
- return true;
106
- return proseMakesCheckableClaim(summary, artifacts);
107
- }
108
- /**
109
- * True when the REPORT PROSE itself asserts something checkable — a count, a
110
- * named file that matches reality, or an "all green"/"no files changed"
111
- * qualifier — independent of any observed re-run. Lets the verdict tell apart a
112
- * green the AGENT earned with a real claim from one codehere produced purely by
113
- * re-running the suite itself (so the summary doesn't say "Claims match reality"
114
- * when there was no claim — R5 refuter, 2026-06-09).
115
- */
116
- function proseMakesCheckableClaim(summary, artifacts) {
117
- const lower = summary.toLowerCase();
118
- if (matchTestCountClaim(summary) !== null)
119
- return true;
120
- if (matchFilesCountClaim(summary) !== null)
121
- return true;
122
- // Named-file claims ("Added subtract() to calc.js") count as checkable when
123
- // the named file appears in the recorded file changes — the dominant shape
124
- // of a REAL run's report (2026-06-07 v1 walk, session ab9cdcc9: a perfect
125
- // run earned only "nothing to verify" because it phrased no numbers).
126
- // A named file that does NOT match artifacts produced a warn finding in
127
- // verifyNamedFileClaims, so status is 'partial' before this is consulted.
128
- if (extractNamedFileClaims(summary).some((p) => fileMatchesArtifacts(p, artifacts)))
129
- return true;
130
- // Universal "all tests pass / green / no failures" qualifiers.
131
- if (/\b(?:all|every|each)\s+tests?\s+(?:pass(?:ed|ing)?|green|succeed(?:ed)?)\b/i.test(lower) ||
132
- /\btests?\s+all\s+(?:pass(?:ed|ing)?|green)\b/i.test(lower) ||
133
- /\ball\s+(?:tests?\s+)?(?:green|passing)\b/i.test(lower) ||
134
- /\bno\s+(?:test\s+)?(?:failures|regressions?)\b/i.test(lower))
135
- return true;
136
- // "no files changed / read-only / nothing written" claims. Deliberate: a
137
- // no-op run that SAYS "no files were changed" gets 'verified' (the claim is
138
- // true and checked against reality), not 'nothing-to-verify'. The vacuous
139
- // green this function kills is the report that claims NOTHING (e.g. a bare
140
- // startup banner) — an honest no-op claim is exactly what verification is for.
141
- if (/\bno\s+files?\s+(were\s+)?(changed|modified|touched|written|created)\b/i.test(summary) ||
142
- /\bsession\s+was\s+read-only\b/i.test(summary) ||
143
- /\bnothing\s+(was\s+)?(written|changed|modified)\b/i.test(summary))
144
- return true;
145
- return false;
146
- }
147
- /* ──────────────────────────────────────────────────────────────────────── */
148
- /* Tests claim verification */
149
- /* ──────────────────────────────────────────────────────────────────────── */
150
- /**
151
- * Detect tests-related claims in the report and cross-check against
152
- * artifacts.tests. Catches "X tests passed" exaggeration AND "all tests
153
- * green" when failed > 0.
154
- */
155
- export function verifyTestsClaim(summary, artifacts) {
156
- const out = [];
157
- const lower = summary.toLowerCase();
158
- // Check the prose against the AGENT'S OWN scraped numbers. When codehere
159
- // re-ran (observed), `artifacts.tests` holds the re-run result and the original
160
- // scrape is preserved in `.claimed` — use the scrape here so a prose-vs-own-run
161
- // self-contradiction ("all pass" while its own stdout showed failures) is still
162
- // caught as a real lie, separate from a re-run that fails on an env difference
163
- // (handled as a warn in verifyClaim). Self-audit 2026-06-09.
164
- const scrape = artifacts?.tests?.claimed;
165
- const tests = scrape ? { passed: scrape.passed, failed: scrape.failed } : artifacts?.tests;
166
- // Extract numeric "X tests passed" / "X passed" / "X of Y" patterns.
167
- // We deliberately keep the regex tight to avoid false positives on prose
168
- // like "this is the 5th attempt".
169
- const countClaim = matchTestCountClaim(summary);
170
- if (countClaim !== null) {
171
- if (!tests) {
172
- // The agent claimed a test count but the adapter recorded no test
173
- // artifact. Suspicious but not necessarily wrong (the adapter may not
174
- // capture test results yet). Flag as warn.
175
- out.push({
176
- severity: 'warn',
177
- kind: 'tests',
178
- claim: countClaim.quote,
179
- reality: 'no test artifact captured',
180
- message: `Final Report claims "${countClaim.quote}" but no test results were recorded by the agent.`,
181
- });
182
- }
183
- else {
184
- const actualPassed = tests.passed;
185
- const actualFailed = tests.failed;
186
- const actualTotal = actualPassed + actualFailed;
187
- // Per codex Round 1: "X of Y tests passed" must check BOTH numbers.
188
- // A claim like "18 of 18 tests passed" with reality {passed: 17, failed: 1}
189
- // has matching total (18 ran) but mismatched passed (17 ≠ 18) · we flag.
190
- if (countClaim.passedCount !== undefined && countClaim.passedCount !== actualPassed) {
191
- out.push({
192
- severity: 'fail',
193
- kind: 'tests',
194
- claim: countClaim.quote,
195
- reality: `${actualPassed} passed, ${actualFailed} failed (${actualTotal} ran)`,
196
- message: `Final Report claims ${countClaim.passedCount} tests passed but reality is ${actualPassed} passed and ${actualFailed} failed.`,
197
- });
198
- }
199
- if (countClaim.totalCount !== undefined && countClaim.totalCount !== actualTotal) {
200
- out.push({
201
- severity: 'fail',
202
- kind: 'tests',
203
- claim: countClaim.quote,
204
- reality: `${actualTotal} ran (${actualPassed} passed, ${actualFailed} failed)`,
205
- message: `Final Report claims ${countClaim.totalCount} tests ran but reality is ${actualTotal} (${actualPassed} passed, ${actualFailed} failed).`,
206
- });
207
- }
208
- }
209
- }
210
- // Universal "all tests pass" / "all green" claims. Requires explicit
211
- // universal qualifier ("all", "every", "each", "no failures") so that
212
- // count claims like "18 tests passed" don't also match — count
213
- // mismatches are caught above by matchTestCountClaim.
214
- const claimsAllPass = /\b(?:all|every|each)\s+tests?\s+(?:pass(?:ed|ing)?|green|succeed(?:ed)?)\b/i.test(lower) ||
215
- /\btests?\s+all\s+(?:pass(?:ed|ing)?|green)\b/i.test(lower) ||
216
- /\ball\s+(?:tests?\s+)?(?:green|passing)\b/i.test(lower) ||
217
- /\bno\s+(?:test\s+)?(?:failures|regressions?)\b/i.test(lower);
218
- if (claimsAllPass && tests && tests.failed > 0) {
219
- out.push({
220
- severity: 'fail',
221
- kind: 'tests',
222
- claim: 'all tests pass / green / no failures',
223
- reality: `${tests.failed} test${tests.failed === 1 ? '' : 's'} failed`,
224
- message: `Final Report claims all tests pass but ${tests.failed} actually failed.`,
225
- });
226
- }
227
- return out;
228
- }
229
- /**
230
- * Parse the first explicit test-count claim from the summary.
231
- * Returns null if no specific count is mentioned.
232
- *
233
- * Patterns matched (a single claim can assert BOTH a passed-count AND a total):
234
- * "18 tests passed" → { passedCount: 18 }
235
- * "18 of 18 tests passed" → { passedCount: 18, totalCount: 18 }
236
- * "passed 18 tests" → { passedCount: 18 }
237
- * "ran 25 tests" → { totalCount: 25 }
238
- *
239
- * Per codex Round 1 review on commit 8481412: "X of Y tests passed" must
240
- * check BOTH numbers, not just total, otherwise "18 of 18 passed" with
241
- * reality {passed: 17, failed: 1} slips through (total still equals 18).
242
- */
243
- function matchTestCountClaim(summary) {
244
- // "X of Y tests passed/green/succeeded" → X is passed AND Y is total.
245
- // Per codex Round 2: require explicit pass-context to populate passedCount
246
- // so negative phrasing ("1 of 3 tests failed") doesn't become a false-positive
247
- // "1 passed" claim.
248
- const ofPassMatch = summary.match(/\b(\d+)\s+(?:of|\/)\s+(\d+)\s+tests?\s+(?:pass(?:ed|ing)?|green|succeed(?:ed)?)\b/i);
249
- if (ofPassMatch) {
250
- return {
251
- quote: ofPassMatch[0],
252
- passedCount: parseInt(ofPassMatch[1], 10),
253
- totalCount: parseInt(ofPassMatch[2], 10),
254
- };
255
- }
256
- // "X of Y tests" without pass-context → only Y is a total-count claim. X
257
- // is ambient (failed / ran / partial / etc) and we don't pretend to know.
258
- // Per codex Round 3: this branch preserves total-count detection for
259
- // bare X-of-Y phrasing so the verifier still catches mismatched totals.
260
- const ofMatch = summary.match(/\b(\d+)\s+(?:of|\/)\s+(\d+)\s+tests?\b/i);
261
- if (ofMatch) {
262
- return {
263
- quote: ofMatch[0],
264
- totalCount: parseInt(ofMatch[2], 10),
265
- };
266
- }
267
- // "X tests passed" / "X tests pass"
268
- const passedMatch = summary.match(/\b(\d+)\s+tests?\s+(?:pass(?:ed|ing)?|green)\b/i);
269
- if (passedMatch) {
270
- return { quote: passedMatch[0], passedCount: parseInt(passedMatch[1], 10) };
271
- }
272
- // "passed X tests" / "ran X tests"
273
- const reverseMatch = summary.match(/\b(?:pass(?:ed)?|ran)\s+(\d+)\s+tests?\b/i);
274
- if (reverseMatch) {
275
- const isPass = /pass/i.test(reverseMatch[0]);
276
- return {
277
- quote: reverseMatch[0],
278
- passedCount: isPass ? parseInt(reverseMatch[1], 10) : undefined,
279
- totalCount: isPass ? undefined : parseInt(reverseMatch[1], 10),
280
- };
281
- }
282
- return null;
283
- }
284
- /* ──────────────────────────────────────────────────────────────────────── */
285
- /* Files claim verification */
286
- /* ──────────────────────────────────────────────────────────────────────── */
287
- /**
288
- * Detect files-related claims in the report and cross-check against
289
- * artifacts.files. Catches "no files changed" when files DID change,
290
- * and "X files changed" count mismatches.
291
- */
292
- export function verifyFilesClaim(summary, artifacts) {
293
- const out = [];
294
- // Claim: "no files were changed" / "no files modified" / similar
295
- const claimsNoFiles = /\bno\s+files?\s+(were\s+)?(changed|modified|touched|written|created)\b/i.test(summary) ||
296
- /\bsession\s+was\s+read-only\b/i.test(summary) ||
297
- /\bnothing\s+(was\s+)?(written|changed|modified)\b/i.test(summary);
298
- // Claim: "X files changed" / "modified X files" / "wrote X files"
299
- const countClaim = matchFilesCountClaim(summary);
300
- // Per codex Round 1: if the adapter didn't capture file artifacts at all,
301
- // we can't verify file claims · warn rather than fail. Mirrors the tests
302
- // path (missing tests artifact → warn). Distinguishes from `files: []`
303
- // which is a valid "explicitly captured zero files" reality.
304
- if (artifacts?.files === undefined) {
305
- if (claimsNoFiles) {
306
- out.push({
307
- severity: 'warn',
308
- kind: 'files',
309
- claim: 'no files changed / read-only',
310
- reality: 'no file artifact captured',
311
- message: 'Final Report claims no files changed but no file results were recorded by the agent.',
312
- });
313
- }
314
- if (countClaim !== null) {
315
- out.push({
316
- severity: 'warn',
317
- kind: 'files',
318
- claim: countClaim.quote,
319
- reality: 'no file artifact captured',
320
- message: `Final Report claims "${countClaim.quote}" but no file results were recorded by the agent.`,
321
- });
322
- }
323
- return out;
324
- }
325
- const actualCount = artifacts.files.length;
326
- if (claimsNoFiles && actualCount > 0) {
327
- out.push({
328
- severity: 'fail',
329
- kind: 'files',
330
- claim: 'no files changed / read-only',
331
- reality: `${actualCount} file${actualCount === 1 ? '' : 's'} changed`,
332
- message: `Final Report claims no files were changed but ${actualCount} actually were.`,
333
- });
334
- }
335
- if (countClaim !== null && countClaim.value !== actualCount) {
336
- out.push({
337
- severity: 'fail',
338
- kind: 'files',
339
- claim: countClaim.quote,
340
- reality: `${actualCount} file${actualCount === 1 ? '' : 's'} changed`,
341
- message: `Final Report claims ${countClaim.value} file${countClaim.value === 1 ? '' : 's'} were changed but ${actualCount} actually were.`,
342
- });
343
- }
344
- return out;
345
- }
346
- /* ──────────────────────────────────────────────────────────────────────── */
347
- /* Named-file claims · "Added subtract() to calc.js" */
348
- /* ──────────────────────────────────────────────────────────────────────── */
349
- // Library/runtime names that look like files but never are ("migrated to
350
- // Next.js", "Node.js 20"). Skipped to keep the tripwire conservative.
351
- const NON_FILE_NAMES = /^(node|next|vue|react|express|d3|three|angular|nuxt|deno|bun|ember|backbone)\.js$/i;
352
- /**
353
- * Extract file paths the report claims to have CHANGED — an edit verb followed
354
- * (within the same sentence) by a path-shaped token. Deliberately verb-gated:
355
- * "read calc.js" or a bare mention is NOT a change claim.
356
- */
357
- export function extractNamedFileClaims(summary) {
358
- const out = new Set();
359
- const re = /\b(?:added|created|wrote|modified|updated|changed|edited|fixed|patched|refactored|renamed|implemented|extended)\b[^.!?\n]{0,80}?`?([\w][\w./-]*\.[A-Za-z0-9]{1,8})(?::\d+)?`?/gi;
360
- let m;
361
- while ((m = re.exec(summary)) !== null) {
362
- const path = m[1];
363
- if (NON_FILE_NAMES.test(path))
364
- continue;
365
- out.add(path);
366
- }
367
- return [...out];
368
- }
369
- /** True when a claimed path matches a recorded file change (exact, suffix, or basename). */
370
- function fileMatchesArtifacts(claimPath, artifacts) {
371
- if (!artifacts?.files?.length)
372
- return false;
373
- // LOW #26 (atomic audit) · the bare-basename fallback only applies when the
374
- // CLAIM is a bare basename. A directory-qualified claim ('src/a/calc.js')
375
- // must not green-match a same-named file elsewhere ('lib/b/calc.js').
376
- const claimIsBare = !claimPath.includes('/');
377
- return artifacts.files.some((f) => {
378
- const p = f.path ?? '';
379
- if (p === claimPath || p.endsWith('/' + claimPath) || claimPath.endsWith('/' + p))
380
- return true;
381
- return claimIsBare && (p.split('/').pop() ?? p) === claimPath;
382
- });
383
- }
384
- /**
385
- * Tripwire side of named-file claims: the report says it changed a specific
386
- * file but the recorded changes don't include it. Warn, not fail — the token
387
- * might be a tool/library mention the verb regex over-read; the count/no-files
388
- * checks above own the hard contradictions.
389
- */
390
- export function verifyNamedFileClaims(summary, artifacts) {
391
- if (artifacts?.files === undefined)
392
- return []; // can't check (e.g. non-git cwd) — stays unverifiable
393
- const out = [];
394
- for (const path of extractNamedFileClaims(summary)) {
395
- if (!fileMatchesArtifacts(path, artifacts)) {
396
- out.push({
397
- severity: 'warn',
398
- kind: 'files',
399
- claim: `changed ${path}`,
400
- reality: artifacts.files.length === 0
401
- ? 'no file changes recorded'
402
- : `recorded changes: ${artifacts.files.map((f) => f.path).join(', ')}`,
403
- message: `Final Report says it changed ${path} but the recorded file changes don't include it.`,
404
- });
405
- }
406
- }
407
- return out;
408
- }
409
- function matchFilesCountClaim(summary) {
410
- // "X files changed" / "X files modified" / "X files were touched"
411
- const m1 = summary.match(/\b(\d+)\s+files?\s+(were\s+)?(?:changed|modified|touched|written|created|added|updated)\b/i);
412
- if (m1)
413
- return { quote: m1[0], value: parseInt(m1[1], 10) };
414
- // "changed X files" / "modified X files"
415
- const m2 = summary.match(/\b(?:changed|modified|touched|wrote|created|added|updated)\s+(\d+)\s+files?\b/i);
416
- if (m2)
417
- return { quote: m2[0], value: parseInt(m2[1], 10) };
418
- return null;
419
- }
420
- /**
421
- * The "verified requires git" SURFACE invariant, single-sourced (R11 / M2).
422
- * A rendered verifier panel must never show 'verified' while its git-tracked
423
- * flag is off: without git-tracked artifacts the file-claims are unverifiable
424
- * (see verifyNamedFileClaims's undefined-files early-return above), so a green
425
- * "verified" there would be an overclaim. The /verify dogfood page (H1 in
426
- * verify-page.ts) interpolates THIS function's compiled source into its client
427
- * script and executes it — it no longer re-encodes the rule, so the rule can't
428
- * drift between the engine and the harness. Keep it self-contained (no captured
429
- * scope, no imports) — its .toString() must run standalone in a browser.
430
- */
431
- export function violatesGitInvariant(verdict, gitTracked) {
432
- return verdict === 'verified' && !gitTracked;
433
- }
434
- /* ──────────────────────────────────────────────────────────────────────── */
435
- /* Verdict summary */
436
- /* ──────────────────────────────────────────────────────────────────────── */
437
- function buildVerdictSummary(status, findings, observedOnly = false) {
438
- if (status === 'verified')
439
- return observedOnly
440
- ? "codehere re-ran the test suite and it passed — observed by codehere, not just the agent's word."
441
- : 'Claims match the recorded reality. No gaps detected.';
442
- if (status === 'no-report')
443
- return 'No Final Report to verify.';
444
- if (status === 'nothing-to-verify')
445
- return "The report makes no checkable claim, so there's nothing to verify against the recorded reality.";
446
- const failCount = findings.filter((f) => f.severity === 'fail').length;
447
- const warnCount = findings.filter((f) => f.severity === 'warn').length;
448
- if (failCount > 0 && warnCount > 0) {
449
- return `${failCount} claim${failCount === 1 ? '' : 's'} contradict the recorded reality, plus ${warnCount} unverifiable claim${warnCount === 1 ? '' : 's'}.`;
450
- }
451
- if (failCount > 0) {
452
- return `${failCount} claim${failCount === 1 ? '' : 's'} contradict the recorded reality.`;
453
- }
454
- return `${warnCount} unverifiable claim${warnCount === 1 ? '' : 's'}.`;
455
- }
456
- //# sourceMappingURL=verify-claim.js.map