codeam-cli 2.23.31 → 2.23.33

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
@@ -4,6 +4,19 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.23.32] — 2026-05-31
8
+
9
+ ### Added
10
+
11
+ - **shared:** Add CommitEntry + BlameLine wire types for git enrichment
12
+ - **cli:** Capture git log + blame per changed file at turn end
13
+
14
+ ## [2.23.31] — 2026-05-30
15
+
16
+ ### Fixed
17
+
18
+ - **cli:** TurnFileAggregator captures pre-pair baseline silently
19
+
7
20
  ## [2.23.30] — 2026-05-30
8
21
 
9
22
  ### Chore
package/dist/index.js CHANGED
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
441
441
  // package.json
442
442
  var package_default = {
443
443
  name: "codeam-cli",
444
- version: "2.23.31",
444
+ version: "2.23.33",
445
445
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
446
446
  type: "commonjs",
447
447
  main: "dist/index.js",
@@ -5774,7 +5774,7 @@ function readAnonId() {
5774
5774
  }
5775
5775
  function superProperties() {
5776
5776
  return {
5777
- cliVersion: true ? "2.23.31" : "0.0.0-dev",
5777
+ cliVersion: true ? "2.23.33" : "0.0.0-dev",
5778
5778
  nodeVersion: process.version,
5779
5779
  platform: process.platform,
5780
5780
  arch: process.arch,
@@ -12825,8 +12825,12 @@ function _post2(url, headers, payload) {
12825
12825
  // src/services/file-watcher.service.ts
12826
12826
  var API_BASE5 = resolveApiBaseUrl();
12827
12827
  var DEBOUNCE_MS = 250;
12828
+ var COALESCE_WINDOW_MS = 250;
12829
+ var COALESCE_MAX_HOLD_MS = 2e3;
12828
12830
  var MAX_RETRIES = 2;
12829
12831
  var RETRY_BACKOFF_MS = 300;
12832
+ var HISTORY_MAX_COMMITS = 50;
12833
+ var BLAME_MAX_LINES = 500;
12830
12834
  var WINDOWS_LEGACY_JUNCTIONS = [
12831
12835
  /[\\/]Application Data([\\/]|$)/i,
12832
12836
  /[\\/]Cookies([\\/]|$)/i,
@@ -12906,6 +12910,17 @@ var FileWatcherService = class {
12906
12910
  */
12907
12911
  gitRootByDir = /* @__PURE__ */ new Map();
12908
12912
  stopped = false;
12913
+ /**
12914
+ * Cross-file coalescing buffer. Keyed by absPath so multiple
12915
+ * scheduled emits for the same file collapse to the latest
12916
+ * `changeType`. The buffer drains via `coalesceTimer` after
12917
+ * `COALESCE_WINDOW_MS` of quiescence, or forcibly after
12918
+ * `COALESCE_MAX_HOLD_MS` so the UI never starves during a long
12919
+ * continuous edit.
12920
+ */
12921
+ coalesceBuffer = /* @__PURE__ */ new Map();
12922
+ coalesceTimer = null;
12923
+ coalesceMaxHoldTimer = null;
12909
12924
  /**
12910
12925
  * Start watching `opts.workingDir`. Idempotent (second call is a
12911
12926
  * no-op). Resolves once chokidar's initial scan completes; that
@@ -13007,6 +13022,15 @@ var FileWatcherService = class {
13007
13022
  clearTimeout(entry.timer);
13008
13023
  }
13009
13024
  this.pending.clear();
13025
+ if (this.coalesceTimer) {
13026
+ clearTimeout(this.coalesceTimer);
13027
+ this.coalesceTimer = null;
13028
+ }
13029
+ if (this.coalesceMaxHoldTimer) {
13030
+ clearTimeout(this.coalesceMaxHoldTimer);
13031
+ this.coalesceMaxHoldTimer = null;
13032
+ }
13033
+ this.coalesceBuffer.clear();
13010
13034
  if (this.watcher) {
13011
13035
  try {
13012
13036
  await this.watcher.close();
@@ -13034,7 +13058,7 @@ var FileWatcherService = class {
13034
13058
  if (existing) clearTimeout(existing.timer);
13035
13059
  const timer = setTimeout(() => {
13036
13060
  this.pending.delete(absPath);
13037
- void this.emitForFile(absPath, changeType);
13061
+ this.enqueueForCoalesce(absPath, changeType);
13038
13062
  }, DEBOUNCE_MS);
13039
13063
  this.pending.set(absPath, {
13040
13064
  lastEventAt: Date.now(),
@@ -13042,6 +13066,49 @@ var FileWatcherService = class {
13042
13066
  changeType
13043
13067
  });
13044
13068
  }
13069
+ /**
13070
+ * Drop the file into the cross-file coalescing buffer. The buffer
13071
+ * flushes after `COALESCE_WINDOW_MS` of quiescence (resets on each
13072
+ * new enqueue) or after `COALESCE_MAX_HOLD_MS` regardless. Same
13073
+ * file enqueued twice in a row keeps only the latest `changeType`
13074
+ * (typically the most recent FS event wins).
13075
+ */
13076
+ enqueueForCoalesce(absPath, changeType) {
13077
+ if (this.stopped) return;
13078
+ this.coalesceBuffer.set(absPath, { absPath, changeType });
13079
+ if (this.coalesceTimer) clearTimeout(this.coalesceTimer);
13080
+ this.coalesceTimer = setTimeout(() => {
13081
+ void this.flushCoalesceBuffer();
13082
+ }, COALESCE_WINDOW_MS);
13083
+ if (!this.coalesceMaxHoldTimer) {
13084
+ this.coalesceMaxHoldTimer = setTimeout(() => {
13085
+ void this.flushCoalesceBuffer();
13086
+ }, COALESCE_MAX_HOLD_MS);
13087
+ }
13088
+ }
13089
+ /**
13090
+ * Drain the coalesce buffer. Snapshots the entries up-front so any
13091
+ * emissions that arrive mid-flush (chokidar fires again, agent
13092
+ * keeps writing) land in a fresh buffer rather than competing with
13093
+ * the in-flight one.
13094
+ */
13095
+ async flushCoalesceBuffer() {
13096
+ if (this.coalesceTimer) {
13097
+ clearTimeout(this.coalesceTimer);
13098
+ this.coalesceTimer = null;
13099
+ }
13100
+ if (this.coalesceMaxHoldTimer) {
13101
+ clearTimeout(this.coalesceMaxHoldTimer);
13102
+ this.coalesceMaxHoldTimer = null;
13103
+ }
13104
+ if (this.coalesceBuffer.size === 0) return;
13105
+ const entries = Array.from(this.coalesceBuffer.values());
13106
+ this.coalesceBuffer.clear();
13107
+ for (const entry of entries) {
13108
+ if (this.stopped) return;
13109
+ await this.emitForFile(entry.absPath, entry.changeType);
13110
+ }
13111
+ }
13045
13112
  /**
13046
13113
  * Visible for tests — lets vitest pump a synthetic file event
13047
13114
  * through the debounce + diff + emit pipeline without spinning up
@@ -13137,6 +13204,31 @@ var FileWatcherService = class {
13137
13204
  linesRemoved: hunk.linesRemoved
13138
13205
  });
13139
13206
  }
13207
+ await this.emitGitEnrichment(gitRoot, relPathInRepo, repoPath, repoName, fileStatus);
13208
+ }
13209
+ async emitGitEnrichment(gitRoot, relPathInRepo, repoPath, repoName, fileStatus) {
13210
+ if (this.stopped) return;
13211
+ const commits = await captureHistory(gitRoot, relPathInRepo, HISTORY_MAX_COMMITS);
13212
+ if (this.stopped) return;
13213
+ await this.postReviewHistory({
13214
+ sessionId: this.opts.sessionId,
13215
+ pluginId: this.opts.pluginId,
13216
+ filePath: relPathInRepo,
13217
+ repoPath,
13218
+ repoName,
13219
+ commits
13220
+ });
13221
+ if (this.stopped || fileStatus === "deleted") return;
13222
+ const blameLines = await captureBlame(gitRoot, relPathInRepo, BLAME_MAX_LINES);
13223
+ if (this.stopped) return;
13224
+ await this.postReviewBlame({
13225
+ sessionId: this.opts.sessionId,
13226
+ pluginId: this.opts.pluginId,
13227
+ filePath: relPathInRepo,
13228
+ repoPath,
13229
+ repoName,
13230
+ lines: blameLines
13231
+ });
13140
13232
  }
13141
13233
  /**
13142
13234
  * Compute the unified diff for a single path relative to the
@@ -13173,6 +13265,12 @@ var FileWatcherService = class {
13173
13265
  async postReviewHunk(body) {
13174
13266
  await this.postWithRetries(`${this.apiBase}/api/review/hunks`, body);
13175
13267
  }
13268
+ async postReviewHistory(body) {
13269
+ await this.postWithRetries(`${this.apiBase}/api/review/history`, body);
13270
+ }
13271
+ async postReviewBlame(body) {
13272
+ await this.postWithRetries(`${this.apiBase}/api/review/blame`, body);
13273
+ }
13176
13274
  async postWithRetries(url, body) {
13177
13275
  const payload = JSON.stringify(body);
13178
13276
  const headers = {
@@ -13223,6 +13321,78 @@ var FileWatcherService = class {
13223
13321
  function sleep(ms) {
13224
13322
  return new Promise((r) => setTimeout(r, ms));
13225
13323
  }
13324
+ async function captureHistory(repoRoot, relPath, maxCommits) {
13325
+ const out2 = await runGit(repoRoot, [
13326
+ "log",
13327
+ `--max-count=${maxCommits}`,
13328
+ "--no-color",
13329
+ "--format=%H%x09%an%x09%ae%x09%aI%x09%s",
13330
+ "--",
13331
+ relPath
13332
+ ]);
13333
+ if (!out2) return [];
13334
+ const commits = [];
13335
+ for (const line of out2.split("\n")) {
13336
+ if (!line) continue;
13337
+ const cols = line.split(" ");
13338
+ if (cols.length < 5) continue;
13339
+ const [sha, authorName, authorEmail, committedAt, ...subjectParts] = cols;
13340
+ commits.push({
13341
+ sha,
13342
+ authorName: authorName ?? "",
13343
+ authorEmail: authorEmail ?? "",
13344
+ committedAt: committedAt ?? "",
13345
+ subject: subjectParts.join(" ")
13346
+ });
13347
+ }
13348
+ return commits;
13349
+ }
13350
+ async function captureBlame(repoRoot, relPath, maxLines) {
13351
+ const out2 = await runGit(repoRoot, [
13352
+ "blame",
13353
+ "--line-porcelain",
13354
+ "--no-progress",
13355
+ "-L",
13356
+ `1,${maxLines}`,
13357
+ "--",
13358
+ relPath
13359
+ ]);
13360
+ if (!out2) return [];
13361
+ const lines = [];
13362
+ const blocks = out2.split(/(?=^[0-9a-f]{40} )/m);
13363
+ for (const block of blocks) {
13364
+ if (!block) continue;
13365
+ const blockLines = block.split("\n");
13366
+ const headerMatch = blockLines[0].match(/^([0-9a-f]{40}) \d+ (\d+)/);
13367
+ if (!headerMatch) continue;
13368
+ const sha = headerMatch[1];
13369
+ const lineNumber = parseInt(headerMatch[2], 10);
13370
+ let authorName = "";
13371
+ let authorTime = null;
13372
+ let text = "";
13373
+ for (let i = 1; i < blockLines.length; i += 1) {
13374
+ const bl = blockLines[i];
13375
+ if (bl.startsWith("author ")) {
13376
+ authorName = bl.slice(7);
13377
+ } else if (bl.startsWith("author-time ")) {
13378
+ const parsed = parseInt(bl.slice(12), 10);
13379
+ if (!Number.isNaN(parsed)) authorTime = parsed;
13380
+ } else if (bl.startsWith(" ")) {
13381
+ text = bl.slice(1);
13382
+ break;
13383
+ }
13384
+ }
13385
+ const committedAt = authorTime !== null ? new Date(authorTime * 1e3).toISOString() : "";
13386
+ lines.push({
13387
+ lineNumber,
13388
+ sha,
13389
+ authorName,
13390
+ committedAt,
13391
+ text
13392
+ });
13393
+ }
13394
+ return lines;
13395
+ }
13226
13396
  async function runGit(cwd, args2, opts = {}) {
13227
13397
  return _runGit(cwd, args2, opts);
13228
13398
  }
@@ -19001,7 +19171,7 @@ function checkChokidar() {
19001
19171
  }
19002
19172
  async function doctor(args2 = []) {
19003
19173
  const json = args2.includes("--json");
19004
- const cliVersion = true ? "2.23.31" : "0.0.0-dev";
19174
+ const cliVersion = true ? "2.23.33" : "0.0.0-dev";
19005
19175
  const apiBase = resolveApiBaseUrl();
19006
19176
  const diagnosticId = (0, import_node_crypto6.randomUUID)();
19007
19177
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -19200,7 +19370,7 @@ async function completion(args2) {
19200
19370
  // src/commands/version.ts
19201
19371
  var import_picocolors13 = __toESM(require("picocolors"));
19202
19372
  function version2() {
19203
- const v = true ? "2.23.31" : "unknown";
19373
+ const v = true ? "2.23.33" : "unknown";
19204
19374
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
19205
19375
  }
19206
19376
 
@@ -19428,7 +19598,7 @@ function checkForUpdates() {
19428
19598
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
19429
19599
  if (process.env.CI) return;
19430
19600
  if (!process.stdout.isTTY) return;
19431
- const current = true ? "2.23.31" : null;
19601
+ const current = true ? "2.23.33" : null;
19432
19602
  if (!current) return;
19433
19603
  const cache = readCache();
19434
19604
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.23.31",
3
+ "version": "2.23.33",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",