compound-agent 1.3.1 → 1.3.3

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,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.3.3] - 2026-02-21
13
+
14
+ ### Changed
15
+
16
+ - **Banner audio**: Rewritten as vaporwave composition with PolyBLEP anti-aliased sawtooth synthesis, biquad filters, Schroeder reverb, and delay. E minor ambiguity resolves to E major at bloom, synced 300ms ahead of the visual climax (neural brain lighting up). Post-animation reverb tail holds 1.8s for natural dissolution.
17
+
18
+ ## [1.3.2] - 2026-02-21
19
+
20
+ ### Added
21
+
22
+ - **Banner audio**: Pure TypeScript WAV synthesis during the tendril animation. Cross-platform: `afplay` (macOS), `aplay` (Linux), PowerShell (Windows). Silently skips if player unavailable. Zero dependencies.
23
+ - **Test coverage**: 19 new tests for `ca about` command, changelog extraction/escaping, and `--update` doc migration path
24
+
25
+ ### Fixed
26
+
27
+ - **`setup --update` doc migration**: `--update` now installs the 5 split docs before removing legacy `HOW_TO_COMPOUND.md`, preventing empty `docs/compound/`
28
+ - **Fresh checkout type-check**: `src/changelog-data.ts` tracked in git so `tsc --noEmit` passes without a prior build
29
+ - **Trailing status text**: Banner animation no longer leaves "al tendrils..." remnant from previous phase
30
+
31
+ ### Changed
32
+
33
+ - **`ca about` command**: Renamed from `ca version-show` for brevity
34
+ - **Changelog extraction**: Core parsing/escaping logic extracted to `scripts/changelog-utils.ts` (shared between prebuild script and tests)
35
+ - **Narrowed `.gitignore`**: Setup-generated patterns scoped to `compound/` subdirectories to avoid hiding tracked TDD agent definitions
36
+
12
37
  ## [1.3.1] - 2026-02-21
13
38
 
14
39
  ### Added
