compound-agent 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,11 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.3.2] - 2026-02-21
13
+
14
+ ### Added
15
+
16
+ - **Banner audio**: Pure TypeScript WAV synthesis plays a rising pentatonic melody during the tendril animation. Cross-platform: `afplay` (macOS), `aplay` (Linux), PowerShell (Windows). Silently skips if player unavailable. Zero dependencies.
17
+ - **Test coverage**: 19 new tests for `ca about` command, changelog extraction/escaping, and `--update` doc migration path
18
+
19
+ ### Fixed
20
+
21
+ - **`setup --update` doc migration**: `--update` now installs the 5 split docs before removing legacy `HOW_TO_COMPOUND.md`, preventing empty `docs/compound/`
22
+ - **Fresh checkout type-check**: `src/changelog-data.ts` tracked in git so `tsc --noEmit` passes without a prior build
23
+ - **Trailing status text**: Banner animation no longer leaves "al tendrils..." remnant from previous phase
24
+
25
+ ### Changed
26
+
27
+ - **`ca about` command**: Renamed from `ca version-show` for brevity
28
+ - **Changelog extraction**: Core parsing/escaping logic extracted to `scripts/changelog-utils.ts` (shared between prebuild script and tests)
29
+ - **Narrowed `.gitignore`**: Setup-generated patterns scoped to `compound/` subdirectories to avoid hiding tracked TDD agent definitions
30
+
12
31
  ## [1.3.1] - 2026-02-21
13
32
 
14
33
  ### Added
15
34
 
16
- - **`ca version-show` command**: Displays version with terminal animation (tendril growth) and recent changelog entries. Non-TTY environments get plain text output. Changelog is embedded at build time from CHANGELOG.md.
35
+ - **`ca about` command**: Displays version with terminal animation (tendril growth) and recent changelog entries. Non-TTY environments get plain text output. Changelog is embedded at build time from CHANGELOG.md.
17
36
  - **3 new doctor checks**: Beads initialized (`.beads/` dir), beads healthy (`bd doctor`), codebase scope (user-scope detection)
18
37
  - **Beads + scope status in init/setup output**: Full beads health display (CLI available, initialized, healthy) and scope status shown after `ca init`, `ca setup`, and `ca setup --update`
19
38
  - **Banner on `--update`**: Terminal art animation now plays during `ca setup --update` and `ca init --update` (same TTY/quiet guards as fresh install)
package/README.md CHANGED
@@ -210,7 +210,7 @@ Generated scripts detect three markers: `EPIC_COMPLETE` (success), `EPIC_FAILED`
210
210
  | `ca setup claude --status` | Check Claude Code integration health |
211
211
  | `ca setup claude --uninstall` | Remove Claude hooks only |
212
212
  | `ca download-model` | Download the embedding model |
213
- | `ca version-show` | Show version with animation and recent changelog |
213
+ | `ca about` | Show version, animation, and recent changelog |
214
214
  | `ca doctor` | Verify external dependencies and project health |
215
215
 
216
216
  ## Workflow Commands
package/dist/cli.js CHANGED
@@ -2,14 +2,14 @@
2
2
  import { Command } from 'commander';
3
3
  import { getLlama, resolveModelFile } from 'node-llama-cpp';
4
4
  import { mkdirSync, writeFileSync, statSync, unlinkSync, existsSync, readFileSync, copyFileSync, chmodSync, readdirSync } from 'fs';
5
- import { homedir } from 'os';
5
+ import { homedir, tmpdir } from 'os';
6
6
  import path, { join, dirname, resolve, relative } from 'path';
7
7
  import * as fs from 'fs/promises';
8
8
  import { readFile, mkdir, appendFile, writeFile, chmod, rm, rename, readdir } from 'fs/promises';
9
9
  import { createHash } from 'crypto';
10
10
  import { z } from 'zod';
11
11
  import { createRequire } from 'module';
12
- import { execSync, execFileSync } from 'child_process';
12
+ import { execSync, execFileSync, spawn } from 'child_process';
13
13
  import chalk from 'chalk';
14
14
 
15
15
  // src/cli-utils.ts
