codehere 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -193
- 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,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* export-aat — map codehere's hash-chained audit log to the IETF
|
|
3
|
-
* draft-sharif-agent-audit-trail-00 (AAT) record structure, JSONL (the
|
|
4
|
-
* draft's RECOMMENDED primary export format).
|
|
5
|
-
*
|
|
6
|
-
* Why: the 2026 wave-4 swarm discourse converged on audit-trail
|
|
7
|
-
* standardization (EU AI Act Art. 12 logging lands Aug 2026; the AAT draft
|
|
8
|
-
* maps to SOC 2 / ISO 42001). codehere's chain already carries the
|
|
9
|
-
* substance — agent identity, action, outcome, risk, hash-chained
|
|
10
|
-
* SHA-256 linking — so speaking the emerging standard is a mapping, not a
|
|
11
|
-
* rebuild.
|
|
12
|
-
*
|
|
13
|
-
* HONEST MAPPING NOTES (each also stated in the export header record):
|
|
14
|
-
* - trust_level is "L0" for every record: AAT's levels describe the
|
|
15
|
-
* AGENT's cryptographic identity, and codehere agents carry none (the
|
|
16
|
-
* DAEMON's keystore signs the chain tip — that is recorder identity,
|
|
17
|
-
* not agent identity; claiming L1 would overstate).
|
|
18
|
-
* - prev_hash/record hashing is codehere's sorted-key JSON canon over the
|
|
19
|
-
* original entries — same SHA-256 chain idea as the draft, but NOT
|
|
20
|
-
* RFC 8785 JCS, and the hashes commit to the ORIGINAL codehere entries,
|
|
21
|
-
* not to the AAT projection. Verify against the source chain.
|
|
22
|
-
* - input_hash is SHA-256 of the recorded toolInput; the raw input stays
|
|
23
|
-
* local (the draft's privacy-by-design posture).
|
|
24
|
-
* - record_id is the entry's own content hash formatted as a UUID-shaped id —
|
|
25
|
-
* deterministic, no invented identity (UUID-shaped, not an RFC 4122 UUID).
|
|
26
|
-
*/
|
|
27
|
-
import { createHash } from 'crypto';
|
|
28
|
-
import { existsSync, readFileSync } from 'fs';
|
|
29
|
-
function hashToUuid(hexHash) {
|
|
30
|
-
const h = hexHash.padEnd(32, '0').slice(0, 32);
|
|
31
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
32
|
-
}
|
|
33
|
-
function agentOf(entry) {
|
|
34
|
-
try {
|
|
35
|
-
const ti = JSON.parse(entry.toolInput);
|
|
36
|
-
if (typeof ti.agent === 'string' && ti.agent)
|
|
37
|
-
return ti.agent;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
/* non-JSON toolInput */
|
|
41
|
-
}
|
|
42
|
-
return 'unrecorded';
|
|
43
|
-
}
|
|
44
|
-
export function exportAat(auditFile, now = new Date().toISOString()) {
|
|
45
|
-
const out = {
|
|
46
|
-
header: {
|
|
47
|
-
format: 'codehere-aat-export',
|
|
48
|
-
source_format: 'draft-sharif-agent-audit-trail-00',
|
|
49
|
-
exported_at: now,
|
|
50
|
-
record_count: 0,
|
|
51
|
-
notes: [
|
|
52
|
-
'trust_level is L0 for all records: AAT trust levels describe the AGENT’s cryptographic identity and codehere agents carry none — the daemon’s keystore signs the chain tip (recorder identity, not agent identity).',
|
|
53
|
-
'prev_hash values are codehere’s SHA-256 chain over the ORIGINAL entries (sorted-key JSON canonicalization, not RFC 8785 JCS); verify against the source chain, not this projection.',
|
|
54
|
-
'input_hash is SHA-256 of the recorded tool input; raw inputs stay local (privacy by design).',
|
|
55
|
-
'record_id is the source entry’s content hash formatted as a UUID-shaped id — deterministic, not an invented identity.',
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
records: [],
|
|
59
|
-
skippedLegacy: 0,
|
|
60
|
-
};
|
|
61
|
-
if (!existsSync(auditFile))
|
|
62
|
-
return out;
|
|
63
|
-
for (const line of readFileSync(auditFile, 'utf-8').split('\n')) {
|
|
64
|
-
const raw = line.trim();
|
|
65
|
-
if (!raw)
|
|
66
|
-
continue;
|
|
67
|
-
let e;
|
|
68
|
-
try {
|
|
69
|
-
e = JSON.parse(raw);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
out.skippedLegacy++;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (typeof e.seq !== 'number' || typeof e.entryHash !== 'string' || typeof e.prevHash !== 'string') {
|
|
76
|
-
out.skippedLegacy++; // pre-chain legacy — no hash-chained position to export
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const rec = {
|
|
80
|
-
record_id: hashToUuid(e.entryHash),
|
|
81
|
-
timestamp: e.timestamp,
|
|
82
|
-
agent_id: `urn:agent:${agentOf(e)}.codehere.local`,
|
|
83
|
-
session_id: e.sessionId,
|
|
84
|
-
action_type: e.toolName === 'pre_prompt' ? 'decision' : 'tool_call',
|
|
85
|
-
action_detail: {
|
|
86
|
-
tool_name: e.toolName,
|
|
87
|
-
input_hash: createHash('sha256')
|
|
88
|
-
.update(e.toolInput ?? '')
|
|
89
|
-
.digest('hex'),
|
|
90
|
-
...(e.writer ? { writer: e.writer } : {}),
|
|
91
|
-
},
|
|
92
|
-
outcome: e.blocked ? 'blocked' : 'success',
|
|
93
|
-
trust_level: 'L0',
|
|
94
|
-
prev_hash: e.prevHash,
|
|
95
|
-
...(typeof e.riskScore === 'number' && e.riskScore > 0 ? { risk_score: Math.min(1, e.riskScore / 100) } : {}),
|
|
96
|
-
};
|
|
97
|
-
out.records.push(rec);
|
|
98
|
-
}
|
|
99
|
-
out.header.record_count = out.records.length;
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
/** Serialize per the draft's recommended JSONL: header line first, then one
|
|
103
|
-
* record per line. */
|
|
104
|
-
export function aatToJsonl(exp) {
|
|
105
|
-
return [JSON.stringify(exp.header), ...exp.records.map((r) => JSON.stringify(r))].join('\n') + '\n';
|
|
106
|
-
}
|
|
107
|
-
//# sourceMappingURL=export-aat.js.map
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Acceptance-criteria agent — REQUEST_METHOD.md stage 2 (the Contract), the
|
|
3
|
-
* keystone of the request method.
|
|
4
|
-
*
|
|
5
|
-
* Before an agent runs, derive 2-5 testable acceptance criteria from the
|
|
6
|
-
* request: the up-front, checkable definition of "done" that verify (stage 5)
|
|
7
|
-
* later grades AGAINST, instead of judging the result post-hoc. This is the one
|
|
8
|
-
* change that turns "route a prompt and audit it after" into "make the agent
|
|
9
|
-
* work to a contract and prove it met it."
|
|
10
|
-
*
|
|
11
|
-
* Mirrors generate-thread-title.ts: single-shot, no streaming, `claude --bare`,
|
|
12
|
-
* ~1 cheap call, degrades SILENTLY to [] when no provider responds (a request
|
|
13
|
-
* with no contract still runs — the method is additive, never a hard gate here).
|
|
14
|
-
*/
|
|
15
|
-
import { spawn } from 'child_process';
|
|
16
|
-
import { isMockMode } from '../../infrastructure/orchestration/mocks/resolve-agent-spawn.js';
|
|
17
|
-
const CRITERIA_TIMEOUT_MS = 15_000;
|
|
18
|
-
const MAX_CRITERIA = 5;
|
|
19
|
-
const PROMPT_TEMPLATE = (firstPrompt, cwd) => `Generate 2 to 5 acceptance criteria for this coding request. Each criterion is one short, testable statement of what "done" means — something a reviewer could check pass/fail.
|
|
20
|
-
|
|
21
|
-
Working directory: ${cwd}
|
|
22
|
-
Request: ${firstPrompt}
|
|
23
|
-
|
|
24
|
-
Rules:
|
|
25
|
-
- 2 to 5 criteria, ONE per line
|
|
26
|
-
- Each is a checkable OUTCOME (e.g. "greet() throws a TypeError when name is not a string"), not a step and not a vibe
|
|
27
|
-
- Ground each in the request only; do not invent scope
|
|
28
|
-
- No numbering, no bullets, no preamble, no trailing period
|
|
29
|
-
- Output ONLY the criteria, one per line
|
|
30
|
-
|
|
31
|
-
Criteria:`;
|
|
32
|
-
/**
|
|
33
|
-
* Parse model output into criteria. Rejects refusal/error-shaped lines (the
|
|
34
|
-
* `claude --bare` call can return rate-limit / auth text on stdout with exit 0,
|
|
35
|
-
* same failure mode the title agent guards against).
|
|
36
|
-
*/
|
|
37
|
-
export function sanitizeCriteria(raw) {
|
|
38
|
-
const bad = /\b(i cannot|i can'?t|i apologi[sz]e|i'?m sorry|as an ai|rate.?limit|unauthorized|request id|api key)\b/i;
|
|
39
|
-
return String(raw || '')
|
|
40
|
-
.split('\n')
|
|
41
|
-
.map((l) => l.replace(/^[\s\-*•–>#\d.)\]]+/, '').trim())
|
|
42
|
-
.filter((l) => l.length >= 3 && l.length <= 140 && !bad.test(l))
|
|
43
|
-
.slice(0, MAX_CRITERIA)
|
|
44
|
-
.map((text) => ({ text, met: null }));
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* The scaling principle (REQUEST_METHOD §"no ceremony tax"): a trivial ask gets
|
|
48
|
-
* no contract. Trivial = very short, or a short read-only lookup. Everything
|
|
49
|
-
* else earns a contract. Keeps "just type and go" free for simple requests and
|
|
50
|
-
* skips the per-request LLM call where it would only be noise.
|
|
51
|
-
*/
|
|
52
|
-
export function isTrivialRequest(prompt) {
|
|
53
|
-
const p = String(prompt || '')
|
|
54
|
-
.trim()
|
|
55
|
-
.toLowerCase();
|
|
56
|
-
if (!p)
|
|
57
|
-
return true;
|
|
58
|
-
const words = p.split(/\s+/).filter(Boolean).length;
|
|
59
|
-
if (words <= 3)
|
|
60
|
-
return true;
|
|
61
|
-
if (words <= 6 && /^(list|ls|show|cat|print|echo|pwd|help|read|open|what is|what'?s)\b/.test(p))
|
|
62
|
-
return true;
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
/** Deterministic mock: grounded-enough criteria for demos + tests, no LLM. */
|
|
66
|
-
export function mockCriteria(firstPrompt) {
|
|
67
|
-
const p = String(firstPrompt || '').toLowerCase();
|
|
68
|
-
const out = [];
|
|
69
|
-
if (/rate.?limit|throttle/.test(p))
|
|
70
|
-
out.push('The rate limit is enforced under load');
|
|
71
|
-
if (/throw|error|validation|validate|guard|invalid/.test(p))
|
|
72
|
-
out.push('Invalid input is rejected with a clear error');
|
|
73
|
-
if (/test/.test(p))
|
|
74
|
-
out.push('Tests are added and pass');
|
|
75
|
-
if (!out.length)
|
|
76
|
-
out.push('The change does what the request asked', 'No existing behaviour regresses');
|
|
77
|
-
return out.slice(0, MAX_CRITERIA).map((text) => ({ text, met: null }));
|
|
78
|
-
}
|
|
79
|
-
function selectProviderBinary() {
|
|
80
|
-
const override = process.env.CODEHERE_CRITERIA_BINARY;
|
|
81
|
-
if (override)
|
|
82
|
-
return [override];
|
|
83
|
-
return ['claude'];
|
|
84
|
-
}
|
|
85
|
-
const GRADE_PROMPT = (criteria, ctx) => `You are grading whether a coding task met its acceptance criteria. For EACH criterion, answer strictly MET or UNMET based only on the evidence. If the evidence does not clearly show a criterion was met, answer UNMET (never guess "met").
|
|
86
|
-
|
|
87
|
-
Request: ${ctx.prompt}
|
|
88
|
-
Result summary: ${ctx.reportSummary || '(none)'}
|
|
89
|
-
Files changed: ${(ctx.files || []).join(', ') || '(none)'}
|
|
90
|
-
|
|
91
|
-
Criteria (one per line):
|
|
92
|
-
${criteria.map((c, i) => `${i + 1}. ${c.text}`).join('\n')}
|
|
93
|
-
|
|
94
|
-
Output exactly one line per criterion, in order, each "MET" or "UNMET" — nothing else.`;
|
|
95
|
-
/** Parse "MET/UNMET" lines back onto the criteria, in order. */
|
|
96
|
-
export function applyGrades(criteria, raw) {
|
|
97
|
-
const verdicts = String(raw || '')
|
|
98
|
-
.split('\n')
|
|
99
|
-
.map((l) => l.trim().toUpperCase())
|
|
100
|
-
.filter((l) => l === 'MET' || l === 'UNMET');
|
|
101
|
-
return criteria.map((c, i) => {
|
|
102
|
-
const v = verdicts[i];
|
|
103
|
-
return { text: c.text, met: v === 'MET' ? true : v === 'UNMET' ? false : (c.met ?? null) };
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Deterministic mock grade — honest by the same rule as the real one: a
|
|
108
|
-
* criterion is met ONLY if the run was verified AND the criterion's salient
|
|
109
|
-
* terms show up in the result; otherwise unmet. No fake green.
|
|
110
|
-
*/
|
|
111
|
-
export function mockGrade(criteria, ctx) {
|
|
112
|
-
const hay = ((ctx.reportSummary || '') + ' ' + (ctx.files || []).join(' ')).toLowerCase();
|
|
113
|
-
return criteria.map((c) => {
|
|
114
|
-
if (!ctx.verdictGreen)
|
|
115
|
-
return { text: c.text, met: false };
|
|
116
|
-
const terms = c.text
|
|
117
|
-
.toLowerCase()
|
|
118
|
-
.split(/\s+/)
|
|
119
|
-
.filter((w) => w.length > 4);
|
|
120
|
-
const hit = terms.filter((t) => hay.includes(t)).length;
|
|
121
|
-
return { text: c.text, met: hit >= Math.max(1, Math.ceil(terms.length * 0.3)) };
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Grade the contract against the result (REQUEST_METHOD stage 5). Runs at
|
|
126
|
-
* completion. Real path is a small single-shot assessment; folding this into
|
|
127
|
-
* the existing final-report call (truly no new call) is a noted follow-up.
|
|
128
|
-
* Returns the criteria unchanged (all pending) if grading can't run — never
|
|
129
|
-
* marks met without evidence.
|
|
130
|
-
*/
|
|
131
|
-
export async function gradeAcceptanceCriteria(criteria, ctx) {
|
|
132
|
-
if (!Array.isArray(criteria) || !criteria.length)
|
|
133
|
-
return criteria || [];
|
|
134
|
-
if (isMockMode())
|
|
135
|
-
return mockGrade(criteria, ctx);
|
|
136
|
-
const prompt = GRADE_PROMPT(criteria, ctx);
|
|
137
|
-
for (const binary of selectProviderBinary()) {
|
|
138
|
-
const raw = await new Promise((resolve) => {
|
|
139
|
-
let out = '';
|
|
140
|
-
let settled = false;
|
|
141
|
-
const done = (r) => {
|
|
142
|
-
if (!settled) {
|
|
143
|
-
settled = true;
|
|
144
|
-
resolve(r);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
const child = spawn(binary, ['--bare', '-p', prompt], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
148
|
-
const to = setTimeout(() => {
|
|
149
|
-
child.kill('SIGTERM');
|
|
150
|
-
done(null);
|
|
151
|
-
}, CRITERIA_TIMEOUT_MS);
|
|
152
|
-
child.on('error', () => {
|
|
153
|
-
clearTimeout(to);
|
|
154
|
-
done(null);
|
|
155
|
-
});
|
|
156
|
-
child.stdout?.on('data', (c) => {
|
|
157
|
-
out += c.toString('utf-8');
|
|
158
|
-
});
|
|
159
|
-
child.on('exit', (code) => {
|
|
160
|
-
clearTimeout(to);
|
|
161
|
-
done(code === 0 ? out : null);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
if (raw)
|
|
165
|
-
return applyGrades(criteria, raw);
|
|
166
|
-
}
|
|
167
|
-
return criteria; // no grade available → stay pending, never fake a green
|
|
168
|
-
}
|
|
169
|
-
function tryProvider(binary, prompt) {
|
|
170
|
-
return new Promise((resolve) => {
|
|
171
|
-
let stdout = '';
|
|
172
|
-
let settled = false;
|
|
173
|
-
const finish = (result) => {
|
|
174
|
-
if (settled)
|
|
175
|
-
return;
|
|
176
|
-
settled = true;
|
|
177
|
-
resolve(result);
|
|
178
|
-
};
|
|
179
|
-
const child = spawn(binary, ['--bare', '-p', prompt], {
|
|
180
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
181
|
-
});
|
|
182
|
-
const timeout = setTimeout(() => {
|
|
183
|
-
child.kill('SIGTERM');
|
|
184
|
-
if (process.env.CODEHERE_DEBUG === '1') {
|
|
185
|
-
console.error(`[criteria-agent] ${binary} timeout after ${CRITERIA_TIMEOUT_MS}ms`);
|
|
186
|
-
}
|
|
187
|
-
finish(null);
|
|
188
|
-
}, CRITERIA_TIMEOUT_MS);
|
|
189
|
-
child.on('error', () => {
|
|
190
|
-
clearTimeout(timeout);
|
|
191
|
-
finish(null);
|
|
192
|
-
});
|
|
193
|
-
child.stdout?.on('data', (chunk) => {
|
|
194
|
-
stdout += chunk.toString('utf-8');
|
|
195
|
-
});
|
|
196
|
-
child.on('exit', (code) => {
|
|
197
|
-
clearTimeout(timeout);
|
|
198
|
-
if (code !== 0) {
|
|
199
|
-
finish(null);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const criteria = sanitizeCriteria(stdout);
|
|
203
|
-
finish(criteria.length ? criteria : null);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* The contract. Returns [] (not null) when no provider responds so callers can
|
|
209
|
-
* store the field unconditionally; an empty contract simply means the request
|
|
210
|
-
* ran without one (never blocks the run).
|
|
211
|
-
*/
|
|
212
|
-
export async function generateAcceptanceCriteria(firstPrompt, cwd) {
|
|
213
|
-
if (isMockMode())
|
|
214
|
-
return mockCriteria(firstPrompt);
|
|
215
|
-
const prompt = PROMPT_TEMPLATE(firstPrompt, cwd);
|
|
216
|
-
for (const binary of selectProviderBinary()) {
|
|
217
|
-
const result = await tryProvider(binary, prompt);
|
|
218
|
-
if (result)
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
|
-
return [];
|
|
222
|
-
}
|
|
223
|
-
//# sourceMappingURL=generate-acceptance-criteria.js.map
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Final Report — the headline deliverable. At task exit, produce a plain-English
|
|
3
|
-
* summary of what the agent did.
|
|
4
|
-
*
|
|
5
|
-
* Engine (locked 2026-06-03, after the real dogfood found NO report on a real
|
|
6
|
-
* conductor run): REUSE THE AGENT SESSION. The agent already narrates what it
|
|
7
|
-
* did (kind:'say' steps — e.g. "Added subtract(a,b) to utils.js:5"); we use that
|
|
8
|
-
* directly. No fresh summarizer spawn.
|
|
9
|
-
*
|
|
10
|
-
* Why the old path was replaced: it spawned a SEPARATE provider binary chain
|
|
11
|
-
* (`claude-free` → `claude`) to summarize. `claude-free` (the FCC/NIM proxy
|
|
12
|
-
* binary) is on PATH but its proxy was down → 30s hang → null; the `claude`
|
|
13
|
-
* fallback also returned null in the conductor exit-handler. Net: a completed
|
|
14
|
-
* real task showed "⚠ Final Report unavailable" — the headline deliverable
|
|
15
|
-
* missing. Reusing the agent's own words is cheaper, has full context, and can't
|
|
16
|
-
* be taken down by an external proxy.
|
|
17
|
-
*
|
|
18
|
-
* Fallback (locked): when the agent left no usable summary (or it looks like a
|
|
19
|
-
* provider failure), build a DETERMINISTIC report from the recorded steps +
|
|
20
|
-
* artifacts. It states ONLY verified facts, says plainly that no written summary
|
|
21
|
-
* was available, and NEVER implies success — so a completed task always shows a
|
|
22
|
-
* deliverable, and it's honest about which kind it is (see `source`).
|
|
23
|
-
*/
|
|
24
|
-
// Generous cap — the old 1600 clipped reports mid-code-block (a code fence +
|
|
25
|
-
// prose easily exceeds it). High enough to never trim a real report's code,
|
|
26
|
-
// still bounded against a runaway agent dump.
|
|
27
|
-
const MAX_SUMMARY_CHARS = 20000;
|
|
28
|
-
/**
|
|
29
|
-
* UAT-003 — patterns indicating the agent's own text is actually a network /
|
|
30
|
-
* auth / quota failure, not a real summary. Such "summaries" must NOT be shown
|
|
31
|
-
* as the report; they fall through to the deterministic fallback.
|
|
32
|
-
*/
|
|
33
|
-
const NETWORK_FAILURE_PATTERNS = /(?:all connection attempts failed|ECONNREFUSED|ENOTFOUND|fetch failed|network error|request id:\s*req_|connection timed out|getaddrinfo)/i;
|
|
34
|
-
function isLikelyNetworkFailureSummary(text) {
|
|
35
|
-
return NETWORK_FAILURE_PATTERNS.test(text);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* UAT (real-mode dogfood 2026-06-13) — some CLIs (aider) have no narration
|
|
39
|
-
* channel; their captured "say" text is setup/housekeeping chatter, not a
|
|
40
|
-
* summary of the work ("You can skip this check with --no-gitignore", the
|
|
41
|
-
* `.gitignore` housekeeping, the version banner). Shown as the report it reads
|
|
42
|
-
* as broken — and it buries the honest deterministic facts (files changed +
|
|
43
|
-
* trust score). Strip the housekeeping; if nothing real is left, fall through.
|
|
44
|
-
*/
|
|
45
|
-
const TOOL_NOISE_PATTERNS = [
|
|
46
|
-
/you can skip this check with --no-gitignore/gi,
|
|
47
|
-
/added\s+\.?aider\*?\s+to\s+\.?gitignore/gi,
|
|
48
|
-
/\baider v[\d.]+(?:\s*\([^)]*\))?/gi,
|
|
49
|
-
/\binitialized empty git repository[^\n]*/gi,
|
|
50
|
-
// aider session-banner lines (loop-1 sweep 2026-07-06: real runs whose whole
|
|
51
|
-
// "summary" was this chatter passed the length bar and rendered as the report)
|
|
52
|
-
/\bnote: in-chat filenames are always relative to[^\n.]*\.?/gi,
|
|
53
|
-
/\bcur working dir:[^\n]*/gi,
|
|
54
|
-
/\bgit working dir:[^\n]*/gi,
|
|
55
|
-
/\brepo-map can't include[^\n]*/gi,
|
|
56
|
-
/\bhas it been deleted from the file system but not from git\?/gi,
|
|
57
|
-
];
|
|
58
|
-
function stripToolNoise(text) {
|
|
59
|
-
let t = text;
|
|
60
|
-
for (const re of TOOL_NOISE_PATTERNS)
|
|
61
|
-
t = t.replace(re, ' ');
|
|
62
|
-
return t.replace(/\s+/g, ' ').trim();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* ENGINE: the agent's own narration. kind:'say' steps are the words the agent
|
|
66
|
-
* already emitted (reasoning + wrap-up). Joined newest-context-last, they ARE
|
|
67
|
-
* the summary — no extra spawn. Returns null if the agent said nothing usable.
|
|
68
|
-
*/
|
|
69
|
-
function extractAgentSummary(steps) {
|
|
70
|
-
const says = steps
|
|
71
|
-
.filter((s) => s.kind === 'say' && typeof s.label === 'string' && s.label.trim().length > 0)
|
|
72
|
-
.map((s) => s.label.trim());
|
|
73
|
-
if (!says.length)
|
|
74
|
-
return null;
|
|
75
|
-
// Inverted pyramid (loop-1 lesson 2026-07-06): agents narrate chronologically,
|
|
76
|
-
// so joined-in-order reports LEAD with opening narration ("Let me check…")
|
|
77
|
-
// while the finding sits at the end — and every summary-of-the-summary
|
|
78
|
-
// downstream (glance, cards, search snippets) inherits the wrong lead. The
|
|
79
|
-
// agent's LAST say is its wrap-up: put it first, keep the earlier narration
|
|
80
|
-
// below under an honest label. Content intact, order presented, not invented.
|
|
81
|
-
const ordered = says.length > 1 ? [says[says.length - 1], 'Earlier notes:', ...says.slice(0, -1)] : says;
|
|
82
|
-
const text = ordered.join('\n\n').slice(0, MAX_SUMMARY_CHARS);
|
|
83
|
-
// A "summary" that is only tool housekeeping (no real prose left after
|
|
84
|
-
// stripping it) is NOT a summary → let the deterministic fallback give the
|
|
85
|
-
// honest facts (files changed + trust score) instead of chatter.
|
|
86
|
-
if (stripToolNoise(text).length < 12)
|
|
87
|
-
return null;
|
|
88
|
-
// Too short to be a real summary (e.g. a one-word ack) → let the fallback give facts.
|
|
89
|
-
return text.length >= 12 ? text : null;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* FALLBACK: deterministic, honest. Facts only, from steps + artifacts. Says
|
|
93
|
-
* plainly that no written summary was available. Never implies success.
|
|
94
|
-
*/
|
|
95
|
-
function buildDeterministicReport(input) {
|
|
96
|
-
const files = input.artifacts?.files ?? [];
|
|
97
|
-
const toolSteps = input.steps.filter((s) => s.kind !== 'say');
|
|
98
|
-
const noGit = input.artifacts?.noGitTracking;
|
|
99
|
-
const agentName = input.agent === 'conductor' ? 'the agent codehere chose' : input.agent;
|
|
100
|
-
const parts = [
|
|
101
|
-
`No written summary for this run. ${agentName} didn't leave one, and codehere doesn't fabricate one. Here are the recorded facts.`,
|
|
102
|
-
`The session ran ${input.steps.length} step${input.steps.length === 1 ? '' : 's'}` +
|
|
103
|
-
(toolSteps.length
|
|
104
|
-
? ` (${toolSteps
|
|
105
|
-
.slice(0, 5)
|
|
106
|
-
.map((s) => s.label)
|
|
107
|
-
.join(', ')}${toolSteps.length > 5 ? ', …' : ''})`
|
|
108
|
-
: '') +
|
|
109
|
-
`.`,
|
|
110
|
-
];
|
|
111
|
-
if (noGit) {
|
|
112
|
-
parts.push(`This folder isn't a git repository, so codehere couldn't track which files changed — check the working tree yourself.`);
|
|
113
|
-
}
|
|
114
|
-
else if (files.length) {
|
|
115
|
-
parts.push(`Files changed: ` +
|
|
116
|
-
files
|
|
117
|
-
.map((f) => `${f.path} (+${f.added}/-${f.removed}${f.trust !== undefined ? `, trust ${f.trust}` : ''})`)
|
|
118
|
-
.join('; ') +
|
|
119
|
-
`.`);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
parts.push(`No files were changed.`);
|
|
123
|
-
}
|
|
124
|
-
return { summary: parts.join('\n\n'), generatedAt: Date.now(), durationMs: 0, source: 'deterministic' };
|
|
125
|
-
}
|
|
126
|
-
/** Per-vendor recovery hints — the SPECIFIC auth/quota fix for EACH agent, so a
|
|
127
|
-
* failure report names the right login command + provider, not a claude-centric
|
|
128
|
-
* or generic guess. Commands/URLs mirror the readiness authHints (Phase 1):
|
|
129
|
-
* codex→`codex login`/platform.openai.com, gemini→AI-Studio free tier, aider→its
|
|
130
|
-
* LiteLLM model provider (free Gemini path), opencode→`opencode auth`. */
|
|
131
|
-
const VENDOR_FIX = {
|
|
132
|
-
'claude-code': {
|
|
133
|
-
label: 'Claude',
|
|
134
|
-
auth: 'codehere runs Claude through your *subscription* (your `claude` login) — it ignores `ANTHROPIC_API_KEY` on purpose. Open a terminal, run `claude`, complete `/login`, then rerun.',
|
|
135
|
-
quota: "If you authenticate with an API key, top up at console.anthropic.com; if you're on a Claude subscription, check your plan's usage limits — then rerun.",
|
|
136
|
-
},
|
|
137
|
-
codex: {
|
|
138
|
-
label: 'Codex',
|
|
139
|
-
auth: 'Run `codex login` with a ChatGPT Plus/Pro account (flat, no per-token charge), or set `OPENAI_API_KEY` — then rerun.',
|
|
140
|
-
quota: 'OpenAI quota exhausted — top up at platform.openai.com, or switch to a free agent (gemini / opencode).',
|
|
141
|
-
},
|
|
142
|
-
gemini: {
|
|
143
|
-
label: 'Gemini',
|
|
144
|
-
auth: 'Set `GEMINI_API_KEY` from the free tier (https://aistudio.google.com/apikey), then rerun.',
|
|
145
|
-
quota: "Gemini's free-tier limit was hit — wait for it to reset, raise your AI Studio limits, or switch agents.",
|
|
146
|
-
},
|
|
147
|
-
aider: {
|
|
148
|
-
label: 'Aider',
|
|
149
|
-
// Provider-neutral on purpose: codehere can't know WHICH provider aider was
|
|
150
|
-
// pointed at, so name the credential for each path rather than leading with one.
|
|
151
|
-
auth: 'Aider needs a model + a matching provider key. Set the key for whatever provider you point it at — a billed `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` (with credit), or a free `GEMINI_API_KEY` run as `--model gemini/gemini-2.5-flash` — then rerun.',
|
|
152
|
-
quota: 'The provider key aider is using has no credit — top up that provider, or switch aider to a free one (e.g. a Gemini key with `--model gemini/…`, or a local Ollama model).',
|
|
153
|
-
},
|
|
154
|
-
opencode: {
|
|
155
|
-
label: 'OpenCode',
|
|
156
|
-
auth: 'Run `opencode auth` once to add a provider (a local Ollama model or a free endpoint = $0), then rerun.',
|
|
157
|
-
quota: 'The provider OpenCode is using hit its limit — run `opencode auth` to switch providers, or point at a local Ollama model.',
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
const GENERIC_FIX = {
|
|
161
|
-
label: 'The agent',
|
|
162
|
-
auth: "Sign in to the agent's provider — run `codehere health` to see which agent and how — then rerun.",
|
|
163
|
-
quota: "Top up or switch the agent's provider; `codehere health` shows the per-agent status. codehere is vendor-neutral.",
|
|
164
|
-
};
|
|
165
|
-
/** Read the captured error/output text and name the SPECIFIC failure + the fix
|
|
166
|
-
* FOR THIS AGENT, instead of a generic "network hiccup" or a claude-only guess.
|
|
167
|
-
* Uses input.errorText (codehere:error events) + steps' errorText/output. The
|
|
168
|
-
* detection patterns are vendor-neutral (each adapter forwards the provider's
|
|
169
|
-
* native auth/quota/rate words); the RECOVERY text is selected per input.agent.
|
|
170
|
-
* Returns null when the text doesn't clearly indicate a known failure — honest:
|
|
171
|
-
* we don't fabricate a specific cause we can't see. */
|
|
172
|
-
function diagnoseFailure(input) {
|
|
173
|
-
const blob = (input.errorText ? [input.errorText] : [])
|
|
174
|
-
.concat((input.steps || []).map((s) => `${s.errorText || ''} ${s.output || ''} ${s.kind === 'say' ? s.label : ''}`))
|
|
175
|
-
.join(' ')
|
|
176
|
-
.toLowerCase();
|
|
177
|
-
if (!blob.trim())
|
|
178
|
-
return null;
|
|
179
|
-
// Prefer the direct agent's fix; for a conductor task, fall back to the
|
|
180
|
-
// specialist it delegated to (so a conductor→codex failure names codex's fix,
|
|
181
|
-
// not the generic one); only then the vendor-neutral generic.
|
|
182
|
-
const fix = VENDOR_FIX[input.agent] ?? (input.delegatedTo ? VENDOR_FIX[input.delegatedTo] : undefined) ?? GENERIC_FIX;
|
|
183
|
-
if (/not logged in|please run.{0,6}\/login|\/login|no valid session|unauthorized|invalid api key|api key not valid|401\b/.test(blob)) {
|
|
184
|
-
return `**${fix.label} isn't logged in.** ${fix.auth}`;
|
|
185
|
-
}
|
|
186
|
-
if (/credit balance is too low|insufficient.{0,12}credit|out of credit|quota|billing/.test(blob)) {
|
|
187
|
-
return `**Out of credits / quota.** ${fix.quota}`;
|
|
188
|
-
}
|
|
189
|
-
if (/rate.?limit|\b429\b|too many requests/.test(blob)) {
|
|
190
|
-
return `**Rate limited by the provider.** Wait a minute and rerun, or switch agents — codehere is vendor-neutral.`;
|
|
191
|
-
}
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
function buildErrorRecoveryReport(input, durationMs) {
|
|
195
|
-
const agentName = input.agent === 'conductor' ? 'the agent codehere chose' : input.agent;
|
|
196
|
-
const specific = diagnoseFailure(input);
|
|
197
|
-
// When we can't classify the failure but the agent TOLD us why, show its real
|
|
198
|
-
// words — not a generic three-way guess. A product whose pitch is "show the
|
|
199
|
-
// truth" should surface "You've hit your usage limit — try again Jul 14", not
|
|
200
|
-
// "this usually means a network hiccup, an expired login, or a rate limit".
|
|
201
|
-
const realReason = (input.errorText || '').trim();
|
|
202
|
-
// NEVER claim "no files changed" without checking. A failed run that still
|
|
203
|
-
// edited files must say so — claiming otherwise is a lie the dogfood caught
|
|
204
|
-
// (opencode edited the file, codehere reported "no files were changed").
|
|
205
|
-
const files = (input.artifacts?.files || []).filter(Boolean);
|
|
206
|
-
const filesTail = files.length
|
|
207
|
-
? `Note: ${files.length} file${files.length === 1 ? '' : 's'} ${files.length === 1 ? 'was' : 'were'} already changed before it stopped (${files.slice(0, 5).join(', ')}${files.length > 5 ? ', …' : ''}) — review before retrying.`
|
|
208
|
-
: `codehere couldn't confirm any file changes from this attempt.`;
|
|
209
|
-
const summary = specific
|
|
210
|
-
? `The ${agentName} session couldn't complete.\n\n${specific}\n\n${filesTail}`
|
|
211
|
-
: realReason
|
|
212
|
-
? `The ${agentName} session couldn't complete. It reported:\n\n${realReason}\n\n${filesTail}`
|
|
213
|
-
: `The ${agentName} session ended without completing the task. ` +
|
|
214
|
-
`This usually means a network hiccup, an expired login, or a rate limit on the provider.\n\n` +
|
|
215
|
-
`What to try: check your internet, run \`codehere health\` to confirm reachability + auth, ` +
|
|
216
|
-
`or wait a minute and rerun the same prompt. If it keeps happening, try a different agent ` +
|
|
217
|
-
`from the dropdown — codehere is vendor-neutral on purpose.\n\n${filesTail}`;
|
|
218
|
-
return { summary, generatedAt: Date.now(), durationMs, source: 'error-recovery' };
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Generate a Final Report. Reuses the agent's own summary; falls back to a
|
|
222
|
-
* deterministic, honest facts report. A completed task ALWAYS gets a report
|
|
223
|
-
* (never null for non-error terminal states) — no external dependency.
|
|
224
|
-
*/
|
|
225
|
-
export async function generateFinalReport(input) {
|
|
226
|
-
// No mock short-circuit: under CODEHERE_MOCK the SAME engine runs, so the
|
|
227
|
-
// narration gap is reproduced faithfully — claude/aider (emit `say`) get a
|
|
228
|
-
// rich agent-summary; codex/gemini/opencode (no `say`) get the honest
|
|
229
|
-
// deterministic report, exactly as on a real run. (A prior mock-only
|
|
230
|
-
// short-circuit synthesized a report from steps for every agent, hiding the
|
|
231
|
-
// cross-vendor gap — you couldn't dogfood "codex's report is thin" under mock.)
|
|
232
|
-
// Errored session — deterministic recovery hint, no summarization of nothing.
|
|
233
|
-
if (input.status === 'error') {
|
|
234
|
-
return buildErrorRecoveryReport(input, 0);
|
|
235
|
-
}
|
|
236
|
-
// ENGINE: reuse the agent's own narration (no fresh spawn).
|
|
237
|
-
const agentSummary = extractAgentSummary(input.steps);
|
|
238
|
-
if (agentSummary && !isLikelyNetworkFailureSummary(agentSummary)) {
|
|
239
|
-
return { summary: agentSummary, generatedAt: Date.now(), durationMs: 0, source: 'agent-summary' };
|
|
240
|
-
}
|
|
241
|
-
// FALLBACK: deterministic, honest — always a deliverable, never a fake ✓.
|
|
242
|
-
return buildDeterministicReport(input);
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Pretty-print to terminal. Used by session-manager when a spawned task exits.
|
|
246
|
-
* Wrapped in try/catch upstream — never let a print failure surface.
|
|
247
|
-
*/
|
|
248
|
-
export function printFinalReportToTerminal(input, output) {
|
|
249
|
-
const banner = '─'.repeat(60);
|
|
250
|
-
process.stdout.write('\n');
|
|
251
|
-
process.stdout.write(`${banner}\n`);
|
|
252
|
-
process.stdout.write(`Final Report · ${input.agent} · ${input.sessionId.slice(0, 8)}\n`);
|
|
253
|
-
process.stdout.write(`${banner}\n`);
|
|
254
|
-
process.stdout.write(`${output.summary}\n`);
|
|
255
|
-
process.stdout.write(`${banner}\n`);
|
|
256
|
-
process.stdout.write(`(generated in ${output.durationMs}ms)\n`);
|
|
257
|
-
process.stdout.write('\n');
|
|
258
|
-
}
|
|
259
|
-
//# sourceMappingURL=generate-final-report.js.map
|