15
40
 
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.
41
+ - **`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
42
  - **3 new doctor checks**: Beads initialized (`.beads/` dir), beads healthy (`bd doctor`), codebase scope (user-scope detection)
18
43
  - **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
44
  - **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,311 @@ 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 SR = 44100;
1139
+ var MAX_AMP = 28672;
1140
+ var E2 = 82.41;
1141
+ var B2 = 123.47;
1142
+ var E3 = 164.81;
1143
+ var B3 = 246.94;
1144
+ var E4 = 329.63;
1145
+ var Fs4 = 369.99;
1146
+ var Gs4 = 415.3;
1147
+ var B4 = 493.88;
1148
+ function polyBlep(phase, dt) {
1149
+ if (phase < dt) {
1150
+ const t = phase / dt;
1151
+ return t + t - t * t - 1;
1152
+ }
1153
+ if (phase > 1 - dt) {
1154
+ const t = (phase - 1) / dt;
1155
+ return t * t + t + t + 1;
1156
+ }
1157
+ return 0;
1158
+ }
1159
+ function sawBL(freq, t, offset = 0) {
1160
+ const dt = freq / SR;
1161
+ const phase = ((freq * t + offset) % 1 + 1) % 1;
1162
+ return 2 * phase - 1 - polyBlep(phase, dt);
1163
+ }
1164
+ function wideSaw(freq, t, cents = 10) {
1165
+ const r = Math.pow(2, cents / 1200);
1166
+ return (sawBL(freq, t, 0) + sawBL(freq * r, t, 0.33) + sawBL(freq / r, t, 0.66)) / 3;
1167
+ }
1168
+ var BiquadLPF = class {
1169
+ constructor(Q = 0.707) {
1170
+ this.Q = Q;
1171
+ }
1172
+ x1 = 0;
1173
+ x2 = 0;
1174
+ y1 = 0;
1175
+ y2 = 0;
1176
+ b0 = 0;
1177
+ b1 = 0;
1178
+ b2 = 0;
1179
+ a1 = 0;
1180
+ a2 = 0;
1181
+ lastCut = -1;
1182
+ process(x, cutoff) {
1183
+ if (cutoff !== this.lastCut) {
1184
+ this.lastCut = cutoff;
1185
+ const w0 = 2 * Math.PI * Math.min(cutoff, SR * 0.45) / SR;
1186
+ const sin0 = Math.sin(w0), cos0 = Math.cos(w0);
1187
+ const alpha = sin0 / (2 * this.Q);
1188
+ const a0 = 1 + alpha;
1189
+ this.b0 = (1 - cos0) / 2 / a0;
1190
+ this.b1 = (1 - cos0) / a0;
1191
+ this.b2 = this.b0;
1192
+ this.a1 = -2 * cos0 / a0;
1193
+ this.a2 = (1 - alpha) / a0;
1194
+ }
1195
+ const y = this.b0 * x + this.b1 * this.x1 + this.b2 * this.x2 - this.a1 * this.y1 - this.a2 * this.y2;
1196
+ this.x2 = this.x1;
1197
+ this.x1 = x;
1198
+ this.y2 = this.y1;
1199
+ this.y1 = y;
1200
+ return y;
1201
+ }
1202
+ };
1203
+ function applyDelay(samples, ms, fb, wet, lpHz = 3e3) {
1204
+ const len = Math.floor(SR * ms / 1e3);
1205
+ const buf = new Float64Array(len);
1206
+ const lpf = new BiquadLPF();
1207
+ const out2 = [];
1208
+ let i = 0;
1209
+ for (const s of samples) {
1210
+ const d = buf[i];
1211
+ buf[i] = s + lpf.process(d, lpHz) * fb;
1212
+ i = (i + 1) % len;
1213
+ out2.push(s + d * wet);
1214
+ }
1215
+ return out2;
1216
+ }
1217
+ function applyReverb(samples, decayMs, wet) {
1218
+ const combDs = [1116, 1188, 1277, 1356, 1422, 1491];
1219
+ const apDs = [225, 341, 556];
1220
+ function comb(input, delay, decay) {
1221
+ const fb = Math.pow(1e-3, delay / (decay * SR / 1e3));
1222
+ const buf = new Float64Array(delay);
1223
+ const damp = new BiquadLPF(0.5);
1224
+ const out3 = [];
1225
+ let i = 0;
1226
+ for (const s of input) {
1227
+ const d = buf[i];
1228
+ buf[i] = s + damp.process(d, 4500) * fb;
1229
+ i = (i + 1) % delay;
1230
+ out3.push(d);
1231
+ }
1232
+ return out3;
1233
+ }
1234
+ function allpass(input, delay, c = 0.5) {
1235
+ const buf = new Float64Array(delay);
1236
+ const out3 = [];
1237
+ let i = 0;
1238
+ for (const s of input) {
1239
+ const d = buf[i];
1240
+ const v = s + d * c;
1241
+ buf[i] = v;
1242
+ i = (i + 1) % delay;
1243
+ out3.push(d - v * c);
1244
+ }
1245
+ return out3;
1246
+ }
1247
+ const pre = Math.floor(SR * 0.04);
1248
+ const delayed = new Array(pre).fill(0);
1249
+ for (const s of samples) delayed.push(s);
1250
+ const combs = combDs.map((d) => comb(delayed, d, decayMs));
1251
+ const sum = new Array(delayed.length).fill(0);
1252
+ for (const c of combs)
1253
+ for (let i = 0; i < sum.length; i++) sum[i] += (c[i] ?? 0) / combDs.length;
1254
+ let ap = sum;
1255
+ for (const d of apDs) ap = allpass(ap, d);
1256
+ let hpY = 0;
1257
+ const hpAlpha = 1 / SR / (1 / (2 * Math.PI * 120) + 1 / SR);
1258
+ const clean = [];
1259
+ for (const s of ap) {
1260
+ hpY += hpAlpha * (s - hpY);
1261
+ clean.push(s - hpY);
1262
+ }
1263
+ const outLen = Math.max(samples.length, clean.length);
1264
+ const out2 = [];
1265
+ for (let i = 0; i < outLen; i++) {
1266
+ const dry = i < samples.length ? samples[i] : 0;
1267
+ const w = i < clean.length ? clean[i] : 0;
1268
+ out2.push(dry * (1 - wet * 0.3) + w * wet);
1269
+ }
1270
+ return out2;
1271
+ }
1272
+ function expCurve(x, pow = 2) {
1273
+ return Math.pow(Math.max(0, Math.min(1, x)), pow);
1274
+ }
1275
+ function saturate(x) {
1276
+ return Math.tanh(x * 1.15) / Math.tanh(1.15);
1277
+ }
1278
+ function voiceRoot(i, t, f) {
1279
+ const atk = i < SR * 8e-3 ? i / (SR * 8e-3) : 1;
1280
+ const dec = Math.exp(-t * 2.5);
1281
+ const amp = atk * dec * 0.35;
1282
+ if (amp < 1e-3) return 0;
1283
+ const s = wideSaw(E2, t, 6) * 0.7 + Math.sin(2 * Math.PI * B2 * t) * 0.3;
1284
+ return f.process(s, 800 + dec * 1200) * amp;
1285
+ }
1286
+ function voicePad(t, fE, fB) {
1287
+ if (t < 1) return 0;
1288
+ const padT = t - 1;
1289
+ const fadeIn = expCurve(Math.min(1, padT / 1.8));
1290
+ const fadeOut = t < 4 ? 1 : expCurve(1 - (t - 4) / 3.5, 1.5);
1291
+ const amp = fadeIn * fadeOut * 0.2;
1292
+ if (amp < 1e-3) return 0;
1293
+ let cutoff;
1294
+ if (t < 2.7) cutoff = 350 + padT / 1.7 * 850;
1295
+ else if (t < 3.1) cutoff = 1200 + expCurve((t - 2.7) / 0.4) * 2800;
1296
+ else cutoff = 4e3 - expCurve((t - 3.1) / 4, 1.3) * 3200;
1297
+ cutoff *= 1 + 0.04 * Math.sin(2 * Math.PI * 0.11 * t);
1298
+ const dE = 1 + 8e-4 * Math.sin(2 * Math.PI * 0.09 * t);
1299
+ const dB = 1 + 7e-4 * Math.sin(2 * Math.PI * 0.12 * t + 0.5);
1300
+ const e3 = fE.process(wideSaw(E3 * dE, t, 12), cutoff);
1301
+ const b3 = fB.process(wideSaw(B3 * dB, t, 12), cutoff);
1302
+ return (e3 * 0.55 + b3 * 0.45) * amp;
1303
+ }
1304
+ function voiceForeshadow(t, f) {
1305
+ if (t < 2.3 || t >= 3.5) return 0;
1306
+ const fT = t - 2.3;
1307
+ const fadeIn = expCurve(Math.min(1, fT / 0.7));
1308
+ const fadeOut = fT > 0.7 ? expCurve((1.2 - fT) / 0.5) : 1;
1309
+ const amp = fadeIn * fadeOut * 0.07;
1310
+ if (amp < 1e-3) return 0;
1311
+ return f.process(Math.sin(2 * Math.PI * Fs4 * t), 3e3) * amp;
1312
+ }
1313
+ function voiceBloom(t, fE, fG, fB) {
1314
+ if (t < 2.9) return 0;
1315
+ const bT = t - 2.9;
1316
+ const atk = Math.min(1, bT / 0.08);
1317
+ const sus = bT < 1.5 ? 1 : Math.exp(-(bT - 1.5) * 0.8);
1318
+ const amp = atk * sus;
1319
+ if (amp < 2e-3) return 0;
1320
+ const bloomF = bT < 0.3 ? 1500 + expCurve(bT / 0.3) * 3500 : 5e3 - expCurve(Math.min(1, (bT - 0.3) / 4), 1.2) * 3500;
1321
+ const dE4 = 1 + 5e-4 * Math.sin(2 * Math.PI * 0.08 * t);
1322
+ const e4 = fE.process(wideSaw(E4 * dE4, t, 8), bloomF);
1323
+ let gs4 = 0;
1324
+ if (bT > 0.03) {
1325
+ const gsA = Math.min(1, (bT - 0.03) / 0.1);
1326
+ const gsD = 1 + 6e-4 * Math.sin(2 * Math.PI * 0.1 * t + 1);
1327
+ gs4 = fG.process(wideSaw(Gs4 * gsD, t, 9), bloomF) * gsA;
1328
+ }
1329
+ let b4 = 0;
1330
+ if (bT > 0.05 && bT < 3) {
1331
+ const bA = Math.min(1, (bT - 0.05) / 0.15) * (bT > 2 ? expCurve(1 - (bT - 2)) : 1);
1332
+ b4 = fB.process(
1333
+ Math.sin(2 * Math.PI * B4 * t) * 0.5 + wideSaw(B4, t, 6) * 0.5,
1334
+ bloomF
1335
+ ) * bA;
1336
+ }
1337
+ return (e4 * 0.35 + gs4 * 0.35 + b4 * 0.15) * amp * 0.28;
1338
+ }
1339
+ function voiceSub(t) {
1340
+ const subIn = expCurve(Math.min(1, t / 2));
1341
+ const subOut = t < 4.5 ? 1 : expCurve(1 - (t - 4.5) / 2.5, 1.5);
1342
+ return Math.sin(2 * Math.PI * E2 * t) * subIn * subOut * 0.08;
1343
+ }
1344
+ function compose() {
1345
+ const TOTAL = Math.floor(SR * 7.5);
1346
+ const out2 = new Float64Array(TOTAL);
1347
+ const f = [];
1348
+ for (let i = 0; i < 7; i++) f.push(new BiquadLPF(0.6));
1349
+ for (let i = 0; i < TOTAL; i++) {
1350
+ const t = i / SR;
1351
+ out2[i] = saturate(
1352
+ voiceRoot(i, t, f[0]) + voicePad(t, f[1], f[2]) + voiceForeshadow(t, f[3]) + voiceBloom(t, f[4], f[5], f[6]) + voiceSub(t)
1353
+ );
1354
+ }
1355
+ let result = Array.from(out2);
1356
+ result = applyDelay(result, 520, 0.22, 0.18, 2800);
1357
+ result = applyReverb(result, 4800, 0.4);
1358
+ return result;
1359
+ }
1360
+ function encodeWav(raw) {
1361
+ let peak = 0;
1362
+ for (const s of raw) {
1363
+ const a = Math.abs(s);
1364
+ if (a > peak) peak = a;
1365
+ }
1366
+ const scale = peak > 0 ? 0.92 / peak : 1;
1367
+ const dataSize = raw.length * 2;
1368
+ const buf = Buffer.alloc(44 + dataSize);
1369
+ buf.write("RIFF", 0);
1370
+ buf.writeUInt32LE(36 + dataSize, 4);
1371
+ buf.write("WAVE", 8);
1372
+ buf.write("fmt ", 12);
1373
+ buf.writeUInt32LE(16, 16);
1374
+ buf.writeUInt16LE(1, 20);
1375
+ buf.writeUInt16LE(1, 22);
1376
+ buf.writeUInt32LE(SR, 24);
1377
+ buf.writeUInt32LE(SR * 2, 28);
1378
+ buf.writeUInt16LE(2, 32);
1379
+ buf.writeUInt16LE(16, 34);
1380
+ buf.write("data", 36);
1381
+ buf.writeUInt32LE(dataSize, 40);
1382
+ let off = 44;
1383
+ for (const s of raw) {
1384
+ buf.writeInt16LE(Math.round(Math.max(-1, Math.min(1, s * scale)) * MAX_AMP), off);
1385
+ off += 2;
1386
+ }
1387
+ return buf;
1388
+ }
1389
+ function spawnPlayer(filePath) {
1390
+ try {
1391
+ switch (process.platform) {
1392
+ case "darwin":
1393
+ return spawn("afplay", [filePath], { stdio: "ignore", detached: true });
1394
+ case "linux":
1395
+ return spawn("aplay", ["-q", filePath], { stdio: "ignore", detached: true });
1396
+ case "win32":
1397
+ return spawn("powershell", [
1398
+ "-c",
1399
+ `(New-Object Media.SoundPlayer '${filePath}').PlaySync()`
1400
+ ], { stdio: "ignore", detached: true });
1401
+ default:
1402
+ return null;
1403
+ }
1404
+ } catch {
1405
+ return null;
1406
+ }
1407
+ }
1408
+ function playBannerAudio() {
1409
+ try {
1410
+ const wav = encodeWav(compose());
1411
+ const tmpPath = join(tmpdir(), `ca-banner-${process.pid}.wav`);
1412
+ writeFileSync(tmpPath, wav);
1413
+ const proc = spawnPlayer(tmpPath);
1414
+ if (!proc) {
1415
+ try {
1416
+ unlinkSync(tmpPath);
1417
+ } catch {
1418
+ }
1419
+ return null;
1420
+ }
1421
+ proc.unref();
1422
+ const cleanup2 = () => {
1423
+ try {
1424
+ proc.kill();
1425
+ } catch {
1426
+ }
1427
+ try {
1428
+ unlinkSync(tmpPath);
1429
+ } catch {
1430
+ }
1431
+ };
1432
+ proc.on("exit", () => {
1433
+ try {
1434
+ unlinkSync(tmpPath);
1435
+ } catch {
1436
+ }
1437
+ });
1438
+ return { stop: cleanup2 };
1439
+ } catch {
1440
+ return null;
1441
+ }
1442
+ }
1138
1443
 
1139
1444
  // src/setup/banner.ts
1140
1445
  var W = 62;
@@ -1316,6 +1621,7 @@ async function playInstallBanner() {
1316
1621
  const ps = [];
1317
1622
  const write = (s) => process.stdout.write(s);
1318
1623
  write("\x1B[?25l\x1B[2J\x1B[H");
1624
+ const audio = playBannerAudio();
1319
1625
  const restoreCursor = () => process.stdout.write("\x1B[?25h\x1B[0m");
1320
1626
  process.on("exit", restoreCursor);
1321
1627
  try {
@@ -1327,7 +1633,7 @@ async function playInstallBanner() {
1327
1633
  put(cvs, nx(CENTER), ny(CENTER), seedCh[f % 4], seedCo[f % 4]);
1328
1634
  write(flush(cvs));
1329
1635
  write(`
