jinzd-ai-cli 0.4.79 → 0.4.81

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-34OKHMTQ.js";
4
+ } from "./chunk-JWE53S2U.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-RRL5572W.js";
6
+ import "./chunk-BPIKBXGZ.js";
7
7
 
8
8
  // src/cli/batch.ts
9
9
  import Anthropic from "@anthropic-ai/sdk";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-RRL5572W.js";
4
+ } from "./chunk-BPIKBXGZ.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.79";
4
+ var VERSION = "0.4.81";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -12,8 +12,42 @@ import {
12
12
  } from "./chunk-PFYAAX2S.js";
13
13
 
14
14
  // src/symbols/semantic.ts
15
- function buildEmbeddingText(s) {
16
- const parts = [s.kind, s.name];
15
+ function pathTokens(absFile, root) {
16
+ if (!absFile) return "";
17
+ const norm = absFile.replace(/\\/g, "/");
18
+ let rel = norm;
19
+ if (root) {
20
+ const rootNorm = root.replace(/\\/g, "/").replace(/\/$/, "") + "/";
21
+ if (norm.toLowerCase().startsWith(rootNorm.toLowerCase())) {
22
+ rel = norm.slice(rootNorm.length);
23
+ }
24
+ }
25
+ const noExt = rel.replace(/\.[^./]+$/, "");
26
+ const parts = noExt.split(/[/.\-_]/).filter((t) => {
27
+ if (!t) return false;
28
+ if (t.length < 2) return false;
29
+ if (/^\d+$/.test(t)) return false;
30
+ if (t === "src" || t === "lib" || t === "app" || t === "dist") return false;
31
+ return true;
32
+ });
33
+ const seen = /* @__PURE__ */ new Set();
34
+ const out = [];
35
+ for (const t of parts) {
36
+ const lower = t.toLowerCase();
37
+ if (seen.has(lower)) continue;
38
+ seen.add(lower);
39
+ out.push(t);
40
+ if (out.length >= 6) break;
41
+ }
42
+ return out.join(" ");
43
+ }
44
+ function buildEmbeddingText(s, root) {
45
+ const parts = [];
46
+ if (root) {
47
+ const tokens = pathTokens(s.location.file, root);
48
+ if (tokens) parts.push(tokens);
49
+ }
50
+ parts.push(s.kind, s.name);
17
51
  if (s.container) parts.push(`in ${s.container}`);
18
52
  if (s.signature) parts.push(s.signature);
19
53
  if (s.doc) parts.push(s.doc);
@@ -42,7 +76,7 @@ async function rebuildSemanticIndex(root, opts = {}) {
42
76
  const batch = [];
43
77
  for (let j = i; j < end; j++) {
44
78
  symbolIdx[j] = j;
45
- batch.push(buildEmbeddingText(index.symbols[j]));
79
+ batch.push(buildEmbeddingText(index.symbols[j], index.root));
46
80
  }
47
81
  const batchStart = Date.now();
48
82
  const rows = await embed(batch);
@@ -77,6 +111,7 @@ function hasSemanticIndex(root) {
77
111
  }
78
112
 
79
113
  export {
114
+ pathTokens,
80
115
  buildEmbeddingText,
81
116
  rebuildSemanticIndex,
82
117
  semanticSearch,
@@ -11,8 +11,42 @@ import {
11
11
  } from "./chunk-XMA222FQ.js";
12
12
 
13
13
  // src/symbols/semantic.ts
14
- function buildEmbeddingText(s) {
15
- const parts = [s.kind, s.name];
14
+ function pathTokens(absFile, root) {
15
+ if (!absFile) return "";
16
+ const norm = absFile.replace(/\\/g, "/");
17
+ let rel = norm;
18
+ if (root) {
19
+ const rootNorm = root.replace(/\\/g, "/").replace(/\/$/, "") + "/";
20
+ if (norm.toLowerCase().startsWith(rootNorm.toLowerCase())) {
21
+ rel = norm.slice(rootNorm.length);
22
+ }
23
+ }
24
+ const noExt = rel.replace(/\.[^./]+$/, "");
25
+ const parts = noExt.split(/[/.\-_]/).filter((t) => {
26
+ if (!t) return false;
27
+ if (t.length < 2) return false;
28
+ if (/^\d+$/.test(t)) return false;
29
+ if (t === "src" || t === "lib" || t === "app" || t === "dist") return false;
30
+ return true;
31
+ });
32
+ const seen = /* @__PURE__ */ new Set();
33
+ const out = [];
34
+ for (const t of parts) {
35
+ const lower = t.toLowerCase();
36
+ if (seen.has(lower)) continue;
37
+ seen.add(lower);
38
+ out.push(t);
39
+ if (out.length >= 6) break;
40
+ }
41
+ return out.join(" ");
42
+ }
43
+ function buildEmbeddingText(s, root) {
44
+ const parts = [];
45
+ if (root) {
46
+ const tokens = pathTokens(s.location.file, root);
47
+ if (tokens) parts.push(tokens);
48
+ }
49
+ parts.push(s.kind, s.name);
16
50
  if (s.container) parts.push(`in ${s.container}`);
17
51
  if (s.signature) parts.push(s.signature);
18
52
  if (s.doc) parts.push(s.doc);
@@ -41,7 +75,7 @@ async function rebuildSemanticIndex(root, opts = {}) {
41
75
  const batch = [];
42
76
  for (let j = i; j < end; j++) {
43
77
  symbolIdx[j] = j;
44
- batch.push(buildEmbeddingText(index.symbols[j]));
78
+ batch.push(buildEmbeddingText(index.symbols[j], index.root));
45
79
  }
46
80
  const batchStart = Date.now();
47
81
  const rows = await embed(batch);
@@ -76,6 +110,7 @@ function hasSemanticIndex(root) {
76
110
  }
77
111
 
78
112
  export {
113
+ pathTokens,
79
114
  buildEmbeddingText,
80
115
  rebuildSemanticIndex,
81
116
  semanticSearch,
@@ -13,13 +13,13 @@ import {
13
13
  import {
14
14
  hasSemanticIndex,
15
15
  semanticSearch
16
- } from "./chunk-HPDDAXFY.js";
16
+ } from "./chunk-CQQQFNND.js";
17
17
  import {
18
18
  loadIndex
19
19
  } from "./chunk-6VRJGH25.js";
20
20
  import {
21
21
  runTestsTool
22
- } from "./chunk-ZT7RZYWE.js";
22
+ } from "./chunk-44MKM6OO.js";
23
23
  import {
24
24
  CONFIG_DIR_NAME,
25
25
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -27,7 +27,7 @@ import {
27
27
  SUBAGENT_ALLOWED_TOOLS,
28
28
  SUBAGENT_DEFAULT_MAX_ROUNDS,
29
29
  SUBAGENT_MAX_ROUNDS_LIMIT
30
- } from "./chunk-RRL5572W.js";
30
+ } from "./chunk-BPIKBXGZ.js";
31
31
 
32
32
  // src/tools/builtin/bash.ts
33
33
  import { execSync } from "child_process";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-RRL5572W.js";
11
+ } from "./chunk-BPIKBXGZ.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-MOE2D3NR.js";
5
+ } from "./chunk-HFSSDFQN.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -18,7 +18,7 @@ import {
18
18
  MCP_PROTOCOL_VERSION,
19
19
  MCP_TOOL_PREFIX,
20
20
  VERSION
21
- } from "./chunk-RRL5572W.js";
21
+ } from "./chunk-BPIKBXGZ.js";
22
22
 
23
23
  // src/providers/claude.ts
24
24
  import Anthropic from "@anthropic-ai/sdk";
@@ -2135,6 +2135,12 @@ function getContentText(content) {
2135
2135
  function makeBranchId() {
2136
2136
  return Math.random().toString(16).slice(2, 8);
2137
2137
  }
2138
+ function messagesEqual(a, b) {
2139
+ if (a.role !== b.role) return false;
2140
+ if (JSON.stringify(a.content) !== JSON.stringify(b.content)) return false;
2141
+ if (JSON.stringify(a.toolCalls ?? null) !== JSON.stringify(b.toolCalls ?? null)) return false;
2142
+ return true;
2143
+ }
2138
2144
  var Session = class _Session {
2139
2145
  id;
2140
2146
  provider;
@@ -2370,12 +2376,88 @@ var Session = class _Session {
2370
2376
  this.updated = /* @__PURE__ */ new Date();
2371
2377
  return true;
2372
2378
  }
2379
+ /**
2380
+ * Resolve a user-supplied branch reference (v0.4.81+).
2381
+ *
2382
+ * Accepts the 6-hex branch id, the branch's display title, or an unambiguous
2383
+ * id prefix (≥2 chars). This exists because users naturally type the title
2384
+ * they chose at `/branch new`, not the auto-generated id — every other
2385
+ * `/branch <sub>` used to error on that with "not found".
2386
+ *
2387
+ * Resolution order:
2388
+ * 1. Exact id match
2389
+ * 2. Exact title match (unique)
2390
+ * 3. Id prefix match (unique, length ≥ 2)
2391
+ *
2392
+ * @returns `{ ok: true, id }` on success; on failure `{ ok: false, reason, matches }`
2393
+ * where `matches` lists candidates for ambiguous input so callers
2394
+ * can render a helpful error.
2395
+ */
2396
+ resolveBranchRef(ref) {
2397
+ if (!ref) return { ok: false, reason: "not-found", matches: [] };
2398
+ const byId = this.branches.find((b) => b.id === ref);
2399
+ if (byId) return { ok: true, id: byId.id };
2400
+ const byTitle = this.branches.filter((b) => b.title === ref);
2401
+ if (byTitle.length === 1) return { ok: true, id: byTitle[0].id };
2402
+ if (byTitle.length > 1) return { ok: false, reason: "ambiguous", matches: byTitle };
2403
+ if (ref.length >= 2) {
2404
+ const byPrefix = this.branches.filter((b) => b.id.startsWith(ref));
2405
+ if (byPrefix.length === 1) return { ok: true, id: byPrefix[0].id };
2406
+ if (byPrefix.length > 1) return { ok: false, reason: "ambiguous", matches: byPrefix };
2407
+ }
2408
+ return { ok: false, reason: "not-found", matches: [] };
2409
+ }
2373
2410
  /** Messages of any branch (active or inactive) — read-only copy. */
2374
2411
  getBranchMessages(id) {
2375
2412
  if (id === this.activeBranchId) return this.messages.slice();
2376
2413
  const m = this._inactiveBranchMessages.get(id);
2377
2414
  return m ? m.slice() : null;
2378
2415
  }
2416
+ // ── B3 Branch diff + cherry-pick (v0.4.80+) ────────────────────────
2417
+ /**
2418
+ * Compare messages between two branches. Finds the longest common prefix
2419
+ * by message equality (role + content + toolCalls shape) — NOT by fork
2420
+ * point metadata, so user-edited histories still diff cleanly.
2421
+ *
2422
+ * @param sourceId Branch to compare
2423
+ * @param targetId Branch to compare against (defaults to active branch)
2424
+ * @returns BranchDiff, or null if either branch id is unknown
2425
+ */
2426
+ diffBranches(sourceId, targetId) {
2427
+ const tgt = targetId ?? this.activeBranchId;
2428
+ const src = this.getBranchMessages(sourceId);
2429
+ const dst = this.getBranchMessages(tgt);
2430
+ if (!src || !dst) return null;
2431
+ let i = 0;
2432
+ while (i < src.length && i < dst.length && messagesEqual(src[i], dst[i])) i++;
2433
+ return {
2434
+ sourceId,
2435
+ targetId: tgt,
2436
+ commonPrefix: i,
2437
+ sourceOnly: src.slice(i),
2438
+ targetOnly: dst.slice(i)
2439
+ };
2440
+ }
2441
+ /**
2442
+ * Copy a single message from another branch into the active branch
2443
+ * (appended at the end). Timestamp is reset to now so it's clear when
2444
+ * the cherry-pick happened.
2445
+ *
2446
+ * @returns the cloned message, or null if sourceId / msgIndex is invalid
2447
+ */
2448
+ cherryPickMessage(sourceId, msgIndex) {
2449
+ const src = this.getBranchMessages(sourceId);
2450
+ if (!src) return null;
2451
+ if (msgIndex < 0 || msgIndex >= src.length) return null;
2452
+ const orig = src[msgIndex];
2453
+ const copy = {
2454
+ ...orig,
2455
+ timestamp: /* @__PURE__ */ new Date()
2456
+ };
2457
+ this.messages.push(copy);
2458
+ this.updated = /* @__PURE__ */ new Date();
2459
+ return copy;
2460
+ }
2379
2461
  getMeta() {
2380
2462
  return {
2381
2463
  id: this.id,
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.79";
9
+ var VERSION = "0.4.81";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -36,11 +36,11 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-FU6WMYXS.js";
39
+ } from "./chunk-VEWRTIMX.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
43
- } from "./chunk-UTCC3UMT.js";
43
+ } from "./chunk-GTKJUEBS.js";
44
44
  import {
45
45
  loadIndex
46
46
  } from "./chunk-BJAT4GNC.js";
@@ -2516,6 +2516,12 @@ function getContentText(content) {
2516
2516
  function makeBranchId() {
2517
2517
  return Math.random().toString(16).slice(2, 8);
2518
2518
  }
2519
+ function messagesEqual(a, b) {
2520
+ if (a.role !== b.role) return false;
2521
+ if (JSON.stringify(a.content) !== JSON.stringify(b.content)) return false;
2522
+ if (JSON.stringify(a.toolCalls ?? null) !== JSON.stringify(b.toolCalls ?? null)) return false;
2523
+ return true;
2524
+ }
2519
2525
  var Session = class _Session {
2520
2526
  id;
2521
2527
  provider;
@@ -2751,12 +2757,88 @@ var Session = class _Session {
2751
2757
  this.updated = /* @__PURE__ */ new Date();
2752
2758
  return true;
2753
2759
  }
2760
+ /**
2761
+ * Resolve a user-supplied branch reference (v0.4.81+).
2762
+ *
2763
+ * Accepts the 6-hex branch id, the branch's display title, or an unambiguous
2764
+ * id prefix (≥2 chars). This exists because users naturally type the title
2765
+ * they chose at `/branch new`, not the auto-generated id — every other
2766
+ * `/branch <sub>` used to error on that with "not found".
2767
+ *
2768
+ * Resolution order:
2769
+ * 1. Exact id match
2770
+ * 2. Exact title match (unique)
2771
+ * 3. Id prefix match (unique, length ≥ 2)
2772
+ *
2773
+ * @returns `{ ok: true, id }` on success; on failure `{ ok: false, reason, matches }`
2774
+ * where `matches` lists candidates for ambiguous input so callers
2775
+ * can render a helpful error.
2776
+ */
2777
+ resolveBranchRef(ref) {
2778
+ if (!ref) return { ok: false, reason: "not-found", matches: [] };
2779
+ const byId = this.branches.find((b) => b.id === ref);
2780
+ if (byId) return { ok: true, id: byId.id };
2781
+ const byTitle = this.branches.filter((b) => b.title === ref);
2782
+ if (byTitle.length === 1) return { ok: true, id: byTitle[0].id };
2783
+ if (byTitle.length > 1) return { ok: false, reason: "ambiguous", matches: byTitle };
2784
+ if (ref.length >= 2) {
2785
+ const byPrefix = this.branches.filter((b) => b.id.startsWith(ref));
2786
+ if (byPrefix.length === 1) return { ok: true, id: byPrefix[0].id };
2787
+ if (byPrefix.length > 1) return { ok: false, reason: "ambiguous", matches: byPrefix };
2788
+ }
2789
+ return { ok: false, reason: "not-found", matches: [] };
2790
+ }
2754
2791
  /** Messages of any branch (active or inactive) — read-only copy. */
2755
2792
  getBranchMessages(id) {
2756
2793
  if (id === this.activeBranchId) return this.messages.slice();
2757
2794
  const m = this._inactiveBranchMessages.get(id);
2758
2795
  return m ? m.slice() : null;
2759
2796
  }
2797
+ // ── B3 Branch diff + cherry-pick (v0.4.80+) ────────────────────────
2798
+ /**
2799
+ * Compare messages between two branches. Finds the longest common prefix
2800
+ * by message equality (role + content + toolCalls shape) — NOT by fork
2801
+ * point metadata, so user-edited histories still diff cleanly.
2802
+ *
2803
+ * @param sourceId Branch to compare
2804
+ * @param targetId Branch to compare against (defaults to active branch)
2805
+ * @returns BranchDiff, or null if either branch id is unknown
2806
+ */
2807
+ diffBranches(sourceId, targetId) {
2808
+ const tgt = targetId ?? this.activeBranchId;
2809
+ const src = this.getBranchMessages(sourceId);
2810
+ const dst = this.getBranchMessages(tgt);
2811
+ if (!src || !dst) return null;
2812
+ let i = 0;
2813
+ while (i < src.length && i < dst.length && messagesEqual(src[i], dst[i])) i++;
2814
+ return {
2815
+ sourceId,
2816
+ targetId: tgt,
2817
+ commonPrefix: i,
2818
+ sourceOnly: src.slice(i),
2819
+ targetOnly: dst.slice(i)
2820
+ };
2821
+ }
2822
+ /**
2823
+ * Copy a single message from another branch into the active branch
2824
+ * (appended at the end). Timestamp is reset to now so it's clear when
2825
+ * the cherry-pick happened.
2826
+ *
2827
+ * @returns the cloned message, or null if sourceId / msgIndex is invalid
2828
+ */
2829
+ cherryPickMessage(sourceId, msgIndex) {
2830
+ const src = this.getBranchMessages(sourceId);
2831
+ if (!src) return null;
2832
+ if (msgIndex < 0 || msgIndex >= src.length) return null;
2833
+ const orig = src[msgIndex];
2834
+ const copy = {
2835
+ ...orig,
2836
+ timestamp: /* @__PURE__ */ new Date()
2837
+ };
2838
+ this.messages.push(copy);
2839
+ this.updated = /* @__PURE__ */ new Date();
2840
+ return copy;
2841
+ }
2760
2842
  getMeta() {
2761
2843
  return {
2762
2844
  id: this.id,
@@ -10241,7 +10323,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
10241
10323
  " /diff [--stats] \u2014 Show file modifications in this session",
10242
10324
  " /checkpoint [save|restore|delete] <name> \u2014 Session checkpoints",
10243
10325
  " /fork [checkpoint] \u2014 Fork session from checkpoint or current",
10244
- " /branch [list|new|switch|delete|rename] \u2014 Manage conversation branches",
10326
+ " /branch [list|new|switch|delete|rename|diff|cherry-pick] \u2014 Manage conversation branches (fork tree, cross-branch picks)",
10245
10327
  " /index [status|rebuild|clear|semantic-rebuild|semantic-clear] \u2014 Symbol + semantic index (find_symbol / search_code)",
10246
10328
  " /review [--staged] \u2014 AI code review from git diff",
10247
10329
  " /security-review \u2014 Security vulnerability scan on git diff",
@@ -10571,6 +10653,17 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10571
10653
  break;
10572
10654
  }
10573
10655
  const sub = args[0]?.toLowerCase();
10656
+ const resolve6 = (ref) => {
10657
+ const r = session.resolveBranchRef(ref);
10658
+ if (r.ok) return r.id;
10659
+ if (r.reason === "ambiguous") {
10660
+ const list = r.matches.map((m) => `${m.id} (${m.title})`).join(", ");
10661
+ this.send({ type: "error", message: `Ambiguous branch reference "${ref}". Matches: ${list}. Use the 6-char id.` });
10662
+ } else {
10663
+ this.send({ type: "error", message: `Branch "${ref}" not found. Use /branch list to see all branches.` });
10664
+ }
10665
+ return null;
10666
+ };
10574
10667
  if (!sub || sub === "list") {
10575
10668
  const branches = session.listBranches();
10576
10669
  const lines = [`\u{1F33F} Branches (${branches.length}):`, ""];
@@ -10581,7 +10674,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10581
10674
  lines.push(` ${marker}${b.id.padEnd(10)} ${b.title.padEnd(20)} (${count} msgs)${parent}`);
10582
10675
  }
10583
10676
  lines.push("");
10584
- lines.push("Usage: /branch new <msgIndex> [title] | switch <id> | delete <id> | rename <id> <title>");
10677
+ lines.push("Usage: /branch new <msgIndex> [title] | switch <id|title> | delete <id|title> | rename <id|title> <new title> | diff <id|title> | cherry-pick <id|title> <msgIndex>");
10585
10678
  this.send({ type: "info", message: lines.join("\n") });
10586
10679
  break;
10587
10680
  }
@@ -10609,11 +10702,13 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10609
10702
  break;
10610
10703
  }
10611
10704
  if (sub === "switch") {
10612
- const id = args[1];
10613
- if (!id) {
10614
- this.send({ type: "error", message: "Usage: /branch switch <id>" });
10705
+ const ref = args[1];
10706
+ if (!ref) {
10707
+ this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
10615
10708
  break;
10616
10709
  }
10710
+ const id = resolve6(ref);
10711
+ if (!id) break;
10617
10712
  const ok = session.switchBranch(id);
10618
10713
  if (ok) {
10619
10714
  await this.sessions.save();
@@ -10622,33 +10717,37 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10622
10717
  this.sendSessionMessages();
10623
10718
  this.sendStatus();
10624
10719
  } else {
10625
- this.send({ type: "error", message: `Cannot switch to "${id}" (not found or already active).` });
10720
+ this.send({ type: "error", message: `Cannot switch to "${id}" (already active).` });
10626
10721
  }
10627
10722
  break;
10628
10723
  }
10629
10724
  if (sub === "delete") {
10630
- const id = args[1];
10631
- if (!id) {
10632
- this.send({ type: "error", message: "Usage: /branch delete <id>" });
10725
+ const ref = args[1];
10726
+ if (!ref) {
10727
+ this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
10633
10728
  break;
10634
10729
  }
10730
+ const id = resolve6(ref);
10731
+ if (!id) break;
10635
10732
  const ok = session.deleteBranch(id);
10636
10733
  if (ok) {
10637
10734
  await this.sessions.save();
10638
10735
  this.send({ type: "info", message: `\u2713 Deleted branch "${id}"` });
10639
10736
  this.sendStatus();
10640
10737
  } else {
10641
- this.send({ type: "error", message: `Cannot delete "${id}" (not found, active, or last remaining branch).` });
10738
+ this.send({ type: "error", message: `Cannot delete "${id}" (active, or last remaining branch).` });
10642
10739
  }
10643
10740
  break;
10644
10741
  }
10645
10742
  if (sub === "rename") {
10646
- const id = args[1];
10743
+ const ref = args[1];
10647
10744
  const title = args.slice(2).join(" ").trim();
10648
- if (!id || !title) {
10649
- this.send({ type: "error", message: "Usage: /branch rename <id> <new title>" });
10745
+ if (!ref || !title) {
10746
+ this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
10650
10747
  break;
10651
10748
  }
10749
+ const id = resolve6(ref);
10750
+ if (!id) break;
10652
10751
  const ok = session.renameBranch(id, title);
10653
10752
  if (ok) {
10654
10753
  await this.sessions.save();
@@ -10659,7 +10758,74 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10659
10758
  }
10660
10759
  break;
10661
10760
  }
10662
- this.send({ type: "error", message: `Unknown subcommand: ${sub}. Use list/new/switch/delete/rename.` });
10761
+ if (sub === "diff") {
10762
+ const ref = args[1];
10763
+ if (!ref) {
10764
+ this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
10765
+ break;
10766
+ }
10767
+ const id = resolve6(ref);
10768
+ if (!id) break;
10769
+ const d = session.diffBranches(id);
10770
+ if (!d) {
10771
+ this.send({ type: "error", message: `Branch "${id}" not found.` });
10772
+ break;
10773
+ }
10774
+ const preview = (m) => {
10775
+ const text = typeof m.content === "string" ? m.content : "[complex content]";
10776
+ return text.replace(/\s+/g, " ").trim().slice(0, 70);
10777
+ };
10778
+ const lines = [
10779
+ `\u{1F500} diff ${id} (source) \u2194 ${session.activeBranchId} (active)`,
10780
+ `Common prefix: ${d.commonPrefix} message(s)`
10781
+ ];
10782
+ if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
10783
+ lines.push("Branches are identical.");
10784
+ } else {
10785
+ if (d.sourceOnly.length > 0) {
10786
+ lines.push("", `Only in ${id} (${d.sourceOnly.length}):`);
10787
+ d.sourceOnly.forEach(
10788
+ (m, i) => lines.push(` [${d.commonPrefix + i}] ${m.role}: ${preview(m)}`)
10789
+ );
10790
+ }
10791
+ if (d.targetOnly.length > 0) {
10792
+ lines.push("", `Only in ${session.activeBranchId} (active) (${d.targetOnly.length}):`);
10793
+ d.targetOnly.forEach(
10794
+ (m, i) => lines.push(` [${d.commonPrefix + i}] ${m.role}: ${preview(m)}`)
10795
+ );
10796
+ }
10797
+ lines.push("", `Use /branch cherry-pick ${id} <index> to copy a message into the active branch.`);
10798
+ }
10799
+ this.send({ type: "info", message: lines.join("\n") });
10800
+ break;
10801
+ }
10802
+ if (sub === "cherry-pick") {
10803
+ const ref = args[1];
10804
+ const idxStr = args[2];
10805
+ if (!ref || !idxStr) {
10806
+ this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
10807
+ break;
10808
+ }
10809
+ const id = resolve6(ref);
10810
+ if (!id) break;
10811
+ const idx = parseInt(idxStr, 10);
10812
+ if (Number.isNaN(idx)) {
10813
+ this.send({ type: "error", message: `Invalid message index: "${idxStr}"` });
10814
+ break;
10815
+ }
10816
+ const picked = session.cherryPickMessage(id, idx);
10817
+ if (!picked) {
10818
+ this.send({ type: "error", message: `Cherry-pick failed \u2014 index ${idx} out of range for branch "${id}".` });
10819
+ break;
10820
+ }
10821
+ await this.sessions.save();
10822
+ const preview = typeof picked.content === "string" ? picked.content.replace(/\s+/g, " ").trim().slice(0, 60) : "[complex content]";
10823
+ this.send({ type: "info", message: `\u2713 Cherry-picked ${picked.role} message from "${id}"[${idx}] \u2192 active branch (${session.messages.length} msgs): ${preview}` });
10824
+ this.sendSessionMessages();
10825
+ this.sendStatus();
10826
+ break;
10827
+ }
10828
+ this.send({ type: "error", message: `Unknown subcommand: ${sub}. Use list/new/switch/delete/rename/diff/cherry-pick.` });
10663
10829
  break;
10664
10830
  }
10665
10831
  // ── /index ──────────────────────────────────────────────────────
@@ -10717,7 +10883,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10717
10883
  message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
10718
10884
  });
10719
10885
  try {
10720
- const { rebuildSemanticIndex } = await import("./semantic-MD7HYPWZ.js");
10886
+ const { rebuildSemanticIndex } = await import("./semantic-MYAXLDCZ.js");
10721
10887
  const stats = await rebuildSemanticIndex(root);
10722
10888
  const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
10723
10889
  this.send({
@@ -10904,7 +11070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
10904
11070
  case "test": {
10905
11071
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
10906
11072
  try {
10907
- const { executeTests } = await import("./run-tests-O2CYJF3Y.js");
11073
+ const { executeTests } = await import("./run-tests-Q2GGLRK6.js");
10908
11074
  const argStr = args.join(" ").trim();
10909
11075
  let testArgs = {};
10910
11076
  if (argStr) {
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-MWZ7NMES.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-2GGIA6EG.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -30,10 +30,10 @@ import {
30
30
  saveDevState,
31
31
  sessionHasMeaningfulContent,
32
32
  setupProxy
33
- } from "./chunk-EGJAMKAZ.js";
33
+ } from "./chunk-U7ZSIOKV.js";
34
34
  import {
35
35
  ConfigManager
36
- } from "./chunk-34OKHMTQ.js";
36
+ } from "./chunk-JWE53S2U.js";
37
37
  import {
38
38
  ToolExecutor,
39
39
  ToolRegistry,
@@ -49,16 +49,16 @@ import {
49
49
  spawnAgentContext,
50
50
  theme,
51
51
  undoStack
52
- } from "./chunk-MOE2D3NR.js";
52
+ } from "./chunk-HFSSDFQN.js";
53
53
  import "./chunk-2ZD3YTVM.js";
54
54
  import {
55
55
  fileCheckpoints
56
56
  } from "./chunk-4BKXL7SM.js";
57
57
  import "./chunk-NHNWUBXB.js";
58
- import "./chunk-HPDDAXFY.js";
58
+ import "./chunk-CQQQFNND.js";
59
59
  import "./chunk-6VRJGH25.js";
60
60
  import "./chunk-PFYAAX2S.js";
61
- import "./chunk-ZT7RZYWE.js";
61
+ import "./chunk-44MKM6OO.js";
62
62
  import {
63
63
  AGENTIC_BEHAVIOR_GUIDELINE,
64
64
  AUTHOR,
@@ -80,7 +80,7 @@ import {
80
80
  SKILLS_DIR_NAME,
81
81
  VERSION,
82
82
  buildUserIdentityPrompt
83
- } from "./chunk-RRL5572W.js";
83
+ } from "./chunk-BPIKBXGZ.js";
84
84
 
85
85
  // src/index.ts
86
86
  import { program } from "commander";
@@ -978,7 +978,7 @@ function createDefaultCommands() {
978
978
  " /bug [--copy] - Generate bug report template (--copy to clipboard)",
979
979
  " /diff [--stats] - Show all file modifications in this session",
980
980
  " /fork [checkpoint] - Fork session from checkpoint or current position",
981
- " /branch [list|new|switch|delete|rename] - Manage conversation branches (fork tree)",
981
+ " /branch [list|new|switch|delete|rename|diff|cherry-pick] - Manage conversation branches (fork tree, cross-branch picks)",
982
982
  " /index [status|rebuild|clear|semantic-rebuild|semantic-clear] - Symbol + semantic index (find_symbol / search_code)",
983
983
  " /yolo [on|off] - Toggle session auto-approve (skip confirmations)",
984
984
  " /exit - Exit"
@@ -2254,7 +2254,7 @@ ${hint}` : "")
2254
2254
  {
2255
2255
  name: "branch",
2256
2256
  description: "Manage conversation branches (fork/switch/list/delete/rename)",
2257
- usage: "/branch [list | new <msgIndex> [title] | switch <id> | delete <id> | rename <id> <title>]",
2257
+ usage: "/branch [list | new <msgIndex> [title] | switch <id|title> | delete <id|title> | rename <id|title> <new> | diff <id|title> | cherry-pick <id|title> <msgIndex>]",
2258
2258
  async execute(args, ctx) {
2259
2259
  const session = ctx.sessions.current;
2260
2260
  if (!session) {
@@ -2262,6 +2262,17 @@ ${hint}` : "")
2262
2262
  return;
2263
2263
  }
2264
2264
  const sub = args[0]?.toLowerCase();
2265
+ const resolve3 = (ref) => {
2266
+ const r = session.resolveBranchRef(ref);
2267
+ if (r.ok) return r.id;
2268
+ if (r.reason === "ambiguous") {
2269
+ const list = r.matches.map((m) => `${m.id} (${m.title})`).join(", ");
2270
+ ctx.renderer.renderError(`Ambiguous branch reference "${ref}". Matches: ${list}. Use the 6-char id.`);
2271
+ } else {
2272
+ ctx.renderer.renderError(`Branch "${ref}" not found. Try /branch list to see all branches.`);
2273
+ }
2274
+ return null;
2275
+ };
2265
2276
  if (!sub || sub === "list") {
2266
2277
  const branches = session.listBranches();
2267
2278
  console.log(theme.heading(`
@@ -2276,10 +2287,13 @@ ${hint}` : "")
2276
2287
  );
2277
2288
  }
2278
2289
  console.log();
2279
- console.log(theme.dim(" /branch new <msgIndex> [title] \u2014 fork active branch at message N"));
2280
- console.log(theme.dim(" /branch switch <id> \u2014 switch active branch"));
2281
- console.log(theme.dim(" /branch delete <id> \u2014 delete inactive branch"));
2282
- console.log(theme.dim(" /branch rename <id> <title> \u2014 rename a branch"));
2290
+ console.log(theme.dim(" /branch new <msgIndex> [title] \u2014 fork active branch at message N"));
2291
+ console.log(theme.dim(" /branch switch <id|title> \u2014 switch active branch"));
2292
+ console.log(theme.dim(" /branch delete <id|title> \u2014 delete inactive branch"));
2293
+ console.log(theme.dim(" /branch rename <id|title> <new> \u2014 rename a branch"));
2294
+ console.log(theme.dim(" /branch diff <id|title> \u2014 compare branch with active, list divergent messages"));
2295
+ console.log(theme.dim(" /branch cherry-pick <id|title> <idx> \u2014 copy one message from branch into active"));
2296
+ console.log(theme.dim(" (id, title, or unique id-prefix all work)"));
2283
2297
  console.log();
2284
2298
  return;
2285
2299
  }
@@ -2308,11 +2322,13 @@ ${hint}` : "")
2308
2322
  return;
2309
2323
  }
2310
2324
  if (sub === "switch") {
2311
- const id = args[1];
2312
- if (!id) {
2313
- ctx.renderer.renderError("Usage: /branch switch <id>");
2325
+ const ref = args[1];
2326
+ if (!ref) {
2327
+ ctx.renderer.renderError("Usage: /branch switch <id|title>");
2314
2328
  return;
2315
2329
  }
2330
+ const id = resolve3(ref);
2331
+ if (!id) return;
2316
2332
  const ok = session.switchBranch(id);
2317
2333
  if (ok) {
2318
2334
  await ctx.sessions.save();
@@ -2322,16 +2338,18 @@ ${hint}` : "")
2322
2338
  console.log(theme.dim(` ${session.messages.length} messages on this branch
2323
2339
  `));
2324
2340
  } else {
2325
- ctx.renderer.renderError(`Cannot switch to "${id}" (not found or already active).`);
2341
+ ctx.renderer.renderError(`Cannot switch to "${id}" (already active).`);
2326
2342
  }
2327
2343
  return;
2328
2344
  }
2329
2345
  if (sub === "delete") {
2330
- const id = args[1];
2331
- if (!id) {
2332
- ctx.renderer.renderError("Usage: /branch delete <id>");
2346
+ const ref = args[1];
2347
+ if (!ref) {
2348
+ ctx.renderer.renderError("Usage: /branch delete <id|title>");
2333
2349
  return;
2334
2350
  }
2351
+ const id = resolve3(ref);
2352
+ if (!id) return;
2335
2353
  const ok = session.deleteBranch(id);
2336
2354
  if (ok) {
2337
2355
  await ctx.sessions.save();
@@ -2340,18 +2358,20 @@ ${hint}` : "")
2340
2358
  `));
2341
2359
  } else {
2342
2360
  ctx.renderer.renderError(
2343
- `Cannot delete "${id}" (not found, active, or last remaining branch).`
2361
+ `Cannot delete "${id}" (active, or last remaining branch).`
2344
2362
  );
2345
2363
  }
2346
2364
  return;
2347
2365
  }
2348
2366
  if (sub === "rename") {
2349
- const id = args[1];
2367
+ const ref = args[1];
2350
2368
  const title = args.slice(2).join(" ").trim();
2351
- if (!id || !title) {
2352
- ctx.renderer.renderError("Usage: /branch rename <id> <new title>");
2369
+ if (!ref || !title) {
2370
+ ctx.renderer.renderError("Usage: /branch rename <id|title> <new title>");
2353
2371
  return;
2354
2372
  }
2373
+ const id = resolve3(ref);
2374
+ if (!id) return;
2355
2375
  const ok = session.renameBranch(id, title);
2356
2376
  if (ok) {
2357
2377
  await ctx.sessions.save();
@@ -2363,7 +2383,78 @@ ${hint}` : "")
2363
2383
  }
2364
2384
  return;
2365
2385
  }
2366
- ctx.renderer.renderError(`Unknown subcommand: ${sub}. Use list/new/switch/delete/rename.`);
2386
+ if (sub === "diff") {
2387
+ const ref = args[1];
2388
+ if (!ref) {
2389
+ ctx.renderer.renderError("Usage: /branch diff <id|title> \u2014 diff the given branch against the active branch");
2390
+ return;
2391
+ }
2392
+ const id = resolve3(ref);
2393
+ if (!id) return;
2394
+ const d = session.diffBranches(id);
2395
+ if (!d) {
2396
+ ctx.renderer.renderError(`Branch "${id}" not found.`);
2397
+ return;
2398
+ }
2399
+ const active = session.activeBranchId;
2400
+ console.log(`
2401
+ ${theme.info("diff")} ${id} (source) \u2194 ${active} (active)`);
2402
+ console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
2403
+ if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
2404
+ console.log(theme.success(" Branches are identical.\n"));
2405
+ return;
2406
+ }
2407
+ const preview = (m) => {
2408
+ const text = typeof m.content === "string" ? m.content : "[complex content]";
2409
+ return text.replace(/\s+/g, " ").trim().slice(0, 70);
2410
+ };
2411
+ if (d.sourceOnly.length > 0) {
2412
+ console.log(theme.dim(`
2413
+ Only in ${id} (${d.sourceOnly.length}):`));
2414
+ d.sourceOnly.forEach((m, i) => {
2415
+ console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
2416
+ });
2417
+ }
2418
+ if (d.targetOnly.length > 0) {
2419
+ console.log(theme.dim(`
2420
+ Only in ${active} (active) (${d.targetOnly.length}):`));
2421
+ d.targetOnly.forEach((m, i) => {
2422
+ console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
2423
+ });
2424
+ }
2425
+ console.log(theme.dim(`
2426
+ Use /branch cherry-pick ${id} <index> to copy a message into the active branch.
2427
+ `));
2428
+ return;
2429
+ }
2430
+ if (sub === "cherry-pick") {
2431
+ const ref = args[1];
2432
+ const idxStr = args[2];
2433
+ if (!ref || !idxStr) {
2434
+ ctx.renderer.renderError("Usage: /branch cherry-pick <source-id|title> <msg-index>");
2435
+ return;
2436
+ }
2437
+ const id = resolve3(ref);
2438
+ if (!id) return;
2439
+ const idx = parseInt(idxStr, 10);
2440
+ if (Number.isNaN(idx)) {
2441
+ ctx.renderer.renderError(`Invalid message index: "${idxStr}"`);
2442
+ return;
2443
+ }
2444
+ const picked = session.cherryPickMessage(id, idx);
2445
+ if (!picked) {
2446
+ ctx.renderer.renderError(`Cherry-pick failed \u2014 index ${idx} out of range for branch "${id}".`);
2447
+ return;
2448
+ }
2449
+ await ctx.sessions.save();
2450
+ const preview = typeof picked.content === "string" ? picked.content.replace(/\s+/g, " ").trim().slice(0, 60) : "[complex content]";
2451
+ console.log(theme.success(`
2452
+ \u2713 Cherry-picked ${picked.role} message from "${id}"[${idx}] \u2192 active branch (now ${session.messages.length} msgs)`));
2453
+ console.log(theme.dim(` ${preview}
2454
+ `));
2455
+ return;
2456
+ }
2457
+ ctx.renderer.renderError(`Unknown subcommand: ${sub}. Use list/new/switch/delete/rename/diff/cherry-pick.`);
2367
2458
  }
2368
2459
  },
2369
2460
  // ── /index ────────────────────────────────────────────────────
@@ -2435,7 +2526,7 @@ ${hint}` : "")
2435
2526
  }
2436
2527
  console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
2437
2528
  console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
2438
- const { rebuildSemanticIndex } = await import("./semantic-RBWU76MD.js");
2529
+ const { rebuildSemanticIndex } = await import("./semantic-ICJ536BG.js");
2439
2530
  try {
2440
2531
  const stats = await rebuildSemanticIndex(root, {
2441
2532
  onProgress: (done, total) => {
@@ -2501,7 +2592,7 @@ ${hint}` : "")
2501
2592
  usage: "/test [command|filter]",
2502
2593
  async execute(args, ctx) {
2503
2594
  try {
2504
- const { executeTests } = await import("./run-tests-WPSQCOIG.js");
2595
+ const { executeTests } = await import("./run-tests-UX6KNA4R.js");
2505
2596
  const argStr = args.join(" ").trim();
2506
2597
  let testArgs = {};
2507
2598
  if (argStr) {
@@ -6394,7 +6485,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6394
6485
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6395
6486
  process.exit(1);
6396
6487
  }
6397
- const { startWebServer } = await import("./server-MHY4E453.js");
6488
+ const { startWebServer } = await import("./server-LXE5HXOQ.js");
6398
6489
  await startWebServer({ port, host: options.host });
6399
6490
  });
6400
6491
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6517,7 +6608,7 @@ program.command("sessions").description("List recent conversation sessions").act
6517
6608
  });
6518
6609
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6519
6610
  try {
6520
- const batch = await import("./batch-SMUURFMA.js");
6611
+ const batch = await import("./batch-JOYUXWBK.js");
6521
6612
  switch (action) {
6522
6613
  case "submit":
6523
6614
  if (!arg) {
@@ -6677,7 +6768,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
6677
6768
  }),
6678
6769
  config.get("customProviders")
6679
6770
  );
6680
- const { startHub } = await import("./hub-BDEFIXFF.js");
6771
+ const { startHub } = await import("./hub-WLBLPBT2.js");
6681
6772
  await startHub(
6682
6773
  {
6683
6774
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-FU6WMYXS.js";
4
+ } from "./chunk-VEWRTIMX.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-ZT7RZYWE.js";
6
- import "./chunk-RRL5572W.js";
5
+ } from "./chunk-44MKM6OO.js";
6
+ import "./chunk-BPIKBXGZ.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -2,14 +2,16 @@
2
2
  import {
3
3
  buildEmbeddingText,
4
4
  hasSemanticIndex,
5
+ pathTokens,
5
6
  rebuildSemanticIndex,
6
7
  semanticSearch
7
- } from "./chunk-HPDDAXFY.js";
8
+ } from "./chunk-CQQQFNND.js";
8
9
  import "./chunk-6VRJGH25.js";
9
10
  import "./chunk-PFYAAX2S.js";
10
11
  export {
11
12
  buildEmbeddingText,
12
13
  hasSemanticIndex,
14
+ pathTokens,
13
15
  rebuildSemanticIndex,
14
16
  semanticSearch
15
17
  };
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  buildEmbeddingText,
3
3
  hasSemanticIndex,
4
+ pathTokens,
4
5
  rebuildSemanticIndex,
5
6
  semanticSearch
6
- } from "./chunk-UTCC3UMT.js";
7
+ } from "./chunk-GTKJUEBS.js";
7
8
  import "./chunk-BJAT4GNC.js";
8
9
  import "./chunk-XMA222FQ.js";
9
10
  export {
10
11
  buildEmbeddingText,
11
12
  hasSemanticIndex,
13
+ pathTokens,
12
14
  rebuildSemanticIndex,
13
15
  semanticSearch
14
16
  };
@@ -20,10 +20,10 @@ import {
20
20
  persistToolRound,
21
21
  rebuildExtraMessages,
22
22
  setupProxy
23
- } from "./chunk-EGJAMKAZ.js";
23
+ } from "./chunk-U7ZSIOKV.js";
24
24
  import {
25
25
  ConfigManager
26
- } from "./chunk-34OKHMTQ.js";
26
+ } from "./chunk-JWE53S2U.js";
27
27
  import {
28
28
  ToolExecutor,
29
29
  ToolRegistry,
@@ -41,14 +41,14 @@ import {
41
41
  spawnAgentContext,
42
42
  truncateOutput,
43
43
  undoStack
44
- } from "./chunk-MOE2D3NR.js";
44
+ } from "./chunk-HFSSDFQN.js";
45
45
  import "./chunk-2ZD3YTVM.js";
46
46
  import "./chunk-4BKXL7SM.js";
47
47
  import "./chunk-NHNWUBXB.js";
48
- import "./chunk-HPDDAXFY.js";
48
+ import "./chunk-CQQQFNND.js";
49
49
  import "./chunk-6VRJGH25.js";
50
50
  import "./chunk-PFYAAX2S.js";
51
- import "./chunk-ZT7RZYWE.js";
51
+ import "./chunk-44MKM6OO.js";
52
52
  import {
53
53
  AGENTIC_BEHAVIOR_GUIDELINE,
54
54
  AUTHOR,
@@ -67,7 +67,7 @@ import {
67
67
  SKILLS_DIR_NAME,
68
68
  VERSION,
69
69
  buildUserIdentityPrompt
70
- } from "./chunk-RRL5572W.js";
70
+ } from "./chunk-BPIKBXGZ.js";
71
71
  import {
72
72
  AuthManager
73
73
  } from "./chunk-BYNY5JPB.js";
@@ -1482,7 +1482,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
1482
1482
  " /diff [--stats] \u2014 Show file modifications in this session",
1483
1483
  " /checkpoint [save|restore|delete] <name> \u2014 Session checkpoints",
1484
1484
  " /fork [checkpoint] \u2014 Fork session from checkpoint or current",
1485
- " /branch [list|new|switch|delete|rename] \u2014 Manage conversation branches",
1485
+ " /branch [list|new|switch|delete|rename|diff|cherry-pick] \u2014 Manage conversation branches (fork tree, cross-branch picks)",
1486
1486
  " /index [status|rebuild|clear|semantic-rebuild|semantic-clear] \u2014 Symbol + semantic index (find_symbol / search_code)",
1487
1487
  " /review [--staged] \u2014 AI code review from git diff",
1488
1488
  " /security-review \u2014 Security vulnerability scan on git diff",
@@ -1812,6 +1812,17 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1812
1812
  break;
1813
1813
  }
1814
1814
  const sub = args[0]?.toLowerCase();
1815
+ const resolve3 = (ref) => {
1816
+ const r = session.resolveBranchRef(ref);
1817
+ if (r.ok) return r.id;
1818
+ if (r.reason === "ambiguous") {
1819
+ const list = r.matches.map((m) => `${m.id} (${m.title})`).join(", ");
1820
+ this.send({ type: "error", message: `Ambiguous branch reference "${ref}". Matches: ${list}. Use the 6-char id.` });
1821
+ } else {
1822
+ this.send({ type: "error", message: `Branch "${ref}" not found. Use /branch list to see all branches.` });
1823
+ }
1824
+ return null;
1825
+ };
1815
1826
  if (!sub || sub === "list") {
1816
1827
  const branches = session.listBranches();
1817
1828
  const lines = [`\u{1F33F} Branches (${branches.length}):`, ""];
@@ -1822,7 +1833,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1822
1833
  lines.push(` ${marker}${b.id.padEnd(10)} ${b.title.padEnd(20)} (${count} msgs)${parent}`);
1823
1834
  }
1824
1835
  lines.push("");
1825
- lines.push("Usage: /branch new <msgIndex> [title] | switch <id> | delete <id> | rename <id> <title>");
1836
+ lines.push("Usage: /branch new <msgIndex> [title] | switch <id|title> | delete <id|title> | rename <id|title> <new title> | diff <id|title> | cherry-pick <id|title> <msgIndex>");
1826
1837
  this.send({ type: "info", message: lines.join("\n") });
1827
1838
  break;
1828
1839
  }
@@ -1850,11 +1861,13 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1850
1861
  break;
1851
1862
  }
1852
1863
  if (sub === "switch") {
1853
- const id = args[1];
1854
- if (!id) {
1855
- this.send({ type: "error", message: "Usage: /branch switch <id>" });
1864
+ const ref = args[1];
1865
+ if (!ref) {
1866
+ this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
1856
1867
  break;
1857
1868
  }
1869
+ const id = resolve3(ref);
1870
+ if (!id) break;
1858
1871
  const ok = session.switchBranch(id);
1859
1872
  if (ok) {
1860
1873
  await this.sessions.save();
@@ -1863,33 +1876,37 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1863
1876
  this.sendSessionMessages();
1864
1877
  this.sendStatus();
1865
1878
  } else {
1866
- this.send({ type: "error", message: `Cannot switch to "${id}" (not found or already active).` });
1879
+ this.send({ type: "error", message: `Cannot switch to "${id}" (already active).` });
1867
1880
  }
1868
1881
  break;
1869
1882
  }
1870
1883
  if (sub === "delete") {
1871
- const id = args[1];
1872
- if (!id) {
1873
- this.send({ type: "error", message: "Usage: /branch delete <id>" });
1884
+ const ref = args[1];
1885
+ if (!ref) {
1886
+ this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
1874
1887
  break;
1875
1888
  }
1889
+ const id = resolve3(ref);
1890
+ if (!id) break;
1876
1891
  const ok = session.deleteBranch(id);
1877
1892
  if (ok) {
1878
1893
  await this.sessions.save();
1879
1894
  this.send({ type: "info", message: `\u2713 Deleted branch "${id}"` });
1880
1895
  this.sendStatus();
1881
1896
  } else {
1882
- this.send({ type: "error", message: `Cannot delete "${id}" (not found, active, or last remaining branch).` });
1897
+ this.send({ type: "error", message: `Cannot delete "${id}" (active, or last remaining branch).` });
1883
1898
  }
1884
1899
  break;
1885
1900
  }
1886
1901
  if (sub === "rename") {
1887
- const id = args[1];
1902
+ const ref = args[1];
1888
1903
  const title = args.slice(2).join(" ").trim();
1889
- if (!id || !title) {
1890
- this.send({ type: "error", message: "Usage: /branch rename <id> <new title>" });
1904
+ if (!ref || !title) {
1905
+ this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
1891
1906
  break;
1892
1907
  }
1908
+ const id = resolve3(ref);
1909
+ if (!id) break;
1893
1910
  const ok = session.renameBranch(id, title);
1894
1911
  if (ok) {
1895
1912
  await this.sessions.save();
@@ -1900,7 +1917,74 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1900
1917
  }
1901
1918
  break;
1902
1919
  }
1903
- this.send({ type: "error", message: `Unknown subcommand: ${sub}. Use list/new/switch/delete/rename.` });
1920
+ if (sub === "diff") {
1921
+ const ref = args[1];
1922
+ if (!ref) {
1923
+ this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
1924
+ break;
1925
+ }
1926
+ const id = resolve3(ref);
1927
+ if (!id) break;
1928
+ const d = session.diffBranches(id);
1929
+ if (!d) {
1930
+ this.send({ type: "error", message: `Branch "${id}" not found.` });
1931
+ break;
1932
+ }
1933
+ const preview = (m) => {
1934
+ const text = typeof m.content === "string" ? m.content : "[complex content]";
1935
+ return text.replace(/\s+/g, " ").trim().slice(0, 70);
1936
+ };
1937
+ const lines = [
1938
+ `\u{1F500} diff ${id} (source) \u2194 ${session.activeBranchId} (active)`,
1939
+ `Common prefix: ${d.commonPrefix} message(s)`
1940
+ ];
1941
+ if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
1942
+ lines.push("Branches are identical.");
1943
+ } else {
1944
+ if (d.sourceOnly.length > 0) {
1945
+ lines.push("", `Only in ${id} (${d.sourceOnly.length}):`);
1946
+ d.sourceOnly.forEach(
1947
+ (m, i) => lines.push(` [${d.commonPrefix + i}] ${m.role}: ${preview(m)}`)
1948
+ );
1949
+ }
1950
+ if (d.targetOnly.length > 0) {
1951
+ lines.push("", `Only in ${session.activeBranchId} (active) (${d.targetOnly.length}):`);
1952
+ d.targetOnly.forEach(
1953
+ (m, i) => lines.push(` [${d.commonPrefix + i}] ${m.role}: ${preview(m)}`)
1954
+ );
1955
+ }
1956
+ lines.push("", `Use /branch cherry-pick ${id} <index> to copy a message into the active branch.`);
1957
+ }
1958
+ this.send({ type: "info", message: lines.join("\n") });
1959
+ break;
1960
+ }
1961
+ if (sub === "cherry-pick") {
1962
+ const ref = args[1];
1963
+ const idxStr = args[2];
1964
+ if (!ref || !idxStr) {
1965
+ this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
1966
+ break;
1967
+ }
1968
+ const id = resolve3(ref);
1969
+ if (!id) break;
1970
+ const idx = parseInt(idxStr, 10);
1971
+ if (Number.isNaN(idx)) {
1972
+ this.send({ type: "error", message: `Invalid message index: "${idxStr}"` });
1973
+ break;
1974
+ }
1975
+ const picked = session.cherryPickMessage(id, idx);
1976
+ if (!picked) {
1977
+ this.send({ type: "error", message: `Cherry-pick failed \u2014 index ${idx} out of range for branch "${id}".` });
1978
+ break;
1979
+ }
1980
+ await this.sessions.save();
1981
+ const preview = typeof picked.content === "string" ? picked.content.replace(/\s+/g, " ").trim().slice(0, 60) : "[complex content]";
1982
+ this.send({ type: "info", message: `\u2713 Cherry-picked ${picked.role} message from "${id}"[${idx}] \u2192 active branch (${session.messages.length} msgs): ${preview}` });
1983
+ this.sendSessionMessages();
1984
+ this.sendStatus();
1985
+ break;
1986
+ }
1987
+ this.send({ type: "error", message: `Unknown subcommand: ${sub}. Use list/new/switch/delete/rename/diff/cherry-pick.` });
1904
1988
  break;
1905
1989
  }
1906
1990
  // ── /index ──────────────────────────────────────────────────────
@@ -1958,7 +2042,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1958
2042
  message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
1959
2043
  });
1960
2044
  try {
1961
- const { rebuildSemanticIndex } = await import("./semantic-RBWU76MD.js");
2045
+ const { rebuildSemanticIndex } = await import("./semantic-ICJ536BG.js");
1962
2046
  const stats = await rebuildSemanticIndex(root);
1963
2047
  const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
1964
2048
  this.send({
@@ -2145,7 +2229,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2145
2229
  case "test": {
2146
2230
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2147
2231
  try {
2148
- const { executeTests } = await import("./run-tests-WPSQCOIG.js");
2232
+ const { executeTests } = await import("./run-tests-UX6KNA4R.js");
2149
2233
  const argStr = args.join(" ").trim();
2150
2234
  let testArgs = {};
2151
2235
  if (argStr) {
@@ -4,17 +4,17 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-MOE2D3NR.js";
7
+ } from "./chunk-HFSSDFQN.js";
8
8
  import "./chunk-2ZD3YTVM.js";
9
9
  import "./chunk-4BKXL7SM.js";
10
10
  import "./chunk-NHNWUBXB.js";
11
- import "./chunk-HPDDAXFY.js";
11
+ import "./chunk-CQQQFNND.js";
12
12
  import "./chunk-6VRJGH25.js";
13
13
  import "./chunk-PFYAAX2S.js";
14
- import "./chunk-ZT7RZYWE.js";
14
+ import "./chunk-44MKM6OO.js";
15
15
  import {
16
16
  SUBAGENT_ALLOWED_TOOLS
17
- } from "./chunk-RRL5572W.js";
17
+ } from "./chunk-BPIKBXGZ.js";
18
18
 
19
19
  // src/hub/task-orchestrator.ts
20
20
  import { createInterface } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.79",
3
+ "version": "0.4.81",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",