compound-agent 1.7.4 → 1.8.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 +50 -0
- package/README.md +213 -60
- package/dist/cli.js +637 -84
- package/dist/cli.js.map +1 -1
- package/dist/index.js +26 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { z } from 'zod';
|
|
|
8
8
|
import * as fs from 'fs/promises';
|
|
9
9
|
import { readFile, mkdir, appendFile, writeFile, chmod, rm, rename, readdir } from 'fs/promises';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
|
-
import { execSync, execFileSync, spawn } from 'child_process';
|
|
11
|
+
import { execSync, spawnSync, execFileSync, spawn } from 'child_process';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import { Command } from 'commander';
|
|
14
14
|
import chalk5 from 'chalk';
|
|
@@ -3132,7 +3132,7 @@ function checkBeadsAvailable() {
|
|
|
3132
3132
|
} catch {
|
|
3133
3133
|
return {
|
|
3134
3134
|
available: false,
|
|
3135
|
-
message: "Beads CLI not found. Recommended for full workflow (issue tracking, deps, TDD pipeline)
|
|
3135
|
+
message: "Beads CLI not found. Recommended for full workflow (issue tracking, deps, TDD pipeline).\nInstall: curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash\nOr run: ca install-beads"
|
|
3136
3136
|
};
|
|
3137
3137
|
}
|
|
3138
3138
|
}
|
|
@@ -3313,7 +3313,7 @@ This project uses compound-agent for session memory via **CLI commands**.
|
|
|
3313
3313
|
| Command | Purpose |
|
|
3314
3314
|
|---------|---------|
|
|
3315
3315
|
| \`npx ca search "query"\` | Search lessons - MUST call before architectural decisions; use anytime you need context |
|
|
3316
|
-
| \`npx ca knowledge "query"\` |
|
|
3316
|
+
| \`npx ca knowledge "query"\` | Semantic search over project docs - MUST call before architectural decisions; use keyword phrases, not questions |
|
|
3317
3317
|
| \`npx ca learn "insight"\` | Capture lessons - use AFTER corrections or discoveries |
|
|
3318
3318
|
| \`npx ca list\` | List all stored lessons |
|
|
3319
3319
|
| \`npx ca show <id>\` | Show details of a specific lesson |
|
|
@@ -3680,6 +3680,8 @@ function printBeadsFullStatus(check) {
|
|
|
3680
3680
|
if (check.initialized) {
|
|
3681
3681
|
console.log(` Beads health: ${check.healthy ? "OK" : `issues found${check.healthMessage ? ` \u2014 ${check.healthMessage}` : ""}`}`);
|
|
3682
3682
|
}
|
|
3683
|
+
} else if (check.healthMessage) {
|
|
3684
|
+
console.log(` ${check.healthMessage}`);
|
|
3683
3685
|
}
|
|
3684
3686
|
}
|
|
3685
3687
|
function printScopeStatus(scope) {
|
|
@@ -5694,7 +5696,7 @@ npx ca loop # Generate infinity loop script for autonomous pr
|
|
|
5694
5696
|
npx ca loop --epics epic-1 epic-2
|
|
5695
5697
|
npx ca loop --output my-loop.sh
|
|
5696
5698
|
npx ca loop --max-retries 5
|
|
5697
|
-
npx ca loop --model claude-opus-4-6
|
|
5699
|
+
npx ca loop --model claude-opus-4-6[1m]
|
|
5698
5700
|
npx ca loop --force # Overwrite existing script
|
|
5699
5701
|
\`\`\`
|
|
5700
5702
|
|
|
@@ -9434,6 +9436,8 @@ var LESSON_COUNT_WARNING_THRESHOLD = 20;
|
|
|
9434
9436
|
var AGE_FLAG_THRESHOLD_DAYS = 90;
|
|
9435
9437
|
var ISO_DATE_PREFIX_LENGTH = 10;
|
|
9436
9438
|
var AVG_DECIMAL_PLACES = 1;
|
|
9439
|
+
var DEFAULT_LOOP_MODEL = "claude-opus-4-6[1m]";
|
|
9440
|
+
var MODEL_PATTERN = /^[a-zA-Z0-9_.:[\]/-]+$/;
|
|
9437
9441
|
|
|
9438
9442
|
// src/commands/management-helpers.ts
|
|
9439
9443
|
function formatLessonHuman(lesson) {
|
|
@@ -9651,7 +9655,7 @@ async function runDoctor(repoRoot) {
|
|
|
9651
9655
|
checks.push(pnpmCheck);
|
|
9652
9656
|
}
|
|
9653
9657
|
const beadsResult = checkBeadsAvailable();
|
|
9654
|
-
checks.push(beadsResult.available ? { name: "Beads CLI", status: "pass" } : { name: "Beads CLI", status: "warn", fix: "
|
|
9658
|
+
checks.push(beadsResult.available ? { name: "Beads CLI", status: "pass" } : { name: "Beads CLI", status: "warn", fix: "Run: ca install-beads" });
|
|
9655
9659
|
checks.push(checkGitignoreHealth(repoRoot) ? { name: ".gitignore health", status: "pass" } : { name: ".gitignore health", status: "warn", fix: "Run: npx ca setup --update" });
|
|
9656
9660
|
const docPath = join(repoRoot, "docs", "compound", "README.md");
|
|
9657
9661
|
checks.push(existsSync(docPath) ? { name: "Usage documentation", status: "pass" } : { name: "Usage documentation", status: "warn", fix: "Run: npx ca setup" });
|
|
@@ -9986,14 +9990,13 @@ var FETCH_TIMEOUT_MS = 3e3;
|
|
|
9986
9990
|
var CACHE_FILENAME = "update-check.json";
|
|
9987
9991
|
async function fetchLatestVersion(packageName = "compound-agent") {
|
|
9988
9992
|
try {
|
|
9989
|
-
const res = await fetch(
|
|
9990
|
-
|
|
9991
|
-
|
|
9993
|
+
const res = await fetch(
|
|
9994
|
+
`https://registry.npmjs.org/-/package/${packageName}/dist-tags`,
|
|
9995
|
+
{ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }
|
|
9996
|
+
);
|
|
9992
9997
|
if (!res.ok) return null;
|
|
9993
9998
|
const data = await res.json();
|
|
9994
|
-
const
|
|
9995
|
-
if (typeof tags !== "object" || tags === null) return null;
|
|
9996
|
-
const latest = tags["latest"];
|
|
9999
|
+
const latest = data["latest"];
|
|
9997
10000
|
return typeof latest === "string" ? latest : null;
|
|
9998
10001
|
} catch {
|
|
9999
10002
|
return null;
|
|
@@ -10027,13 +10030,41 @@ async function checkForUpdate(cacheDir) {
|
|
|
10027
10030
|
return null;
|
|
10028
10031
|
}
|
|
10029
10032
|
}
|
|
10033
|
+
function isMajorUpdate(current, latest) {
|
|
10034
|
+
return parseInt(latest.split(".")[0], 10) > parseInt(current.split(".")[0], 10);
|
|
10035
|
+
}
|
|
10030
10036
|
function formatUpdateNotification(current, latest) {
|
|
10031
|
-
|
|
10032
|
-
|
|
10037
|
+
const label = isMajorUpdate(current, latest) ? "Major update" : "Update available";
|
|
10038
|
+
const warning = isMajorUpdate(current, latest) ? "\n May contain breaking changes -- check the changelog." : "";
|
|
10039
|
+
return [
|
|
10040
|
+
`${label}: ${current} -> ${latest}${warning}`,
|
|
10041
|
+
`Run: npm update -g compound-agent (global)`,
|
|
10042
|
+
` pnpm add -D compound-agent@latest (dev dependency)`
|
|
10043
|
+
].join("\n");
|
|
10044
|
+
}
|
|
10045
|
+
function formatUpdateNotificationMarkdown(current, latest) {
|
|
10046
|
+
const urgency = isMajorUpdate(current, latest) ? " (MAJOR - may contain breaking changes)" : "";
|
|
10047
|
+
return `
|
|
10048
|
+
---
|
|
10049
|
+
# Update Available
|
|
10050
|
+
compound-agent v${latest} is available (current: v${current})${urgency}.
|
|
10051
|
+
Run: \`npm update -g compound-agent\` (global) or \`pnpm add -D compound-agent@latest\` (dev dependency)
|
|
10052
|
+
`;
|
|
10053
|
+
}
|
|
10054
|
+
function shouldCheckForUpdate() {
|
|
10055
|
+
if (!process.stdout.isTTY) return false;
|
|
10056
|
+
if (process.env["CI"]) return false;
|
|
10057
|
+
if (process.env["NO_UPDATE_NOTIFIER"]) return false;
|
|
10058
|
+
if (process.env["NODE_ENV"] === "test") return false;
|
|
10059
|
+
return true;
|
|
10033
10060
|
}
|
|
10034
10061
|
function semverGt(a, b) {
|
|
10035
10062
|
const parse = (v) => {
|
|
10036
|
-
const
|
|
10063
|
+
const clean = v.split("-")[0];
|
|
10064
|
+
const parts = clean.split(".").map((n) => {
|
|
10065
|
+
const num = parseInt(n, 10);
|
|
10066
|
+
return isNaN(num) ? 0 : num;
|
|
10067
|
+
});
|
|
10037
10068
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
10038
10069
|
};
|
|
10039
10070
|
const [aMaj, aMin, aPat] = parse(a);
|
|
@@ -10048,7 +10079,7 @@ function readCache(cachePath) {
|
|
|
10048
10079
|
if (Date.now() - stat.mtimeMs > CACHE_TTL_MS) return null;
|
|
10049
10080
|
const raw = readFileSync(cachePath, "utf-8");
|
|
10050
10081
|
const data = JSON.parse(raw);
|
|
10051
|
-
if (!data.latest) return null;
|
|
10082
|
+
if (typeof data.latest !== "string" || !data.latest) return null;
|
|
10052
10083
|
return data;
|
|
10053
10084
|
} catch {
|
|
10054
10085
|
return null;
|
|
@@ -10067,7 +10098,7 @@ var TRUST_LANGUAGE_TEMPLATE = `# Compound Agent Active
|
|
|
10067
10098
|
| Command | Purpose |
|
|
10068
10099
|
|---------|---------|
|
|
10069
10100
|
| \`npx ca search "query"\` | Search lessons - MUST call before architectural decisions; use anytime you need context |
|
|
10070
|
-
| \`npx ca knowledge "query"\` |
|
|
10101
|
+
| \`npx ca knowledge "query"\` | Semantic search over project docs - MUST call before architectural decisions; use keyword phrases, not questions |
|
|
10071
10102
|
| \`npx ca learn "insight"\` | Capture lessons - call AFTER corrections or discoveries |
|
|
10072
10103
|
|
|
10073
10104
|
## Core Constraints
|
|
@@ -10167,15 +10198,11 @@ ${formattedLessons}
|
|
|
10167
10198
|
if (cookitSection !== null) {
|
|
10168
10199
|
output += cookitSection;
|
|
10169
10200
|
}
|
|
10170
|
-
if (!process.stdout.isTTY) {
|
|
10201
|
+
if (!process.stdout.isTTY && !process.env["CI"] && !process.env["NO_UPDATE_NOTIFIER"]) {
|
|
10171
10202
|
try {
|
|
10172
10203
|
const updateResult = await checkForUpdate(join(root, ".claude", ".cache"));
|
|
10173
10204
|
if (updateResult?.updateAvailable) {
|
|
10174
|
-
output +=
|
|
10175
|
-
---
|
|
10176
|
-
# Update Available
|
|
10177
|
-
compound-agent v${updateResult.latest} is available (current: v${updateResult.current}). Run \`pnpm update --latest compound-agent\` to update.
|
|
10178
|
-
`;
|
|
10205
|
+
output += formatUpdateNotificationMarkdown(updateResult.current, updateResult.latest);
|
|
10179
10206
|
}
|
|
10180
10207
|
} catch {
|
|
10181
10208
|
}
|
|
@@ -10905,70 +10932,59 @@ function registerVerifyGatesCommand(program) {
|
|
|
10905
10932
|
}
|
|
10906
10933
|
|
|
10907
10934
|
// src/changelog-data.ts
|
|
10908
|
-
var CHANGELOG_RECENT = `## [1.
|
|
10935
|
+
var CHANGELOG_RECENT = `## [1.8.0] - 2026-03-15
|
|
10909
10936
|
|
|
10910
10937
|
### Added
|
|
10911
10938
|
|
|
10912
|
-
-
|
|
10913
|
-
|
|
10914
|
-
- **Spec-dev**: Cynefin classification (Clear/Complicated/Complex), composition EARS templates (timeout/retry interactions), change volatility assessment
|
|
10915
|
-
- **Plan**: boundary stability check, Last Responsible Moment identification, change coupling prevention
|
|
10916
|
-
- **Work**: Fowler technical debt quadrant (only Prudent/Deliberate accepted), composition boundary verification with metastable failure checks
|
|
10917
|
-
- **Review**: composition-specific reviewers (boundary-reviewer, control-structure-reviewer, observability-reviewer), architect assumption validation
|
|
10918
|
-
- **Compound**: decomposition quality assessment, assumption tracking (predicted vs actual), emergence root cause classification (Garlan/STPA/phase transition)
|
|
10919
|
-
- **Lint graduation in compound phase**: The compound phase (step 10) now spawns a \`lint-classifier\` subagent that classifies each captured insight as LINTABLE, PARTIAL, or NOT_LINTABLE. High-confidence lintable insights are promoted to beads tasks under a "Linting Improvement" epic with self-contained rule specifications. Two rule classes: Class A (native \`rules.json\` \u2014 regex/glob) and Class B (external linter \u2014 AST analysis).
|
|
10920
|
-
- **Linter detection module** (\`src/lint/\`): Scans repos for ESLint (flat + legacy configs including TypeScript variants), Ruff (including \`pyproject.toml\`), Clippy, golangci-lint, ast-grep, and Semgrep. Exported from the package as \`detectLinter()\`, \`LinterInfoSchema\`, \`LinterNameSchema\`.
|
|
10921
|
-
- **Lint-classifier agent template**: Ships via \`npx ca init\` to \`.claude/agents/compound/lint-classifier.md\`. Includes 7 few-shot examples, Class A/B routing, and linter-aware task creation.
|
|
10939
|
+
- **\`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.
|
|
10940
|
+
- **\`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.
|
|
10922
10941
|
|
|
10923
10942
|
### Fixed
|
|
10924
10943
|
|
|
10925
|
-
-
|
|
10944
|
+
- **\`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.
|
|
10945
|
+
- **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.
|
|
10946
|
+
- **\`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.
|
|
10947
|
+
- **\`--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.
|
|
10948
|
+
- **Update-check hardening**: Switched to a lightweight npm registry endpoint, added CI environment guards, and corrected the update command shown to users.
|
|
10926
10949
|
|
|
10927
|
-
## [1.7.
|
|
10950
|
+
## [1.7.6] - 2026-03-12
|
|
10928
10951
|
|
|
10929
10952
|
### Added
|
|
10930
10953
|
|
|
10931
|
-
-
|
|
10954
|
+
- **\`ca install-beads\` command**: Standalone subcommand to install the beads CLI via the official script. Includes a platform guard (skips on Windows with \`exitCode 1\`), an "already installed" short-circuit, a \`--yes\` flag to bypass the confirmation hint (safe: never runs \`curl | bash\` without explicit opt-in), \`spawnSync\` with a 60-second timeout, and a post-install shell-reload warning. Non-TTY mode without \`--yes\` prints the install command as a copy-pasteable hint rather than silently doing nothing.
|
|
10932
10955
|
|
|
10933
10956
|
### Fixed
|
|
10934
10957
|
|
|
10935
|
-
- **
|
|
10936
|
-
- **
|
|
10958
|
+
- **Beads hint display**: \`printBeadsFullStatus\` was silently swallowing the install hint message when the beads CLI was not found. The curl install command is now printed below the "not found" line.
|
|
10959
|
+
- **Beads hint text**: \`checkBeadsAvailable\` now returns the actual \`curl -sSL ... | bash\` install command in its message instead of a bare repo URL.
|
|
10960
|
+
- **Doctor fix message**: \`ca doctor\` now shows \`Run: ca install-beads\` for the missing-beads check instead of pointing to a URL.
|
|
10961
|
+
- **\`ca knowledge\` description**: Reframed from "Ask the project docs any question" to "Semantic search over project docs \u2014 use keyword phrases, not questions" in both the live prime template and the setup template, reflecting the underlying embedding RAG retrieval mechanism.
|
|
10937
10962
|
|
|
10938
|
-
## [1.7.
|
|
10963
|
+
## [1.7.5] - 2026-03-12
|
|
10939
10964
|
|
|
10940
10965
|
### Added
|
|
10941
10966
|
|
|
10942
|
-
-
|
|
10943
|
-
|
|
10944
|
-
### Fixed
|
|
10945
|
-
|
|
10946
|
-
- **Security: command injection in \`ca test-summary --cmd\`**: User-supplied test command is now validated against an allowlist of safe prefixes (\`pnpm\`, \`npm\`, \`vitest\`, etc.) and shell metacharacters are rejected.
|
|
10947
|
-
- **Security: shell injection in \`ca doctor\`**: Replaced \`execSync(cmd, {shell})\` with \`execFileSync('bd', ['doctor'])\` to avoid shell interpretation.
|
|
10948
|
-
- **Portable timeout for macOS**: Generated loop scripts now use a \`portable_timeout()\` wrapper that tries GNU \`timeout\`, then \`gtimeout\` (Homebrew coreutils), then a shell-based kill/watchdog fallback. Previously failed silently on macOS.
|
|
10949
|
-
- **Session ID python3 fallback**: Review phase session ID management now falls back to python3 when \`jq\` is unavailable, with a centralized \`read_session_id()\` helper.
|
|
10950
|
-
- **Git diff window stability**: Replaced fragile \`HEAD~$N..HEAD\` commit-count arithmetic with SHA-based \`$REVIEW_BASE_SHA..HEAD\` diff ranges, immune to rebases and cherry-picks.
|
|
10951
|
-
- **ID collision risk**: Memory item IDs now use 64-bit entropy (16 hex chars) instead of 32-bit (8 hex chars).
|
|
10952
|
-
- **JSONL resilience**: Malformed lines in JSONL files are now skipped with try/catch per line instead of crashing the entire read.
|
|
10953
|
-
- **Stdin timeout leak**: \`clearTimeout\` now called in \`finally\` block for stdin reads in retrieval and hooks.
|
|
10954
|
-
- **Double JSONL read eliminated**: \`readMemoryItems()\` now returns \`deletedIds\` set, removing the need for a separate \`wasLessonDeleted()\` file read.
|
|
10955
|
-
- **FTS5 trigger optimization**: SQLite update trigger now scoped to FTS-indexed columns only, reducing unnecessary FTS rebuilds.
|
|
10956
|
-
- **Clustering noise accuracy**: Single-item clusters now correctly returned as \`noise\` instead of an always-empty noise array.
|
|
10957
|
-
- **Embed-worker path validation**: \`embed-worker\` command now validates that \`repoRoot\` exists and is a directory before proceeding.
|
|
10958
|
-
- **Script check timeout**: Rule-based script checks now have a 30-second default timeout, configurable via \`check.timeout\`.
|
|
10967
|
+
- **\`ca feedback\` command**: Surfaces the GitHub Discussions URL for bug reports and feature requests. \`ca feedback --open\` opens the page directly in the browser. Cross-platform (macOS \`open\`, Windows \`start\`, Linux \`xdg-open\`).
|
|
10968
|
+
- **Star and feedback prompt in \`ca about\`**: TTY sessions now see a star-us link and the GitHub Discussions URL after the changelog output.
|
|
10959
10969
|
|
|
10960
10970
|
### Changed
|
|
10961
10971
|
|
|
10962
|
-
- **
|
|
10963
|
-
-
|
|
10964
|
-
-
|
|
10965
|
-
-
|
|
10966
|
-
-
|
|
10967
|
-
-
|
|
10968
|
-
-
|
|
10969
|
-
-
|
|
10972
|
+
- **README overhaul**: Complete rewrite to present compound-agent as a full agentic development environment rather than a memory plugin.
|
|
10973
|
+
- New thesis-driven one-liner that names category, mechanism, and benefit
|
|
10974
|
+
- "What gets installed" inventory table (15 commands, 24 agent role skills, 7 hooks, 5 phase skills, 5 docs)
|
|
10975
|
+
- Three principles section mapping each architecture layer to the problem it solves (Memory / Feedback Loops / Navigable Structure)
|
|
10976
|
+
- "Agents are interchangeable" design principle explained in the overview
|
|
10977
|
+
- Levels of use replacing flat Quick Start: memory-only, structured workflow, and factory mode with code examples
|
|
10978
|
+
- \`/compound:architect\` promoted to its own section with 4-phase description and context-window motivation
|
|
10979
|
+
- Infinity loop elevated from CLI table row to its own section with full flag examples and honest maturity note
|
|
10980
|
+
- Automatic hooks table with per-hook descriptions
|
|
10981
|
+
- Architecture diagram updated to reflect three-principle mapping and accurate counts
|
|
10982
|
+
- Compound loop diagram updated with architect as optional upstream entry point
|
|
10983
|
+
- "Open with an AI agent" entry point in the Documentation section`;
|
|
10970
10984
|
|
|
10971
10985
|
// src/commands/about.ts
|
|
10986
|
+
var REPO_URL = "https://github.com/Nathandela/compound-agent";
|
|
10987
|
+
var DISCUSSIONS_URL = `${REPO_URL}/discussions`;
|
|
10972
10988
|
function registerAboutCommand(program) {
|
|
10973
10989
|
program.command("about").description("Show version, animation, and recent changelog").action(async () => {
|
|
10974
10990
|
if (process.stdout.isTTY) {
|
|
@@ -10978,6 +10994,30 @@ function registerAboutCommand(program) {
|
|
|
10978
10994
|
}
|
|
10979
10995
|
console.log("");
|
|
10980
10996
|
console.log(CHANGELOG_RECENT);
|
|
10997
|
+
if (process.stdout.isTTY) {
|
|
10998
|
+
console.log("");
|
|
10999
|
+
console.log(`Find this useful? Star us: ${REPO_URL}`);
|
|
11000
|
+
console.log(`Feedback & discussions: ${DISCUSSIONS_URL}`);
|
|
11001
|
+
}
|
|
11002
|
+
});
|
|
11003
|
+
}
|
|
11004
|
+
var REPO_URL2 = "https://github.com/Nathandela/compound-agent";
|
|
11005
|
+
var DISCUSSIONS_URL2 = `${REPO_URL2}/discussions`;
|
|
11006
|
+
function openUrl(url) {
|
|
11007
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
11008
|
+
spawn(opener, [url], { detached: true, stdio: "ignore" }).unref();
|
|
11009
|
+
}
|
|
11010
|
+
function registerFeedbackCommand(program) {
|
|
11011
|
+
program.command("feedback").description("Open GitHub Discussions to share feedback or report issues").option("--open", "Open the Discussions page in your browser").action((opts) => {
|
|
11012
|
+
console.log(`Feedback & discussions: ${DISCUSSIONS_URL2}`);
|
|
11013
|
+
console.log(`Repository: ${REPO_URL2}`);
|
|
11014
|
+
if (opts.open && process.stdout.isTTY) {
|
|
11015
|
+
openUrl(DISCUSSIONS_URL2);
|
|
11016
|
+
console.log("Opening in browser...");
|
|
11017
|
+
} else if (!opts.open) {
|
|
11018
|
+
console.log("");
|
|
11019
|
+
console.log("Run `ca feedback --open` to open in your browser.");
|
|
11020
|
+
}
|
|
10981
11021
|
});
|
|
10982
11022
|
}
|
|
10983
11023
|
|
|
@@ -11160,8 +11200,8 @@ function registerKnowledgeIndexCommand(program) {
|
|
|
11160
11200
|
}
|
|
11161
11201
|
});
|
|
11162
11202
|
program.command("embed-worker <repoRoot>", { hidden: true }).description("Internal: background embedding worker").action(async (repoRoot) => {
|
|
11163
|
-
const { existsSync:
|
|
11164
|
-
if (!
|
|
11203
|
+
const { existsSync: existsSync25, statSync: statSync7 } = await import('fs');
|
|
11204
|
+
if (!existsSync25(repoRoot) || !statSync7(repoRoot).isDirectory()) {
|
|
11165
11205
|
out.error(`Invalid repoRoot: "${repoRoot}" is not a directory`);
|
|
11166
11206
|
process.exitCode = 1;
|
|
11167
11207
|
return;
|
|
@@ -11264,6 +11304,41 @@ async function cleanLessonsAction() {
|
|
|
11264
11304
|
function registerCleanLessonsCommand(program) {
|
|
11265
11305
|
program.command("clean-lessons").description("Analyze lessons for semantic duplicates and contradictions").action(cleanLessonsAction);
|
|
11266
11306
|
}
|
|
11307
|
+
var INSTALL_SCRIPT_URL = "https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh";
|
|
11308
|
+
var INSTALL_CMD = `curl -sSL ${INSTALL_SCRIPT_URL} | bash`;
|
|
11309
|
+
function registerInstallBeadsCommand(program) {
|
|
11310
|
+
program.command("install-beads").description("Install the beads CLI via the official install script").option("--yes", "Skip confirmation prompt and install immediately").action((opts) => {
|
|
11311
|
+
if (process.platform === "win32") {
|
|
11312
|
+
console.error("Beads installation is not supported on Windows.");
|
|
11313
|
+
process.exitCode = 1;
|
|
11314
|
+
return;
|
|
11315
|
+
}
|
|
11316
|
+
if (checkBeadsAvailable().available) {
|
|
11317
|
+
console.log("Beads CLI (bd) is already installed.");
|
|
11318
|
+
return;
|
|
11319
|
+
}
|
|
11320
|
+
console.log(`Install script: ${INSTALL_SCRIPT_URL}`);
|
|
11321
|
+
if (!opts.yes) {
|
|
11322
|
+
console.log(`Run manually: ${INSTALL_CMD}`);
|
|
11323
|
+
return;
|
|
11324
|
+
}
|
|
11325
|
+
const result = spawnSync("bash", ["-c", INSTALL_CMD], {
|
|
11326
|
+
stdio: "inherit",
|
|
11327
|
+
timeout: 6e4
|
|
11328
|
+
});
|
|
11329
|
+
if (result.error) {
|
|
11330
|
+
console.error(`Installation error: ${result.error.message}`);
|
|
11331
|
+
process.exitCode = 1;
|
|
11332
|
+
return;
|
|
11333
|
+
}
|
|
11334
|
+
if (result.status !== 0) {
|
|
11335
|
+
console.error(`Install error: process exited with code ${result.status}.`);
|
|
11336
|
+
process.exitCode = 1;
|
|
11337
|
+
return;
|
|
11338
|
+
}
|
|
11339
|
+
console.log("Restart your shell or run: source ~/.bashrc");
|
|
11340
|
+
});
|
|
11341
|
+
}
|
|
11267
11342
|
|
|
11268
11343
|
// src/commands/capture.ts
|
|
11269
11344
|
function createLessonFromFlags(trigger, insight, confirmed) {
|
|
@@ -11879,15 +11954,304 @@ function registerManagementCommands(program) {
|
|
|
11879
11954
|
registerTestSummaryCommand(program);
|
|
11880
11955
|
registerVerifyGatesCommand(program);
|
|
11881
11956
|
registerAboutCommand(program);
|
|
11957
|
+
registerFeedbackCommand(program);
|
|
11882
11958
|
registerKnowledgeCommand(program);
|
|
11883
11959
|
registerKnowledgeIndexCommand(program);
|
|
11884
11960
|
registerCleanLessonsCommand(program);
|
|
11961
|
+
registerInstallBeadsCommand(program);
|
|
11885
11962
|
program.command("worktree").description("(removed) Use Claude Code native worktree support").action(() => {
|
|
11886
11963
|
console.error("ca worktree has been removed. Use Claude Code's native EnterWorktree support instead.");
|
|
11887
11964
|
process.exitCode = 1;
|
|
11888
11965
|
});
|
|
11889
11966
|
}
|
|
11890
11967
|
|
|
11968
|
+
// src/commands/improve-templates.ts
|
|
11969
|
+
function buildTopicDiscovery() {
|
|
11970
|
+
return `
|
|
11971
|
+
get_topics() {
|
|
11972
|
+
local improve_dir="\${IMPROVE_DIR:-improve}"
|
|
11973
|
+
local topics=""
|
|
11974
|
+
if [ -n "$TOPIC_FILTER" ]; then
|
|
11975
|
+
# Use explicit topic list from CLI --topics
|
|
11976
|
+
for topic in $TOPIC_FILTER; do
|
|
11977
|
+
if [ -f "$improve_dir/\${topic}.md" ]; then
|
|
11978
|
+
topics="$topics $topic"
|
|
11979
|
+
else
|
|
11980
|
+
log "WARN: $improve_dir/\${topic}.md not found, skipping"
|
|
11981
|
+
fi
|
|
11982
|
+
done
|
|
11983
|
+
else
|
|
11984
|
+
for f in "$improve_dir"/*.md; do
|
|
11985
|
+
[ -f "$f" ] || continue
|
|
11986
|
+
local topic
|
|
11987
|
+
topic=$(basename "$f" .md)
|
|
11988
|
+
topics="$topics $topic"
|
|
11989
|
+
done
|
|
11990
|
+
fi
|
|
11991
|
+
topics="\${topics# }"
|
|
11992
|
+
if [ -z "$topics" ]; then
|
|
11993
|
+
log "No improve/*.md files found"
|
|
11994
|
+
return 1
|
|
11995
|
+
fi
|
|
11996
|
+
echo "$topics"
|
|
11997
|
+
return 0
|
|
11998
|
+
}
|
|
11999
|
+
`;
|
|
12000
|
+
}
|
|
12001
|
+
function buildImprovePrompt() {
|
|
12002
|
+
return `
|
|
12003
|
+
build_improve_prompt() {
|
|
12004
|
+
local topic="$1"
|
|
12005
|
+
local improve_dir="\${IMPROVE_DIR:-improve}"
|
|
12006
|
+
local program_file="$improve_dir/\${topic}.md"
|
|
12007
|
+
|
|
12008
|
+
if [ ! -f "$program_file" ]; then
|
|
12009
|
+
log "ERROR: $program_file not found"
|
|
12010
|
+
return 1
|
|
12011
|
+
fi
|
|
12012
|
+
|
|
12013
|
+
# Stream static parts via quoted heredoc (no expansion) + file content via cat
|
|
12014
|
+
# Avoids heredoc delimiter collision if .md file contains the delimiter string
|
|
12015
|
+
cat <<'IMPROVE_PROMPT_HEADER'
|
|
12016
|
+
You are running in an autonomous improvement loop. Your task is to make ONE improvement to the codebase.
|
|
12017
|
+
|
|
12018
|
+
## Your Program
|
|
12019
|
+
IMPROVE_PROMPT_HEADER
|
|
12020
|
+
|
|
12021
|
+
cat "$program_file"
|
|
12022
|
+
|
|
12023
|
+
cat <<'IMPROVE_PROMPT_FOOTER'
|
|
12024
|
+
|
|
12025
|
+
## Rules
|
|
12026
|
+
- Make ONE focused improvement per iteration.
|
|
12027
|
+
- Run the validation described in your program.
|
|
12028
|
+
- If you successfully improved something and validation passes, commit your changes then output on its own line:
|
|
12029
|
+
IMPROVED
|
|
12030
|
+
- If you tried but found nothing to improve (or improvements don't pass validation), output:
|
|
12031
|
+
NO_IMPROVEMENT
|
|
12032
|
+
- If you encountered an error that prevents you from working, output:
|
|
12033
|
+
FAILED
|
|
12034
|
+
- Do NOT ask questions -- there is no human.
|
|
12035
|
+
- Commit your changes before outputting the marker.
|
|
12036
|
+
- You can inspect what changed with git diff before committing.
|
|
12037
|
+
IMPROVE_PROMPT_FOOTER
|
|
12038
|
+
}
|
|
12039
|
+
`;
|
|
12040
|
+
}
|
|
12041
|
+
function buildImproveMarkerDetection() {
|
|
12042
|
+
return `
|
|
12043
|
+
# detect_improve_marker() - Check for improvement markers in log and trace
|
|
12044
|
+
# Primary: macro log (anchored patterns). Fallback: trace JSONL (unanchored).
|
|
12045
|
+
# Usage: MARKER=$(detect_improve_marker "$LOGFILE" "$TRACEFILE")
|
|
12046
|
+
# Returns: "improved", "no_improvement", "failed", or "none"
|
|
12047
|
+
detect_improve_marker() {
|
|
12048
|
+
local logfile="$1" tracefile="$2"
|
|
12049
|
+
|
|
12050
|
+
# Primary: check extracted text with anchored patterns
|
|
12051
|
+
if [ -s "$logfile" ]; then
|
|
12052
|
+
if grep -q "^IMPROVED$" "$logfile"; then
|
|
12053
|
+
echo "improved"; return 0
|
|
12054
|
+
elif grep -q "^NO_IMPROVEMENT$" "$logfile"; then
|
|
12055
|
+
echo "no_improvement"; return 0
|
|
12056
|
+
elif grep -q "^FAILED$" "$logfile"; then
|
|
12057
|
+
echo "failed"; return 0
|
|
12058
|
+
fi
|
|
12059
|
+
fi
|
|
12060
|
+
|
|
12061
|
+
# Fallback: check raw trace JSONL (unanchored -- markers are inside JSON strings)
|
|
12062
|
+
if [ -s "$tracefile" ]; then
|
|
12063
|
+
if grep -q "IMPROVED" "$tracefile"; then
|
|
12064
|
+
echo "improved"; return 0
|
|
12065
|
+
elif grep -q "NO_IMPROVEMENT" "$tracefile"; then
|
|
12066
|
+
echo "no_improvement"; return 0
|
|
12067
|
+
elif grep -q "FAILED" "$tracefile"; then
|
|
12068
|
+
echo "failed"; return 0
|
|
12069
|
+
fi
|
|
12070
|
+
fi
|
|
12071
|
+
|
|
12072
|
+
echo "none"
|
|
12073
|
+
}
|
|
12074
|
+
`;
|
|
12075
|
+
}
|
|
12076
|
+
function buildImproveObservability() {
|
|
12077
|
+
return `
|
|
12078
|
+
# Observability: status file and execution log
|
|
12079
|
+
IMPROVE_STATUS_FILE="$LOG_DIR/.improve-status.json"
|
|
12080
|
+
IMPROVE_EXEC_LOG="$LOG_DIR/improvement-log.jsonl"
|
|
12081
|
+
|
|
12082
|
+
write_improve_status() {
|
|
12083
|
+
local status="$1"
|
|
12084
|
+
local topic="\${2:-}"
|
|
12085
|
+
local iteration="\${3:-0}"
|
|
12086
|
+
if [ "$status" = "idle" ]; then
|
|
12087
|
+
echo "{\\"status\\":\\"idle\\",\\"timestamp\\":\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\"}" > "$IMPROVE_STATUS_FILE"
|
|
12088
|
+
else
|
|
12089
|
+
echo "{\\"topic\\":\\"$topic\\",\\"iteration\\":$iteration,\\"started_at\\":\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\",\\"status\\":\\"$status\\"}" > "$IMPROVE_STATUS_FILE"
|
|
12090
|
+
fi
|
|
12091
|
+
}
|
|
12092
|
+
|
|
12093
|
+
log_improve_result() {
|
|
12094
|
+
local topic="$1" result="$2" improvements="$3" duration="$4"
|
|
12095
|
+
echo "{\\"topic\\":\\"$topic\\",\\"result\\":\\"$result\\",\\"improvements\\":$improvements,\\"duration_s\\":$duration,\\"timestamp\\":\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\"}" >> "$IMPROVE_EXEC_LOG"
|
|
12096
|
+
}
|
|
12097
|
+
`;
|
|
12098
|
+
}
|
|
12099
|
+
function buildImproveSessionRunner() {
|
|
12100
|
+
return `
|
|
12101
|
+
# Run claude session with two-scope logging
|
|
12102
|
+
PROMPT=$(build_improve_prompt "$TOPIC")
|
|
12103
|
+
|
|
12104
|
+
claude --dangerously-skip-permissions \\
|
|
12105
|
+
--model "$MODEL" \\
|
|
12106
|
+
--output-format stream-json \\
|
|
12107
|
+
--verbose \\
|
|
12108
|
+
-p "$PROMPT" \\
|
|
12109
|
+
2>"$LOGFILE.stderr" | tee "$TRACEFILE" | extract_text > "$LOGFILE" || true
|
|
12110
|
+
|
|
12111
|
+
# Append stderr to macro log
|
|
12112
|
+
[ -f "$LOGFILE.stderr" ] && cat "$LOGFILE.stderr" >> "$LOGFILE" && rm -f "$LOGFILE.stderr"
|
|
12113
|
+
|
|
12114
|
+
# Health check: warn if macro log extraction failed
|
|
12115
|
+
if [ -s "$TRACEFILE" ] && [ ! -s "$LOGFILE" ]; then
|
|
12116
|
+
log "WARN: Macro log is empty but trace has content (extract_text may have failed)"
|
|
12117
|
+
fi
|
|
12118
|
+
|
|
12119
|
+
MARKER=$(detect_improve_marker "$LOGFILE" "$TRACEFILE")
|
|
12120
|
+
`;
|
|
12121
|
+
}
|
|
12122
|
+
function buildImproveIterationBody() {
|
|
12123
|
+
return buildImproveSessionRunner() + `
|
|
12124
|
+
|
|
12125
|
+
case "$MARKER" in
|
|
12126
|
+
(improved)
|
|
12127
|
+
# Verify the agent actually committed
|
|
12128
|
+
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
12129
|
+
log "WARN: Uncommitted changes detected after IMPROVED marker"
|
|
12130
|
+
fi
|
|
12131
|
+
log "Topic $TOPIC improved (iter $ITER)"
|
|
12132
|
+
TOPIC_IMPROVED=$((TOPIC_IMPROVED + 1))
|
|
12133
|
+
CONSECUTIVE_NO_IMPROVE=0
|
|
12134
|
+
git tag -d "$TAG" 2>/dev/null || true
|
|
12135
|
+
;;
|
|
12136
|
+
(no_improvement)
|
|
12137
|
+
log "Topic $TOPIC: no improvement (iter $ITER), reverting"
|
|
12138
|
+
git reset --hard "$TAG"
|
|
12139
|
+
git clean -fd -e "$LOG_DIR/" 2>/dev/null || true
|
|
12140
|
+
git tag -d "$TAG" 2>/dev/null || true
|
|
12141
|
+
CONSECUTIVE_NO_IMPROVE=$((CONSECUTIVE_NO_IMPROVE + 1))
|
|
12142
|
+
if [ $CONSECUTIVE_NO_IMPROVE -ge 2 ]; then
|
|
12143
|
+
log "Diminishing returns for $TOPIC, moving on"
|
|
12144
|
+
break
|
|
12145
|
+
fi
|
|
12146
|
+
;;
|
|
12147
|
+
(failed)
|
|
12148
|
+
log "Topic $TOPIC failed (iter $ITER), reverting"
|
|
12149
|
+
git reset --hard "$TAG"
|
|
12150
|
+
git clean -fd -e "$LOG_DIR/" 2>/dev/null || true
|
|
12151
|
+
git tag -d "$TAG" 2>/dev/null || true
|
|
12152
|
+
TOPIC_FAILED=1
|
|
12153
|
+
break
|
|
12154
|
+
;;
|
|
12155
|
+
(*)
|
|
12156
|
+
log "Topic $TOPIC: no marker detected (iter $ITER), reverting"
|
|
12157
|
+
git reset --hard "$TAG"
|
|
12158
|
+
git clean -fd -e "$LOG_DIR/" 2>/dev/null || true
|
|
12159
|
+
git tag -d "$TAG" 2>/dev/null || true
|
|
12160
|
+
TOPIC_FAILED=1
|
|
12161
|
+
break
|
|
12162
|
+
;;
|
|
12163
|
+
esac
|
|
12164
|
+
done
|
|
12165
|
+
|
|
12166
|
+
TOPIC_DURATION=$(( $(date +%s) - TOPIC_START ))
|
|
12167
|
+
|
|
12168
|
+
if [ $TOPIC_IMPROVED -gt 0 ]; then
|
|
12169
|
+
IMPROVED_COUNT=$((IMPROVED_COUNT + TOPIC_IMPROVED))
|
|
12170
|
+
log_improve_result "$TOPIC" "improved" "$TOPIC_IMPROVED" "$TOPIC_DURATION"
|
|
12171
|
+
elif [ $TOPIC_FAILED -eq 1 ]; then
|
|
12172
|
+
FAILED_TOPICS=$((FAILED_TOPICS + 1))
|
|
12173
|
+
log_improve_result "$TOPIC" "failed" "0" "$TOPIC_DURATION"
|
|
12174
|
+
else
|
|
12175
|
+
SKIPPED_TOPICS=$((SKIPPED_TOPICS + 1))
|
|
12176
|
+
log_improve_result "$TOPIC" "no_improvement" "0" "$TOPIC_DURATION"
|
|
12177
|
+
fi
|
|
12178
|
+
done`;
|
|
12179
|
+
}
|
|
12180
|
+
function buildImproveMainLoop(options) {
|
|
12181
|
+
const embedded = options.embedded ?? false;
|
|
12182
|
+
const guardOpen = embedded ? "IMPROVE_RESULT=1\nelse" : "exit 1\nfi";
|
|
12183
|
+
const guardClose = embedded ? "fi" : "";
|
|
12184
|
+
return `
|
|
12185
|
+
# Improve loop
|
|
12186
|
+
MAX_ITERS=${options.maxIters}
|
|
12187
|
+
TIME_BUDGET=${options.timeBudget}
|
|
12188
|
+
IMPROVED_COUNT=0
|
|
12189
|
+
FAILED_TOPICS=0
|
|
12190
|
+
SKIPPED_TOPICS=0
|
|
12191
|
+
IMPROVE_START=$(date +%s)
|
|
12192
|
+
|
|
12193
|
+
# Worktree-clean preflight: refuse to run with dirty working tree
|
|
12194
|
+
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
12195
|
+
log "ERROR: Working tree is dirty. Commit or stash changes before running the improvement loop."
|
|
12196
|
+
log " git status:"
|
|
12197
|
+
git status --short
|
|
12198
|
+
${guardOpen}
|
|
12199
|
+
|
|
12200
|
+
TOPICS=$(get_topics) || { log "No topics found, exiting"; ${embedded ? "IMPROVE_RESULT=0" : "exit 0"}; }
|
|
12201
|
+
log "Improve loop starting"
|
|
12202
|
+
log "Config: max_iters=$MAX_ITERS time_budget=$TIME_BUDGET model=$MODEL"
|
|
12203
|
+
log "Topics: $TOPICS"
|
|
12204
|
+
|
|
12205
|
+
for TOPIC in $TOPICS; do
|
|
12206
|
+
log "Starting topic: $TOPIC"
|
|
12207
|
+
TOPIC_IMPROVED=0
|
|
12208
|
+
TOPIC_FAILED=0
|
|
12209
|
+
CONSECUTIVE_NO_IMPROVE=0
|
|
12210
|
+
TOPIC_START=$(date +%s)
|
|
12211
|
+
|
|
12212
|
+
ITER=0
|
|
12213
|
+
while [ $ITER -lt $MAX_ITERS ]; do
|
|
12214
|
+
ITER=$((ITER + 1))
|
|
12215
|
+
|
|
12216
|
+
# Time budget check
|
|
12217
|
+
if [ $TIME_BUDGET -gt 0 ]; then
|
|
12218
|
+
ELAPSED=$(( $(date +%s) - IMPROVE_START ))
|
|
12219
|
+
if [ $ELAPSED -ge $TIME_BUDGET ]; then
|
|
12220
|
+
log "Time budget exhausted ($ELAPSED >= $TIME_BUDGET seconds)"
|
|
12221
|
+
break 2
|
|
12222
|
+
fi
|
|
12223
|
+
fi
|
|
12224
|
+
|
|
12225
|
+
# Dry-run check BEFORE any side effects (tags, sessions)
|
|
12226
|
+
if [ -n "\${IMPROVE_DRY_RUN:-}" ]; then
|
|
12227
|
+
log "[DRY RUN] Would run claude session for $TOPIC (iter $ITER)"
|
|
12228
|
+
TOPIC_IMPROVED=$((TOPIC_IMPROVED + 1))
|
|
12229
|
+
continue
|
|
12230
|
+
fi
|
|
12231
|
+
|
|
12232
|
+
TS=$(timestamp)
|
|
12233
|
+
LOGFILE="$LOG_DIR/loop_improve_\${TOPIC}-\${TS}.log"
|
|
12234
|
+
TRACEFILE="$LOG_DIR/trace_improve_\${TOPIC}-\${TS}.jsonl"
|
|
12235
|
+
TAG="improve/\${TOPIC}/iter-\${ITER}/pre"
|
|
12236
|
+
|
|
12237
|
+
git tag -f "$TAG"
|
|
12238
|
+
write_improve_status "running" "$TOPIC" "$ITER"
|
|
12239
|
+
ln -sf "$(basename "$TRACEFILE")" "$LOG_DIR/.latest"
|
|
12240
|
+
|
|
12241
|
+
log "Iteration $ITER/$MAX_ITERS for $TOPIC"
|
|
12242
|
+
|
|
12243
|
+
` + buildImproveIterationBody() + `
|
|
12244
|
+
|
|
12245
|
+
# Summary
|
|
12246
|
+
TOTAL_DURATION=$(( $(date +%s) - IMPROVE_START ))
|
|
12247
|
+
echo "{\\"type\\":\\"summary\\",\\"improved\\":$IMPROVED_COUNT,\\"failed_topics\\":$FAILED_TOPICS,\\"skipped_topics\\":$SKIPPED_TOPICS,\\"total_duration_s\\":$TOTAL_DURATION}" >> "$IMPROVE_EXEC_LOG"
|
|
12248
|
+
write_improve_status "idle"
|
|
12249
|
+
log "Improve loop finished. Improvements: $IMPROVED_COUNT, Failed topics: $FAILED_TOPICS, Skipped: $SKIPPED_TOPICS"
|
|
12250
|
+
${embedded ? "IMPROVE_RESULT=$( [ $FAILED_TOPICS -eq 0 ] && echo 0 || echo 1 )" : "[ $FAILED_TOPICS -eq 0 ] && exit 0 || exit 1"}
|
|
12251
|
+
${guardClose}
|
|
12252
|
+
`;
|
|
12253
|
+
}
|
|
12254
|
+
|
|
11891
12255
|
// src/commands/loop-templates.ts
|
|
11892
12256
|
function buildDependencyCheck() {
|
|
11893
12257
|
return `
|
|
@@ -12203,7 +12567,7 @@ fi
|
|
|
12203
12567
|
`
|
|
12204
12568
|
};
|
|
12205
12569
|
}
|
|
12206
|
-
function buildMainLoop(reviewOptions) {
|
|
12570
|
+
function buildMainLoop(reviewOptions, skipExit) {
|
|
12207
12571
|
const { counterInit, periodic, final } = buildReviewTriggers(
|
|
12208
12572
|
reviewOptions?.hasReview ?? false,
|
|
12209
12573
|
reviewOptions?.reviewEvery ?? 0
|
|
@@ -12282,7 +12646,162 @@ TOTAL_DURATION=$(( $(date +%s) - LOOP_START ))
|
|
|
12282
12646
|
echo "{\\"type\\":\\"summary\\",\\"completed\\":$COMPLETED,\\"failed\\":$FAILED,\\"skipped\\":$SKIPPED,\\"total_duration_s\\":$TOTAL_DURATION}" >> "$EXEC_LOG"
|
|
12283
12647
|
write_status "idle"
|
|
12284
12648
|
log "Loop finished. Completed: $COMPLETED, Failed: $FAILED, Skipped: $SKIPPED"
|
|
12285
|
-
[ $FAILED -eq 0 ] && exit 0 || exit 1`;
|
|
12649
|
+
${skipExit ? "" : "[ $FAILED -eq 0 ] && exit 0 || exit 1"}`;
|
|
12650
|
+
}
|
|
12651
|
+
|
|
12652
|
+
// src/commands/improve.ts
|
|
12653
|
+
var TOPIC_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
12654
|
+
function buildImproveScriptHeader(timestamp, maxIters, timeBudget, model, topicFilter) {
|
|
12655
|
+
return `#!/usr/bin/env bash
|
|
12656
|
+
# Improvement Loop - Generated by: ca improve
|
|
12657
|
+
# Date: ${timestamp}
|
|
12658
|
+
# Iterates over improve/*.md programs to autonomously improve the codebase.
|
|
12659
|
+
#
|
|
12660
|
+
# Usage:
|
|
12661
|
+
# ./improvement-loop.sh
|
|
12662
|
+
# IMPROVE_DRY_RUN=1 ./improvement-loop.sh # Preview without executing
|
|
12663
|
+
|
|
12664
|
+
set -euo pipefail
|
|
12665
|
+
|
|
12666
|
+
# Config
|
|
12667
|
+
MAX_ITERS=${maxIters}
|
|
12668
|
+
TIME_BUDGET=${timeBudget}
|
|
12669
|
+
MODEL="${model}"
|
|
12670
|
+
TOPIC_FILTER="${topicFilter}"
|
|
12671
|
+
LOG_DIR="agent_logs"
|
|
12672
|
+
|
|
12673
|
+
# Helpers
|
|
12674
|
+
timestamp() { date '+%Y-%m-%d_%H-%M-%S'; }
|
|
12675
|
+
log() { echo "[$(timestamp)] $*"; }
|
|
12676
|
+
die() { log "FATAL: $*"; exit 1; }
|
|
12677
|
+
|
|
12678
|
+
command -v claude >/dev/null || die "claude CLI required"
|
|
12679
|
+
command -v git >/dev/null || die "git required"
|
|
12680
|
+
|
|
12681
|
+
# Detect JSON parser: prefer jq, fall back to python3
|
|
12682
|
+
HAS_JQ=false
|
|
12683
|
+
command -v jq >/dev/null 2>&1 && HAS_JQ=true
|
|
12684
|
+
if [ "$HAS_JQ" = false ]; then
|
|
12685
|
+
command -v python3 >/dev/null 2>&1 || die "jq or python3 required for JSON parsing"
|
|
12686
|
+
fi
|
|
12687
|
+
|
|
12688
|
+
mkdir -p "$LOG_DIR"
|
|
12689
|
+
`;
|
|
12690
|
+
}
|
|
12691
|
+
function validateImproveOptions(options) {
|
|
12692
|
+
if (!Number.isInteger(options.maxIters) || options.maxIters <= 0) {
|
|
12693
|
+
throw new Error(`Invalid maxIters: must be a positive integer, got ${options.maxIters}`);
|
|
12694
|
+
}
|
|
12695
|
+
if (!Number.isInteger(options.timeBudget) || options.timeBudget < 0) {
|
|
12696
|
+
throw new Error(`Invalid timeBudget: must be a non-negative integer, got ${options.timeBudget}`);
|
|
12697
|
+
}
|
|
12698
|
+
if (!MODEL_PATTERN.test(options.model)) {
|
|
12699
|
+
throw new Error(`Invalid model "${options.model}": must match ${MODEL_PATTERN}`);
|
|
12700
|
+
}
|
|
12701
|
+
if (options.topics) {
|
|
12702
|
+
for (const topic of options.topics) {
|
|
12703
|
+
if (!TOPIC_NAME_PATTERN.test(topic)) {
|
|
12704
|
+
throw new Error(`Invalid topic "${topic}": must match ${TOPIC_NAME_PATTERN}`);
|
|
12705
|
+
}
|
|
12706
|
+
}
|
|
12707
|
+
}
|
|
12708
|
+
}
|
|
12709
|
+
function generateImproveScript(options) {
|
|
12710
|
+
validateImproveOptions(options);
|
|
12711
|
+
const topicFilter = options.topics?.join(" ") ?? "";
|
|
12712
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
12713
|
+
return buildImproveScriptHeader(timestamp, options.maxIters, options.timeBudget, options.model, topicFilter) + buildTopicDiscovery() + buildImprovePrompt() + buildStreamExtractor() + buildImproveMarkerDetection() + buildImproveObservability() + buildImproveMainLoop({ maxIters: options.maxIters, timeBudget: options.timeBudget });
|
|
12714
|
+
}
|
|
12715
|
+
async function handleImprove(cmd, options) {
|
|
12716
|
+
const maxIters = Number(options.maxIters ?? 5);
|
|
12717
|
+
const timeBudget = Number(options.timeBudget ?? 0);
|
|
12718
|
+
const scriptOptions = {
|
|
12719
|
+
topics: options.topics,
|
|
12720
|
+
maxIters,
|
|
12721
|
+
timeBudget,
|
|
12722
|
+
model: options.model ?? DEFAULT_LOOP_MODEL
|
|
12723
|
+
};
|
|
12724
|
+
try {
|
|
12725
|
+
validateImproveOptions(scriptOptions);
|
|
12726
|
+
} catch (err) {
|
|
12727
|
+
out.error(err.message);
|
|
12728
|
+
process.exitCode = 1;
|
|
12729
|
+
return;
|
|
12730
|
+
}
|
|
12731
|
+
if (options.dryRun) {
|
|
12732
|
+
out.info("Dry run - improvement loop plan:");
|
|
12733
|
+
out.info(` Topics: ${scriptOptions.topics?.join(", ") ?? "(all improve/*.md)"}`);
|
|
12734
|
+
out.info(` Max iterations per topic: ${scriptOptions.maxIters}`);
|
|
12735
|
+
out.info(` Time budget: ${scriptOptions.timeBudget === 0 ? "unlimited" : `${scriptOptions.timeBudget}s`}`);
|
|
12736
|
+
out.info(` Model: ${scriptOptions.model}`);
|
|
12737
|
+
if (existsSync("improve")) {
|
|
12738
|
+
out.info(" improve/ directory exists");
|
|
12739
|
+
} else {
|
|
12740
|
+
out.warn(" improve/ directory not found - create it with improve/*.md files");
|
|
12741
|
+
}
|
|
12742
|
+
return;
|
|
12743
|
+
}
|
|
12744
|
+
const outputPath = resolve(options.output ?? "./improvement-loop.sh");
|
|
12745
|
+
if (existsSync(outputPath) && !options.force) {
|
|
12746
|
+
out.error(`File already exists: ${outputPath}`);
|
|
12747
|
+
out.info("Use --force to overwrite");
|
|
12748
|
+
process.exitCode = 1;
|
|
12749
|
+
return;
|
|
12750
|
+
}
|
|
12751
|
+
let script;
|
|
12752
|
+
try {
|
|
12753
|
+
script = generateImproveScript(scriptOptions);
|
|
12754
|
+
} catch (err) {
|
|
12755
|
+
out.error(err.message);
|
|
12756
|
+
process.exitCode = 1;
|
|
12757
|
+
return;
|
|
12758
|
+
}
|
|
12759
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
12760
|
+
await writeFile(outputPath, script, "utf-8");
|
|
12761
|
+
await chmod(outputPath, 493);
|
|
12762
|
+
out.success(`Generated improvement loop script: ${outputPath}`);
|
|
12763
|
+
out.info("Run it with: " + outputPath);
|
|
12764
|
+
out.info("Preview with: IMPROVE_DRY_RUN=1 " + outputPath);
|
|
12765
|
+
}
|
|
12766
|
+
var EXAMPLE_PROGRAM = `# Linting
|
|
12767
|
+
|
|
12768
|
+
## What to improve
|
|
12769
|
+
Find and fix lint violations in the codebase.
|
|
12770
|
+
|
|
12771
|
+
## How to find work
|
|
12772
|
+
Run the project linter and look for violations:
|
|
12773
|
+
\`\`\`bash
|
|
12774
|
+
pnpm lint 2>&1 | head -50
|
|
12775
|
+
\`\`\`
|
|
12776
|
+
|
|
12777
|
+
## How to validate
|
|
12778
|
+
After fixing, run the linter again and confirm fewer violations:
|
|
12779
|
+
\`\`\`bash
|
|
12780
|
+
pnpm lint
|
|
12781
|
+
pnpm test
|
|
12782
|
+
\`\`\`
|
|
12783
|
+
`;
|
|
12784
|
+
async function handleImproveInit(cmd) {
|
|
12785
|
+
const improveDir = "improve";
|
|
12786
|
+
const examplePath = resolve(improveDir, "example.md");
|
|
12787
|
+
if (existsSync(examplePath)) {
|
|
12788
|
+
out.info(`improve/ directory already exists with ${examplePath}`);
|
|
12789
|
+
out.info("Add more .md files to define additional improvement topics.");
|
|
12790
|
+
return;
|
|
12791
|
+
}
|
|
12792
|
+
await mkdir(improveDir, { recursive: true });
|
|
12793
|
+
await writeFile(examplePath, EXAMPLE_PROGRAM, "utf-8");
|
|
12794
|
+
out.success(`Created ${examplePath}`);
|
|
12795
|
+
out.info("Edit this file or add more .md files to define improvement topics.");
|
|
12796
|
+
out.info("Then run: ca improve");
|
|
12797
|
+
}
|
|
12798
|
+
function registerImproveCommands(program) {
|
|
12799
|
+
const improveCmd = program.command("improve").description("Generate improvement loop script").option("--topics <names...>", "Specific topics to process").option("-o, --output <path>", "Output script path", "./improvement-loop.sh").option("--max-iters <n>", "Max iterations per topic", "5").option("--time-budget <seconds>", "Total time budget, 0=unlimited", "0").option("--model <model>", "Claude model to use", DEFAULT_LOOP_MODEL).option("--force", "Overwrite existing script").option("--dry-run", "Validate and print plan without generating").action(async function(options) {
|
|
12800
|
+
await handleImprove(this, options);
|
|
12801
|
+
});
|
|
12802
|
+
improveCmd.command("init").description("Print guidance about creating improve/*.md files").action(async function() {
|
|
12803
|
+
await handleImproveInit();
|
|
12804
|
+
});
|
|
12286
12805
|
}
|
|
12287
12806
|
|
|
12288
12807
|
// src/commands/loop-review-templates.ts
|
|
@@ -12453,8 +12972,8 @@ spawn_reviewers() {
|
|
|
12453
12972
|
case "$reviewer" in
|
|
12454
12973
|
(claude-sonnet|claude-opus)
|
|
12455
12974
|
local model_name
|
|
12456
|
-
if [ "$reviewer" = "claude-sonnet" ]; then model_name="claude-sonnet-4-6"
|
|
12457
|
-
else model_name="claude-opus-4-6"; fi
|
|
12975
|
+
if [ "$reviewer" = "claude-sonnet" ]; then model_name="claude-sonnet-4-6[1m]"
|
|
12976
|
+
else model_name="claude-opus-4-6[1m]"; fi
|
|
12458
12977
|
local sid=""
|
|
12459
12978
|
sid=$(read_session_id "$reviewer" "$REVIEW_DIR/sessions.json")
|
|
12460
12979
|
if [ "$cycle" -eq 1 ]; then
|
|
@@ -12580,8 +13099,6 @@ run_review_phase() {
|
|
|
12580
13099
|
|
|
12581
13100
|
// src/commands/loop.ts
|
|
12582
13101
|
var LOOP_EPIC_ID_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
|
12583
|
-
var DEFAULT_MODEL = "claude-opus-4-6";
|
|
12584
|
-
var MODEL_PATTERN = /^[a-zA-Z0-9_.:/-]+$/;
|
|
12585
13102
|
function buildScriptHeader(timestamp, maxRetries, model, epicIds) {
|
|
12586
13103
|
return `#!/usr/bin/env bash
|
|
12587
13104
|
# Infinity Loop - Generated by: ca loop
|
|
@@ -12675,6 +13192,14 @@ function validateOptions(options) {
|
|
|
12675
13192
|
throw new Error(`Invalid maxReviewCycles: must be a positive integer, got ${options.maxReviewCycles}`);
|
|
12676
13193
|
}
|
|
12677
13194
|
}
|
|
13195
|
+
if (options.improve) {
|
|
13196
|
+
if (!Number.isInteger(options.improve.maxIters) || options.improve.maxIters <= 0) {
|
|
13197
|
+
throw new Error(`Invalid improve maxIters: must be a positive integer, got ${options.improve.maxIters}`);
|
|
13198
|
+
}
|
|
13199
|
+
if (!Number.isInteger(options.improve.timeBudget) || options.improve.timeBudget < 0) {
|
|
13200
|
+
throw new Error(`Invalid improve timeBudget: must be a non-negative integer, got ${options.improve.timeBudget}`);
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
12678
13203
|
}
|
|
12679
13204
|
function generateLoopScript(options) {
|
|
12680
13205
|
validateOptions(options);
|
|
@@ -12687,7 +13212,7 @@ function generateLoopScript(options) {
|
|
|
12687
13212
|
reviewers: options.reviewers,
|
|
12688
13213
|
maxReviewCycles: options.maxReviewCycles ?? 3,
|
|
12689
13214
|
reviewBlocking: options.reviewBlocking ?? false,
|
|
12690
|
-
reviewModel: options.reviewModel ??
|
|
13215
|
+
reviewModel: options.reviewModel ?? DEFAULT_LOOP_MODEL,
|
|
12691
13216
|
reviewEvery: options.reviewEvery ?? 0
|
|
12692
13217
|
});
|
|
12693
13218
|
script += buildReviewerDetection();
|
|
@@ -12697,7 +13222,24 @@ function generateLoopScript(options) {
|
|
|
12697
13222
|
script += buildImplementerPhase();
|
|
12698
13223
|
script += buildReviewLoop();
|
|
12699
13224
|
}
|
|
12700
|
-
|
|
13225
|
+
const hasImprove = !!options.improve;
|
|
13226
|
+
script += buildMainLoop(
|
|
13227
|
+
hasReview ? { hasReview: true, reviewEvery: options.reviewEvery ?? 0 } : void 0,
|
|
13228
|
+
hasImprove
|
|
13229
|
+
// skipExit when improvement phase follows
|
|
13230
|
+
);
|
|
13231
|
+
if (options.improve) {
|
|
13232
|
+
script += "\n# Improvement phase (runs after epic loop completes successfully)\n";
|
|
13233
|
+
script += "if [ $FAILED -eq 0 ]; then\n";
|
|
13234
|
+
script += ' log "Epic loop completed successfully, starting improvement phase"\n';
|
|
13235
|
+
script += buildTopicDiscovery();
|
|
13236
|
+
script += buildImprovePrompt();
|
|
13237
|
+
script += buildImproveMarkerDetection();
|
|
13238
|
+
script += buildImproveObservability();
|
|
13239
|
+
script += buildImproveMainLoop({ ...options.improve, embedded: true });
|
|
13240
|
+
script += "\nfi\n";
|
|
13241
|
+
script += '[ $FAILED -eq 0 ] && [ "${IMPROVE_RESULT:-0}" -eq 0 ] && exit 0 || exit 1\n';
|
|
13242
|
+
}
|
|
12701
13243
|
return script;
|
|
12702
13244
|
}
|
|
12703
13245
|
async function handleLoop(cmd, options) {
|
|
@@ -12716,17 +13258,20 @@ async function handleLoop(cmd, options) {
|
|
|
12716
13258
|
}
|
|
12717
13259
|
const reviewEvery = Number(options.reviewEvery ?? 0);
|
|
12718
13260
|
const maxReviewCycles = Number(options.maxReviewCycles ?? 3);
|
|
13261
|
+
const improveMaxIters = Number(options.improveMaxIters ?? 5);
|
|
13262
|
+
const improveTimeBudget = Number(options.improveTimeBudget ?? 0);
|
|
12719
13263
|
let script;
|
|
12720
13264
|
try {
|
|
12721
13265
|
script = generateLoopScript({
|
|
12722
13266
|
epics: options.epics,
|
|
12723
13267
|
maxRetries,
|
|
12724
|
-
model: options.model ??
|
|
13268
|
+
model: options.model ?? DEFAULT_LOOP_MODEL,
|
|
12725
13269
|
reviewers: options.reviewers,
|
|
12726
13270
|
reviewEvery,
|
|
12727
13271
|
maxReviewCycles,
|
|
12728
13272
|
reviewBlocking: options.reviewBlocking,
|
|
12729
|
-
reviewModel: options.reviewModel ??
|
|
13273
|
+
reviewModel: options.reviewModel ?? DEFAULT_LOOP_MODEL,
|
|
13274
|
+
improve: options.improve ? { maxIters: improveMaxIters, timeBudget: improveTimeBudget } : void 0
|
|
12730
13275
|
});
|
|
12731
13276
|
} catch (err) {
|
|
12732
13277
|
out.error(err.message);
|
|
@@ -12741,7 +13286,7 @@ async function handleLoop(cmd, options) {
|
|
|
12741
13286
|
out.info("Preview with: LOOP_DRY_RUN=1 " + outputPath);
|
|
12742
13287
|
}
|
|
12743
13288
|
function registerLoopCommands(program) {
|
|
12744
|
-
program.command("loop").description("Generate infinity loop script for epic tasks").option("--epics <ids...>", "Specific epic IDs to process").option("-o, --output <path>", "Output script path", "./infinity-loop.sh").option("--max-retries <n>", "Max retries per epic on failure", "1").option("--model <model>", "Claude model to use",
|
|
13289
|
+
program.command("loop").description("Generate infinity loop script for epic tasks").option("--epics <ids...>", "Specific epic IDs to process").option("-o, --output <path>", "Output script path", "./infinity-loop.sh").option("--max-retries <n>", "Max retries per epic on failure", "1").option("--model <model>", "Claude model to use", DEFAULT_LOOP_MODEL).option("--force", "Overwrite existing script").option("--review-every <n>", "Review every N completed epics (0=end-only)", "0").option("--reviewers <names...>", "Reviewers to use (claude-sonnet claude-opus gemini codex)").option("--max-review-cycles <n>", "Max review/fix iterations", "3").option("--review-blocking", "Fail loop if review not approved after max cycles").option("--review-model <model>", "Model for implementer fix sessions", DEFAULT_LOOP_MODEL).option("--improve", "Run improvement phase after all epics complete").option("--improve-max-iters <n>", "Max improvement iterations per topic", "5").option("--improve-time-budget <seconds>", "Total improvement time budget, 0=unlimited", "0").action(async function(options) {
|
|
12745
13290
|
await handleLoop(this, options);
|
|
12746
13291
|
});
|
|
12747
13292
|
}
|
|
@@ -12798,7 +13343,7 @@ function formatStreamEvent(event) {
|
|
|
12798
13343
|
}
|
|
12799
13344
|
case "result": {
|
|
12800
13345
|
const text = typeof event.result === "string" ? event.result : "";
|
|
12801
|
-
const markers = ["EPIC_COMPLETE", "EPIC_FAILED", "HUMAN_REQUIRED"];
|
|
13346
|
+
const markers = ["EPIC_COMPLETE", "EPIC_FAILED", "HUMAN_REQUIRED", "NO_IMPROVEMENT", "IMPROVED", "FAILED"];
|
|
12802
13347
|
const found = markers.find((m) => text.includes(m));
|
|
12803
13348
|
if (found) {
|
|
12804
13349
|
const markerLine = text.split("\n").find((l) => l.includes(found)) ?? found;
|
|
@@ -12811,19 +13356,19 @@ function formatStreamEvent(event) {
|
|
|
12811
13356
|
return null;
|
|
12812
13357
|
}
|
|
12813
13358
|
}
|
|
12814
|
-
function findLatestTraceFile(logDir) {
|
|
13359
|
+
function findLatestTraceFile(logDir, prefix = "trace_") {
|
|
12815
13360
|
if (!existsSync(logDir)) return null;
|
|
12816
13361
|
const latestPath = join(logDir, ".latest");
|
|
12817
13362
|
if (existsSync(latestPath)) {
|
|
12818
13363
|
try {
|
|
12819
13364
|
const target = readlinkSync(latestPath);
|
|
12820
13365
|
const resolved = resolve(logDir, target);
|
|
12821
|
-
if (existsSync(resolved)) return resolved;
|
|
13366
|
+
if (existsSync(resolved) && target.startsWith(prefix)) return resolved;
|
|
12822
13367
|
} catch {
|
|
12823
13368
|
}
|
|
12824
13369
|
}
|
|
12825
13370
|
try {
|
|
12826
|
-
const files = readdirSync(logDir).filter((f) => f.startsWith(
|
|
13371
|
+
const files = readdirSync(logDir).filter((f) => f.startsWith(prefix) && f.endsWith(".jsonl")).sort().reverse();
|
|
12827
13372
|
const first = files[0];
|
|
12828
13373
|
if (first) return join(logDir, first);
|
|
12829
13374
|
} catch {
|
|
@@ -12888,7 +13433,7 @@ async function handleWatch(cmd, options) {
|
|
|
12888
13433
|
}
|
|
12889
13434
|
if (existsSync(logDir)) {
|
|
12890
13435
|
try {
|
|
12891
|
-
const files = readdirSync(logDir).filter((f) => f.startsWith(`trace_${options.epic}
|
|
13436
|
+
const files = readdirSync(logDir).filter((f) => f.startsWith(`trace_${options.epic}-`) && f.endsWith(".jsonl")).sort().reverse();
|
|
12892
13437
|
const first = files[0];
|
|
12893
13438
|
if (first) traceFile = join(logDir, first);
|
|
12894
13439
|
} catch {
|
|
@@ -12899,6 +13444,13 @@ async function handleWatch(cmd, options) {
|
|
|
12899
13444
|
process.exitCode = 1;
|
|
12900
13445
|
return;
|
|
12901
13446
|
}
|
|
13447
|
+
} else if (options.improve) {
|
|
13448
|
+
traceFile = findLatestTraceFile(logDir, "trace_improve_");
|
|
13449
|
+
if (!traceFile) {
|
|
13450
|
+
out.info("No improvement trace found. Run `ca improve` to generate an improvement loop script first.");
|
|
13451
|
+
process.exitCode = 0;
|
|
13452
|
+
return;
|
|
13453
|
+
}
|
|
12902
13454
|
} else {
|
|
12903
13455
|
traceFile = findLatestTraceFile(logDir);
|
|
12904
13456
|
if (!traceFile) {
|
|
@@ -12911,7 +13463,7 @@ async function handleWatch(cmd, options) {
|
|
|
12911
13463
|
await tailFile(traceFile, follow);
|
|
12912
13464
|
}
|
|
12913
13465
|
function registerWatchCommand(program) {
|
|
12914
|
-
program.command("watch").description("Tail and pretty-print live trace from infinity loop sessions").option("--epic <id>", "Watch a specific epic trace").option("--no-follow", "Print existing trace and exit (no live tail)").action(async function(options) {
|
|
13466
|
+
program.command("watch").description("Tail and pretty-print live trace from infinity loop sessions").option("--epic <id>", "Watch a specific epic trace").option("--improve", "Watch improvement loop traces").option("--no-follow", "Print existing trace and exit (no live tail)").action(async function(options) {
|
|
12915
13467
|
await handleWatch(this, options);
|
|
12916
13468
|
});
|
|
12917
13469
|
}
|
|
@@ -13051,6 +13603,7 @@ function createProgram() {
|
|
|
13051
13603
|
registerManagementCommands(program);
|
|
13052
13604
|
registerSetupCommands(program);
|
|
13053
13605
|
registerCompoundCommands(program);
|
|
13606
|
+
registerImproveCommands(program);
|
|
13054
13607
|
registerLoopCommands(program);
|
|
13055
13608
|
registerWatchCommand(program);
|
|
13056
13609
|
registerPhaseCheckCommand(program);
|
|
@@ -13072,7 +13625,7 @@ function createProgram() {
|
|
|
13072
13625
|
}
|
|
13073
13626
|
async function runProgram(program, argv = process.argv) {
|
|
13074
13627
|
let updatePromise = null;
|
|
13075
|
-
if (
|
|
13628
|
+
if (shouldCheckForUpdate()) {
|
|
13076
13629
|
try {
|
|
13077
13630
|
const cacheDir = join(getRepoRoot(), ".claude", ".cache");
|
|
13078
13631
|
updatePromise = checkForUpdate(cacheDir);
|