codehere 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +36 -192
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,611 +0,0 @@
1
- /**
2
- * Aider adapter — third concrete AgentAdapter.
3
- *
4
- * `aider --message "<prompt>" --yes-always --no-check-update --no-pretty --no-stream`
5
- *
6
- * Aider's output is free-form text (not JSON), so the event log shows raw lines
7
- * instead of semantic tool_use blocks. Step aggregator produces zero steps — but
8
- * Final report still works via git-diff (artifacts-extractor is agent-agnostic)
9
- * and token accounting works through Aider's cost summary line at session end.
10
- *
11
- * Same structural pattern as Claude Code / Codex adapters: emit buffer,
12
- * SIGTERM→SIGKILL escalation, known-error fingerprints. Intentionally keeping
13
- * the duplication for now — if we grow to 5+ adapters, extract a shared helper.
14
- */
15
- import { spawn, execFileSync } from 'child_process';
16
- import { EventEmitter } from 'events';
17
- import { randomUUID } from 'crypto';
18
- import { startRecording } from './cassette-recorder.js';
19
- import { ProgressAggregator } from './progress-aggregator.js';
20
- import { resolveAgentSpawn } from './mocks/resolve-agent-spawn.js';
21
- import { detectOllama } from './detect-ollama.js';
22
- import { applyMemoryPrefix } from './memory-injector.js';
23
- import { mirrorToolCallToAudit } from '../hooks/mirror-tool-call-to-audit.js';
24
- import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
25
- const HEADLESS_FLAGS = [
26
- '--yes-always', // auto-accept all file-edit confirmations
27
- '--no-check-update', // skip aider self-update probe
28
- '--no-pretty', // disable ANSI colors in output
29
- '--no-stream', // stream=false → one chunk per turn, cleaner stdout parsing
30
- '--no-auto-commits', // don't auto-commit — leave file changes for artifacts-extractor
31
- // Aider detects URLs in the message and, under --yes-always, silently
32
- // scrapes them. Our injected context (memory pack, repo docs) legitimately
33
- // contains URLs — live 2026-07-02 a verify run burned its whole turn failing
34
- // to scrape `codehere.uk` (markdown backtick glued onto the URL) and that
35
- // noise became the report. Headless runs never want surprise scraping.
36
- '--no-detect-urls',
37
- ];
38
- // v0.7 HIGH-1 fix — extended ERROR_PATTERNS for the 18+ provider families
39
- // Aider supports. Pre-fix: 4 regexes shaped only for Anthropic + OpenAI text.
40
- // Aider routes through LiteLLM which forwards each provider's native error
41
- // message, so DeepSeek "Insufficient Balance", Ollama "connection refused",
42
- // Bedrock "UnrecognizedClientException", Gemini "API key not valid", and even
43
- // Anthropic's actual "invalid x-api-key" all fell through and never fired
44
- // failover. Per docs/contracts/aider.md §3 + docs/contracts/00-FINDINGS.md HIGH-1.
45
- const ERROR_PATTERNS = [
46
- {
47
- code: 'credit_low',
48
- // Anthropic + OpenAI canonical + DeepSeek ("Insufficient Balance").
49
- // Adjacent words only — a spanning /insufficient.*credit/ combines distant
50
- // benign prose under the de-wrap collapse (2026-06-07 cold review).
51
- regex: /credit balance is too low|insufficient\s+credits?\b|no credits\b|you exceeded your current quota|insufficient\s+balance/i,
52
- message: 'The API key has no credit (or balance is too low).',
53
- recovery: 'Aider needs a billed API key with balance. Top up at your provider (console.anthropic.com / ' +
54
- 'platform.openai.com / platform.deepseek.com), or switch the task to Claude Code / Codex / Gemini ' +
55
- 'which use subscription or free-tier auth.',
56
- },
57
- {
58
- code: 'auth_invalid',
59
- // Anthropic + OpenAI + Anthropic's actual "invalid x-api-key" + Gemini's
60
- // "API key not valid" + Bedrock's "UnrecognizedClientException" + generic.
61
- regex: /invalid api key|invalid x-api-key|api key not valid|authentication\s+(?:request\s+)?failed|401 unauth|please provide your\s+\S{0,20}\s*api key|unrecognizedclientexception|the security token included in the request is invalid/i,
62
- message: 'Aider auth was rejected or missing.',
63
- recovery: 'Check the API key for your model provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, ' +
64
- 'GEMINI_API_KEY, AWS_ACCESS_KEY_ID for Bedrock, etc.). Re-export and retry.',
65
- },
66
- {
67
- code: 'rate_limit',
68
- // Generic 429 + named providers (Gemini RESOURCE_EXHAUSTED, Bedrock ThrottlingException).
69
- // Deliberately requires error-shaped context ("rate limited", "rate limit
70
- // exceeded") — a bare /rate.?limit/ matches the literal filename
71
- // rate-limit.ts in an "Applied edit to …" line and flags a SUCCESSFUL run
72
- // as failed (2026-06-07 batch cold review, HIGH).
73
- regex: /\brate.?limited\b|rate.?limit\s*(?:exceeded|reached|hit|error)|hit.{0,20}rate.?limit|\b429\b|too many requests|resource.?exhausted|throttlingexception/i,
74
- message: 'Aider was rate-limited by the provider.',
75
- recovery: 'Wait a minute and retry, or switch --model via extraArgs to a different provider.',
76
- },
77
- {
78
- code: 'model_unavailable',
79
- // Bounded gap (≤40 chars, no sentence boundary) so "model X not found"
80
- // matches but "model" + distant "not found" in prose doesn't combine.
81
- regex: /model\b[^.!?]{0,40}(not found|not available|unavailable|does not exist)|invalidmodelid/i,
82
- message: 'Aider could not find the model.',
83
- recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
84
- },
85
- {
86
- code: 'network_failure',
87
- // Ollama (local), self-hosted, generic socket errors. Local providers
88
- // typically fail at the transport layer with these strings.
89
- regex: /connection refused|econnrefused|enotfound|fetch failed|getaddrinfo|network is unreachable/i,
90
- message: "Aider couldn't reach the model provider.",
91
- recovery: 'Network or provider endpoint issue. For local Ollama: confirm `ollama serve` is running. ' +
92
- 'For cloud providers: check internet + provider status page.',
93
- },
94
- // 2026-06-07 user-#1 dogfood (session 4b6bc481): aider HARD-WRAPS its error
95
- // body at ~80 cols, so "Your credit\nbalance is too low" lands on two lines.
96
- // The per-line scan above never sees the contiguous phrase → no failover →
97
- // exit 0 → a no-op run presented as "verified". The litellm exception CLASS
98
- // name always rides ONE line (it's the prefix before the wrapped JSON), so
99
- // these are wrap-proof fingerprints. Keep them last so the more specific
100
- // phrase patterns above win when they DO land on a single line.
101
- {
102
- code: 'credit_low',
103
- regex: /litellm\.(?:BadRequestError|InsufficientCreditsError).*(?:credit|balance|quota)/i,
104
- message: 'The API key has no credit (or balance is too low).',
105
- recovery: 'Aider needs a billed API key with balance. Top up at your provider (console.anthropic.com / ' +
106
- 'platform.openai.com / platform.deepseek.com), or switch the task to Claude Code / Codex / Gemini ' +
107
- 'which use subscription or free-tier auth.',
108
- },
109
- {
110
- code: 'auth_invalid',
111
- regex: /litellm\.(?:AuthenticationError|PermissionDeniedError)/i,
112
- message: 'Aider auth was rejected or missing.',
113
- recovery: 'Check the API key for your model provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, ' +
114
- 'GEMINI_API_KEY, AWS_ACCESS_KEY_ID for Bedrock, etc.). Re-export and retry.',
115
- },
116
- {
117
- code: 'rate_limit',
118
- regex: /litellm\.RateLimitError/i,
119
- message: 'Aider was rate-limited by the provider.',
120
- recovery: 'Wait a minute and retry, or switch --model via extraArgs to a different provider.',
121
- },
122
- {
123
- code: 'model_unavailable',
124
- regex: /litellm\.NotFoundError/i,
125
- message: 'Aider could not find the model.',
126
- recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
127
- },
128
- {
129
- code: 'network_failure',
130
- regex: /litellm\.(?:APIConnectionError|Timeout|ServiceUnavailableError)/i,
131
- message: "Aider couldn't reach the model provider.",
132
- recovery: 'Network or provider endpoint issue. For local Ollama: confirm `ollama serve` is running. ' +
133
- 'For cloud providers: check internet + provider status page.',
134
- },
135
- ];
136
- export function scanForKnownError(text) {
137
- // Aider wraps long error bodies across lines; collapse whitespace so a phrase
138
- // split by a hard wrap ("Your credit\nbalance is too low") is matched as one
139
- // contiguous string. Scan BOTH the raw text (preserves single-line patterns
140
- // like litellm class names) and the de-wrapped text (recovers wrapped phrases).
141
- const dewrapped = text.replace(/\s+/g, ' ');
142
- for (const p of ERROR_PATTERNS) {
143
- if (p.regex.test(text) || p.regex.test(dewrapped)) {
144
- return { code: p.code, message: p.message, recovery: p.recovery };
145
- }
146
- }
147
- return null;
148
- }
149
- export function spawnAider(prompt, options = {}) {
150
- const sessionId = randomUUID();
151
- const startedAt = Date.now();
152
- const emitter = new EventEmitter();
153
- const buffer = [];
154
- let statusValue = 'running';
155
- let killRequested = false;
156
- const cwd = options.cwd ?? process.cwd();
157
- // F3 Task 8 — memory injection (same shape as every other adapter).
158
- const injected = applyMemoryPrefix(prompt, cwd, options);
159
- const finalPrompt = injected.prompt;
160
- const snapshot = snapshotCwd(cwd);
161
- // ProgressAggregator parses aider's stdout for `Running ...`, `Applied edit
162
- // to ...`, and `Creating empty file ...` lines and emits Run/Edit/Write
163
- // ProgressSteps. Observability parity (Path B) — see ingestAider() in
164
- // progress-aggregator.ts. Blocking parity (Path A) needs the upstream
165
- // `--pre-action-hook` PR.
166
- const aggregator = new ProgressAggregator('aider');
167
- let stepsSnapshot = [];
168
- let artifactsSnapshot;
169
- let textAccum = '';
170
- // Bounded tail of recent stdout for error scanning. Aider hard-wraps error
171
- // bodies at ~80 cols, so a wrapped phrase spans 2-3 ADJACENT lines — a small
172
- // tail catches it. Scanning the WHOLE accumulated buffer instead would let
173
- // prose from unrelated parts of a successful run combine into a phrase match
174
- // (2026-06-07 batch cold review, HIGH: a session about rate limiting read as
175
- // a rate-limit failure).
176
- let scanTail = '';
177
- const SCAN_TAIL_CHARS = 600;
178
- let usageSnapshot;
179
- let aiderModel; // model aider announced in stdout → per-model cost
180
- let errorEmitted = false;
181
- const emit = (event) => {
182
- buffer.push(event);
183
- emitter.emit('event', event);
184
- if (aggregator.ingest(event)) {
185
- stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
186
- const snapEvt = { type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } };
187
- buffer.push(snapEvt);
188
- emitter.emit('event', snapEvt);
189
- }
190
- // Text accumulator for test-summary parsing + aider cost-line token extraction
191
- if (event.type === 'stdout' && event.data?.line) {
192
- const line = String(event.data.line);
193
- textAccum += line + '\n';
194
- // Aider announces its model: "Main model: claude-3-5-sonnet-... with ...".
195
- if (!aiderModel) {
196
- const mm = line.match(/(?:main )?model:\s*([A-Za-z0-9._/:-]+)/i);
197
- if (mm)
198
- aiderModel = mm[1];
199
- }
200
- // #110 slice 3 — mirror aider tool calls to the audit log. Match
201
- // the same stdout patterns the aggregator parses (per
202
- // learning_aider.md): "Running <cmd>" → Bash, "Applied edit to
203
- // <path>" → Edit, "Creating empty file <path>" → Write.
204
- // Strip ANSI as defense-in-depth (HEADLESS_FLAGS already includes
205
- // --no-pretty but extraArgs could override).
206
- const stripAnsi = (s) => s.replace(/\[[0-9;]*m/g, '');
207
- const clean = stripAnsi(line).trim();
208
- let toolName = '';
209
- let input = '';
210
- let mirrorMatch;
211
- if ((mirrorMatch = clean.match(/^Running\s+(.+)$/))) {
212
- toolName = 'Bash';
213
- input = mirrorMatch[1];
214
- }
215
- else if ((mirrorMatch = clean.match(/^Applied edit to\s+(.+)$/))) {
216
- toolName = 'Edit';
217
- input = mirrorMatch[1];
218
- }
219
- else if ((mirrorMatch = clean.match(/^Creating empty file\s+(.+)$/))) {
220
- toolName = 'Write';
221
- input = mirrorMatch[1];
222
- }
223
- if (toolName && input) {
224
- mirrorToolCallToAudit({ sessionId, agent: 'aider', toolName, toolInput: input, cwd });
225
- }
226
- // Aider emits a cost summary like:
227
- // "Tokens: 1.2k sent, 412 received. Cost: $0.01 request, $0.01 session."
228
- const m = line.match(/Tokens:\s*([\d.]+)([kM]?)\s*sent,\s*([\d.]+)([kM]?)\s*received/i);
229
- if (m) {
230
- const scale = (s) => (s === 'k' ? 1000 : s === 'M' ? 1_000_000 : 1);
231
- const sent = Math.round(parseFloat(m[1]) * scale(m[2]));
232
- const recv = Math.round(parseFloat(m[3]) * scale(m[4]));
233
- usageSnapshot = {
234
- inputTokens: (usageSnapshot?.inputTokens ?? 0) + sent,
235
- outputTokens: (usageSnapshot?.outputTokens ?? 0) + recv,
236
- ...(aiderModel ? { model: aiderModel } : {}),
237
- };
238
- }
239
- }
240
- if (!errorEmitted) {
241
- const data = event.data;
242
- let text = '';
243
- if ((event.type === 'stderr' || event.type === 'stdout') && typeof data?.line === 'string') {
244
- // Scan a bounded RECENT tail, not just this one line and not the whole
245
- // buffer. Aider hard-wraps error bodies across lines (user-#1 dogfood,
246
- // session 4b6bc481), so a single-line scan misses "Your credit\nbalance
247
- // is too low" — but the whole accumulated buffer lets distant prose
248
- // combine into false matches. The wrapped phrase always sits on
249
- // adjacent lines, so the tail window catches it. The de-wrap collapse
250
- // in scanForKnownError joins the wrapped phrase. stderr is scanned
251
- // line-by-line (it isn't accumulated).
252
- if (event.type === 'stdout') {
253
- scanTail = (scanTail + '\n' + data.line).slice(-SCAN_TAIL_CHARS);
254
- text = scanTail;
255
- }
256
- else {
257
- text = data.line;
258
- }
259
- }
260
- if (text) {
261
- const hit = scanForKnownError(text);
262
- if (hit) {
263
- errorEmitted = true;
264
- const errEvent = {
265
- type: 'codehere:error',
266
- timestamp: Date.now(),
267
- data: { code: hit.code, message: hit.message, recovery: hit.recovery },
268
- };
269
- buffer.push(errEvent);
270
- emitter.emit('event', errEvent);
271
- }
272
- }
273
- }
274
- };
275
- // Whenever a codehere:error fires, we treat the session as effectively failed
276
- // even if the subprocess later exits 0. Many CLIs print a logical error to
277
- // stdout then return success — the status pill would otherwise lie.
278
- emitter.on('event', (ev) => {
279
- if (ev.type === 'codehere:error' && statusValue === 'running') {
280
- // Don't overwrite if exit already settled a final status.
281
- // We rely on exit handler to flip running→completed/killed/error; we pre-empt
282
- // that with 'error' here so the status pill is honest from the moment the
283
- // error is detected.
284
- statusValue = 'error';
285
- }
286
- });
287
- const spawnEnv = { ...process.env, ...(options.env ?? {}) };
288
- // Local-first reliable vendor: when the user hasn't pinned a model (no
289
- // AIDER_MODEL / CODEHERE_AIDER_MODEL env, no --model in extraArgs) and Ollama
290
- // is serving a local model, default aider to it. That turns aider into a FREE,
291
- // RELIABLE, LOCAL second vendor (no rate limits, no cloud key, no waste) — the
292
- // unlock for the cross-vendor wedge on a machine that has Ollama. Always
293
- // overridable; opt out with CODEHERE_NO_LOCAL_OLLAMA=1.
294
- const extra = options.extraArgs ?? [];
295
- const modelPinned = !!spawnEnv.AIDER_MODEL ||
296
- !!spawnEnv.CODEHERE_AIDER_MODEL ||
297
- extra.some((a) => a === '--model' || a.startsWith('--model='));
298
- const localArgs = [];
299
- if (!modelPinned && spawnEnv.CODEHERE_NO_LOCAL_OLLAMA !== '1') {
300
- const ollama = detectOllama();
301
- if (ollama.available && ollama.model) {
302
- localArgs.push('--model', `ollama_chat/${ollama.model}`);
303
- if (!spawnEnv.OLLAMA_API_BASE)
304
- spawnEnv.OLLAMA_API_BASE = 'http://localhost:11434';
305
- }
306
- }
307
- let child;
308
- try {
309
- const sp = resolveAgentSpawn('aider', 'aider', ['--message', finalPrompt, ...HEADLESS_FLAGS, ...localArgs, ...extra], finalPrompt);
310
- child = spawn(sp.command, sp.args, {
311
- cwd,
312
- env: spawnEnv,
313
- stdio: ['ignore', 'pipe', 'pipe'],
314
- });
315
- }
316
- catch (err) {
317
- statusValue = 'error';
318
- const msg = err instanceof Error ? err.message : String(err);
319
- queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
320
- return {
321
- sessionId,
322
- startedAt,
323
- prompt,
324
- agent: 'aider',
325
- get status() {
326
- return statusValue;
327
- },
328
- get steps() {
329
- return stepsSnapshot;
330
- },
331
- get artifacts() {
332
- return artifactsSnapshot;
333
- },
334
- subscribe(handler) {
335
- for (const e of buffer)
336
- handler(e);
337
- emitter.on('event', handler);
338
- return () => emitter.off('event', handler);
339
- },
340
- kill() {
341
- /* already not running */
342
- },
343
- };
344
- }
345
- // Organic cassette recording (S2b · capture-first plan) · no-op unless
346
- // CODEHERE_RECORD_CASSETTES=1, so zero overhead by default.
347
- const recorder = startRecording({ adapter: 'aider', sessionId, prompt: finalPrompt, cwd });
348
- let stdoutBuf = '';
349
- child.stdout?.on('data', (chunk) => {
350
- const text = chunk.toString('utf-8');
351
- recorder.onStdout(text);
352
- stdoutBuf += text;
353
- let idx;
354
- while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
355
- const line = stdoutBuf.slice(0, idx).trim();
356
- stdoutBuf = stdoutBuf.slice(idx + 1);
357
- if (!line)
358
- continue;
359
- emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
360
- }
361
- });
362
- child.stderr?.on('data', (chunk) => {
363
- const text = chunk.toString('utf-8');
364
- recorder.onStderr(text);
365
- for (const line of text.split('\n')) {
366
- if (line.trim())
367
- emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
368
- }
369
- });
370
- child.on('exit', (code, signal) => {
371
- recorder.onExit(code ?? -1, signal);
372
- // Preserve 'error' if we already flipped to it on codehere:error.
373
- statusValue =
374
- killRequested || signal !== null ? 'killed' : errorEmitted ? 'error' : code === 0 ? 'completed' : 'error';
375
- const finalizeChanged = aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error');
376
- // Extract artifacts BEFORE emitting steps so the git-diff fallback can run.
377
- if (statusValue === 'completed') {
378
- try {
379
- artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
380
- }
381
- catch {
382
- /* best-effort */
383
- }
384
- }
385
- // Aider parity safety net: if the brittle stdout-parse produced no
386
- // file-action steps but git shows real changes, synthesize them from the
387
- // diff so the timeline + report can't claim "did nothing" on a real edit.
388
- const fallbackChanged = aggregator.applyFileFallback(artifactsSnapshot?.files);
389
- // PM steps BEFORE artifacts BEFORE exit (client closes SSE on exit; order matters).
390
- if (finalizeChanged || fallbackChanged) {
391
- stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
392
- emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
393
- }
394
- if (artifactsSnapshot) {
395
- emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
396
- }
397
- emit({
398
- type: 'exit',
399
- timestamp: Date.now(),
400
- data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
401
- });
402
- });
403
- child.on('error', (err) => {
404
- statusValue = 'error';
405
- emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
406
- });
407
- return {
408
- sessionId,
409
- startedAt,
410
- prompt,
411
- agent: 'aider',
412
- memoryInjection: injected.record,
413
- get status() {
414
- return statusValue;
415
- },
416
- get steps() {
417
- return stepsSnapshot;
418
- },
419
- get artifacts() {
420
- return artifactsSnapshot;
421
- },
422
- subscribe(handler) {
423
- for (const e of buffer)
424
- handler(e);
425
- emitter.on('event', handler);
426
- return () => emitter.off('event', handler);
427
- },
428
- kill() {
429
- if (statusValue !== 'running')
430
- return;
431
- killRequested = true;
432
- child.kill('SIGTERM');
433
- setTimeout(() => {
434
- if (statusValue === 'running') {
435
- try {
436
- child.kill('SIGKILL');
437
- }
438
- catch {
439
- /* already dead */
440
- }
441
- }
442
- }, 3000);
443
- },
444
- canAppendMessage: true,
445
- /**
446
- * v0.6.0 — Slice 2 of REPL persistence. Aider has no per-session UUID
447
- * (unlike claude-code's session_id or codex's thread_id); it persists chat
448
- * history to ./.aider.chat.history.md in the cwd. `--restore-chat-history`
449
- * reads that file before processing the new --message, giving multi-turn
450
- * continuity scoped to the cwd.
451
- *
452
- * Limitation: aider is single-cwd-single-session. Two concurrent aider
453
- * sessions in the same cwd would collide on the same history file. Same
454
- * limitation aider's own users have when running it interactively;
455
- * codehere doesn't make it worse.
456
- *
457
- * Same shape as the other appendMessage implementations: one-shot Q&A,
458
- * returns assistant text + duration, no streaming.
459
- */
460
- appendMessage(text) {
461
- const startedAt = Date.now();
462
- return new Promise((resolve, reject) => {
463
- let stdout = '';
464
- let stderr = '';
465
- // Route follow-ups through the mock seam too — under CODEHERE_MOCK=1 this
466
- // hits the mock REPL, not real aider. (Was a real-spend leak.)
467
- // BUG-1 (E2E 2.4) · pin the follow-up to the SAME model as the original
468
- // spawn (localArgs + extra). Without them, a free ollama session's reply
469
- // fell back to aider's default cloud model + env API key — a silent
470
- // vendor/spend switch, observed live 2026-07-18.
471
- const sp2 = resolveAgentSpawn('aider', 'aider', ['--restore-chat-history', '--message', text, ...HEADLESS_FLAGS, ...localArgs, ...extra], text);
472
- const followup = spawn(sp2.command, sp2.args, {
473
- cwd,
474
- env: spawnEnv,
475
- stdio: ['ignore', 'pipe', 'pipe'],
476
- });
477
- followup.on('error', (err) => reject(err));
478
- followup.stdout?.on('data', (chunk) => {
479
- stdout += chunk.toString('utf-8');
480
- });
481
- followup.stderr?.on('data', (chunk) => {
482
- stderr += chunk.toString('utf-8');
483
- });
484
- followup.on('exit', (code) => {
485
- if (code !== 0) {
486
- reject(new Error(`aider --restore-chat-history exited ${code}: ${stderr.slice(0, 300)}`));
487
- return;
488
- }
489
- // Aider's --no-stream + --no-pretty output is plain text; the
490
- // assistant's reply lands as a top-level paragraph after any
491
- // tool-execution lines. Strip aider's own prefix lines (Added,
492
- // Tokens, Cost, Applied, etc.) and keep the conversational body.
493
- const stripped = stdout
494
- .split('\n')
495
- .filter((l) => {
496
- const t = l.trim();
497
- if (!t)
498
- return false;
499
- if (/^(Added|Removed|Tokens?:|Cost:|Applied|Creating|Running|Commit|Aider|Model:|Main model:|Weak model:)/i.test(t))
500
- return false;
501
- return true;
502
- })
503
- .join('\n')
504
- .trim();
505
- const result = stripped || '(aider returned no conversational output)';
506
- resolve({ result, durationMs: Date.now() - startedAt });
507
- });
508
- });
509
- },
510
- };
511
- }
512
- export const AiderAdapter = {
513
- id: 'aider',
514
- label: 'Aider',
515
- description: 'Best when you already know which file to change and what to change in it. Tight, focused edits — no plan-then-explore phase.',
516
- processPattern: /(?:^|\/)aider(?:\s|$)/,
517
- isInstalled() {
518
- try {
519
- const out = execFileSync('which', ['aider'], { encoding: 'utf-8', timeout: 2000 });
520
- return out.trim().length > 0;
521
- }
522
- catch {
523
- return false;
524
- }
525
- },
526
- spawn(prompt, options) {
527
- return spawnAider(prompt, options);
528
- },
529
- checkReadiness() {
530
- const installed = this.isInstalled();
531
- if (!installed) {
532
- return {
533
- id: this.id,
534
- label: this.label,
535
- state: 'needs_install',
536
- installed: false,
537
- installHint: { command: 'brew install aider', url: 'https://aider.chat/docs/install.html' },
538
- };
539
- }
540
- // Aider has no subscription path, but it routes through LiteLLM — so it can
541
- // run on a BILLED key (Anthropic/OpenAI, needs credit) OR for FREE on a
542
- // Gemini AI-Studio key / DeepSeek / a local Ollama model, by passing
543
- // `--model <provider>/<model>`. The pre-May "needs a BILLED key" hint was
544
- // misleading for free-tier-first users (Phase 1): it sent them to top up a
545
- // card when a free Gemini key would have worked. We can't verify credit
546
- // pre-spawn without an API call, so we surface BOTH paths honestly.
547
- const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
548
- const hasOpenai = !!process.env.OPENAI_API_KEY;
549
- const hasGemini = !!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY;
550
- const hasDeepseek = !!process.env.DEEPSEEK_API_KEY;
551
- const FREE_PATH = 'Free option: set GEMINI_API_KEY (aistudio.google.com/apikey) and run aider with `--model gemini/gemini-2.5-flash`, or point it at a local Ollama model (`--model ollama/…`) — aider routes through LiteLLM, so no paid key is required.';
552
- // A configured model + its matching key = genuinely READY (the user picked a
553
- // working combo, so don't fall through to billed_key_required). AIDER_MODEL
554
- // is aider's own env override (= `--model`); it's how codehere routes aider
555
- // to a free provider, since the task intake doesn't pass --model. A local
556
- // model (ollama/…) needs no cloud key — ready by config, like claude's
557
- // subscription (runtime catches a down endpoint via the network fingerprint).
558
- const model = (process.env.AIDER_MODEL || '').toLowerCase();
559
- const modelReady = (model.startsWith('gemini') && hasGemini) ||
560
- (model.startsWith('deepseek') && hasDeepseek) ||
561
- (model.startsWith('openai') && hasOpenai) ||
562
- (model.startsWith('anthropic') && hasAnthropic) ||
563
- model.startsWith('ollama') ||
564
- model.startsWith('ollama_chat');
565
- if (modelReady) {
566
- return { id: this.id, label: this.label, state: 'ready', installed: true };
567
- }
568
- // Local-first: a serving Ollama model makes aider ready for FREE with no
569
- // cloud key. The spawn path auto-routes aider to it (detect-ollama) when no
570
- // model is pinned — so readiness agrees, or the intake gate would refuse a
571
- // spawn that will actually run. Mirrors the spawn-side guard on the ENV pins
572
- // (AIDER_MODEL / CODEHERE_AIDER_MODEL / CODEHERE_NO_LOCAL_OLLAMA); it cannot
573
- // see a per-task extraArgs `--model` (readiness has never seen extraArgs —
574
- // the product's /api/task intake doesn't pass them, so this is unreachable
575
- // through the surface, just honestly noted).
576
- const localPinnedAway = !!process.env.CODEHERE_AIDER_MODEL || process.env.CODEHERE_NO_LOCAL_OLLAMA === '1';
577
- if (!model && !localPinnedAway && detectOllama().available) {
578
- return { id: this.id, label: this.label, state: 'ready', installed: true };
579
- }
580
- if (!hasAnthropic && !hasOpenai && !hasGemini && !hasDeepseek) {
581
- return {
582
- id: this.id,
583
- label: this.label,
584
- state: 'needs_auth',
585
- installed: true,
586
- authHint: {
587
- command: 'export GEMINI_API_KEY=... # free tier: https://aistudio.google.com/apikey',
588
- detail: `Aider has no subscription path, but it doesn't need a paid key either. ${FREE_PATH} A billed Anthropic/OpenAI key with credit also works.`,
589
- url: 'https://aistudio.google.com/apikey',
590
- },
591
- };
592
- }
593
- // A provider key is present. If it's only the free Gemini/DeepSeek key,
594
- // aider runs at $0 but needs `--model …` to target that provider. If it's a
595
- // billed Anthropic/OpenAI key, that key needs real credit. Either way the
596
- // key's mere presence doesn't guarantee a successful first call.
597
- const onlyFree = !hasAnthropic && !hasOpenai && (hasGemini || hasDeepseek);
598
- return {
599
- id: this.id,
600
- label: this.label,
601
- state: 'billed_key_required',
602
- installed: true,
603
- authHint: {
604
- detail: onlyFree
605
- ? `Free provider key detected (${hasGemini ? 'Gemini' : 'DeepSeek'}). Aider runs at $0 on it, but target it explicitly with \`--model ${hasGemini ? 'gemini/gemini-2.5-flash' : 'deepseek/deepseek-chat'}\` — aider's default model expects an Anthropic/OpenAI key.`
606
- : `API key detected. Aider needs the key to have billing credit (not just presence). If you run on a Claude Code subscription, Aider will fail at the first model call. ${FREE_PATH}`,
607
- },
608
- };
609
- },
610
- };
611
- //# sourceMappingURL=aider-adapter.js.map