compound-agent 1.7.6 → 2.0.0
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/CHANGELOG.md +45 -1
- package/README.md +70 -47
- package/bin/ca +32 -0
- package/package.json +19 -78
- package/scripts/postinstall.cjs +221 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -13158
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts +0 -3730
- package/dist/index.js +0 -3240
- package/dist/index.js.map +0 -1
- package/docs/research/AgenticAiCodebaseGuide.md +0 -1206
- package/docs/research/BuildingACCompilerAnthropic.md +0 -116
- package/docs/research/HarnessEngineeringOpenAi.md +0 -220
- package/docs/research/code-review/systematic-review-methodology.md +0 -409
- package/docs/research/index.md +0 -76
- package/docs/research/learning-systems/knowledge-compounding-for-agents.md +0 -695
- package/docs/research/property-testing/property-based-testing-and-invariants.md +0 -742
- package/docs/research/scenario-testing/advanced-and-emerging.md +0 -470
- package/docs/research/scenario-testing/core-foundations.md +0 -507
- package/docs/research/scenario-testing/domain-specific-and-human-factors.md +0 -474
- package/docs/research/security/auth-patterns.md +0 -138
- package/docs/research/security/data-exposure.md +0 -185
- package/docs/research/security/dependency-security.md +0 -91
- package/docs/research/security/injection-patterns.md +0 -249
- package/docs/research/security/overview.md +0 -81
- package/docs/research/security/secrets-checklist.md +0 -92
- package/docs/research/security/secure-coding-failure.md +0 -297
- package/docs/research/software_architecture/01-science-of-decomposition.md +0 -615
- package/docs/research/software_architecture/02-architecture-under-uncertainty.md +0 -649
- package/docs/research/software_architecture/03-emergent-behavior-in-composed-systems.md +0 -644
- package/docs/research/spec_design/decision_theory_specifications_and_multi_criteria_tradeoffs.md +0 -0
- package/docs/research/spec_design/design_by_contract.md +0 -251
- package/docs/research/spec_design/domain_driven_design_strategic_modeling.md +0 -183
- package/docs/research/spec_design/formal_specification_methods.md +0 -161
- package/docs/research/spec_design/logic_and_proof_theory_under_the_curry_howard_correspondence.md +0 -250
- package/docs/research/spec_design/natural_language_formal_semantics_abuguity_in_specifications.md +0 -259
- package/docs/research/spec_design/requirements_engineering.md +0 -234
- package/docs/research/spec_design/systems_engineering_specifications_emergent_behavior_interface_contracts.md +0 -149
- package/docs/research/spec_design/what_is_this_about.md +0 -305
- package/docs/research/tdd/test-driven-development-methodology.md +0 -547
- package/docs/research/test-optimization-strategies.md +0 -401
- package/scripts/postinstall.mjs +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ All notable changes to this project will be documented in this file.
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Replace node-llama-cpp with Transformers.js**: Swap EmbeddingGemma-300M (node-llama-cpp, 431MB RSS) for nomic-embed-text-v1.5 (@huggingface/transformers, 23MB RSS) — 95% memory reduction (E5b).
|
|
15
|
+
- **Remove all node-llama-cpp residue**: Update setup templates, doctor diagnostics, comments, and vitest config to reference Transformers.js and onnxruntime-node instead of node-llama-cpp (E5c).
|
|
16
|
+
- **Gemini adapter is now opt-in**: `installGeminiAdapter()` no longer runs automatically during setup. Users enable it explicitly via `npx ca setup gemini` (sets `gemini: true` in `compound-agent.json`). Use `npx ca setup gemini --disable` / `cleanGeminiCompoundFiles()` for clean removal.
|
|
17
|
+
- **Stale cleanup refactored**: Removed hardcoded deprecation lists from upgrade logic, replaced with `cleanStaleArtifacts()` pattern that declaratively defines what to remove.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Research-specialist shipped agent**: New general-purpose research subagent (`research-specialist.md`) shipped via `npx ca init`. Has full tool access (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch) so it can conduct deep PhD-level research, write survey papers, run experiments, and validate claims with code. Referenced by the `get-a-phd` workflow for parallel research execution.
|
|
22
|
+
- **`model-info.ts` module**: Extracted embedding model metadata (name, repo, dimensions, file) into a standalone module with zero native imports, decoupling the import graph so that CLI entry points no longer transitively load `node-llama-cpp` or `better-sqlite3` at parse time.
|
|
23
|
+
- **Architect decomposition spec**: Added specification for embedding memory pressure remediation (`embedding-memory-pressure-remediation.md`).
|
|
24
|
+
- **Hypothesis validation protocol**: Added to spec-dev skill — specs can now define falsifiable hypotheses with validation criteria.
|
|
25
|
+
- **`cleanStaleArtifacts` and `cleanStaleGeminiArtifacts`**: New setup utilities that remove deprecated files and directories during upgrades instead of relying on hardcoded deprecation lists.
|
|
26
|
+
- **LinkedIn architecture diagrams**: Integrated visual architecture diagrams into README (`docs/assets/`).
|
|
27
|
+
- **Independent reviews**: Added Opus and Sonnet independent review documents for embedding memory pressure analysis.
|
|
28
|
+
- **Embedding memory pressure investigation**: Added root-cause analysis, measurement data, and proposal documents in `docs/research/`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **Embedding memory pressure remediation**: Lazy-load native modules (@huggingface/transformers (onnxruntime-node), better-sqlite3) behind dynamic `import()`, reducing CLI cold-start RSS. Singleton embedding model uses explicit `dispose()`. Added RSS measurement script and integration tests for memory lifecycle.
|
|
33
|
+
- **Review phase resilience**: Fixed jq stdin pipe handling, added auth health checks, and improved error isolation in loop review templates.
|
|
34
|
+
- **Quality-filter-before-storage test ordering**: Resolved flaky test ordering in compound skill tests.
|
|
35
|
+
- **Merged worktree review findings**: Addressed Opus/Sonnet review findings for worktree merges (loop-review-templates, stale-cleanup tests).
|
|
36
|
+
- **Knowledge index integration tests**: Fixed test configuration for embedding integration tests in vitest workspace.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## [1.8.0] - 2026-03-15
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **`ca improve` command**: Generates a bash script that autonomously improves the codebase using `improve/*.md` program files. Each program defines what to improve, how to find work, and how to validate changes. Options: `--topics` (filter specific topics), `--max-iters` (iterations per topic, default 5), `--time-budget` (total seconds, 0=unlimited), `--model`, `--force`, `--dry-run`. Includes `ca improve init` subcommand to scaffold an example program file.
|
|
44
|
+
- **`ca watch` command**: Tails and pretty-prints live trace JSONL from infinity loop and improvement loop sessions. Supports `--epic <id>` to watch a specific epic, `--improve` to watch improvement loop traces, and `--no-follow` to print existing trace and exit. Formats tool calls, thinking blocks, token usage, and result markers into a compact, color-coded stream.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`git clean` scoping in improvement loop**: Bare `git clean -fd` on rollback was removing all untracked files including the script's own log directory, causing crashes. All three rollback paths now use `git clean -fd -e "$LOG_DIR/"` to exclude agent logs.
|
|
49
|
+
- **Embedded dirty-worktree guard fallthrough**: In embedded mode (when improvement loop runs inside `ca loop --improve`), setting `IMPROVE_RESULT=1` on a dirty worktree did not prevent the loop body from executing. Restructured to use `if/else` so the loop body only runs inside the `else` branch.
|
|
50
|
+
- **`ca watch --improve` ignoring `.latest` symlink**: The `--improve` code path had inline logic that only did reverse filename sort, bypassing the `.latest` symlink that the improvement loop maintains. Refactored `findLatestTraceFile()` with a `prefix` parameter to unify both code paths.
|
|
51
|
+
- **`--topics` flag ignored in `get_topics()`**: The `TOPIC_FILTER` variable from the CLI `--topics` flag was not used in the generated bash `get_topics()` function, causing all topics to run regardless of filtering.
|
|
52
|
+
- **Update-check hardening**: Switched to a lightweight npm registry endpoint, added CI environment guards, and corrected the update command shown to users.
|
|
53
|
+
|
|
10
54
|
## [1.7.6] - 2026-03-12
|
|
11
55
|
|
|
12
56
|
### Added
|
|
@@ -249,7 +293,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
249
293
|
- **Eliminate double model initialization**: `ca search` now uses `isModelAvailable()` (fs.existsSync, zero cost) instead of `isModelUsable()` which loaded the 278MB native model just to probe availability, then loaded it again for actual embedding
|
|
250
294
|
- **Bulk-read cached embeddings**: `getCachedEmbeddingsBulk()` replaces N individual `getCachedEmbedding()` SQLite queries with a single bulk read
|
|
251
295
|
- **Eliminate redundant JSONL parsing**: `searchVector()` and `findSimilarLessons()` now use `readAllFromSqlite()` after `syncIfNeeded()` instead of re-parsing the JSONL file
|
|
252
|
-
- **Float32Array consistency**: Lesson embedding path now keeps `Float32Array` from
|
|
296
|
+
- **Float32Array consistency**: Lesson embedding path now keeps `Float32Array` from the embedding pipeline instead of converting via `Array.from()` (4x memory savings per vector)
|
|
253
297
|
- **Pre-warm lesson embedding cache**: `ca init` now pre-computes embeddings for all lessons with missing or stale cache entries, eliminating cold-start latency on first search
|
|
254
298
|
- **Graceful embedding fallback**: `ca search` falls back to keyword-only search on runtime embedding failures instead of crashing
|
|
255
299
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="docs/assets/diagram-4.png" alt="Compound-agent ecosystem overview: Architect phase decomposes work via Socratic dialogue into a dependency graph. ca loop chains tasks with cross-model review, retry, and fresh sessions. Scenario evaluation validates changes with iterative refinement. All backed by persistent memory (lessons + knowledge across all sessions) and verification gates (tests, lint, type checks on every task)." width="700">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
9
13
|
AI coding agents forget everything between sessions. Each session starts with whatever context was prepared for it — nothing more. Because agents carry no persistent state, that state must live in the codebase itself, and any agent that reads the same well-structured context should be able to pick up where another left off. Compound Agent implements this: it captures mistakes once, retrieves them precisely when relevant, and can hand entire systems to an autonomous loop that processes epic by epic with no human intervention.
|
|
10
14
|
|
|
11
15
|
## What gets installed
|
|
@@ -24,43 +28,22 @@ This is not a memory plugin bolted onto a text editor. It is the environment you
|
|
|
24
28
|
|
|
25
29
|
## How it works
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
flowchart TD
|
|
29
|
-
A["/compound:architect\nDecompose large system\ninto epics via DDD"] -->|produces epics| L
|
|
31
|
+
Two memory systems persist across sessions:
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
P --> W[WORK]
|
|
35
|
-
W --> R[REVIEW]
|
|
36
|
-
R --> C[COMPOUND]
|
|
37
|
-
end
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="docs/assets/diagram-1.png" alt="A task session between two memory systems: Lessons (JSONL + SQLite with semantic + keyword search) are retrieved before and captured after each task. Knowledge (project docs chunked and embedded) is queried on demand." width="700">
|
|
35
|
+
</p>
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
- **Lessons** — mistakes, corrections, and patterns stored as git-tracked JSONL, indexed in SQLite FTS5 with local embeddings for hybrid search. Retrieved at the start of each task, captured at the end.
|
|
38
|
+
- **Knowledge** — project documentation chunked and embedded for semantic retrieval. Any phase can query it on demand.
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
style A fill:#e8f4fd,stroke:#4a9ede
|
|
44
|
-
```
|
|
40
|
+
Each task runs through five phases, with review findings looping back to rework. Each phase runs as its own slash command so instructions are re-injected fresh (surviving context compaction):
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
<p align="center">
|
|
43
|
+
<img src="docs/assets/diagram-2.png" alt="Inside a task: five phases (Spec, Plan, Work, Review, Compound) connected in sequence with a feedback loop from Review back to Work. Each phase runs as its own slash command with fresh instructions. Lessons are retrieved at start and captured at end. Knowledge is queryable from any phase." width="700">
|
|
44
|
+
</p>
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
block-beta
|
|
50
|
-
columns 1
|
|
51
|
-
block:L3["Workflows · Feedback Loops"]
|
|
52
|
-
A["15 slash commands"] B["24 specialized agents"] C["Autonomous loop"]
|
|
53
|
-
end
|
|
54
|
-
block:L2["Semantic Memory · Codebase Memory"]
|
|
55
|
-
D["Vector search"] E["Hybrid retrieval"] F["Cross-session persistence"]
|
|
56
|
-
end
|
|
57
|
-
block:L1["Beads Foundation · Navigable Structure"]
|
|
58
|
-
G["Issue tracking"] H["Git-backed sync"] I["Dependency graphs"]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
L3 --> L2
|
|
62
|
-
L2 --> L1
|
|
63
|
-
```
|
|
46
|
+
Each cycle through the loop makes the next one smarter. The architect step is optional — use it for systems too large for a single feature cycle.
|
|
64
47
|
|
|
65
48
|
## Three principles
|
|
66
49
|
|
|
@@ -154,6 +137,10 @@ ca loop --reviewers claude-sonnet --review-every 3
|
|
|
154
137
|
|
|
155
138
|
## The infinity loop
|
|
156
139
|
|
|
140
|
+
<p align="center">
|
|
141
|
+
<img src="docs/assets/diagram-3.png" alt="ca loop chains tasks in dependency order: Task 1 through Task 4, each running a full cycle in a fresh session. Cross-model review (R) gates between tasks. Failed tasks retry automatically. Tasks can escalate to human-required. Generated bash script with deterministic orchestration." width="700">
|
|
142
|
+
</p>
|
|
143
|
+
|
|
157
144
|
`ca loop` generates a bash script that processes your beads epics sequentially, running the full cook-it cycle on each one. No human intervention required between epics.
|
|
158
145
|
|
|
159
146
|
```bash
|
|
@@ -174,6 +161,35 @@ The loop respects beads dependency graphs — it only processes epics whose depe
|
|
|
174
161
|
|
|
175
162
|
**Current maturity**: the loop works and has been used to ship real projects, including compound-agent itself. Two things still required human involvement: specifications had to be written before the loop started, and a human applied fixes after the first review pass surfaced real problems (missing error handling, a migration gap, insufficient test coverage). Fully unattended long-duration runs across many epics are the current area of hardening.
|
|
176
163
|
|
|
164
|
+
## The improvement loop
|
|
165
|
+
|
|
166
|
+
`ca improve` generates a bash script that iterates over `improve/*.md` program files, spawning Claude Code sessions to make focused improvements. Each program file defines what to improve, how to find work, and how to validate changes.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Scaffold an example program file
|
|
170
|
+
ca improve init
|
|
171
|
+
# Creates improve/example.md with a linting template
|
|
172
|
+
|
|
173
|
+
# Generate the improvement script
|
|
174
|
+
ca improve
|
|
175
|
+
|
|
176
|
+
# Filter to specific topics
|
|
177
|
+
ca improve --topics lint tests --max-iters 3
|
|
178
|
+
|
|
179
|
+
# Preview without generating
|
|
180
|
+
ca improve --dry-run
|
|
181
|
+
|
|
182
|
+
# Run the generated script
|
|
183
|
+
./improvement-loop.sh
|
|
184
|
+
|
|
185
|
+
# Preview without executing Claude sessions
|
|
186
|
+
IMPROVE_DRY_RUN=1 ./improvement-loop.sh
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Each iteration makes one focused improvement, commits it, and moves on. If an iteration finds nothing to improve or fails validation, it reverts cleanly and moves to the next topic. The loop tracks consecutive no-improvement results and stops early to avoid diminishing returns.
|
|
190
|
+
|
|
191
|
+
Monitor progress with `ca watch --improve` to see live trace output from improvement sessions.
|
|
192
|
+
|
|
177
193
|
## Automatic hooks
|
|
178
194
|
|
|
179
195
|
Once installed, seven Claude Code hooks fire without any commands:
|
|
@@ -213,18 +229,15 @@ Three human approval gates separate the phases. Each output epic is sized for on
|
|
|
213
229
|
# Install as dev dependency
|
|
214
230
|
pnpm add -D compound-agent
|
|
215
231
|
|
|
216
|
-
# One-shot setup (creates dirs, hooks,
|
|
232
|
+
# One-shot setup (creates dirs, hooks, templates)
|
|
217
233
|
npx ca setup
|
|
218
|
-
|
|
219
|
-
# Skip the ~278MB model download (do it later)
|
|
220
|
-
npx ca setup --skip-model
|
|
221
234
|
```
|
|
222
235
|
|
|
223
236
|
### Requirements
|
|
224
237
|
|
|
225
238
|
- Node.js >= 20
|
|
226
239
|
- ~278MB disk space for the embedding model (one-time download, shared across projects)
|
|
227
|
-
- ~
|
|
240
|
+
- ~23MB RAM during embedding operations (nomic-embed-text-v1.5 via Transformers.js)
|
|
228
241
|
|
|
229
242
|
### pnpm Users
|
|
230
243
|
|
|
@@ -235,7 +248,7 @@ If you prefer to configure manually, add to your `package.json`:
|
|
|
235
248
|
```json
|
|
236
249
|
{
|
|
237
250
|
"pnpm": {
|
|
238
|
-
"onlyBuiltDependencies": ["better-sqlite3", "node
|
|
251
|
+
"onlyBuiltDependencies": ["better-sqlite3", "onnxruntime-node"]
|
|
239
252
|
}
|
|
240
253
|
}
|
|
241
254
|
```
|
|
@@ -303,6 +316,17 @@ The CLI binary is `ca` (alias: `compound-agent`).
|
|
|
303
316
|
| `ca loop --max-review-cycles <n>` | Max review/fix iterations (default: 3) |
|
|
304
317
|
| `ca loop --review-blocking` | Fail loop if review not approved after max cycles |
|
|
305
318
|
| `ca loop --review-model <model>` | Model for implementer fix sessions (default: claude-opus-4-6) |
|
|
319
|
+
| `ca improve` | Generate improvement loop script from `improve/*.md` programs |
|
|
320
|
+
| `ca improve --topics <names...>` | Run only specific topics |
|
|
321
|
+
| `ca improve --max-iters <n>` | Max iterations per topic (default: 5) |
|
|
322
|
+
| `ca improve --time-budget <seconds>` | Total time budget, 0=unlimited (default: 0) |
|
|
323
|
+
| `ca improve --dry-run` | Validate and print plan without generating |
|
|
324
|
+
| `ca improve --force` | Overwrite existing script |
|
|
325
|
+
| `ca improve init` | Scaffold an example `improve/*.md` program file |
|
|
326
|
+
| `ca watch` | Tail and pretty-print live trace from loop sessions |
|
|
327
|
+
| `ca watch --epic <id>` | Watch a specific epic trace |
|
|
328
|
+
| `ca watch --improve` | Watch improvement loop traces |
|
|
329
|
+
| `ca watch --no-follow` | Print existing trace and exit (no live tail) |
|
|
306
330
|
|
|
307
331
|
### Knowledge
|
|
308
332
|
|
|
@@ -315,15 +339,14 @@ The CLI binary is `ca` (alias: `compound-agent`).
|
|
|
315
339
|
|
|
316
340
|
| Command | Description |
|
|
317
341
|
|---------|-------------|
|
|
318
|
-
| `ca setup` | One-shot setup (hooks +
|
|
319
|
-
| `ca setup --skip-
|
|
320
|
-
| `ca setup --
|
|
321
|
-
| `ca setup --
|
|
322
|
-
| `ca setup
|
|
323
|
-
| `ca setup --dry-run` | Show what would change without changing |
|
|
342
|
+
| `ca setup` | One-shot setup (hooks + templates) |
|
|
343
|
+
| `ca setup --skip-hooks` | Setup without installing hooks |
|
|
344
|
+
| `ca setup --json` | Output result as JSON |
|
|
345
|
+
| `ca setup --repo-root <path>` | Specify repository root |
|
|
346
|
+
| `ca setup claude` | Install Claude Code hooks only |
|
|
324
347
|
| `ca setup claude --status` | Check Claude Code integration health |
|
|
325
348
|
| `ca setup claude --uninstall` | Remove Claude hooks only |
|
|
326
|
-
| `ca
|
|
349
|
+
| `ca init` | Initialize compound-agent in current repo |
|
|
327
350
|
| `ca about` | Show version, animation, and recent changelog |
|
|
328
351
|
| `ca doctor` | Verify external dependencies and project health |
|
|
329
352
|
|
|
@@ -354,7 +377,7 @@ confirmation_boost: confirmed=1.3, unconfirmed=1.0
|
|
|
354
377
|
A: mem0 is a cloud memory layer for general AI agents. Compound Agent is local-first with git-tracked storage and local embeddings — no API keys or cloud services needed. It also goes beyond memory with structured workflows, multi-agent review, and issue tracking.
|
|
355
378
|
|
|
356
379
|
**Q: Does this work offline?**
|
|
357
|
-
A: Yes, completely. Embeddings run locally via
|
|
380
|
+
A: Yes, completely. Embeddings run locally via @huggingface/transformers (Transformers.js). No network requests after the initial model download.
|
|
358
381
|
|
|
359
382
|
**Q: How much disk space does it need?**
|
|
360
383
|
A: ~278MB for the embedding model (one-time download, shared across projects) plus negligible space for lessons.
|
|
@@ -394,7 +417,7 @@ pnpm lint # Type check + ESLint
|
|
|
394
417
|
| Build | tsup |
|
|
395
418
|
| Testing | Vitest + fast-check (property tests) |
|
|
396
419
|
| Storage | better-sqlite3 + FTS5 |
|
|
397
|
-
| Embeddings |
|
|
420
|
+
| Embeddings | @huggingface/transformers + nomic-embed-text-v1.5 (Q8 ONNX) |
|
|
398
421
|
| CLI | Commander.js |
|
|
399
422
|
| Schema | Zod |
|
|
400
423
|
| Issue Tracking | Beads (bd) |
|
package/bin/ca
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Thin wrapper that spawns the Go binary.
|
|
4
|
+
// Uses Node.js only for locating the binary; all work is done by the Go process.
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { resolve, dirname } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
// Resolution order: env override → postinstall binary → local Go build
|
|
14
|
+
const candidates = [
|
|
15
|
+
process.env.CA_BINARY_PATH,
|
|
16
|
+
resolve(__dirname, "ca-binary"),
|
|
17
|
+
resolve(__dirname, "..", "go", "dist", "ca"),
|
|
18
|
+
].filter(Boolean);
|
|
19
|
+
|
|
20
|
+
const binaryPath = candidates.find((p) => existsSync(p));
|
|
21
|
+
|
|
22
|
+
if (!binaryPath) {
|
|
23
|
+
console.error("[compound-agent] Binary not found. Try reinstalling compound-agent or run: cd go && make build");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// execFileSync throws on non-zero exit codes; forward the exit code
|
|
31
|
+
process.exit(err.status || 1);
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compound-agent",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Learning system for Claude Code — avoids repeating mistakes across sessions",
|
|
8
5
|
"bin": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
6
|
+
"ca": "./bin/ca",
|
|
7
|
+
"compound-agent": "./bin/ca"
|
|
11
8
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"import": "./dist/index.js",
|
|
15
|
-
"types": "./dist/index.d.ts"
|
|
16
|
-
}
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
17
11
|
},
|
|
18
12
|
"files": [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/postinstall.cjs",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
22
17
|
"CHANGELOG.md",
|
|
23
18
|
"llms.txt",
|
|
24
19
|
"context7.json"
|
|
25
20
|
],
|
|
21
|
+
"os": [
|
|
22
|
+
"darwin",
|
|
23
|
+
"linux"
|
|
24
|
+
],
|
|
25
|
+
"cpu": [
|
|
26
|
+
"x64",
|
|
27
|
+
"arm64"
|
|
28
|
+
],
|
|
26
29
|
"repository": {
|
|
27
30
|
"type": "git",
|
|
28
31
|
"url": "git+https://github.com/Nathandela/compound-agent.git"
|
|
@@ -32,84 +35,22 @@
|
|
|
32
35
|
},
|
|
33
36
|
"homepage": "https://github.com/Nathandela/compound-agent#readme",
|
|
34
37
|
"llms": "https://raw.githubusercontent.com/Nathandela/compound-agent/main/llms.txt",
|
|
35
|
-
"scripts": {
|
|
36
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
37
|
-
"prebuild": "tsx scripts/extract-changelog.ts",
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"dev": "tsup --watch",
|
|
40
|
-
"test": "pnpm build && vitest run",
|
|
41
|
-
"test:fast": "vitest run --project unit --project embedding",
|
|
42
|
-
"test:unit": "vitest run --project unit",
|
|
43
|
-
"test:integration": "pnpm build && vitest run --project integration",
|
|
44
|
-
"test:watch": "vitest",
|
|
45
|
-
"test:changed": "vitest run --changed HEAD~1",
|
|
46
|
-
"test:all": "pnpm build && pnpm download-model && vitest run",
|
|
47
|
-
"test:segment": "tsx src/test-utils/run-segment.ts",
|
|
48
|
-
"test:random": "tsx src/test-utils/run-random.ts",
|
|
49
|
-
"test:critical": "vitest run --project unit -- critical",
|
|
50
|
-
"lint": "tsc --noEmit && eslint . --max-warnings=0",
|
|
51
|
-
"download-model": "node ./dist/cli.js download-model",
|
|
52
|
-
"prepublishOnly": "pnpm build"
|
|
53
|
-
},
|
|
54
38
|
"keywords": [
|
|
55
39
|
"claude",
|
|
56
40
|
"claude-code",
|
|
57
41
|
"compound-agent",
|
|
58
|
-
"semantic-memory",
|
|
59
|
-
"memory",
|
|
60
|
-
"embeddings",
|
|
61
|
-
"vector-search",
|
|
62
42
|
"ai",
|
|
63
43
|
"agent",
|
|
64
44
|
"llm",
|
|
65
|
-
"plugin",
|
|
66
45
|
"cli",
|
|
67
46
|
"developer-tools",
|
|
68
47
|
"workflow",
|
|
69
48
|
"tdd",
|
|
70
|
-
"sqlite",
|
|
71
49
|
"knowledge-management"
|
|
72
50
|
],
|
|
73
51
|
"author": "Nathan Delacrétaz",
|
|
74
52
|
"license": "MIT",
|
|
75
|
-
"packageManager": "pnpm@10.28.2",
|
|
76
53
|
"engines": {
|
|
77
|
-
"node": ">=
|
|
78
|
-
},
|
|
79
|
-
"devDependencies": {
|
|
80
|
-
"@eslint/js": "^9.39.2",
|
|
81
|
-
"@fast-check/vitest": "0.2.4",
|
|
82
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
83
|
-
"@types/node": "^20.11.0",
|
|
84
|
-
"@typescript-eslint/rule-tester": "8.55.0",
|
|
85
|
-
"@vitest/coverage-v8": "2.1.9",
|
|
86
|
-
"eslint": "^9.39.2",
|
|
87
|
-
"eslint-config-prettier": "10.1.8",
|
|
88
|
-
"eslint-plugin-import-x": "4.16.1",
|
|
89
|
-
"eslint-plugin-vitest": "0.5.4",
|
|
90
|
-
"fast-check": "4.5.3",
|
|
91
|
-
"tsup": "^8.0.0",
|
|
92
|
-
"tsx": "^4.0.0",
|
|
93
|
-
"typescript": "^5.3.0",
|
|
94
|
-
"typescript-eslint": "8.55.0",
|
|
95
|
-
"vitest": "^2.0.0"
|
|
96
|
-
},
|
|
97
|
-
"dependencies": {
|
|
98
|
-
"better-sqlite3": "^11.0.0",
|
|
99
|
-
"chalk": "5.6.2",
|
|
100
|
-
"commander": "^12.0.0",
|
|
101
|
-
"node-llama-cpp": "^3.0.0",
|
|
102
|
-
"zod": "^3.22.0"
|
|
103
|
-
},
|
|
104
|
-
"pnpm": {
|
|
105
|
-
"onlyBuiltDependencies": [
|
|
106
|
-
"better-sqlite3",
|
|
107
|
-
"node-llama-cpp",
|
|
108
|
-
"esbuild"
|
|
109
|
-
],
|
|
110
|
-
"overrides": {
|
|
111
|
-
"tar": ">=7.5.7",
|
|
112
|
-
"axios": ">=1.13.5"
|
|
113
|
-
}
|
|
54
|
+
"node": ">=18"
|
|
114
55
|
}
|
|
115
56
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Downloads platform-specific binaries (ca + ca-embed) from GitHub Releases.
|
|
4
|
+
// Uses Node.js platform detection (NOT Go's runtime.GOARCH) to handle
|
|
5
|
+
// Rosetta/emulation correctly on Apple Silicon.
|
|
6
|
+
//
|
|
7
|
+
// Exports getPlatformKey, verifyChecksum, shouldSkipDownload for testability.
|
|
8
|
+
|
|
9
|
+
const https = require("https");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { execFileSync } = require("child_process");
|
|
13
|
+
const { createHash } = require("crypto");
|
|
14
|
+
|
|
15
|
+
const PLATFORM_MAP = { darwin: "darwin", linux: "linux" };
|
|
16
|
+
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
|
17
|
+
const REPO = "Nathandela/compound-agent";
|
|
18
|
+
|
|
19
|
+
function getPlatformKey(platform, arch) {
|
|
20
|
+
const p = PLATFORM_MAP[platform];
|
|
21
|
+
const a = ARCH_MAP[arch];
|
|
22
|
+
if (!p || !a) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Unsupported platform: ${platform}-${arch}. Supported: darwin-amd64, darwin-arm64, linux-amd64, linux-arm64`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return `${p}-${a}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function verifyChecksum(filePath, artifactName, checksumsPath) {
|
|
31
|
+
const checksums = fs.readFileSync(checksumsPath, "utf-8");
|
|
32
|
+
const lines = checksums.trim().split("\n");
|
|
33
|
+
|
|
34
|
+
let expectedHash = null;
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
// GoReleaser format: <sha256> <filename>
|
|
37
|
+
const parts = line.trim().split(/\s+/);
|
|
38
|
+
if (parts.length >= 2 && parts[1] === artifactName) {
|
|
39
|
+
expectedHash = parts[0];
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!expectedHash) {
|
|
45
|
+
throw new Error(`${artifactName} not found in checksums.txt`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const fileData = fs.readFileSync(filePath);
|
|
49
|
+
const actualHash = createHash("sha256").update(fileData).digest("hex");
|
|
50
|
+
return actualHash === expectedHash;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function shouldSkipDownload(binDir) {
|
|
54
|
+
const caPath = path.join(binDir, "ca-binary");
|
|
55
|
+
const embedPath = path.join(binDir, "ca-embed");
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(caPath) || !fs.existsSync(embedPath)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// P1-2 fix: use execFileSync (no shell) instead of execSync
|
|
63
|
+
execFileSync(caPath, ["version"], { stdio: "pipe" });
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function downloadFile(url, dest) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const follow = (currentUrl, redirects) => {
|
|
73
|
+
if (redirects > 5) {
|
|
74
|
+
reject(new Error("Too many redirects"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// P0-2 fix: validate redirect URLs stay on HTTPS
|
|
79
|
+
if (!currentUrl.startsWith("https://")) {
|
|
80
|
+
reject(new Error(`Refusing non-HTTPS redirect: ${currentUrl}`));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
https
|
|
85
|
+
.get(currentUrl, { timeout: 60000 }, (res) => {
|
|
86
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
87
|
+
follow(res.headers.location, redirects + 1);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (res.statusCode !== 200) {
|
|
92
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode} from ${currentUrl}`));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const file = fs.createWriteStream(dest);
|
|
97
|
+
res.pipe(file);
|
|
98
|
+
file.on("finish", resolve);
|
|
99
|
+
file.on("error", (err) => {
|
|
100
|
+
fs.unlink(dest, () => {});
|
|
101
|
+
reject(err);
|
|
102
|
+
});
|
|
103
|
+
res.on("error", (err) => {
|
|
104
|
+
file.destroy();
|
|
105
|
+
fs.unlink(dest, () => {});
|
|
106
|
+
reject(err);
|
|
107
|
+
});
|
|
108
|
+
})
|
|
109
|
+
.on("timeout", () => {
|
|
110
|
+
reject(new Error(`Download timed out: ${currentUrl}`));
|
|
111
|
+
})
|
|
112
|
+
.on("error", reject);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
follow(url, 0);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// P1-3 fix: download to .tmp name, rename after checksum verification
|
|
120
|
+
async function downloadBinary(binDir, url, destName, label) {
|
|
121
|
+
const tmpPath = path.join(binDir, destName + ".tmp");
|
|
122
|
+
|
|
123
|
+
await downloadFile(url, tmpPath);
|
|
124
|
+
|
|
125
|
+
const stats = fs.statSync(tmpPath);
|
|
126
|
+
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
127
|
+
console.log(`[compound-agent] ${label}: ${sizeMB} MB`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function cleanupBinaries(binDir) {
|
|
131
|
+
for (const name of ["ca-binary", "ca-binary.tmp", "ca-embed", "ca-embed.tmp", "checksums.txt"]) {
|
|
132
|
+
try { fs.unlinkSync(path.join(binDir, name)); } catch { /* ignore */ }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function main() {
|
|
137
|
+
// Skip self-install (when running pnpm install inside compound-agent itself)
|
|
138
|
+
if (process.env.npm_package_name === "compound-agent") return;
|
|
139
|
+
|
|
140
|
+
const platformKey = getPlatformKey(
|
|
141
|
+
require("os").platform(),
|
|
142
|
+
require("os").arch()
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const pkg = require("../package.json");
|
|
146
|
+
const version = pkg.version;
|
|
147
|
+
|
|
148
|
+
const binDir = path.resolve(__dirname, "../bin");
|
|
149
|
+
|
|
150
|
+
if (shouldSkipDownload(binDir)) {
|
|
151
|
+
console.log("[compound-agent] Binaries already installed, skipping download");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`[compound-agent] Platform: ${platformKey}`);
|
|
156
|
+
console.log(`[compound-agent] Downloading: v${version}`);
|
|
157
|
+
|
|
158
|
+
if (!fs.existsSync(binDir)) {
|
|
159
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const baseUrl = `https://github.com/${REPO}/releases/download/v${version}`;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Download checksums first
|
|
166
|
+
const checksumsPath = path.join(binDir, "checksums.txt");
|
|
167
|
+
await downloadFile(`${baseUrl}/checksums.txt`, checksumsPath);
|
|
168
|
+
|
|
169
|
+
// Download both binaries in parallel (to .tmp names)
|
|
170
|
+
const caArtifact = `ca-${platformKey}`;
|
|
171
|
+
const embedArtifact = `ca-embed-${platformKey}`;
|
|
172
|
+
|
|
173
|
+
await Promise.all([
|
|
174
|
+
downloadBinary(binDir, `${baseUrl}/${caArtifact}`, "ca-binary", "CLI binary"),
|
|
175
|
+
downloadBinary(binDir, `${baseUrl}/${embedArtifact}`, "ca-embed", "Embed daemon"),
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
// Verify checksums against .tmp files
|
|
179
|
+
const caOk = verifyChecksum(path.join(binDir, "ca-binary.tmp"), caArtifact, checksumsPath);
|
|
180
|
+
const embedOk = verifyChecksum(path.join(binDir, "ca-embed.tmp"), embedArtifact, checksumsPath);
|
|
181
|
+
|
|
182
|
+
if (!caOk || !embedOk) {
|
|
183
|
+
const failed = [];
|
|
184
|
+
if (!caOk) failed.push("ca");
|
|
185
|
+
if (!embedOk) failed.push("ca-embed");
|
|
186
|
+
// P1-4 fix: clean up bad binaries before throwing
|
|
187
|
+
cleanupBinaries(binDir);
|
|
188
|
+
throw new Error(`Checksum verification failed for: ${failed.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log("[compound-agent] Checksums verified");
|
|
192
|
+
|
|
193
|
+
// Checksums passed — rename .tmp to final names and set executable
|
|
194
|
+
fs.renameSync(path.join(binDir, "ca-binary.tmp"), path.join(binDir, "ca-binary"));
|
|
195
|
+
fs.chmodSync(path.join(binDir, "ca-binary"), 0o755);
|
|
196
|
+
fs.renameSync(path.join(binDir, "ca-embed.tmp"), path.join(binDir, "ca-embed"));
|
|
197
|
+
fs.chmodSync(path.join(binDir, "ca-embed"), 0o755);
|
|
198
|
+
|
|
199
|
+
// Functional verification (P1-2 fix: use execFileSync)
|
|
200
|
+
try {
|
|
201
|
+
execFileSync(path.join(binDir, "ca-binary"), ["version"], { stdio: "pipe" });
|
|
202
|
+
console.log("[compound-agent] Functional check passed");
|
|
203
|
+
} catch {
|
|
204
|
+
cleanupBinaries(binDir);
|
|
205
|
+
throw new Error("Binary downloaded but functional check failed (ca version exited non-zero)");
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error(`[compound-agent] Installation failed: ${err.message}`);
|
|
209
|
+
console.error("[compound-agent] You can manually download binaries from:");
|
|
210
|
+
console.error(`[compound-agent] https://github.com/${REPO}/releases/tag/v${version}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Export for testing
|
|
216
|
+
module.exports = { getPlatformKey, verifyChecksum, shouldSkipDownload };
|
|
217
|
+
|
|
218
|
+
// Run main only when executed directly (not when required for testing)
|
|
219
|
+
if (require.main === module) {
|
|
220
|
+
main();
|
|
221
|
+
}
|
package/dist/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|