@markmdev/pebble 0.1.12 → 0.1.13

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/dist/cli/index.js CHANGED
@@ -37,7 +37,7 @@ var TYPE_LABELS = {
37
37
 
38
38
  // src/cli/lib/storage.ts
39
39
  import * as fs from "fs";
40
- import * as path from "path";
40
+ import * as path2 from "path";
41
41
 
42
42
  // src/cli/lib/id.ts
43
43
  import * as crypto from "crypto";
@@ -59,23 +59,78 @@ function derivePrefix(folderName) {
59
59
  return clean.slice(0, 4).toUpperCase().padEnd(4, "X");
60
60
  }
61
61
 
62
+ // src/cli/lib/git.ts
63
+ import { execSync } from "child_process";
64
+ import path from "path";
65
+ function getMainWorktreeRoot() {
66
+ try {
67
+ const gitCommonDir = execSync("git rev-parse --git-common-dir", {
68
+ encoding: "utf-8",
69
+ stdio: ["pipe", "pipe", "pipe"]
70
+ }).trim();
71
+ const gitDir = execSync("git rev-parse --git-dir", {
72
+ encoding: "utf-8",
73
+ stdio: ["pipe", "pipe", "pipe"]
74
+ }).trim();
75
+ const normalizedCommon = path.resolve(gitCommonDir);
76
+ const normalizedGit = path.resolve(gitDir);
77
+ if (normalizedCommon === normalizedGit) {
78
+ return null;
79
+ }
80
+ const mainRoot = path.dirname(normalizedCommon);
81
+ return mainRoot;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
62
87
  // src/cli/lib/storage.ts
63
88
  var PEBBLE_DIR = ".pebble";
64
89
  var ISSUES_FILE = "issues.jsonl";
65
90
  var CONFIG_FILE = "config.json";
91
+ function getConfigSafe(pebbleDir) {
92
+ try {
93
+ const configPath = path2.join(pebbleDir, CONFIG_FILE);
94
+ if (!fs.existsSync(configPath)) {
95
+ return null;
96
+ }
97
+ const content = fs.readFileSync(configPath, "utf-8");
98
+ return JSON.parse(content);
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+ function resolveWorktreePebbleDir(localPebbleDir) {
104
+ if (process.env.PEBBLE_LOCAL === "1") {
105
+ return localPebbleDir;
106
+ }
107
+ const config = getConfigSafe(localPebbleDir);
108
+ if (config?.useMainTreePebble === false) {
109
+ return localPebbleDir;
110
+ }
111
+ const mainRoot = getMainWorktreeRoot();
112
+ if (!mainRoot) {
113
+ return localPebbleDir;
114
+ }
115
+ const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
116
+ if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
117
+ return mainPebble;
118
+ }
119
+ return localPebbleDir;
120
+ }
66
121
  function discoverPebbleDir(startDir = process.cwd()) {
67
- let currentDir = path.resolve(startDir);
68
- const root = path.parse(currentDir).root;
122
+ let currentDir = path2.resolve(startDir);
123
+ const root = path2.parse(currentDir).root;
69
124
  while (currentDir !== root) {
70
- const pebbleDir = path.join(currentDir, PEBBLE_DIR);
125
+ const pebbleDir = path2.join(currentDir, PEBBLE_DIR);
71
126
  if (fs.existsSync(pebbleDir) && fs.statSync(pebbleDir).isDirectory()) {
72
- return pebbleDir;
127
+ return resolveWorktreePebbleDir(pebbleDir);
73
128
  }
74
- currentDir = path.dirname(currentDir);
129
+ currentDir = path2.dirname(currentDir);
75
130
  }
76
- const rootPebble = path.join(root, PEBBLE_DIR);
131
+ const rootPebble = path2.join(root, PEBBLE_DIR);
77
132
  if (fs.existsSync(rootPebble) && fs.statSync(rootPebble).isDirectory()) {
78
- return rootPebble;
133
+ return resolveWorktreePebbleDir(rootPebble);
79
134
  }
80
135
  return null;
81
136
  }
@@ -87,23 +142,35 @@ function getPebbleDir() {
87
142
  return dir;
88
143
  }
89
144
  function ensurePebbleDir(baseDir = process.cwd()) {
90
- const pebbleDir = path.join(baseDir, PEBBLE_DIR);
145
+ let targetDir = baseDir;
146
+ if (process.env.PEBBLE_LOCAL !== "1") {
147
+ const mainRoot = getMainWorktreeRoot();
148
+ if (mainRoot) {
149
+ const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
150
+ if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
151
+ return mainPebble;
152
+ }
153
+ targetDir = mainRoot;
154
+ }
155
+ }
156
+ const pebbleDir = path2.join(targetDir, PEBBLE_DIR);
91
157
  if (!fs.existsSync(pebbleDir)) {
92
158
  fs.mkdirSync(pebbleDir, { recursive: true });
93
- const folderName = path.basename(baseDir);
159
+ const folderName = path2.basename(targetDir);
94
160
  const config = {
95
161
  prefix: derivePrefix(folderName),
96
- version: "0.1.0"
162
+ version: "0.1.0",
163
+ useMainTreePebble: true
97
164
  };
98
165
  setConfig(config, pebbleDir);
99
- const issuesPath = path.join(pebbleDir, ISSUES_FILE);
166
+ const issuesPath = path2.join(pebbleDir, ISSUES_FILE);
100
167
  fs.writeFileSync(issuesPath, "", "utf-8");
101
168
  }
102
169
  return pebbleDir;
103
170
  }
104
171
  function getIssuesPath(pebbleDir) {
105
172
  const dir = pebbleDir ?? getPebbleDir();
106
- return path.join(dir, ISSUES_FILE);
173
+ return path2.join(dir, ISSUES_FILE);
107
174
  }
108
175
  function appendEvent(event, pebbleDir) {
109
176
  const issuesPath = getIssuesPath(pebbleDir);
@@ -129,7 +196,7 @@ function readEvents(pebbleDir) {
129
196
  if (!dir) {
130
197
  return [];
131
198
  }
132
- const issuesPath = path.join(dir, ISSUES_FILE);
199
+ const issuesPath = path2.join(dir, ISSUES_FILE);
133
200
  if (!fs.existsSync(issuesPath)) {
134
201
  return [];
135
202
  }
@@ -145,7 +212,7 @@ function readEvents(pebbleDir) {
145
212
  }
146
213
  function getConfigPath(pebbleDir) {
147
214
  const dir = pebbleDir ?? getPebbleDir();
148
- return path.join(dir, CONFIG_FILE);
215
+ return path2.join(dir, CONFIG_FILE);
149
216
  }
150
217
  function getConfig(pebbleDir) {
151
218
  const configPath = getConfigPath(pebbleDir);
@@ -157,7 +224,7 @@ function getConfig(pebbleDir) {
157
224
  }
158
225
  function setConfig(config, pebbleDir) {
159
226
  const dir = pebbleDir ?? getPebbleDir();
160
- const configPath = path.join(dir, CONFIG_FILE);
227
+ const configPath = path2.join(dir, CONFIG_FILE);
161
228
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
162
229
  }
163
230
  function getOrCreatePebbleDir() {
@@ -491,6 +558,21 @@ function getOpenBlockers(issueId) {
491
558
  const state = computeState(events);
492
559
  return issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed");
493
560
  }
561
+ function getComputedState() {
562
+ const events = readEvents();
563
+ return computeState(events);
564
+ }
565
+ function getAncestryChain(issueId, state) {
566
+ const chain = [];
567
+ let current = state.get(issueId);
568
+ while (current?.parent) {
569
+ const parent = state.get(current.parent);
570
+ if (!parent) break;
571
+ chain.push({ id: parent.id, title: parent.title });
572
+ current = parent;
573
+ }
574
+ return chain;
575
+ }
494
576
 
495
577
  // src/shared/time.ts
496
578
  import { formatDistanceToNow, parseISO } from "date-fns";
@@ -536,7 +618,10 @@ function formatIssueDetailPretty(issue, ctx) {
536
618
  lines.push(`Type: ${formatType(issue.type)}`);
537
619
  lines.push(`Priority: ${formatPriority(issue.priority)}`);
538
620
  lines.push(`Status: ${formatStatus(issue.status)}`);
539
- if (issue.parent) {
621
+ if (ctx.ancestry && ctx.ancestry.length > 0) {
622
+ const chain = [...ctx.ancestry].reverse().map((a) => a.id).join(" > ");
623
+ lines.push(`Ancestry: ${chain}`);
624
+ } else if (issue.parent) {
540
625
  lines.push(`Parent: ${issue.parent}`);
541
626
  }
542
627
  if (issue.description) {
@@ -602,7 +687,8 @@ function outputIssueDetail(issue, ctx, pretty) {
602
687
  blocking: ctx.blocking.map((i) => i.id),
603
688
  children: ctx.children.map((i) => ({ id: i.id, title: i.title, status: i.status })),
604
689
  verifications: ctx.verifications.map((i) => ({ id: i.id, title: i.title, status: i.status })),
605
- related: ctx.related.map((i) => i.id)
690
+ related: ctx.related.map((i) => i.id),
691
+ ...ctx.ancestry && ctx.ancestry.length > 0 && { ancestry: ctx.ancestry }
606
692
  };
607
693
  console.log(formatJson(output));
608
694
  }
@@ -798,11 +884,12 @@ function formatIssueListVerbose(issues, sectionHeader) {
798
884
  lines.push("");
799
885
  }
800
886
  for (const info of issues) {
801
- const { issue, blocking, children, verifications, blockers, parent } = info;
887
+ const { issue, blocking, children, verifications, blockers, ancestry } = info;
802
888
  lines.push(`${issue.id}: ${issue.title}`);
803
889
  lines.push(` Type: ${formatType(issue.type)} | Priority: P${issue.priority} | Created: ${formatRelativeTime(issue.createdAt)}`);
804
- if (parent) {
805
- lines.push(` Epic: ${parent.id} (${parent.title})`);
890
+ if (ancestry.length > 0) {
891
+ const chain = [...ancestry].reverse().map((a) => a.id).join(" > ");
892
+ lines.push(` Ancestry: ${chain}`);
806
893
  }
807
894
  if (blocking.length > 0) {
808
895
  lines.push(` Blocking: ${blocking.join(", ")}`);
@@ -824,13 +911,13 @@ function outputIssueListVerbose(issues, pretty, sectionHeader, limitInfo) {
824
911
  console.log(formatLimitMessage(limitInfo));
825
912
  }
826
913
  } else {
827
- const output = issues.map(({ issue, blocking, children, verifications, blockers, parent }) => ({
914
+ const output = issues.map(({ issue, blocking, children, verifications, blockers, ancestry }) => ({
828
915
  ...issue,
829
916
  blocking,
830
917
  childrenCount: issue.type === "epic" ? children : void 0,
831
918
  verificationsCount: verifications,
832
919
  ...blockers && { openBlockers: blockers },
833
- ...parent && { parentInfo: parent }
920
+ ...ancestry.length > 0 && { ancestry }
834
921
  }));
835
922
  if (limitInfo?.limited) {
836
923
  console.log(formatJson({ issues: output, _meta: limitInfo }));
@@ -1403,20 +1490,15 @@ function listCommand(program2) {
1403
1490
  limited: limit > 0 && total > limit
1404
1491
  };
1405
1492
  if (options.verbose) {
1493
+ const state = getComputedState();
1406
1494
  const verboseIssues = issues.map((issue) => {
1407
- const info = {
1495
+ return {
1408
1496
  issue,
1409
1497
  blocking: getBlocking(issue.id).map((i) => i.id),
1410
1498
  children: getChildren(issue.id).length,
1411
- verifications: getVerifications(issue.id).length
1499
+ verifications: getVerifications(issue.id).length,
1500
+ ancestry: getAncestryChain(issue.id, state)
1412
1501
  };
1413
- if (issue.parent) {
1414
- const parentIssue = getIssue(issue.parent);
1415
- if (parentIssue) {
1416
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1417
- }
1418
- }
1419
- return info;
1420
1502
  });
1421
1503
  let sectionHeader = "Issues";
1422
1504
  if (filters.status) {
@@ -1453,7 +1535,9 @@ function showCommand(program2) {
1453
1535
  const children = issue.type === "epic" ? getChildren(resolvedId) : [];
1454
1536
  const verifications = getVerifications(resolvedId);
1455
1537
  const related = getRelated(resolvedId);
1456
- outputIssueDetail(issue, { blocking, children, verifications, related }, pretty);
1538
+ const state = getComputedState();
1539
+ const ancestry = getAncestryChain(resolvedId, state);
1540
+ outputIssueDetail(issue, { blocking, children, verifications, related, ancestry }, pretty);
1457
1541
  } catch (error) {
1458
1542
  outputError(error, pretty);
1459
1543
  }
@@ -1486,20 +1570,15 @@ function readyCommand(program2) {
1486
1570
  limited: limit > 0 && total > limit
1487
1571
  };
1488
1572
  if (options.verbose) {
1573
+ const state = getComputedState();
1489
1574
  const verboseIssues = issues.map((issue) => {
1490
- const info = {
1575
+ return {
1491
1576
  issue,
1492
1577
  blocking: getBlocking(issue.id).map((i) => i.id),
1493
1578
  children: getChildren(issue.id).length,
1494
- verifications: getVerifications(issue.id).length
1579
+ verifications: getVerifications(issue.id).length,
1580
+ ancestry: getAncestryChain(issue.id, state)
1495
1581
  };
1496
- if (issue.parent) {
1497
- const parentIssue = getIssue(issue.parent);
1498
- if (parentIssue) {
1499
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1500
- }
1501
- }
1502
- return info;
1503
1582
  });
1504
1583
  outputIssueListVerbose(verboseIssues, pretty, "Ready Issues", limitInfo);
1505
1584
  } else {
@@ -1530,23 +1609,18 @@ function blockedCommand(program2) {
1530
1609
  limited: limit > 0 && total > limit
1531
1610
  };
1532
1611
  if (options.verbose) {
1612
+ const state = getComputedState();
1533
1613
  const verboseIssues = issues.map((issue) => {
1534
1614
  const allBlockers = getBlockers(issue.id);
1535
1615
  const openBlockers = allBlockers.filter((b) => b.status !== "closed").map((b) => b.id);
1536
- const info = {
1616
+ return {
1537
1617
  issue,
1538
1618
  blocking: getBlocking(issue.id).map((i) => i.id),
1539
1619
  children: getChildren(issue.id).length,
1540
1620
  verifications: getVerifications(issue.id).length,
1541
- blockers: openBlockers
1621
+ blockers: openBlockers,
1622
+ ancestry: getAncestryChain(issue.id, state)
1542
1623
  };
1543
- if (issue.parent) {
1544
- const parentIssue = getIssue(issue.parent);
1545
- if (parentIssue) {
1546
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1547
- }
1548
- }
1549
- return info;
1550
1624
  });
1551
1625
  outputIssueListVerbose(verboseIssues, pretty, "Blocked Issues", limitInfo);
1552
1626
  } else {
@@ -2036,7 +2110,7 @@ function formatGraphPretty(issues) {
2036
2110
  import express from "express";
2037
2111
  import cors from "cors";
2038
2112
  import { fileURLToPath } from "url";
2039
- import path2 from "path";
2113
+ import path3 from "path";
2040
2114
  import fs2 from "fs";
2041
2115
  import net from "net";
2042
2116
  import open from "open";
@@ -2131,14 +2205,14 @@ function uiCommand(program2) {
2131
2205
  console.error("Error: --files option requires at least one path");
2132
2206
  process.exit(1);
2133
2207
  }
2134
- issueFiles = issueFiles.map((p) => path2.resolve(process.cwd(), p));
2208
+ issueFiles = issueFiles.map((p) => path3.resolve(process.cwd(), p));
2135
2209
  console.log(`Multi-worktree mode: watching ${issueFiles.length} file(s)`);
2136
2210
  for (const f of issueFiles) {
2137
2211
  console.log(` - ${f}`);
2138
2212
  }
2139
2213
  } else {
2140
2214
  const pebbleDir = getOrCreatePebbleDir();
2141
- issueFiles = [path2.join(pebbleDir, "issues.jsonl")];
2215
+ issueFiles = [path3.join(pebbleDir, "issues.jsonl")];
2142
2216
  }
2143
2217
  if (!options.files) {
2144
2218
  getOrCreatePebbleDir();
@@ -2161,7 +2235,7 @@ function uiCommand(program2) {
2161
2235
  res.status(400).json({ error: "path is required" });
2162
2236
  return;
2163
2237
  }
2164
- const resolved = path2.resolve(process.cwd(), filePath);
2238
+ const resolved = path3.resolve(process.cwd(), filePath);
2165
2239
  if (!fs2.existsSync(resolved)) {
2166
2240
  res.status(400).json({ error: `File not found: ${filePath}` });
2167
2241
  return;
@@ -2199,10 +2273,10 @@ function uiCommand(program2) {
2199
2273
  });
2200
2274
  app.get("/api/worktrees", (_req, res) => {
2201
2275
  try {
2202
- const { execSync } = __require("child_process");
2276
+ const { execSync: execSync2 } = __require("child_process");
2203
2277
  let worktreeOutput;
2204
2278
  try {
2205
- worktreeOutput = execSync("git worktree list --porcelain", {
2279
+ worktreeOutput = execSync2("git worktree list --porcelain", {
2206
2280
  encoding: "utf-8",
2207
2281
  cwd: process.cwd()
2208
2282
  });
@@ -2224,7 +2298,7 @@ function uiCommand(program2) {
2224
2298
  }
2225
2299
  }
2226
2300
  if (worktreePath) {
2227
- const issuesFile = path2.join(worktreePath, ".pebble", "issues.jsonl");
2301
+ const issuesFile = path3.join(worktreePath, ".pebble", "issues.jsonl");
2228
2302
  const hasIssues = fs2.existsSync(issuesFile);
2229
2303
  const isActive = issueFiles.includes(issuesFile);
2230
2304
  let issueCount = 0;
@@ -2318,7 +2392,7 @@ data: ${message}
2318
2392
  }
2319
2393
  targetFile = issueFiles[targetIndex];
2320
2394
  }
2321
- const pebbleDir = targetFile ? path2.dirname(targetFile) : getOrCreatePebbleDir();
2395
+ const pebbleDir = targetFile ? path3.dirname(targetFile) : getOrCreatePebbleDir();
2322
2396
  const config = getConfig(pebbleDir);
2323
2397
  const { title, type, priority, description, parent } = req.body;
2324
2398
  if (!title || typeof title !== "string") {
@@ -2509,7 +2583,7 @@ data: ${message}
2509
2583
  return;
2510
2584
  }
2511
2585
  issue = localIssue;
2512
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2586
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2513
2587
  }
2514
2588
  const { title, type, priority, status, description, parent, relatedTo } = req.body;
2515
2589
  const updates = {};
@@ -2637,7 +2711,7 @@ data: ${message}
2637
2711
  return;
2638
2712
  }
2639
2713
  issue = localIssue;
2640
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2714
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2641
2715
  }
2642
2716
  if (issue.status === "closed") {
2643
2717
  res.status(400).json({ error: "Issue is already closed" });
@@ -2711,7 +2785,7 @@ data: ${message}
2711
2785
  }
2712
2786
  } else {
2713
2787
  const pebbleDir = getOrCreatePebbleDir();
2714
- appendEventToFile(autoCloseEvent, path2.join(pebbleDir, "issues.jsonl"));
2788
+ appendEventToFile(autoCloseEvent, path3.join(pebbleDir, "issues.jsonl"));
2715
2789
  }
2716
2790
  autoClosed = { id: targetIssue.id, title: targetIssue.title };
2717
2791
  }
@@ -2751,7 +2825,7 @@ data: ${message}
2751
2825
  return;
2752
2826
  }
2753
2827
  issue = localIssue;
2754
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2828
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2755
2829
  }
2756
2830
  if (issue.status !== "closed") {
2757
2831
  res.status(400).json({ error: "Issue is not closed" });
@@ -2799,7 +2873,7 @@ data: ${message}
2799
2873
  return;
2800
2874
  }
2801
2875
  issue = localIssue;
2802
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2876
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2803
2877
  }
2804
2878
  const { text, author } = req.body;
2805
2879
  if (!text || typeof text !== "string" || text.trim() === "") {
@@ -2851,7 +2925,7 @@ data: ${message}
2851
2925
  return;
2852
2926
  }
2853
2927
  issue = localIssue;
2854
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2928
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2855
2929
  }
2856
2930
  const { blockerId } = req.body;
2857
2931
  if (!blockerId) {
@@ -2925,7 +2999,7 @@ data: ${message}
2925
2999
  return;
2926
3000
  }
2927
3001
  issue = localIssue;
2928
- targetFile = path2.join(pebbleDir, "issues.jsonl");
3002
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2929
3003
  }
2930
3004
  let resolvedBlockerId;
2931
3005
  if (isMultiWorktree()) {
@@ -2964,11 +3038,11 @@ data: ${message}
2964
3038
  }
2965
3039
  });
2966
3040
  const __filename2 = fileURLToPath(import.meta.url);
2967
- const __dirname2 = path2.dirname(__filename2);
2968
- const uiPath = path2.resolve(__dirname2, "../ui");
3041
+ const __dirname2 = path3.dirname(__filename2);
3042
+ const uiPath = path3.resolve(__dirname2, "../ui");
2969
3043
  app.use(express.static(uiPath));
2970
3044
  app.get("*", (_req, res) => {
2971
- res.sendFile(path2.join(uiPath, "index.html"));
3045
+ res.sendFile(path3.join(uiPath, "index.html"));
2972
3046
  });
2973
3047
  const requestedPort = parseInt(options.port, 10);
2974
3048
  const actualPort = await findAvailablePort(requestedPort);
@@ -2992,7 +3066,7 @@ data: ${message}
2992
3066
  // src/cli/commands/import.ts
2993
3067
  import * as fs3 from "fs";
2994
3068
  import * as readline from "readline";
2995
- import * as path3 from "path";
3069
+ import * as path4 from "path";
2996
3070
  function importCommand(program2) {
2997
3071
  program2.command("import <file>").description("Import issues from a Beads issues.jsonl file").option("--dry-run", "Show what would be imported without writing").option("--prefix <prefix>", "Override the ID prefix (default: derive from folder name)").action(async (file, options) => {
2998
3072
  const pretty = program2.opts().pretty ?? false;
@@ -3005,7 +3079,7 @@ function importCommand(program2) {
3005
3079
  console.log(pretty ? "No issues found in file." : formatJson({ imported: 0 }));
3006
3080
  return;
3007
3081
  }
3008
- const prefix = options.prefix ?? derivePrefix(path3.basename(process.cwd()));
3082
+ const prefix = options.prefix ?? derivePrefix(path4.basename(process.cwd()));
3009
3083
  const { events, idMap, stats } = convertToPebbleEvents(beadsIssues, prefix);
3010
3084
  if (options.dryRun) {
3011
3085
  if (pretty) {
@@ -3183,7 +3257,7 @@ function generateSuffix() {
3183
3257
 
3184
3258
  // src/cli/commands/merge.ts
3185
3259
  import * as fs4 from "fs";
3186
- import * as path4 from "path";
3260
+ import * as path5 from "path";
3187
3261
  function mergeEvents(filePaths) {
3188
3262
  const seen = /* @__PURE__ */ new Map();
3189
3263
  for (const filePath of filePaths) {
@@ -3226,7 +3300,7 @@ function mergeCommand(program2) {
3226
3300
  const pretty = program2.opts().pretty ?? false;
3227
3301
  const filePaths = [];
3228
3302
  for (const file of files) {
3229
- const resolved = path4.resolve(process.cwd(), file);
3303
+ const resolved = path5.resolve(process.cwd(), file);
3230
3304
  if (!fs4.existsSync(resolved)) {
3231
3305
  console.error(`Error: File not found: ${file}`);
3232
3306
  process.exit(1);
@@ -3628,7 +3702,7 @@ function verificationsCommand(program2) {
3628
3702
  }
3629
3703
 
3630
3704
  // src/cli/commands/init.ts
3631
- import * as path5 from "path";
3705
+ import * as path6 from "path";
3632
3706
  function initCommand(program2) {
3633
3707
  program2.command("init").description("Initialize a new .pebble directory in the current directory").option("--force", "Re-initialize even if .pebble already exists").action((options) => {
3634
3708
  const existing = discoverPebbleDir();
@@ -3644,8 +3718,8 @@ function initCommand(program2) {
3644
3718
  console.log(JSON.stringify({
3645
3719
  initialized: true,
3646
3720
  path: pebbleDir,
3647
- configPath: path5.join(pebbleDir, "config.json"),
3648
- issuesPath: path5.join(pebbleDir, "issues.jsonl")
3721
+ configPath: path6.join(pebbleDir, "config.json"),
3722
+ issuesPath: path6.join(pebbleDir, "issues.jsonl")
3649
3723
  }));
3650
3724
  });
3651
3725
  }
@@ -3655,6 +3729,12 @@ var program = new Command();
3655
3729
  program.name("pebble").description("A lightweight JSONL-based issue tracker").version("0.1.0");
3656
3730
  program.option("-P, --pretty", "Human-readable output (default: JSON)");
3657
3731
  program.option("--json", "JSON output (this is the default, flag not needed)");
3732
+ program.option("--local", "Use local .pebble directory even in a git worktree");
3733
+ program.hook("preAction", () => {
3734
+ if (program.opts().local) {
3735
+ process.env.PEBBLE_LOCAL = "1";
3736
+ }
3737
+ });
3658
3738
  createCommand(program);
3659
3739
  updateCommand(program);
3660
3740
  closeCommand(program);