@@ -1135,6 +1135,171 @@ function registerCompoundCommands(program2) {
1135
1135
  var _require = createRequire(import.meta.url);
1136
1136
  var _pkg = _require("../package.json");
1137
1137
  var VERSION = _pkg.version;
1138
+ var SAMPLE_RATE = 22050;
1139
+ var BITS = 16;
1140
+ var MAX_AMP = 24576;
1141
+ var NOTE = {
1142
+ C3: 131,
1143
+ E3: 165,
1144
+ G3: 196,
1145
+ C4: 262,
1146
+ E4: 330,
1147
+ G4: 392,
1148
+ C5: 523,
1149
+ E5: 659,
1150
+ G5: 784,
1151
+ C6: 1047
1152
+ };
1153
+ function envelope(i, total, attack, release) {
1154
+ if (i < attack) return i / attack;
1155
+ if (i > total - release) return (total - i) / release;
1156
+ return 1;
1157
+ }
1158
+ function tone(freq, durationMs, amp = 1) {
1159
+ const samples = Math.floor(SAMPLE_RATE * durationMs / 1e3);
1160
+ const attack = Math.min(Math.floor(samples * 0.05), 200);
1161
+ const release = Math.min(Math.floor(samples * 0.15), 400);
1162
+ const out2 = [];
1163
+ for (let i = 0; i < samples; i++) {
1164
+ const env = envelope(i, samples, attack, release);
1165
+ out2.push(Math.sin(2 * Math.PI * freq * i / SAMPLE_RATE) * env * amp);
1166
+ }
1167
+ return out2;
1168
+ }
1169
+ function chord(freqs, durationMs, amp = 1) {
1170
+ const tones = freqs.map((f) => tone(f, durationMs, 1));
1171
+ const len = tones[0].length;
1172
+ const out2 = [];
1173
+ const scale = amp / freqs.length;
1174
+ for (let i = 0; i < len; i++) {
1175
+ let sum = 0;
1176
+ for (const t of tones) sum += t[i];
1177
+ out2.push(sum * scale);
1178
+ }
1179
+ return out2;
1180
+ }
1181
+ function silence(durationMs) {
1182
+ return new Array(Math.floor(SAMPLE_RATE * durationMs / 1e3)).fill(0);
1183
+ }
1184
+ function composeMelody() {
1185
+ const samples = [];
1186
+ samples.push(...tone(NOTE.C3, 250, 0.3));
1187
+ samples.push(...silence(150));
1188
+ samples.push(...tone(NOTE.C3, 250, 0.35));
1189
+ samples.push(...silence(100));
1190
+ samples.push(...tone(NOTE.E3, 200, 0.3));
1191
+ samples.push(...silence(80));
1192
+ samples.push(...tone(NOTE.C4, 140, 0.45));
1193
+ samples.push(...silence(30));
1194
+ samples.push(...tone(NOTE.E4, 140, 0.5));
1195
+ samples.push(...silence(30));
1196
+ samples.push(...tone(NOTE.G4, 140, 0.5));
1197
+ samples.push(...silence(30));
1198
+ samples.push(...tone(NOTE.C5, 160, 0.55));
1199
+ samples.push(...silence(30));
1200
+ samples.push(...tone(NOTE.E5, 160, 0.55));
1201
+ samples.push(...silence(30));
1202
+ samples.push(...tone(NOTE.G5, 180, 0.6));
1203
+ samples.push(...silence(60));
1204
+ samples.push(...tone(NOTE.C4, 100, 0.4));
1205
+ samples.push(...tone(NOTE.E4, 100, 0.45));
1206
+ samples.push(...tone(NOTE.G4, 100, 0.45));
1207
+ samples.push(...tone(NOTE.C5, 120, 0.5));
1208
+ samples.push(...tone(NOTE.E5, 120, 0.55));
1209
+ samples.push(...tone(NOTE.G5, 140, 0.6));
1210
+ samples.push(...silence(60));
1211
+ samples.push(...tone(NOTE.C6, 80, 0.3));
1212
+ samples.push(...silence(40));
1213
+ samples.push(...tone(NOTE.G5, 80, 0.35));
1214
+ samples.push(...silence(40));
1215
+ samples.push(...tone(NOTE.C6, 80, 0.3));
1216
+ samples.push(...silence(40));
1217
+ samples.push(...tone(NOTE.E5, 100, 0.35));
1218
+ samples.push(...silence(60));
1219
+ samples.push(...chord([NOTE.C4, NOTE.E4, NOTE.G4], 600, 0.7));
1220
+ samples.push(...chord([NOTE.C4, NOTE.E4, NOTE.G4, NOTE.C5], 500, 0.6));
1221
+ samples.push(...chord([NOTE.C3, NOTE.G3, NOTE.C4], 1200, 0.35));
1222
+ return samples;
1223
+ }
1224
+ function encodeWav(samples) {
1225
+ const dataSize = samples.length * (BITS / 8);
1226
+ const fileSize = 44 + dataSize;
1227
+ const buf = Buffer.alloc(fileSize);
1228
+ buf.write("RIFF", 0);
1229
+ buf.writeUInt32LE(fileSize - 8, 4);
1230
+ buf.write("WAVE", 8);
1231
+ buf.write("fmt ", 12);
1232
+ buf.writeUInt32LE(16, 16);
1233
+ buf.writeUInt16LE(1, 20);
1234
+ buf.writeUInt16LE(1, 22);
1235
+ buf.writeUInt32LE(SAMPLE_RATE, 24);
1236
+ buf.writeUInt32LE(SAMPLE_RATE * BITS / 8, 28);
1237
+ buf.writeUInt16LE(BITS / 8, 32);
1238
+ buf.writeUInt16LE(BITS, 34);
1239
+ buf.write("data", 36);
1240
+ buf.writeUInt32LE(dataSize, 40);
1241
+ let offset = 44;
1242
+ for (const s of samples) {
1243
+ const clamped = Math.max(-1, Math.min(1, s));
1244
+ buf.writeInt16LE(Math.round(clamped * MAX_AMP), offset);
1245
+ offset += 2;
1246
+ }
1247
+ return buf;
1248
+ }
1249
+ function spawnPlayer(filePath) {
1250
+ try {
1251
+ switch (process.platform) {
1252
+ case "darwin":
1253
+ return spawn("afplay", [filePath], { stdio: "ignore", detached: true });
1254
+ case "linux":
1255
+ return spawn("aplay", ["-q", filePath], { stdio: "ignore", detached: true });
1256
+ case "win32":
1257
+ return spawn("powershell", [
1258
+ "-c",
1259
+ `(New-Object Media.SoundPlayer '${filePath}').PlaySync()`
1260
+ ], { stdio: "ignore", detached: true });
1261
+ default:
1262
+ return null;
1263
+ }
1264
+ } catch {
1265
+ return null;
1266
+ }
1267
+ }
1268
+ function playBannerAudio() {
1269
+ try {
1270
+ const wav = encodeWav(composeMelody());
1271
+ const tmpPath = join(tmpdir(), `ca-banner-${process.pid}.wav`);
1272
+ writeFileSync(tmpPath, wav);
1273
+ const proc = spawnPlayer(tmpPath);
1274
+ if (!proc) {
1275
+ try {
1276
+ unlinkSync(tmpPath);
1277
+ } catch {
1278
+ }
1279
+ return null;
1280
+ }
1281
+ proc.unref();
1282
+ const cleanup2 = () => {
1283
+ try {
1284
+ proc.kill();
1285
+ } catch {
1286
+ }
1287
+ try {
1288
+ unlinkSync(tmpPath);
1289
+ } catch {
1290
+ }
1291
+ };
1292
+ proc.on("exit", () => {
1293
+ try {
1294
+ unlinkSync(tmpPath);
1295
+ } catch {
1296
+ }
1297
+ });
1298
+ return { stop: cleanup2 };
1299
+ } catch {
1300
+ return null;
1301
+ }
1302
+ }
1138
1303
 
1139
1304
  // src/setup/banner.ts
1140
1305
  var W = 62;
@@ -1316,6 +1481,7 @@ async function playInstallBanner() {
1316
1481
  const ps = [];
1317
1482
  const write = (s) => process.stdout.write(s);
1318
1483
  write("\x1B[?25l\x1B[2J\x1B[H");
1484
+ const audio = playBannerAudio();
1319
1485
  const restoreCursor = () => process.stdout.write("\x1B[?25h\x1B[0m");
1320
1486
  process.on("exit", restoreCursor);
1321
1487
  try {
@@ -1327,7 +1493,7 @@ async function playInstallBanner() {
1327
1493
  put(cvs, nx(CENTER), ny(CENTER), seedCh[f % 4], seedCo[f % 4]);
1328
1494
  write(flush(cvs));
1329
1495
  write(`
1330
- ${CO.DIM}Seed detected...${CO.VOID}
1496
+ ${CO.DIM}Seed detected...\x1B[K${CO.VOID}
1331
1497
  `);
1332
1498
  await sleep(60);
1333
1499
  }
@@ -1374,7 +1540,7 @@ async function playInstallBanner() {
1374
1540
  let active = 0;
1375
1541
  for (let i = 0; i < nc; i++) if (isGrown[i]) active++;
1376
1542
  write(`
1377
- \x1B[0;35mNodes \x1B[1;36m${active}\x1B[0;35m/${nc}\x1B[0m \x1B[0;90mGrowing neural tendrils...\x1B[0m
1543
+ \x1B[0;35mNodes \x1B[1;36m${active}\x1B[0;35m/${nc}\x1B[0m \x1B[0;90mGrowing neural tendrils...\x1B[K\x1B[0m
1378
1544
  `);
1379
1545
  await sleep(40);
1380
1546
  }
@@ -1397,7 +1563,7 @@ async function playInstallBanner() {
1397
1563
  renderParticles(cvs, ps, f);
1398
1564
  write(flush(cvs));
1399
1565
  write(`
1400
- ${CO.SETTLED}Crystallizing pathways...${CO.VOID}
1566
+ ${CO.SETTLED}Crystallizing pathways...\x1B[K${CO.VOID}
1401
1567
  `);
1402
1568
  await sleep(60);
1403
1569
  }
@@ -1473,6 +1639,7 @@ async function playInstallBanner() {
1473
1639
  await sleep(120);
1474
1640
  }
1475
1641
  } finally {
1642
+ audio?.stop();
1476
1643
  process.removeListener("exit", restoreCursor);
1477
1644
  restoreCursor();
1478
1645
  write("\n");
@@ -4532,7 +4699,7 @@ npx ca loop --force # Overwrite existing script
4532
4699
  ## Health, audit, and verification commands
4533
4700
 
4534
4701
  \`\`\`bash
4535
- npx ca version-show # Show version with animation and recent changelog
4702
+ npx ca about # Show version, animation, and recent changelog
4536
4703
  npx ca doctor # Check external dependencies and project health
4537
4704
  npx ca audit # Run pattern, rule, and lesson quality checks
4538
4705
  npx ca rules check # Check codebase against .claude/rules.json
@@ -7365,11 +7532,30 @@ function registerVerifyGatesCommand(program2) {
7365
7532
  }
7366
7533
 
7367
7534
  // src/changelog-data.ts
7368
- var CHANGELOG_RECENT = `## [1.3.1] - 2026-02-21
7535
+ var CHANGELOG_RECENT = `## [1.3.2] - 2026-02-21
7369
7536
 
7370
7537
  ### Added
7371
7538
 
7372
- - **\`ca version-show\` command**: Displays version with terminal animation (tendril growth) and recent changelog entries. Non-TTY environments get plain text output. Changelog is embedded at build time from CHANGELOG.md.
7539
+ - **Banner audio**: Pure TypeScript WAV synthesis plays a rising pentatonic melody during the tendril animation. Cross-platform: \`afplay\` (macOS), \`aplay\` (Linux), PowerShell (Windows). Silently skips if player unavailable. Zero dependencies.
7540
+ - **Test coverage**: 19 new tests for \`ca about\` command, changelog extraction/escaping, and \`--update\` doc migration path
7541
+
7542
+ ### Fixed
7543
+
7544
+ - **\`setup --update\` doc migration**: \`--update\` now installs the 5 split docs before removing legacy \`HOW_TO_COMPOUND.md\`, preventing empty \`docs/compound/\`
7545
+ - **Fresh checkout type-check**: \`src/changelog-data.ts\` tracked in git so \`tsc --noEmit\` passes without a prior build
7546
+ - **Trailing status text**: Banner animation no longer leaves "al tendrils..." remnant from previous phase
7547
+
7548
+ ### Changed
7549
+
7550
+ - **\`ca about\` command**: Renamed from \`ca version-show\` for brevity
7551
+ - **Changelog extraction**: Core parsing/escaping logic extracted to \`scripts/changelog-utils.ts\` (shared between prebuild script and tests)
7552
+ - **Narrowed \`.gitignore\`**: Setup-generated patterns scoped to \`compound/\` subdirectories to avoid hiding tracked TDD agent definitions
7553
+
7554
+ ## [1.3.1] - 2026-02-21
7555
+
7556
+ ### Added
7557
+
7558
+ - **\`ca about\` command**: Displays version with terminal animation (tendril growth) and recent changelog entries. Non-TTY environments get plain text output. Changelog is embedded at build time from CHANGELOG.md.
7373
7559
  - **3 new doctor checks**: Beads initialized (\`.beads/\` dir), beads healthy (\`bd doctor\`), codebase scope (user-scope detection)
7374
7560
  - **Beads + scope status in init/setup output**: Full beads health display (CLI available, initialized, healthy) and scope status shown after \`ca init\`, \`ca setup\`, and \`ca setup --update\`
7375
7561
  - **Banner on \`--update\`**: Terminal art animation now plays during \`ca setup --update\` and \`ca init --update\` (same TTY/quiet guards as fresh install)
@@ -7420,33 +7606,11 @@ var CHANGELOG_RECENT = `## [1.3.1] - 2026-02-21
7420
7606
  ### Removed
7421
7607
 
7422
7608
  - **5 deprecated CLI wrapper commands**: \`search.md\`, \`list.md\`, \`show.md\`, \`stats.md\`, \`wrong.md\` (redundant wrappers around \`npx ca <cmd>\`)
7423
- - **\`GENERATED_MARKER\` on new installs**: New installs use path-based detection; marker retained only for backward-compatible \`--update\` detection
7424
-
7425
- ## [1.2.11] - 2026-02-19
7426
-
7427
- ### Added
7428
-
7429
- - **Git worktree integration** (\`ca worktree\`): Isolate epic work in separate git worktrees for parallel execution. Five subcommands:
7430
- - \`ca worktree create <epic-id>\` \u2014 Create worktree, install deps, copy lessons, create Merge beads task
7431
- - \`ca worktree wire-deps <epic-id>\` \u2014 Connect Review/Compound tasks as merge blockers (graceful no-op without worktree)
7432
- - \`ca worktree merge <epic-id>\` \u2014 Two-phase merge: resolve conflicts in worktree, then land clean on main
7433
- - \`ca worktree list\` \u2014 Show active worktrees with epic and merge task status
7434
- - \`ca worktree cleanup <epic-id>\` \u2014 Remove worktree, branch, and close Merge task (--force for dirty worktrees)
7435
- - **\`/compound:set-worktree\` slash command**: Set up a worktree before running \`/compound:lfg\` for isolated epic execution
7436
- - **Conditional Merge gate in \`verify-gates\`**: Worktree epics require the Merge task to be closed before epic closure. Non-worktree epics unaffected.
7437
- - **Plan skill wire-deps step**: Plan phase now calls \`ca worktree wire-deps\` to connect merge dependencies when a worktree is active.
7438
-
7439
- ### Changed
7440
-
7441
- - **Worktree merge safety hardening**: Added branch verification (asserts main repo is on \`main\`), worktree existence guard, structured error messages with worktree paths for conflict resolution and test failures
7442
- - **JSONL reconciliation**: Switched from ID-based to line-based deduplication to preserve last-write-wins semantics for same-ID updates and deletes
7443
- - **Worktree cleanup safety**: Branch deletion uses \`-d\` (safe) by default; \`-D\` (force) only with \`--force\` flag
7444
- - **Shared beads utilities**: Extracted \`validateEpicId\`, \`parseBdShowDeps\`, and \`shortId\` to \`cli-utils.ts\`, eliminating duplication between \`worktree.ts\` and \`verify-gates.ts\`
7445
- - **Sync API**: All worktree functions are now synchronous (removed misleading \`async\` wrapper around purely synchronous \`execFileSync\` calls)`;
7609
+ - **\`GENERATED_MARKER\` on new installs**: New installs use path-based detection; marker retained only for backward-compatible \`--update\` detection`;
7446
7610
 
7447
- // src/commands/version-show.ts
7448
- function registerVersionShowCommand(program2) {
7449
- program2.command("version-show").description("Show version with animation and recent changelog").action(async () => {
7611
+ // src/commands/about.ts
7612
+ function registerAboutCommand(program2) {
7613
+ program2.command("about").description("Show version, animation, and recent changelog").action(async () => {
7450
7614
  if (process.stdout.isTTY) {
7451
7615
  await playInstallBanner();
7452
7616
  } else {
@@ -8564,7 +8728,7 @@ function registerManagementCommands(program2) {
8564
8728
  registerRulesCommands(program2);
8565
8729
  registerTestSummaryCommand(program2);
8566
8730
  registerVerifyGatesCommand(program2);
8567
- registerVersionShowCommand(program2);
8731
+ registerAboutCommand(program2);
8568
8732
  registerWorktreeCommands(program2);
8569
8733
  }
8570
8734