1330
- ${CO.DIM}Seed detected...${CO.VOID}
1636
+ ${CO.DIM}Seed detected...\x1B[K${CO.VOID}
1331
1637
  `);
1332
1638
  await sleep(60);
1333
1639
  }
@@ -1374,7 +1680,7 @@ async function playInstallBanner() {
1374
1680
  let active = 0;
1375
1681
  for (let i = 0; i < nc; i++) if (isGrown[i]) active++;
1376
1682
  write(`
1377
- \x1B[0;35mNodes \x1B[1;36m${active}\x1B[0;35m/${nc}\x1B[0m \x1B[0;90mGrowing neural tendrils...\x1B[0m
1683
+ \x1B[0;35mNodes \x1B[1;36m${active}\x1B[0;35m/${nc}\x1B[0m \x1B[0;90mGrowing neural tendrils...\x1B[K\x1B[0m
1378
1684
  `);
1379
1685
  await sleep(40);
1380
1686
  }
@@ -1397,7 +1703,7 @@ async function playInstallBanner() {
1397
1703
  renderParticles(cvs, ps, f);
1398
1704
  write(flush(cvs));
1399
1705
  write(`
1400
- ${CO.SETTLED}Crystallizing pathways...${CO.VOID}
1706
+ ${CO.SETTLED}Crystallizing pathways...\x1B[K${CO.VOID}
1401
1707
  `);
1402
1708
  await sleep(60);
1403
1709
  }
@@ -1476,6 +1782,10 @@ async function playInstallBanner() {
1476
1782
  process.removeListener("exit", restoreCursor);
1477
1783
  restoreCursor();
1478
1784
  write("\n");
1785
+ if (audio) {
1786
+ await sleep(1800);
1787
+ audio.stop();
1788
+ }
1479
1789
  }
1480
1790
  }
1481
1791
  function checkBeadsAvailable() {
@@ -4532,7 +4842,7 @@ npx ca loop --force # Overwrite existing script
4532
4842
  ## Health, audit, and verification commands
4533
4843
 
4534
4844
  \`\`\`bash
4535
- npx ca version-show # Show version with animation and recent changelog
4845
+ npx ca about # Show version, animation, and recent changelog
4536
4846
  npx ca doctor # Check external dependencies and project health
4537
4847
  npx ca audit # Run pattern, rule, and lesson quality checks
4538
4848
  npx ca rules check # Check codebase against .claude/rules.json
@@ -7365,88 +7675,51 @@ function registerVerifyGatesCommand(program2) {
7365
7675
  }
7366
7676
 
7367
7677
  // src/changelog-data.ts
7368
- var CHANGELOG_RECENT = `## [1.3.1] - 2026-02-21
7369
-
7370
- ### Added
7371
-
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.
7373
- - **3 new doctor checks**: Beads initialized (\`.beads/\` dir), beads healthy (\`bd doctor\`), codebase scope (user-scope detection)
7374
- - **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
- - **Banner on \`--update\`**: Terminal art animation now plays during \`ca setup --update\` and \`ca init --update\` (same TTY/quiet guards as fresh install)
7678
+ var CHANGELOG_RECENT = `## [1.3.3] - 2026-02-21
7376
7679
 
7377
7680
  ### Changed
7378
7681
 
7379
- - **Split documentation**: \`HOW_TO_COMPOUND.md\` replaced by 5 focused documents in \`docs/compound/\`: \`README.md\`, \`WORKFLOW.md\`, \`CLI_REFERENCE.md\`, \`SKILLS.md\`, \`INTEGRATION.md\`
7380
- - **Test-cleaner Phase 3 strengthened**: Adversarial review phase now mandates iteration loop until both reviewers give unconditional approval. Heading, emphasis, and quality criteria updated.
7381
- - **Update hint on upgrade**: When \`ca init\` or \`ca setup\` detects an existing install, displays tip to run with \`--update\` to regenerate managed files
7382
- - **HOW_TO_COMPOUND.md migration**: \`ca setup --update\` automatically removes old monolithic \`HOW_TO_COMPOUND.md\` if it has version frontmatter (generated by compound-agent)
7383
- - **Doctor doc check**: Now checks for \`docs/compound/README.md\` instead of \`HOW_TO_COMPOUND.md\`
7682
+ - **Banner audio**: Rewritten as vaporwave composition with PolyBLEP anti-aliased sawtooth synthesis, biquad filters, Schroeder reverb, and delay. E minor ambiguity resolves to E major at bloom, synced 300ms ahead of the visual climax (neural brain lighting up). Post-animation reverb tail holds 1.8s for natural dissolution.
7384
7683
 
7385
- ## [1.3.0] - 2026-02-21
7684
+ ## [1.3.2] - 2026-02-21
7386
7685
 
7387
7686
  ### Added
7388
7687
 
7389
- - **Setup hardening**: Four new pre-flight checks during \`ca init\` and \`ca setup\`:
7390
- - **Beads CLI check** (\`beads-check.ts\`): Detects if \`bd\` is available, shows install URL if missing (informational, non-blocking)
7391
- - **User-scope detection** (\`scope-check.ts\`): Warns when installing at home directory level where lessons are shared across projects
7392
- - **.gitignore injection** (\`gitignore.ts\`): Ensures \`node_modules/\` and \`.claude/.cache/\` patterns exist in \`.gitignore\`
7393
- - **Upgrade detection** (\`upgrade.ts\`): Detects existing installs and runs migration pipeline (deprecated command removal, header stripping, doc version update)
7394
- - **Upgrade engine**: Automated migration from v1.2.x to v1.3.0:
7395
- - Removes 5 deprecated CLI wrapper commands (\`search.md\`, \`list.md\`, \`show.md\`, \`stats.md\`, \`wrong.md\`)
7396
- - Strips legacy \`<!-- generated by compound-agent -->\` headers from installed files
7397
- - Updates \`HOW_TO_COMPOUND.md\` version during upgrade
7398
- - **\`/compound:research\` skill**: PhD-depth research producing structured survey documents following \`TEMPLATE_FOR_RESEARCH.md\` format
7399
- - **\`/compound:test-clean\` skill**: 5-phase test suite optimization with adversarial review (audit, design, implement, verify, report)
7400
- - **Documentation template**: \`HOW_TO_COMPOUND.md\` deployed to \`docs/compound/\` during setup with version and date placeholders
7401
- - **Test scripts**: \`test:segment\` (run tests for specific module), \`test:random\` (seeded random subset), \`test:critical\` (*.critical.test.ts convention)
7402
- - **3 new doctor checks**: Beads CLI availability, \`.gitignore\` health, usage documentation presence
7688
+ - **Banner audio**: Pure TypeScript WAV synthesis during the tendril animation. Cross-platform: \`afplay\` (macOS), \`aplay\` (Linux), PowerShell (Windows). Silently skips if player unavailable. Zero dependencies.
7689
+ - **Test coverage**: 19 new tests for \`ca about\` command, changelog extraction/escaping, and \`--update\` doc migration path
7403
7690
 
7404
7691
  ### Fixed
7405
7692
 
7406
- - **\`setup --update --dry-run\` no longer mutates files**: \`runUpgrade()\` now accepts a \`dryRun\` parameter propagated to all sub-functions (removeDeprecatedCommands, stripGeneratedHeaders, upgradeDocVersion)
7407
- - **\`setup --uninstall\` respects plugin.json ownership**: Checks \`name === "compound-agent"\` before deleting; user-owned plugin manifests are preserved
7408
- - **Upgrade ownership guard**: \`removeDeprecatedCommands\` checks file content for compound-agent markers before deleting, preventing silent removal of user-authored files with the same name
7409
- - **Malformed settings.json no longer silently clobbered**: On parse error, \`configureClaudeSettings\` warns and skips instead of overwriting with empty config
7693
+ - **\`setup --update\` doc migration**: \`--update\` now installs the 5 split docs before removing legacy \`HOW_TO_COMPOUND.md\`, preventing empty \`docs/compound/\`
7694
+ - **Fresh checkout type-check**: \`src/changelog-data.ts\` tracked in git so \`tsc --noEmit\` passes without a prior build
7695
+ - **Trailing status text**: Banner animation no longer leaves "al tendrils..." remnant from previous phase
7410
7696
 
7411
7697
  ### Changed
7412
7698
 
7413
- - **\`setup --update\` overhaul**: Now uses path-based file detection (compound/ = managed) instead of marker-based. Runs upgrade pipeline and \`.gitignore\` remediation during update
7414
- - **JSON-first \`bd\` parsing in loop**: \`jq\` primary with \`python3\` fallback via \`parse_json()\` helper
7415
- - **CLI test helpers hardened**: Replaced shell string interpolation with \`execFileSync\` for safety and reliability
7416
- - **Beads check portable**: Uses POSIX \`command -v\` instead of non-portable \`which\`
7417
- - **Template expansion**: Brainstorm and plan skills now cross-reference researcher skill; 9 total skills, 11 total commands
7418
- - **Code organization**: Extracted display utilities to \`display-utils.ts\`, uninstall logic to \`uninstall.ts\`
7699
+ - **\`ca about\` command**: Renamed from \`ca version-show\` for brevity
7700
+ - **Changelog extraction**: Core parsing/escaping logic extracted to \`scripts/changelog-utils.ts\` (shared between prebuild script and tests)
7701
+ - **Narrowed \`.gitignore\`**: Setup-generated patterns scoped to \`compound/\` subdirectories to avoid hiding tracked TDD agent definitions
7419
7702
 
7420
- ### Removed
7421
-
7422
- - **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
7703
+ ## [1.3.1] - 2026-02-21
7426
7704
 
7427
7705
  ### Added
7428
7706
 
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.
7707
+ - **\`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.
7708
+ - **3 new doctor checks**: Beads initialized (\`.beads/\` dir), beads healthy (\`bd doctor\`), codebase scope (user-scope detection)
7709
+ - **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\`
7710
+ - **Banner on \`--update\`**: Terminal art animation now plays during \`ca setup --update\` and \`ca init --update\` (same TTY/quiet guards as fresh install)
7438
7711
 
7439
7712
  ### Changed
7440
7713
 
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)`;
7714
+ - **Split documentation**: \`HOW_TO_COMPOUND.md\` replaced by 5 focused documents in \`docs/compound/\`: \`README.md\`, \`WORKFLOW.md\`, \`CLI_REFERENCE.md\`, \`SKILLS.md\`, \`INTEGRATION.md\`
7715
+ - **Test-cleaner Phase 3 strengthened**: Adversarial review phase now mandates iteration loop until both reviewers give unconditional approval. Heading, emphasis, and quality criteria updated.
7716
+ - **Update hint on upgrade**: When \`ca init\` or \`ca setup\` detects an existing install, displays tip to run with \`--update\` to regenerate managed files
7717
+ - **HOW_TO_COMPOUND.md migration**: \`ca setup --update\` automatically removes old monolithic \`HOW_TO_COMPOUND.md\` if it has version frontmatter (generated by compound-agent)
7718
+ - **Doctor doc check**: Now checks for \`docs/compound/README.md\` instead of \`HOW_TO_COMPOUND.md\``;
7446
7719
 
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 () => {
7720
+ // src/commands/about.ts
7721
+ function registerAboutCommand(program2) {
7722
+ program2.command("about").description("Show version, animation, and recent changelog").action(async () => {
7450
7723
  if (process.stdout.isTTY) {
7451
7724
  await playInstallBanner();
7452
7725
  } else {
@@ -8564,7 +8837,7 @@ function registerManagementCommands(program2) {
8564
8837
  registerRulesCommands(program2);
8565
8838
  registerTestSummaryCommand(program2);
8566
8839
  registerVerifyGatesCommand(program2);
8567
- registerVersionShowCommand(program2);
8840
+ registerAboutCommand(program2);
8568
8841
  registerWorktreeCommands(program2);
8569
8842
  }
8570
8843