codehere 0.8.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +269 -25
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +3 -2
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +90 -15
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +133 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +22 -10
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +0 -10
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +132 -1
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +164 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +238 -21
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1462 -380
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4214
- package/dist/infrastructure/protocols/verify-page.js +849 -155
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +150 -3
- package/dist/infrastructure/storage/project-thread-reader.js +261 -24
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +26 -14
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - Report says "all tests green" but artifacts.tests.failed > 0 → MISMATCH
|
|
13
13
|
* - Report says "no files changed" but artifacts.files has entries → MISMATCH
|
|
14
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
|
|
15
18
|
*
|
|
16
19
|
* NOT a substitute for human review · this is a tripwire for the easy lies.
|
|
17
20
|
* The verifier is intentionally conservative: only flag claims it can
|
|
@@ -43,13 +46,104 @@ export function verifyClaim(input) {
|
|
|
43
46
|
const findings = [
|
|
44
47
|
...verifyTestsClaim(input.summary, input.artifacts),
|
|
45
48
|
...verifyFilesClaim(input.summary, input.artifacts),
|
|
49
|
+
...verifyNamedFileClaims(input.summary, input.artifacts),
|
|
46
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
|
+
}
|
|
47
72
|
const hasFail = findings.some((f) => f.severity === 'fail');
|
|
48
73
|
const hasNon = findings.length > 0;
|
|
49
|
-
|
|
50
|
-
|
|
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);
|
|
51
91
|
return { status, findings, summary };
|
|
52
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
|
+
}
|
|
53
147
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
54
148
|
/* Tests claim verification */
|
|
55
149
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -61,7 +155,14 @@ export function verifyClaim(input) {
|
|
|
61
155
|
export function verifyTestsClaim(summary, artifacts) {
|
|
62
156
|
const out = [];
|
|
63
157
|
const lower = summary.toLowerCase();
|
|
64
|
-
|
|
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;
|
|
65
166
|
// Extract numeric "X tests passed" / "X passed" / "X of Y" patterns.
|
|
66
167
|
// We deliberately keep the regex tight to avoid false positives on prose
|
|
67
168
|
// like "this is the 5th attempt".
|
|
@@ -242,6 +343,69 @@ export function verifyFilesClaim(summary, artifacts) {
|
|
|
242
343
|
}
|
|
243
344
|
return out;
|
|
244
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
|
+
}
|
|
245
409
|
function matchFilesCountClaim(summary) {
|
|
246
410
|
// "X files changed" / "X files modified" / "X files were touched"
|
|
247
411
|
const m1 = summary.match(/\b(\d+)\s+files?\s+(were\s+)?(?:changed|modified|touched|written|created|added|updated)\b/i);
|
|
@@ -253,14 +417,32 @@ function matchFilesCountClaim(summary) {
|
|
|
253
417
|
return { quote: m2[0], value: parseInt(m2[1], 10) };
|
|
254
418
|
return null;
|
|
255
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
|
+
}
|
|
256
434
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
257
435
|
/* Verdict summary */
|
|
258
436
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
259
|
-
function buildVerdictSummary(status, findings) {
|
|
437
|
+
function buildVerdictSummary(status, findings, observedOnly = false) {
|
|
260
438
|
if (status === 'verified')
|
|
261
|
-
return
|
|
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.';
|
|
262
442
|
if (status === 'no-report')
|
|
263
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.";
|
|
264
446
|
const failCount = findings.filter((f) => f.severity === 'fail').length;
|
|
265
447
|
const warnCount = findings.filter((f) => f.severity === 'warn').length;
|
|
266
448
|
if (failCount > 0 && warnCount > 0) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for the tamper-evident-aspiring audit chain.
|
|
3
|
+
*
|
|
4
|
+
* These are pure data shapes — no I/O, no clock, no dependencies. They live in
|
|
5
|
+
* `domain/` (not `infrastructure/hooks/`) so the dependency rule points inward:
|
|
6
|
+
* the hook, the daemon, and the use-cases all import these DOWN from domain,
|
|
7
|
+
* rather than reaching up into a hook file for the core vocabulary.
|
|
8
|
+
* Moved here 2026-06-09 (spec R14 / ARCHITECTURE.md M1).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -82,10 +82,4 @@ function findPackageRoot(startPath) {
|
|
|
82
82
|
}
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Get codebase info for a given file path
|
|
87
|
-
*/
|
|
88
|
-
export function getCodebaseForFile(filepath) {
|
|
89
|
-
return findCodebaseRoot(dirname(filepath));
|
|
90
|
-
}
|
|
91
85
|
//# sourceMappingURL=codebase-detector.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for a task's observable shape — status, progress steps, and the
|
|
3
|
+
* end-of-task artifacts. Pure data: no I/O, no behavior, no dependencies. These
|
|
4
|
+
* live in `domain/` so application + infrastructure import them DOWN, rather than
|
|
5
|
+
* reaching up into an adapter file for the core vocabulary.
|
|
6
|
+
* Moved here 2026-06-09 (spec R14 / ARCHITECTURE.md M1) from
|
|
7
|
+
* infrastructure/orchestration/agent-adapter.ts. The `TaskSession` runtime handle
|
|
8
|
+
* (which has behavior: subscribe/kill/appendMessage) and the `AgentAdapter` port
|
|
9
|
+
* stay with the adapters; only the pure data shapes move here.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* R13/M3 — THE one owned transition for `artifacts.tests`. Before this, the
|
|
13
|
+
* exit-path re-run mutated the artifact inline in the session manager: the
|
|
14
|
+
* exit-code-authoritative merge, the claim-preservation rule, and the
|
|
15
|
+
* silent-blank-when-unobservable gap all lived in untyped infra code. This is
|
|
16
|
+
* now the only writer of the post-exit tests artifact.
|
|
17
|
+
*
|
|
18
|
+
* Mutates `artifacts` IN PLACE by design: the live TaskSession exposes
|
|
19
|
+
* `artifacts` as a readonly getter over one shared object that the API layer
|
|
20
|
+
* re-reads each poll — replacing the reference would detach the live verdict.
|
|
21
|
+
*
|
|
22
|
+
* Rules (verbatim from the prior inline code, now pinned by unit tests):
|
|
23
|
+
* - exit code is authoritative: a non-zero exit with no parseable count still
|
|
24
|
+
* forces failed >= 1, so a "tests pass" claim that didn't pass gets caught;
|
|
25
|
+
* - the agent's ORIGINAL scrape is preserved as `claimed` so the verifier can
|
|
26
|
+
* still catch a prose-vs-own-stdout self-contradiction;
|
|
27
|
+
* - couldn't run → the explicit reason lands in `testsUnobserved` instead of
|
|
28
|
+
* leaving the artifact silently blank (the R5 leftover).
|
|
29
|
+
*/
|
|
30
|
+
export function applyTestObservation(artifacts, observation) {
|
|
31
|
+
if (!observation.ran) {
|
|
32
|
+
artifacts.testsUnobserved = observation.skippedReason ?? 'could not re-run the tests';
|
|
33
|
+
return 'unobserved';
|
|
34
|
+
}
|
|
35
|
+
const claimed = artifacts.tests;
|
|
36
|
+
let failed = observation.failed ?? 0;
|
|
37
|
+
if (observation.exitCode !== 0 && failed === 0)
|
|
38
|
+
failed = 1;
|
|
39
|
+
// LOW #28 (atomic audit) · exit 0 with no parseable count reuses the
|
|
40
|
+
// AGENT'S claimed number. The exit code is ground truth (the suite ran and
|
|
41
|
+
// passed) but the COUNT is not — say which is which (countSource) so the
|
|
42
|
+
// surface can label "re-run ✓ · count as reported" instead of implying the
|
|
43
|
+
// number itself was observed.
|
|
44
|
+
const countParsed = observation.passed !== undefined;
|
|
45
|
+
const passed = observation.passed ?? (observation.exitCode === 0 ? (claimed?.passed ?? 0) : 0);
|
|
46
|
+
artifacts.tests = {
|
|
47
|
+
passed,
|
|
48
|
+
failed,
|
|
49
|
+
observed: true,
|
|
50
|
+
...(countParsed ? {} : { countSource: 'claimed' }),
|
|
51
|
+
...(claimed ? { claimed: { passed: claimed.passed, failed: claimed.failed } } : {}),
|
|
52
|
+
...(observation.command ? { command: observation.command } : {}),
|
|
53
|
+
...(observation.exitCode !== undefined ? { exitCode: observation.exitCode } : {}),
|
|
54
|
+
...(observation.durationMs !== undefined ? { durationMs: observation.durationMs } : {}),
|
|
55
|
+
};
|
|
56
|
+
delete artifacts.testsUnobserved; // an observed result supersedes any earlier couldn't-observe
|
|
57
|
+
return 'observed';
|
|
58
|
+
}
|
|
59
|
+
/** Derive the typed completeness view from a session's recorded parts. */
|
|
60
|
+
export function describeCompleted(input) {
|
|
61
|
+
const a = input.artifacts;
|
|
62
|
+
return {
|
|
63
|
+
sessionId: input.sessionId,
|
|
64
|
+
agent: input.agent,
|
|
65
|
+
status: input.status,
|
|
66
|
+
completedAt: input.completedAt,
|
|
67
|
+
durationMs: input.durationMs,
|
|
68
|
+
steps: input.steps ?? [],
|
|
69
|
+
files: a?.files
|
|
70
|
+
? { present: a.files }
|
|
71
|
+
: {
|
|
72
|
+
absent: a?.noGitTracking
|
|
73
|
+
? "ran in a non-git folder — codehere can't see what changed"
|
|
74
|
+
: 'no file changes recorded',
|
|
75
|
+
},
|
|
76
|
+
tests: a?.tests
|
|
77
|
+
? { present: a.tests }
|
|
78
|
+
: { absent: a?.testsUnobserved ?? 'not test-relevant — no claim made, no test files touched' },
|
|
79
|
+
report: input.finalReport?.summary
|
|
80
|
+
? { present: { summary: input.finalReport.summary } }
|
|
81
|
+
: { absent: input.failedReport ? 'report generation failed' : 'no written report' },
|
|
82
|
+
audit: input.auditRecon
|
|
83
|
+
? { present: input.auditRecon }
|
|
84
|
+
: {
|
|
85
|
+
// Precise: reconciliation RUNS in the daemon at completion but is
|
|
86
|
+
// held in memory, not persisted — so a later reader can't see it.
|
|
87
|
+
absent: 'reconciliation not visible here — it runs in the daemon that observed the exit and is not persisted',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=task.js.map
|
package/dist/error-handler.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Ensures 24/7 operation with automatic recovery
|
|
2
|
+
* Error classification + circuit breaker for API calls.
|
|
4
3
|
*/
|
|
5
4
|
export var ErrorType;
|
|
6
5
|
(function (ErrorType) {
|
|
@@ -73,104 +72,17 @@ export function classifyError(error) {
|
|
|
73
72
|
// Default to retryable
|
|
74
73
|
return ErrorType.RETRYABLE;
|
|
75
74
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Check if error is retryable
|
|
78
|
-
* Rate limit errors (429) should NOT be retried - they indicate quota exceeded
|
|
79
|
-
*/
|
|
80
|
-
export function isRetryable(error) {
|
|
81
|
-
// Check for 429 status code directly - don't retry monthly quota limits
|
|
82
|
-
if (error && typeof error === 'object') {
|
|
83
|
-
const err = error;
|
|
84
|
-
// Cohere SDK error structure
|
|
85
|
-
if (err.statusCode === 429 || err.status === 429) {
|
|
86
|
-
return false; // Monthly quota exceeded - retrying won't help
|
|
87
|
-
}
|
|
88
|
-
// Check error name/class (Cohere SDK uses TooManyRequestsError)
|
|
89
|
-
const errorName = err.constructor?.name || err.name || '';
|
|
90
|
-
if (errorName.includes('TooManyRequests') || errorName === 'TooManyRequestsError') {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
// Check error message for trial key limit message
|
|
94
|
-
const errorMsg = err.message || String(err);
|
|
95
|
-
if (errorMsg.includes('Trial key') && errorMsg.includes('1000 API calls')) {
|
|
96
|
-
return false; // Monthly quota exceeded
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const errorType = classifyError(error);
|
|
100
|
-
// Don't retry rate limit errors - they indicate quota/limit exceeded
|
|
101
|
-
if (errorType === ErrorType.RATE_LIMIT) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
return [ErrorType.RETRYABLE, ErrorType.NETWORK, ErrorType.API_ERROR].includes(errorType);
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Calculate exponential backoff delay
|
|
108
|
-
*/
|
|
109
|
-
export function calculateBackoff(attempt, baseDelay = 1000, maxDelay = 60000) {
|
|
110
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
111
|
-
// Add jitter to prevent thundering herd
|
|
112
|
-
const jitter = Math.random() * 0.3 * delay;
|
|
113
|
-
return Math.floor(delay + jitter);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Retry with exponential backoff
|
|
117
|
-
*/
|
|
118
|
-
export async function retryWithBackoff(operation, options = {}) {
|
|
119
|
-
const { maxRetries = 5, baseDelay = 1000, maxDelay = 60000, operationName = 'operation', onRetry } = options;
|
|
120
|
-
let lastError;
|
|
121
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
122
|
-
try {
|
|
123
|
-
return await operation();
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
lastError = error;
|
|
127
|
-
// Don't retry on fatal errors
|
|
128
|
-
if (!isRetryable(error)) {
|
|
129
|
-
throw new EnterpriseError(`Fatal error in ${operationName}: ${error instanceof Error ? error.message : String(error)}`, classifyError(error), {
|
|
130
|
-
operation: operationName,
|
|
131
|
-
attempt,
|
|
132
|
-
maxRetries,
|
|
133
|
-
timestamp: new Date().toISOString(),
|
|
134
|
-
errorType: classifyError(error),
|
|
135
|
-
originalError: error instanceof Error ? error : new Error(String(error)),
|
|
136
|
-
}, false);
|
|
137
|
-
}
|
|
138
|
-
// Last attempt, throw error
|
|
139
|
-
if (attempt === maxRetries) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
// Calculate delay and wait
|
|
143
|
-
const delay = calculateBackoff(attempt, baseDelay, maxDelay);
|
|
144
|
-
if (onRetry) {
|
|
145
|
-
onRetry(attempt + 1, error);
|
|
146
|
-
}
|
|
147
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
// All retries exhausted
|
|
151
|
-
throw new EnterpriseError(`Operation ${operationName} failed after ${maxRetries} retries: ${lastError instanceof Error ? lastError.message : String(lastError)}`, classifyError(lastError), {
|
|
152
|
-
operation: operationName,
|
|
153
|
-
attempt: maxRetries,
|
|
154
|
-
maxRetries,
|
|
155
|
-
timestamp: new Date().toISOString(),
|
|
156
|
-
errorType: classifyError(lastError),
|
|
157
|
-
originalError: lastError instanceof Error ? lastError : new Error(String(lastError)),
|
|
158
|
-
}, false);
|
|
159
|
-
}
|
|
160
75
|
/**
|
|
161
76
|
* Circuit breaker pattern for API calls
|
|
162
77
|
*/
|
|
163
78
|
export class CircuitBreaker {
|
|
164
79
|
threshold;
|
|
165
|
-
timeout;
|
|
166
80
|
resetTimeout;
|
|
167
81
|
failures = 0;
|
|
168
82
|
lastFailureTime = 0;
|
|
169
83
|
state = 'CLOSED';
|
|
170
|
-
constructor(threshold = 5,
|
|
171
|
-
resetTimeout = 30000) {
|
|
84
|
+
constructor(threshold = 5, resetTimeout = 30000) {
|
|
172
85
|
this.threshold = threshold;
|
|
173
|
-
this.timeout = timeout;
|
|
174
86
|
this.resetTimeout = resetTimeout;
|
|
175
87
|
}
|
|
176
88
|
async execute(operation) {
|
|
@@ -223,29 +135,5 @@ export class CircuitBreaker {
|
|
|
223
135
|
/**
|
|
224
136
|
* Global circuit breaker for Cohere API
|
|
225
137
|
*/
|
|
226
|
-
export const cohereCircuitBreaker = new CircuitBreaker(5,
|
|
227
|
-
/**
|
|
228
|
-
* Graceful error handler with recovery strategies
|
|
229
|
-
*/
|
|
230
|
-
export function handleError(error, context) {
|
|
231
|
-
const errorType = classifyError(error);
|
|
232
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
233
|
-
const fullContext = {
|
|
234
|
-
operation: context.operation || 'unknown',
|
|
235
|
-
filepath: context.filepath,
|
|
236
|
-
attempt: context.attempt,
|
|
237
|
-
maxRetries: context.maxRetries,
|
|
238
|
-
timestamp: new Date().toISOString(),
|
|
239
|
-
errorType,
|
|
240
|
-
originalError: error instanceof Error ? error : new Error(errorMessage),
|
|
241
|
-
metadata: context.metadata,
|
|
242
|
-
};
|
|
243
|
-
// Log error with full context
|
|
244
|
-
console.error('Enterprise Error:', {
|
|
245
|
-
message: errorMessage,
|
|
246
|
-
type: errorType,
|
|
247
|
-
context: fullContext,
|
|
248
|
-
});
|
|
249
|
-
throw new EnterpriseError(errorMessage, errorType, fullContext, isRetryable(error));
|
|
250
|
-
}
|
|
138
|
+
export const cohereCircuitBreaker = new CircuitBreaker(5, 30000);
|
|
251
139
|
//# sourceMappingURL=error-handler.js.map
|