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.
- package/README.md +36 -192
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- 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
|