codehere 0.2.1 → 0.3.0-rc.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 +88 -68
- package/dist/domain/entities/task.js +4 -4
- package/dist/error-handler.js +4 -9
- package/dist/index.js +253 -1219
- package/dist/infrastructure/config/product-context.js +7 -6
- package/dist/infrastructure/hooks/claude-sdk-hook.js +335 -63
- package/dist/infrastructure/observability/execution-tracer.js +11 -6
- package/dist/infrastructure/orchestration/agent-adapter.js +13 -0
- package/dist/infrastructure/orchestration/agent-installer.js +101 -0
- package/dist/infrastructure/orchestration/agents-config-seed.js +56 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +319 -0
- package/dist/infrastructure/orchestration/artifacts-extractor.js +175 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +352 -0
- package/dist/infrastructure/orchestration/codex-adapter.js +310 -0
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +553 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +275 -0
- package/dist/infrastructure/orchestration/progress-aggregator.js +189 -0
- package/dist/infrastructure/orchestration/readiness.js +106 -0
- package/dist/infrastructure/orchestration/session-manager.js +272 -0
- package/dist/infrastructure/orchestration/session-persistence.js +109 -0
- package/dist/infrastructure/protocols/mcp-helpers.js +10 -30
- package/dist/infrastructure/protocols/mcp-server-impl.js +359 -21
- package/dist/infrastructure/protocols/ui-page.js +1827 -0
- package/dist/infrastructure/security/file-collection.js +115 -0
- package/dist/infrastructure/security/trust-scorer.js +301 -56
- package/dist/infrastructure/storage/task-repository.js +8 -7
- package/dist/monitoring.js +10 -49
- package/dist/presentation/cli/commands/help-command.js +96 -139
- package/dist/presentation/cli/commands/hook-command.js +17 -24
- package/dist/presentation/cli/commands/report-command.js +59 -72
- package/dist/presentation/cli/commands/setup-command.js +4 -4
- package/dist/presentation/cli/commands/status-command.js +334 -43
- package/dist/presentation/cli/commands/trust-command.js +115 -99
- package/dist/presentation/cli/error-display.js +17 -21
- package/dist/ui.js +17 -12
- package/package.json +35 -67
- package/dist/agents/base.js +0 -101
- package/dist/agents/orchestrator-model-driven.js +0 -141
- package/dist/agents/react-orchestrator-agent.js +0 -54
- package/dist/application/agents/execution-agent.js +0 -508
- package/dist/application/agents/multi-agent-orchestrator.js +0 -295
- package/dist/application/agents/planning-agent.js +0 -433
- package/dist/application/agents/validation-agent.js +0 -317
- package/dist/application/architectures/plan-and-act.js +0 -242
- package/dist/application/index.js +0 -12
- package/dist/application/services/agent-factory.js +0 -28
- package/dist/application/services/dependency-container.js +0 -596
- package/dist/application/services/git-context-service.js +0 -83
- package/dist/application/services/intelligent-router.js +0 -934
- package/dist/application/services/tool-executor-service.js +0 -94
- package/dist/application/use-cases/ask-question-use-case.js +0 -338
- package/dist/application/use-cases/edit-file-use-case.js +0 -830
- package/dist/application/use-cases/explain-file-use-case.js +0 -50
- package/dist/application/use-cases/index-codebase-use-case.js +0 -113
- package/dist/application/use-cases/planning-use-case.js +0 -276
- package/dist/application/use-cases/react-orchestration-use-case.js +0 -329
- package/dist/application/use-cases/reflexion-use-case.js +0 -30
- package/dist/application/workflows/deterministic-workflows.js +0 -189
- package/dist/ast-parser.js +0 -331
- package/dist/cache.js +0 -180
- package/dist/chat.js +0 -251
- package/dist/code-analysis.js +0 -327
- package/dist/context.js +0 -187
- package/dist/docs.js +0 -146
- package/dist/domain/entities/code-chunk.js +0 -6
- package/dist/domain/entities/decision.js +0 -9
- package/dist/domain/entities/edit.js +0 -6
- package/dist/domain/entities/knowledge-node.js +0 -9
- package/dist/domain/entities/knowledge-preservation.js +0 -10
- package/dist/domain/entities/memory.js +0 -6
- package/dist/domain/entities/pattern.js +0 -7
- package/dist/domain/entities/plan.js +0 -6
- package/dist/domain/entities/query.js +0 -6
- package/dist/domain/errors/embeddings-not-found-error.js +0 -13
- package/dist/domain/index.js +0 -19
- package/dist/domain/interfaces/ai-service.interface.js +0 -6
- package/dist/domain/interfaces/code-analyzer.interface.js +0 -6
- package/dist/domain/interfaces/decision-repository.interface.js +0 -9
- package/dist/domain/interfaces/embedding-repository.interface.js +0 -6
- package/dist/domain/interfaces/knowledge-graph-repository.interface.js +0 -9
- package/dist/domain/interfaces/knowledge-preservation-repository.interface.js +0 -8
- package/dist/domain/interfaces/memory-repository.interface.js +0 -6
- package/dist/domain/interfaces/pattern-repository.interface.js +0 -6
- package/dist/domain/interfaces/verification-service.interface.js +0 -6
- package/dist/domain/services/cross-team-pattern-sharing.js +0 -125
- package/dist/domain/services/decision-recorder.js +0 -223
- package/dist/domain/services/decision-retriever.js +0 -133
- package/dist/domain/services/environment-mode-manager.js +0 -22
- package/dist/domain/services/graded-memory-activation.js +0 -69
- package/dist/domain/services/knowledge-access.js +0 -132
- package/dist/domain/services/knowledge-extractor.js +0 -247
- package/dist/domain/services/knowledge-graph-builder.js +0 -268
- package/dist/domain/services/knowledge-graph-query.js +0 -187
- package/dist/domain/services/memory-retriever.js +0 -82
- package/dist/domain/services/memory-synthesizer.js +0 -91
- package/dist/domain/services/pattern-extractor.js +0 -442
- package/dist/domain/services/query-intent-classifier.js +0 -140
- package/dist/domain/services/react-loop.js +0 -186
- package/dist/domain/services/risk-tier-detector.js +0 -78
- package/dist/edit.js +0 -319
- package/dist/embed.js +0 -459
- package/dist/formatter.js +0 -168
- package/dist/git.js +0 -198
- package/dist/infrastructure/ai/cohere-ai-service.js +0 -607
- package/dist/infrastructure/ai/command-r-evaluation.js +0 -206
- package/dist/infrastructure/ai/model-config.js +0 -84
- package/dist/infrastructure/ai/provider-ai-service.js +0 -477
- package/dist/infrastructure/cache/embedding-cache.js +0 -175
- package/dist/infrastructure/cache/query-result-cache.js +0 -66
- package/dist/infrastructure/cache/response-cache.js +0 -48
- package/dist/infrastructure/cache/security-scan-cache.js +0 -47
- package/dist/infrastructure/chunking/ast-code-chunker.js +0 -19
- package/dist/infrastructure/chunking/code-aware-chunker.js +0 -397
- package/dist/infrastructure/context/context-compressor.js +0 -329
- package/dist/infrastructure/context/conversation-context.js +0 -128
- package/dist/infrastructure/context/session-manager.js +0 -94
- package/dist/infrastructure/cost/cost-tracker.js +0 -360
- package/dist/infrastructure/file-scanner/file-scanner.js +0 -106
- package/dist/infrastructure/governance/nist-ai-rmf.js +0 -292
- package/dist/infrastructure/governance/policy-as-code.js +0 -258
- package/dist/infrastructure/governance/prompt-to-code-lineage.js +0 -239
- package/dist/infrastructure/index.js +0 -7
- package/dist/infrastructure/observability/prompt-versioner.js +0 -145
- package/dist/infrastructure/protocols/a2a-metadata.js +0 -137
- package/dist/infrastructure/protocols/a2a-protocol.js +0 -314
- package/dist/infrastructure/protocols/mcp-oauth.js +0 -184
- package/dist/infrastructure/protocols/mcp-server.js +0 -333
- package/dist/infrastructure/protocols/streaming-handler.js +0 -103
- package/dist/infrastructure/providers/cohere-provider.js +0 -444
- package/dist/infrastructure/providers/fake-provider.js +0 -130
- package/dist/infrastructure/providers/local-llm-provider.js +0 -330
- package/dist/infrastructure/providers/model-config.types.js +0 -9
- package/dist/infrastructure/providers/model-provider.interface.js +0 -8
- package/dist/infrastructure/providers/openai-provider.js +0 -413
- package/dist/infrastructure/providers/openrouter-provider.js +0 -399
- package/dist/infrastructure/providers/operation-provider-resolver.js +0 -105
- package/dist/infrastructure/providers/provider-config-loader.js +0 -120
- package/dist/infrastructure/providers/provider-registry.js +0 -88
- package/dist/infrastructure/safety/pattern-detector.js +0 -133
- package/dist/infrastructure/security/ai-sast-scanner.js +0 -290
- package/dist/infrastructure/security/docker-sandbox.js +0 -178
- package/dist/infrastructure/security/enhanced-security-gate.js +0 -151
- package/dist/infrastructure/security/license-scanner.js +0 -167
- package/dist/infrastructure/security/provider-bias-detector.js +0 -164
- package/dist/infrastructure/security/sandbox-executor.js +0 -64
- package/dist/infrastructure/serialization/toon-serializer.js +0 -119
- package/dist/infrastructure/storage/memory-stream-service.js +0 -204
- package/dist/infrastructure/storage/plan-repository.js +0 -96
- package/dist/infrastructure/storage/sqlite-decision-repository.js +0 -232
- package/dist/infrastructure/storage/sqlite-embedding-repository.js +0 -311
- package/dist/infrastructure/storage/sqlite-knowledge-graph-repository.js +0 -417
- package/dist/infrastructure/storage/sqlite-knowledge-preservation-repository.js +0 -230
- package/dist/infrastructure/storage/sqlite-memory-repository.js +0 -229
- package/dist/infrastructure/storage/sqlite-pattern-repository.js +0 -249
- package/dist/infrastructure/storage/task-helpers.js +0 -197
- package/dist/infrastructure/telemetry/telemetry-service.js +0 -167
- package/dist/infrastructure/ux/appropriate-friction.js +0 -213
- package/dist/infrastructure/ux/bi-directional-learning.js +0 -337
- package/dist/infrastructure/ux/capability-boundaries.js +0 -42
- package/dist/infrastructure/ux/comprehensive-formatter.js +0 -117
- package/dist/infrastructure/ux/contextual-feature-discovery.js +0 -10
- package/dist/infrastructure/ux/expectation-management.js +0 -327
- package/dist/infrastructure/ux/feature-discovery.js +0 -14
- package/dist/infrastructure/ux/feedback-prompt.js +0 -45
- package/dist/infrastructure/ux/hitl-validation.js +0 -297
- package/dist/infrastructure/ux/progress-indicator.js +0 -116
- package/dist/infrastructure/ux/progressive-disclosure.js +0 -236
- package/dist/infrastructure/ux/review-checkpoint.js +0 -72
- package/dist/infrastructure/ux/staged-feature-intro.js +0 -24
- package/dist/infrastructure/ux/stream-markdown.js +0 -151
- package/dist/infrastructure/ux/syntax-highlighter.js +0 -91
- package/dist/infrastructure/ux/team-standards.js +0 -196
- package/dist/infrastructure/ux/transparency.js +0 -71
- package/dist/infrastructure/validation/agent-feedback.js +0 -55
- package/dist/infrastructure/validation/agent-validation-helper.js +0 -137
- package/dist/infrastructure/validation/review-handler.js +0 -40
- package/dist/infrastructure/validation/summary-aggregator.js +0 -70
- package/dist/infrastructure/validation/summary-extractor.js +0 -45
- package/dist/infrastructure/validation/trace-summary.js +0 -52
- package/dist/infrastructure/verification/verification-service.js +0 -47
- package/dist/infrastructure/xai/attention-visualizer.js +0 -172
- package/dist/infrastructure/xai/cot-visualizer.js +0 -283
- package/dist/infrastructure/xai/decision-tree-log.js +0 -212
- package/dist/infrastructure/xai/interactive-xai.js +0 -260
- package/dist/infrastructure/xai/uncertainty-quantifier.js +0 -198
- package/dist/intelligent-retrieval.js +0 -272
- package/dist/intent/classifier.js +0 -2
- package/dist/intent/context.js +0 -2
- package/dist/iterative-refinement.js +0 -189
- package/dist/learning.js +0 -232
- package/dist/log.js +0 -31
- package/dist/migrate.js +0 -133
- package/dist/parallel-processor.js +0 -530
- package/dist/planner.js +0 -198
- package/dist/policy.js +0 -26
- package/dist/presentation/cli/commands/ask-command.js +0 -314
- package/dist/presentation/cli/commands/cost-command.js +0 -191
- package/dist/presentation/cli/commands/decision-command.js +0 -207
- package/dist/presentation/cli/commands/docs-command.js +0 -25
- package/dist/presentation/cli/commands/fix-command.js +0 -53
- package/dist/presentation/cli/commands/knowledge-command.js +0 -129
- package/dist/presentation/cli/commands/learning-dashboard-command.js +0 -183
- package/dist/presentation/cli/commands/models-command.js +0 -194
- package/dist/presentation/cli/commands/orchestrate-command.js +0 -207
- package/dist/presentation/cli/commands/plan-command.js +0 -246
- package/dist/presentation/cli/commands/product-command.js +0 -199
- package/dist/presentation/cli/commands/react-command.js +0 -181
- package/dist/presentation/cli/commands/review-command.js +0 -154
- package/dist/presentation/cli/commands/run-command.js +0 -188
- package/dist/presentation/cli/commands/smart-command.js +0 -395
- package/dist/presentation/cli/commands/suggest-command.js +0 -138
- package/dist/presentation/cli/commands/trace-command.js +0 -129
- package/dist/presentation/cli/commands/undo-command.js +0 -150
- package/dist/query-optimizer.js +0 -205
- package/dist/refactor.js +0 -116
- package/dist/review.js +0 -206
- package/dist/search.js +0 -163
- package/dist/session.js +0 -652
- package/dist/task-verification.js +0 -336
- package/dist/testgen.js +0 -166
- package/dist/token-optimizer.js +0 -277
- package/dist/tools/bash.js +0 -213
- package/dist/tools/edit.js +0 -235
- package/dist/tools/executor.js +0 -153
- package/dist/tools/git-tool.js +0 -292
- package/dist/tools/parser.js +0 -158
- package/dist/tools/registry.js +0 -302
- package/dist/tools/search.js +0 -100
- package/dist/types.js +0 -2
- package/dist/utils/check-api-status.js +0 -100
- package/dist/utils/diff-parser.js +0 -162
- package/dist/verification/backup.js +0 -259
- package/dist/verification/compiler.js +0 -264
- package/dist/verification/index.js +0 -273
- package/dist/verification/test-runner.js +0 -379
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed ~/.codehere/agents.yaml on first daemon start.
|
|
3
|
+
*
|
|
4
|
+
* Ships with NO pre-populated adapters. Users who want codehere to drive a
|
|
5
|
+
* CLI agent beyond the built-in four (Claude Code, Codex, Aider, OpenCode)
|
|
6
|
+
* add their own entry to `~/.codehere/agents.yaml` and restart the daemon.
|
|
7
|
+
*
|
|
8
|
+
* Rationale: shipping scaffolded entries we hadn't actually verified on real
|
|
9
|
+
* machines produced a "9 agents supported" claim that wasn't honest. The
|
|
10
|
+
* generic-cli-adapter mechanism is still available — users opt in per agent
|
|
11
|
+
* by editing the YAML themselves.
|
|
12
|
+
*
|
|
13
|
+
* Re-seed never overwrites an existing file.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
16
|
+
import { homedir } from 'os';
|
|
17
|
+
import { join, dirname } from 'path';
|
|
18
|
+
const CONFIG_PATH = join(homedir(), '.codehere', 'agents.yaml');
|
|
19
|
+
const DEFAULT_YAML = `# codehere — pluggable agent adapters
|
|
20
|
+
#
|
|
21
|
+
# Built-in adapters (no configuration needed — just install the CLI):
|
|
22
|
+
# - Claude Code
|
|
23
|
+
# - Codex
|
|
24
|
+
# - Aider
|
|
25
|
+
# - OpenCode
|
|
26
|
+
#
|
|
27
|
+
# To add a CLI-based AI coding agent not in the built-in list, uncomment
|
|
28
|
+
# and adapt the example below. Safety hook + audit log wrap every subprocess
|
|
29
|
+
# regardless of agent, so adding an adapter does not bypass any security
|
|
30
|
+
# layer.
|
|
31
|
+
#
|
|
32
|
+
# agents:
|
|
33
|
+
# - id: my-agent
|
|
34
|
+
# label: My Agent
|
|
35
|
+
# command: my-agent-cli # must be on PATH
|
|
36
|
+
# args: [--headless] # fixed args before the prompt
|
|
37
|
+
# prompt_via: argv # or "stdin"
|
|
38
|
+
# process_pattern: (?:^|/)my-agent-cli(?:\\\\s|$)
|
|
39
|
+
# format: text # or "jsonl" if the CLI streams JSON
|
|
40
|
+
# install_command: brew install my-agent-cli # optional · enables "Install" button
|
|
41
|
+
# install_url: https://example.com/docs # optional · docs link
|
|
42
|
+
|
|
43
|
+
agents: []
|
|
44
|
+
`;
|
|
45
|
+
export function seedAgentsConfig() {
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(CONFIG_PATH))
|
|
48
|
+
return;
|
|
49
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
50
|
+
writeFileSync(CONFIG_PATH, DEFAULT_YAML, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* best-effort */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=agents-config-seed.js.map
|
|
@@ -0,0 +1,319 @@
|
|
|
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 { ProgressAggregator } from './progress-aggregator.js';
|
|
19
|
+
import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
|
|
20
|
+
const HEADLESS_FLAGS = [
|
|
21
|
+
'--yes-always', // auto-accept all file-edit confirmations
|
|
22
|
+
'--no-check-update', // skip aider self-update probe
|
|
23
|
+
'--no-pretty', // disable ANSI colors in output
|
|
24
|
+
'--no-stream', // stream=false → one chunk per turn, cleaner stdout parsing
|
|
25
|
+
'--no-auto-commits', // don't auto-commit — leave file changes for artifacts-extractor
|
|
26
|
+
];
|
|
27
|
+
const ERROR_PATTERNS = [
|
|
28
|
+
{
|
|
29
|
+
code: 'credit_low',
|
|
30
|
+
// Aider has no subscription path — uses ANTHROPIC_API_KEY / OPENAI_API_KEY
|
|
31
|
+
// directly against the billed API. codehere users who run on a Claude Code
|
|
32
|
+
// subscription typically have a zero-credit API key and hit this fast.
|
|
33
|
+
regex: /credit balance is too low|insufficient.*credit|no credits|you exceeded your current quota/i,
|
|
34
|
+
message: 'The API key has no credit — Aider cannot run on a Claude Code subscription.',
|
|
35
|
+
recovery: 'Aider needs a billed API key (Anthropic or OpenAI). Either top up your key at ' +
|
|
36
|
+
'console.anthropic.com / platform.openai.com, or switch the task to Claude Code / Codex which use ' +
|
|
37
|
+
'subscription auth.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
code: 'auth_invalid',
|
|
41
|
+
regex: /invalid api key|authentication.*failed|401 unauth|Please provide your.*API key/i,
|
|
42
|
+
message: 'Aider auth missing or rejected.',
|
|
43
|
+
recovery: 'Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your shell, then retry.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: 'rate_limit',
|
|
47
|
+
regex: /rate.?limit(ed)?|429|too many requests/i,
|
|
48
|
+
message: 'Aider rate-limited the request.',
|
|
49
|
+
recovery: 'Wait a minute and retry, or switch --model via extraArgs.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: 'model_unavailable',
|
|
53
|
+
regex: /model.*(not found|not available|unavailable|does not exist)/i,
|
|
54
|
+
message: 'Aider could not find the model.',
|
|
55
|
+
recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
function scanForKnownError(text) {
|
|
59
|
+
for (const p of ERROR_PATTERNS) {
|
|
60
|
+
if (p.regex.test(text))
|
|
61
|
+
return { code: p.code, message: p.message, recovery: p.recovery };
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function spawnAider(prompt, options = {}) {
|
|
66
|
+
const sessionId = randomUUID();
|
|
67
|
+
const startedAt = Date.now();
|
|
68
|
+
const emitter = new EventEmitter();
|
|
69
|
+
const buffer = [];
|
|
70
|
+
let statusValue = 'running';
|
|
71
|
+
let killRequested = false;
|
|
72
|
+
const cwd = options.cwd ?? process.cwd();
|
|
73
|
+
const snapshot = snapshotCwd(cwd);
|
|
74
|
+
const aggregator = new ProgressAggregator('aider'); // no-op aggregator; emits no steps
|
|
75
|
+
let stepsSnapshot = [];
|
|
76
|
+
let artifactsSnapshot;
|
|
77
|
+
let textAccum = '';
|
|
78
|
+
let usageSnapshot;
|
|
79
|
+
let errorEmitted = false;
|
|
80
|
+
const emit = (event) => {
|
|
81
|
+
buffer.push(event);
|
|
82
|
+
emitter.emit('event', event);
|
|
83
|
+
if (aggregator.ingest(event)) {
|
|
84
|
+
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
85
|
+
const snapEvt = { type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } };
|
|
86
|
+
buffer.push(snapEvt);
|
|
87
|
+
emitter.emit('event', snapEvt);
|
|
88
|
+
}
|
|
89
|
+
// Text accumulator for test-summary parsing + aider cost-line token extraction
|
|
90
|
+
if (event.type === 'stdout' && event.data?.line) {
|
|
91
|
+
const line = String(event.data.line);
|
|
92
|
+
textAccum += line + '\n';
|
|
93
|
+
// Aider emits a cost summary like:
|
|
94
|
+
// "Tokens: 1.2k sent, 412 received. Cost: $0.01 request, $0.01 session."
|
|
95
|
+
const m = line.match(/Tokens:\s*([\d.]+)([kM]?)\s*sent,\s*([\d.]+)([kM]?)\s*received/i);
|
|
96
|
+
if (m) {
|
|
97
|
+
const scale = (s) => (s === 'k' ? 1000 : s === 'M' ? 1_000_000 : 1);
|
|
98
|
+
const sent = Math.round(parseFloat(m[1]) * scale(m[2]));
|
|
99
|
+
const recv = Math.round(parseFloat(m[3]) * scale(m[4]));
|
|
100
|
+
usageSnapshot = {
|
|
101
|
+
inputTokens: (usageSnapshot?.inputTokens ?? 0) + sent,
|
|
102
|
+
outputTokens: (usageSnapshot?.outputTokens ?? 0) + recv,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!errorEmitted) {
|
|
107
|
+
const data = event.data;
|
|
108
|
+
let text = '';
|
|
109
|
+
if ((event.type === 'stderr' || event.type === 'stdout') && typeof data?.line === 'string') {
|
|
110
|
+
text = data.line;
|
|
111
|
+
}
|
|
112
|
+
if (text) {
|
|
113
|
+
const hit = scanForKnownError(text);
|
|
114
|
+
if (hit) {
|
|
115
|
+
errorEmitted = true;
|
|
116
|
+
const errEvent = {
|
|
117
|
+
type: 'codehere:error',
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
data: { code: hit.code, message: hit.message, recovery: hit.recovery },
|
|
120
|
+
};
|
|
121
|
+
buffer.push(errEvent);
|
|
122
|
+
emitter.emit('event', errEvent);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// Whenever a codehere:error fires, we treat the session as effectively failed
|
|
128
|
+
// even if the subprocess later exits 0. Many CLIs print a logical error to
|
|
129
|
+
// stdout then return success — the status pill would otherwise lie.
|
|
130
|
+
emitter.on('event', (ev) => {
|
|
131
|
+
if (ev.type === 'codehere:error' && statusValue === 'running') {
|
|
132
|
+
// Don't overwrite if exit already settled a final status.
|
|
133
|
+
// We rely on exit handler to flip running→completed/killed/error; we pre-empt
|
|
134
|
+
// that with 'error' here so the status pill is honest from the moment the
|
|
135
|
+
// error is detected.
|
|
136
|
+
statusValue = 'error';
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const spawnEnv = { ...process.env, ...(options.env ?? {}) };
|
|
140
|
+
let child;
|
|
141
|
+
try {
|
|
142
|
+
child = spawn('aider', ['--message', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
|
|
143
|
+
cwd,
|
|
144
|
+
env: spawnEnv,
|
|
145
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
statusValue = 'error';
|
|
150
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
151
|
+
queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
|
|
152
|
+
return {
|
|
153
|
+
sessionId,
|
|
154
|
+
startedAt,
|
|
155
|
+
prompt,
|
|
156
|
+
agent: 'aider',
|
|
157
|
+
get status() {
|
|
158
|
+
return statusValue;
|
|
159
|
+
},
|
|
160
|
+
get steps() {
|
|
161
|
+
return stepsSnapshot;
|
|
162
|
+
},
|
|
163
|
+
get artifacts() {
|
|
164
|
+
return artifactsSnapshot;
|
|
165
|
+
},
|
|
166
|
+
subscribe(handler) {
|
|
167
|
+
for (const e of buffer)
|
|
168
|
+
handler(e);
|
|
169
|
+
emitter.on('event', handler);
|
|
170
|
+
return () => emitter.off('event', handler);
|
|
171
|
+
},
|
|
172
|
+
kill() {
|
|
173
|
+
/* already not running */
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
let stdoutBuf = '';
|
|
178
|
+
child.stdout?.on('data', (chunk) => {
|
|
179
|
+
stdoutBuf += chunk.toString('utf-8');
|
|
180
|
+
let idx;
|
|
181
|
+
while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
|
|
182
|
+
const line = stdoutBuf.slice(0, idx).trim();
|
|
183
|
+
stdoutBuf = stdoutBuf.slice(idx + 1);
|
|
184
|
+
if (!line)
|
|
185
|
+
continue;
|
|
186
|
+
emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
child.stderr?.on('data', (chunk) => {
|
|
190
|
+
const text = chunk.toString('utf-8');
|
|
191
|
+
for (const line of text.split('\n')) {
|
|
192
|
+
if (line.trim())
|
|
193
|
+
emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
child.on('exit', (code, signal) => {
|
|
197
|
+
// Preserve 'error' if we already flipped to it on codehere:error.
|
|
198
|
+
statusValue =
|
|
199
|
+
killRequested || signal !== null ? 'killed' : errorEmitted ? 'error' : code === 0 ? 'completed' : 'error';
|
|
200
|
+
if (aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error')) {
|
|
201
|
+
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
202
|
+
emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
|
|
203
|
+
}
|
|
204
|
+
if (statusValue === 'completed') {
|
|
205
|
+
try {
|
|
206
|
+
artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
|
|
207
|
+
emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
/* best-effort */
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
emit({
|
|
214
|
+
type: 'exit',
|
|
215
|
+
timestamp: Date.now(),
|
|
216
|
+
data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
child.on('error', (err) => {
|
|
220
|
+
statusValue = 'error';
|
|
221
|
+
emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
sessionId,
|
|
225
|
+
startedAt,
|
|
226
|
+
prompt,
|
|
227
|
+
agent: 'aider',
|
|
228
|
+
get status() {
|
|
229
|
+
return statusValue;
|
|
230
|
+
},
|
|
231
|
+
get steps() {
|
|
232
|
+
return stepsSnapshot;
|
|
233
|
+
},
|
|
234
|
+
get artifacts() {
|
|
235
|
+
return artifactsSnapshot;
|
|
236
|
+
},
|
|
237
|
+
subscribe(handler) {
|
|
238
|
+
for (const e of buffer)
|
|
239
|
+
handler(e);
|
|
240
|
+
emitter.on('event', handler);
|
|
241
|
+
return () => emitter.off('event', handler);
|
|
242
|
+
},
|
|
243
|
+
kill() {
|
|
244
|
+
if (statusValue !== 'running')
|
|
245
|
+
return;
|
|
246
|
+
killRequested = true;
|
|
247
|
+
child.kill('SIGTERM');
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
if (statusValue === 'running') {
|
|
250
|
+
try {
|
|
251
|
+
child.kill('SIGKILL');
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
/* already dead */
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}, 3000);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
export const AiderAdapter = {
|
|
262
|
+
id: 'aider',
|
|
263
|
+
label: 'Aider',
|
|
264
|
+
processPattern: /(?:^|\/)aider(?:\s|$)/,
|
|
265
|
+
isInstalled() {
|
|
266
|
+
try {
|
|
267
|
+
const out = execFileSync('which', ['aider'], { encoding: 'utf-8', timeout: 2000 });
|
|
268
|
+
return out.trim().length > 0;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
spawn(prompt, options) {
|
|
275
|
+
return spawnAider(prompt, options);
|
|
276
|
+
},
|
|
277
|
+
checkReadiness() {
|
|
278
|
+
const installed = this.isInstalled();
|
|
279
|
+
if (!installed) {
|
|
280
|
+
return {
|
|
281
|
+
id: this.id,
|
|
282
|
+
label: this.label,
|
|
283
|
+
state: 'needs_install',
|
|
284
|
+
installed: false,
|
|
285
|
+
installHint: { command: 'brew install aider', url: 'https://aider.chat/docs/install.html' },
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// Aider has no subscription path — needs a BILLED API key with credit.
|
|
289
|
+
// If neither Anthropic nor OpenAI key is present → needs_auth.
|
|
290
|
+
// If a key IS present but the user is running on a Claude Code subscription
|
|
291
|
+
// (no billing configured), Aider will hit credit_low at runtime. We can't
|
|
292
|
+
// detect that pre-spawn without making an API call. Flag the risk in the
|
|
293
|
+
// detail string so the UI can surface it.
|
|
294
|
+
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
295
|
+
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
|
296
|
+
if (!hasAnthropic && !hasOpenai) {
|
|
297
|
+
return {
|
|
298
|
+
id: this.id,
|
|
299
|
+
label: this.label,
|
|
300
|
+
state: 'needs_auth',
|
|
301
|
+
installed: true,
|
|
302
|
+
authHint: {
|
|
303
|
+
detail: 'Aider needs a BILLED API key (Anthropic or OpenAI). Unlike Claude Code, it has no subscription path.',
|
|
304
|
+
url: 'https://console.anthropic.com',
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
id: this.id,
|
|
310
|
+
label: this.label,
|
|
311
|
+
state: 'billed_key_required',
|
|
312
|
+
installed: true,
|
|
313
|
+
authHint: {
|
|
314
|
+
detail: 'API key detected. Aider requires 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.',
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
//# sourceMappingURL=aider-adapter.js.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArtifactsExtractor — PM UX Layer 3 (principles #2 "info density", #7 "agent-provenance").
|
|
3
|
+
*
|
|
4
|
+
* When a task finishes, a product manager reports: what changed, was anything
|
|
5
|
+
* risky, did tests pass. We derive that from ground truth:
|
|
6
|
+
* - git diff (working tree + untracked) vs the HEAD snapshot taken at spawn
|
|
7
|
+
* - trust score per changed file via the existing SAST scorer (F0)
|
|
8
|
+
* - test pass/fail counts parsed out of collected stdout (best-effort)
|
|
9
|
+
*
|
|
10
|
+
* Agent-agnostic on purpose. Both Claude and Codex mutate the filesystem;
|
|
11
|
+
* git tells the truth regardless of which tool did the write.
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from 'child_process';
|
|
14
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
15
|
+
import { join, isAbsolute } from 'path';
|
|
16
|
+
import { scoreFile } from '../security/trust-scorer.js';
|
|
17
|
+
/** Take a baseline so Layer 3 can diff against it on exit. Cheap; ~5ms. */
|
|
18
|
+
export function snapshotCwd(cwd) {
|
|
19
|
+
const spawnedAtMs = Date.now();
|
|
20
|
+
try {
|
|
21
|
+
const out = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: 'utf-8',
|
|
24
|
+
timeout: 2000,
|
|
25
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
26
|
+
}).trim();
|
|
27
|
+
return { cwd, head: out, isGitRepo: out.length > 0, spawnedAtMs };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { cwd, head: null, isGitRepo: false, spawnedAtMs };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Collect {path, +, -} from git diff (numstat) + untracked files (wc -l). */
|
|
34
|
+
function changedFiles(snap) {
|
|
35
|
+
if (!snap.isGitRepo)
|
|
36
|
+
return [];
|
|
37
|
+
const out = [];
|
|
38
|
+
try {
|
|
39
|
+
// Staged + unstaged vs HEAD at spawn
|
|
40
|
+
const diff = execFileSync('git', ['diff', '--numstat', snap.head ?? 'HEAD'], {
|
|
41
|
+
cwd: snap.cwd,
|
|
42
|
+
encoding: 'utf-8',
|
|
43
|
+
timeout: 5000,
|
|
44
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
45
|
+
});
|
|
46
|
+
for (const line of diff.split('\n')) {
|
|
47
|
+
const m = line.match(/^(-|\d+)\t(-|\d+)\t(.+)$/);
|
|
48
|
+
if (!m)
|
|
49
|
+
continue;
|
|
50
|
+
const [, aStr, rStr, path] = m;
|
|
51
|
+
out.push({
|
|
52
|
+
path,
|
|
53
|
+
added: aStr === '-' ? 0 : parseInt(aStr, 10) || 0,
|
|
54
|
+
removed: rStr === '-' ? 0 : parseInt(rStr, 10) || 0,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* diff failed — skip */
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
// Untracked files — not in diff output
|
|
63
|
+
const untracked = execFileSync('git', ['ls-files', '--others', '--exclude-standard'], {
|
|
64
|
+
cwd: snap.cwd,
|
|
65
|
+
encoding: 'utf-8',
|
|
66
|
+
timeout: 5000,
|
|
67
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
68
|
+
});
|
|
69
|
+
for (const rel of untracked.split('\n')) {
|
|
70
|
+
if (!rel.trim())
|
|
71
|
+
continue;
|
|
72
|
+
// Count lines in the new file
|
|
73
|
+
try {
|
|
74
|
+
const full = isAbsolute(rel) ? rel : join(snap.cwd, rel);
|
|
75
|
+
if (!existsSync(full))
|
|
76
|
+
continue;
|
|
77
|
+
const size = statSync(full).size;
|
|
78
|
+
if (size > 2 * 1024 * 1024)
|
|
79
|
+
continue; // skip files >2MB, don't need their trust score
|
|
80
|
+
const content = readFileSync(full, 'utf-8');
|
|
81
|
+
const lines = content ? content.split('\n').length : 0;
|
|
82
|
+
out.push({ path: rel, added: lines, removed: 0 });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* skip */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ls-files failed — skip */
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/** Parse "N passed", "M failed", "K tests" out of accumulated stdout. Best-effort.
|
|
95
|
+
* Vitest + jest output both have TWO pass/fail lines ("Test Files 11 passed" then
|
|
96
|
+
* "Tests 47 passed"). We want the second/last. Take the last match in the buffer,
|
|
97
|
+
* which is also what appears in the final summary. */
|
|
98
|
+
function lastMatch(s, re) {
|
|
99
|
+
const g = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
|
|
100
|
+
let last = null;
|
|
101
|
+
let m;
|
|
102
|
+
while ((m = g.exec(s)) !== null)
|
|
103
|
+
last = m;
|
|
104
|
+
return last;
|
|
105
|
+
}
|
|
106
|
+
function parseTestSummary(stdout) {
|
|
107
|
+
if (!stdout)
|
|
108
|
+
return undefined;
|
|
109
|
+
const s = stdout.slice(-8000); // only look at the tail
|
|
110
|
+
// vitest / jest: "Tests 47 passed, 1 failed (48)" or "passing" (mocha)
|
|
111
|
+
const passed = lastMatch(s, /(\d+)\s+pass(?:ed|ing)/i);
|
|
112
|
+
const failed = lastMatch(s, /(\d+)\s+fail(?:ed|ing)/i);
|
|
113
|
+
if (!passed && !failed)
|
|
114
|
+
return undefined;
|
|
115
|
+
const result = {
|
|
116
|
+
passed: passed ? parseInt(passed[1], 10) : 0,
|
|
117
|
+
failed: failed ? parseInt(failed[1], 10) : 0,
|
|
118
|
+
};
|
|
119
|
+
const dur = lastMatch(s, /Duration\s+([\d.]+)s/i) ?? lastMatch(s, /Time:\s+([\d.]+)\s*s/i);
|
|
120
|
+
if (dur)
|
|
121
|
+
result.durationMs = Math.round(parseFloat(dur[1]) * 1000);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
/** Score each changed file with the existing SAST trust scorer. Safe on binaries (skipped). */
|
|
125
|
+
function annotateTrust(snap, files) {
|
|
126
|
+
return files.map((f) => {
|
|
127
|
+
try {
|
|
128
|
+
const full = isAbsolute(f.path) ? f.path : join(snap.cwd, f.path);
|
|
129
|
+
if (!existsSync(full))
|
|
130
|
+
return f;
|
|
131
|
+
const size = statSync(full).size;
|
|
132
|
+
if (size > 2 * 1024 * 1024)
|
|
133
|
+
return f;
|
|
134
|
+
// Only score text-like files — trust scorer regexes operate on UTF-8
|
|
135
|
+
if (!/\.(ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|java|kt|cs|php|sh|bash|zsh|yml|yaml|json|md|sql)$/i.test(f.path))
|
|
136
|
+
return f;
|
|
137
|
+
const content = readFileSync(full, 'utf-8');
|
|
138
|
+
const scored = scoreFile(f.path, content);
|
|
139
|
+
return { ...f, trust: scored.score };
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return f;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Keep only files whose mtime is at or after spawn time — the signal that this
|
|
148
|
+
* session touched them. Prevents Session B in the same cwd from reporting files
|
|
149
|
+
* Session A already edited before B started. Not bulletproof (both sessions
|
|
150
|
+
* hitting the same file both report it) but fixes the common concurrent-sessions
|
|
151
|
+
* pollution observed in beta.3.
|
|
152
|
+
*/
|
|
153
|
+
function filterByMtime(snap, files) {
|
|
154
|
+
const grace = 1000; // ms; allow clock skew between git's touch and our Date.now()
|
|
155
|
+
return files.filter((f) => {
|
|
156
|
+
try {
|
|
157
|
+
const full = isAbsolute(f.path) ? f.path : join(snap.cwd, f.path);
|
|
158
|
+
if (!existsSync(full))
|
|
159
|
+
return true; // file was deleted — keep it
|
|
160
|
+
const { mtimeMs } = statSync(full);
|
|
161
|
+
return mtimeMs >= snap.spawnedAtMs - grace;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return true; // stat failed — be permissive
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
export function extractArtifacts(snap, collectedStdout) {
|
|
169
|
+
const raw = changedFiles(snap);
|
|
170
|
+
const mine = filterByMtime(snap, raw);
|
|
171
|
+
const files = annotateTrust(snap, mine);
|
|
172
|
+
const tests = parseTestSummary(collectedStdout);
|
|
173
|
+
return { files, tests };
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=artifacts-extractor.js.map
|