@markmdev/pebble 0.1.12 → 0.1.15
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
|
@@ -8,6 +8,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
|
|
9
9
|
// src/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
12
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
11
14
|
|
|
12
15
|
// src/shared/types.ts
|
|
13
16
|
var ISSUE_TYPES = ["task", "bug", "epic", "verification"];
|
|
@@ -37,7 +40,7 @@ var TYPE_LABELS = {
|
|
|
37
40
|
|
|
38
41
|
// src/cli/lib/storage.ts
|
|
39
42
|
import * as fs from "fs";
|
|
40
|
-
import * as
|
|
43
|
+
import * as path2 from "path";
|
|
41
44
|
|
|
42
45
|
// src/cli/lib/id.ts
|
|
43
46
|
import * as crypto from "crypto";
|
|
@@ -59,23 +62,90 @@ function derivePrefix(folderName) {
|
|
|
59
62
|
return clean.slice(0, 4).toUpperCase().padEnd(4, "X");
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
// src/cli/lib/git.ts
|
|
66
|
+
import { execSync } from "child_process";
|
|
67
|
+
import path from "path";
|
|
68
|
+
function getMainWorktreeRoot() {
|
|
69
|
+
try {
|
|
70
|
+
const gitCommonDir = execSync("git rev-parse --git-common-dir", {
|
|
71
|
+
encoding: "utf-8",
|
|
72
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
73
|
+
}).trim();
|
|
74
|
+
const gitDir = execSync("git rev-parse --git-dir", {
|
|
75
|
+
encoding: "utf-8",
|
|
76
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
77
|
+
}).trim();
|
|
78
|
+
const normalizedCommon = path.resolve(gitCommonDir);
|
|
79
|
+
const normalizedGit = path.resolve(gitDir);
|
|
80
|
+
if (normalizedCommon === normalizedGit) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const mainRoot = path.dirname(normalizedCommon);
|
|
84
|
+
return mainRoot;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
62
90
|
// src/cli/lib/storage.ts
|
|
63
91
|
var PEBBLE_DIR = ".pebble";
|
|
64
92
|
var ISSUES_FILE = "issues.jsonl";
|
|
65
93
|
var CONFIG_FILE = "config.json";
|
|
94
|
+
function getConfigSafe(pebbleDir) {
|
|
95
|
+
try {
|
|
96
|
+
const configPath = path2.join(pebbleDir, CONFIG_FILE);
|
|
97
|
+
if (!fs.existsSync(configPath)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
101
|
+
return JSON.parse(content);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function resolveWorktreePebbleDir(localPebbleDir) {
|
|
107
|
+
if (process.env.PEBBLE_LOCAL === "1") {
|
|
108
|
+
return localPebbleDir;
|
|
109
|
+
}
|
|
110
|
+
const config = getConfigSafe(localPebbleDir);
|
|
111
|
+
if (config?.useMainTreePebble === false) {
|
|
112
|
+
return localPebbleDir;
|
|
113
|
+
}
|
|
114
|
+
const mainRoot = getMainWorktreeRoot();
|
|
115
|
+
if (!mainRoot) {
|
|
116
|
+
return localPebbleDir;
|
|
117
|
+
}
|
|
118
|
+
const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
|
|
119
|
+
if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
|
|
120
|
+
return mainPebble;
|
|
121
|
+
}
|
|
122
|
+
return localPebbleDir;
|
|
123
|
+
}
|
|
66
124
|
function discoverPebbleDir(startDir = process.cwd()) {
|
|
67
|
-
|
|
68
|
-
|
|
125
|
+
if (process.env.PEBBLE_LOCAL !== "1") {
|
|
126
|
+
const mainRoot = getMainWorktreeRoot();
|
|
127
|
+
if (mainRoot) {
|
|
128
|
+
const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
|
|
129
|
+
if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
|
|
130
|
+
const config = getConfigSafe(mainPebble);
|
|
131
|
+
if (config?.useMainTreePebble !== false) {
|
|
132
|
+
return mainPebble;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let currentDir = path2.resolve(startDir);
|
|
138
|
+
const root = path2.parse(currentDir).root;
|
|
69
139
|
while (currentDir !== root) {
|
|
70
|
-
const pebbleDir =
|
|
140
|
+
const pebbleDir = path2.join(currentDir, PEBBLE_DIR);
|
|
71
141
|
if (fs.existsSync(pebbleDir) && fs.statSync(pebbleDir).isDirectory()) {
|
|
72
|
-
return pebbleDir;
|
|
142
|
+
return resolveWorktreePebbleDir(pebbleDir);
|
|
73
143
|
}
|
|
74
|
-
currentDir =
|
|
144
|
+
currentDir = path2.dirname(currentDir);
|
|
75
145
|
}
|
|
76
|
-
const rootPebble =
|
|
146
|
+
const rootPebble = path2.join(root, PEBBLE_DIR);
|
|
77
147
|
if (fs.existsSync(rootPebble) && fs.statSync(rootPebble).isDirectory()) {
|
|
78
|
-
return rootPebble;
|
|
148
|
+
return resolveWorktreePebbleDir(rootPebble);
|
|
79
149
|
}
|
|
80
150
|
return null;
|
|
81
151
|
}
|
|
@@ -87,23 +157,35 @@ function getPebbleDir() {
|
|
|
87
157
|
return dir;
|
|
88
158
|
}
|
|
89
159
|
function ensurePebbleDir(baseDir = process.cwd()) {
|
|
90
|
-
|
|
160
|
+
let targetDir = baseDir;
|
|
161
|
+
if (process.env.PEBBLE_LOCAL !== "1") {
|
|
162
|
+
const mainRoot = getMainWorktreeRoot();
|
|
163
|
+
if (mainRoot) {
|
|
164
|
+
const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
|
|
165
|
+
if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
|
|
166
|
+
return mainPebble;
|
|
167
|
+
}
|
|
168
|
+
targetDir = mainRoot;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const pebbleDir = path2.join(targetDir, PEBBLE_DIR);
|
|
91
172
|
if (!fs.existsSync(pebbleDir)) {
|
|
92
173
|
fs.mkdirSync(pebbleDir, { recursive: true });
|
|
93
|
-
const folderName =
|
|
174
|
+
const folderName = path2.basename(targetDir);
|
|
94
175
|
const config = {
|
|
95
176
|
prefix: derivePrefix(folderName),
|
|
96
|
-
version: "0.1.0"
|
|
177
|
+
version: "0.1.0",
|
|
178
|
+
useMainTreePebble: true
|
|
97
179
|
};
|
|
98
180
|
setConfig(config, pebbleDir);
|
|
99
|
-
const issuesPath =
|
|
181
|
+
const issuesPath = path2.join(pebbleDir, ISSUES_FILE);
|
|
100
182
|
fs.writeFileSync(issuesPath, "", "utf-8");
|
|
101
183
|
}
|
|
102
184
|
return pebbleDir;
|
|
103
185
|
}
|
|
104
186
|
function getIssuesPath(pebbleDir) {
|
|
105
187
|
const dir = pebbleDir ?? getPebbleDir();
|
|
106
|
-
return
|
|
188
|
+
return path2.join(dir, ISSUES_FILE);
|
|
107
189
|
}
|
|
108
190
|
function appendEvent(event, pebbleDir) {
|
|
109
191
|
const issuesPath = getIssuesPath(pebbleDir);
|
|
@@ -129,7 +211,7 @@ function readEvents(pebbleDir) {
|
|
|
129
211
|
if (!dir) {
|
|
130
212
|
return [];
|
|
131
213
|
}
|
|
132
|
-
const issuesPath =
|
|
214
|
+
const issuesPath = path2.join(dir, ISSUES_FILE);
|
|
133
215
|
if (!fs.existsSync(issuesPath)) {
|
|
134
216
|
return [];
|
|
135
217
|
}
|
|
@@ -145,7 +227,7 @@ function readEvents(pebbleDir) {
|
|
|
145
227
|
}
|
|
146
228
|
function getConfigPath(pebbleDir) {
|
|
147
229
|
const dir = pebbleDir ?? getPebbleDir();
|
|
148
|
-
return
|
|
230
|
+
return path2.join(dir, CONFIG_FILE);
|
|
149
231
|
}
|
|
150
232
|
function getConfig(pebbleDir) {
|
|
151
233
|
const configPath = getConfigPath(pebbleDir);
|
|
@@ -157,7 +239,7 @@ function getConfig(pebbleDir) {
|
|
|
157
239
|
}
|
|
158
240
|
function setConfig(config, pebbleDir) {
|
|
159
241
|
const dir = pebbleDir ?? getPebbleDir();
|
|
160
|
-
const configPath =
|
|
242
|
+
const configPath = path2.join(dir, CONFIG_FILE);
|
|
161
243
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
162
244
|
}
|
|
163
245
|
function getOrCreatePebbleDir() {
|
|
@@ -491,6 +573,21 @@ function getOpenBlockers(issueId) {
|
|
|
491
573
|
const state = computeState(events);
|
|
492
574
|
return issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed");
|
|
493
575
|
}
|
|
576
|
+
function getComputedState() {
|
|
577
|
+
const events = readEvents();
|
|
578
|
+
return computeState(events);
|
|
579
|
+
}
|
|
580
|
+
function getAncestryChain(issueId, state) {
|
|
581
|
+
const chain = [];
|
|
582
|
+
let current = state.get(issueId);
|
|
583
|
+
while (current?.parent) {
|
|
584
|
+
const parent = state.get(current.parent);
|
|
585
|
+
if (!parent) break;
|
|
586
|
+
chain.push({ id: parent.id, title: parent.title });
|
|
587
|
+
current = parent;
|
|
588
|
+
}
|
|
589
|
+
return chain;
|
|
590
|
+
}
|
|
494
591
|
|
|
495
592
|
// src/shared/time.ts
|
|
496
593
|
import { formatDistanceToNow, parseISO } from "date-fns";
|
|
@@ -536,7 +633,10 @@ function formatIssueDetailPretty(issue, ctx) {
|
|
|
536
633
|
lines.push(`Type: ${formatType(issue.type)}`);
|
|
537
634
|
lines.push(`Priority: ${formatPriority(issue.priority)}`);
|
|
538
635
|
lines.push(`Status: ${formatStatus(issue.status)}`);
|
|
539
|
-
if (
|
|
636
|
+
if (ctx.ancestry && ctx.ancestry.length > 0) {
|
|
637
|
+
const chain = [...ctx.ancestry].reverse().map((a) => a.id).join(" > ");
|
|
638
|
+
lines.push(`Ancestry: ${chain}`);
|
|
639
|
+
} else if (issue.parent) {
|
|
540
640
|
lines.push(`Parent: ${issue.parent}`);
|
|
541
641
|
}
|
|
542
642
|
if (issue.description) {
|
|
@@ -602,7 +702,8 @@ function outputIssueDetail(issue, ctx, pretty) {
|
|
|
602
702
|
blocking: ctx.blocking.map((i) => i.id),
|
|
603
703
|
children: ctx.children.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
604
704
|
verifications: ctx.verifications.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
605
|
-
related: ctx.related.map((i) => i.id)
|
|
705
|
+
related: ctx.related.map((i) => i.id),
|
|
706
|
+
...ctx.ancestry && ctx.ancestry.length > 0 && { ancestry: ctx.ancestry }
|
|
606
707
|
};
|
|
607
708
|
console.log(formatJson(output));
|
|
608
709
|
}
|
|
@@ -798,11 +899,12 @@ function formatIssueListVerbose(issues, sectionHeader) {
|
|
|
798
899
|
lines.push("");
|
|
799
900
|
}
|
|
800
901
|
for (const info of issues) {
|
|
801
|
-
const { issue, blocking, children, verifications, blockers,
|
|
902
|
+
const { issue, blocking, children, verifications, blockers, ancestry } = info;
|
|
802
903
|
lines.push(`${issue.id}: ${issue.title}`);
|
|
803
904
|
lines.push(` Type: ${formatType(issue.type)} | Priority: P${issue.priority} | Created: ${formatRelativeTime(issue.createdAt)}`);
|
|
804
|
-
if (
|
|
805
|
-
|
|
905
|
+
if (ancestry.length > 0) {
|
|
906
|
+
const chain = [...ancestry].reverse().map((a) => a.id).join(" > ");
|
|
907
|
+
lines.push(` Ancestry: ${chain}`);
|
|
806
908
|
}
|
|
807
909
|
if (blocking.length > 0) {
|
|
808
910
|
lines.push(` Blocking: ${blocking.join(", ")}`);
|
|
@@ -824,13 +926,13 @@ function outputIssueListVerbose(issues, pretty, sectionHeader, limitInfo) {
|
|
|
824
926
|
console.log(formatLimitMessage(limitInfo));
|
|
825
927
|
}
|
|
826
928
|
} else {
|
|
827
|
-
const output = issues.map(({ issue, blocking, children, verifications, blockers,
|
|
929
|
+
const output = issues.map(({ issue, blocking, children, verifications, blockers, ancestry }) => ({
|
|
828
930
|
...issue,
|
|
829
931
|
blocking,
|
|
830
932
|
childrenCount: issue.type === "epic" ? children : void 0,
|
|
831
933
|
verificationsCount: verifications,
|
|
832
934
|
...blockers && { openBlockers: blockers },
|
|
833
|
-
...
|
|
935
|
+
...ancestry.length > 0 && { ancestry }
|
|
834
936
|
}));
|
|
835
937
|
if (limitInfo?.limited) {
|
|
836
938
|
console.log(formatJson({ issues: output, _meta: limitInfo }));
|
|
@@ -1403,20 +1505,15 @@ function listCommand(program2) {
|
|
|
1403
1505
|
limited: limit > 0 && total > limit
|
|
1404
1506
|
};
|
|
1405
1507
|
if (options.verbose) {
|
|
1508
|
+
const state = getComputedState();
|
|
1406
1509
|
const verboseIssues = issues.map((issue) => {
|
|
1407
|
-
|
|
1510
|
+
return {
|
|
1408
1511
|
issue,
|
|
1409
1512
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1410
1513
|
children: getChildren(issue.id).length,
|
|
1411
|
-
verifications: getVerifications(issue.id).length
|
|
1514
|
+
verifications: getVerifications(issue.id).length,
|
|
1515
|
+
ancestry: getAncestryChain(issue.id, state)
|
|
1412
1516
|
};
|
|
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
1517
|
});
|
|
1421
1518
|
let sectionHeader = "Issues";
|
|
1422
1519
|
if (filters.status) {
|
|
@@ -1453,7 +1550,9 @@ function showCommand(program2) {
|
|
|
1453
1550
|
const children = issue.type === "epic" ? getChildren(resolvedId) : [];
|
|
1454
1551
|
const verifications = getVerifications(resolvedId);
|
|
1455
1552
|
const related = getRelated(resolvedId);
|
|
1456
|
-
|
|
1553
|
+
const state = getComputedState();
|
|
1554
|
+
const ancestry = getAncestryChain(resolvedId, state);
|
|
1555
|
+
outputIssueDetail(issue, { blocking, children, verifications, related, ancestry }, pretty);
|
|
1457
1556
|
} catch (error) {
|
|
1458
1557
|
outputError(error, pretty);
|
|
1459
1558
|
}
|
|
@@ -1486,20 +1585,15 @@ function readyCommand(program2) {
|
|
|
1486
1585
|
limited: limit > 0 && total > limit
|
|
1487
1586
|
};
|
|
1488
1587
|
if (options.verbose) {
|
|
1588
|
+
const state = getComputedState();
|
|
1489
1589
|
const verboseIssues = issues.map((issue) => {
|
|
1490
|
-
|
|
1590
|
+
return {
|
|
1491
1591
|
issue,
|
|
1492
1592
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1493
1593
|
children: getChildren(issue.id).length,
|
|
1494
|
-
verifications: getVerifications(issue.id).length
|
|
1594
|
+
verifications: getVerifications(issue.id).length,
|
|
1595
|
+
ancestry: getAncestryChain(issue.id, state)
|
|
1495
1596
|
};
|
|
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
1597
|
});
|
|
1504
1598
|
outputIssueListVerbose(verboseIssues, pretty, "Ready Issues", limitInfo);
|
|
1505
1599
|
} else {
|
|
@@ -1530,23 +1624,18 @@ function blockedCommand(program2) {
|
|
|
1530
1624
|
limited: limit > 0 && total > limit
|
|
1531
1625
|
};
|
|
1532
1626
|
if (options.verbose) {
|
|
1627
|
+
const state = getComputedState();
|
|
1533
1628
|
const verboseIssues = issues.map((issue) => {
|
|
1534
1629
|
const allBlockers = getBlockers(issue.id);
|
|
1535
1630
|
const openBlockers = allBlockers.filter((b) => b.status !== "closed").map((b) => b.id);
|
|
1536
|
-
|
|
1631
|
+
return {
|
|
1537
1632
|
issue,
|
|
1538
1633
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1539
1634
|
children: getChildren(issue.id).length,
|
|
1540
1635
|
verifications: getVerifications(issue.id).length,
|
|
1541
|
-
blockers: openBlockers
|
|
1636
|
+
blockers: openBlockers,
|
|
1637
|
+
ancestry: getAncestryChain(issue.id, state)
|
|
1542
1638
|
};
|
|
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
1639
|
});
|
|
1551
1640
|
outputIssueListVerbose(verboseIssues, pretty, "Blocked Issues", limitInfo);
|
|
1552
1641
|
} else {
|
|
@@ -2036,7 +2125,7 @@ function formatGraphPretty(issues) {
|
|
|
2036
2125
|
import express from "express";
|
|
2037
2126
|
import cors from "cors";
|
|
2038
2127
|
import { fileURLToPath } from "url";
|
|
2039
|
-
import
|
|
2128
|
+
import path3 from "path";
|
|
2040
2129
|
import fs2 from "fs";
|
|
2041
2130
|
import net from "net";
|
|
2042
2131
|
import open from "open";
|
|
@@ -2131,14 +2220,14 @@ function uiCommand(program2) {
|
|
|
2131
2220
|
console.error("Error: --files option requires at least one path");
|
|
2132
2221
|
process.exit(1);
|
|
2133
2222
|
}
|
|
2134
|
-
issueFiles = issueFiles.map((p) =>
|
|
2223
|
+
issueFiles = issueFiles.map((p) => path3.resolve(process.cwd(), p));
|
|
2135
2224
|
console.log(`Multi-worktree mode: watching ${issueFiles.length} file(s)`);
|
|
2136
2225
|
for (const f of issueFiles) {
|
|
2137
2226
|
console.log(` - ${f}`);
|
|
2138
2227
|
}
|
|
2139
2228
|
} else {
|
|
2140
2229
|
const pebbleDir = getOrCreatePebbleDir();
|
|
2141
|
-
issueFiles = [
|
|
2230
|
+
issueFiles = [path3.join(pebbleDir, "issues.jsonl")];
|
|
2142
2231
|
}
|
|
2143
2232
|
if (!options.files) {
|
|
2144
2233
|
getOrCreatePebbleDir();
|
|
@@ -2161,7 +2250,7 @@ function uiCommand(program2) {
|
|
|
2161
2250
|
res.status(400).json({ error: "path is required" });
|
|
2162
2251
|
return;
|
|
2163
2252
|
}
|
|
2164
|
-
const resolved =
|
|
2253
|
+
const resolved = path3.resolve(process.cwd(), filePath);
|
|
2165
2254
|
if (!fs2.existsSync(resolved)) {
|
|
2166
2255
|
res.status(400).json({ error: `File not found: ${filePath}` });
|
|
2167
2256
|
return;
|
|
@@ -2199,10 +2288,10 @@ function uiCommand(program2) {
|
|
|
2199
2288
|
});
|
|
2200
2289
|
app.get("/api/worktrees", (_req, res) => {
|
|
2201
2290
|
try {
|
|
2202
|
-
const { execSync } = __require("child_process");
|
|
2291
|
+
const { execSync: execSync2 } = __require("child_process");
|
|
2203
2292
|
let worktreeOutput;
|
|
2204
2293
|
try {
|
|
2205
|
-
worktreeOutput =
|
|
2294
|
+
worktreeOutput = execSync2("git worktree list --porcelain", {
|
|
2206
2295
|
encoding: "utf-8",
|
|
2207
2296
|
cwd: process.cwd()
|
|
2208
2297
|
});
|
|
@@ -2224,7 +2313,7 @@ function uiCommand(program2) {
|
|
|
2224
2313
|
}
|
|
2225
2314
|
}
|
|
2226
2315
|
if (worktreePath) {
|
|
2227
|
-
const issuesFile =
|
|
2316
|
+
const issuesFile = path3.join(worktreePath, ".pebble", "issues.jsonl");
|
|
2228
2317
|
const hasIssues = fs2.existsSync(issuesFile);
|
|
2229
2318
|
const isActive = issueFiles.includes(issuesFile);
|
|
2230
2319
|
let issueCount = 0;
|
|
@@ -2318,7 +2407,7 @@ data: ${message}
|
|
|
2318
2407
|
}
|
|
2319
2408
|
targetFile = issueFiles[targetIndex];
|
|
2320
2409
|
}
|
|
2321
|
-
const pebbleDir = targetFile ?
|
|
2410
|
+
const pebbleDir = targetFile ? path3.dirname(targetFile) : getOrCreatePebbleDir();
|
|
2322
2411
|
const config = getConfig(pebbleDir);
|
|
2323
2412
|
const { title, type, priority, description, parent } = req.body;
|
|
2324
2413
|
if (!title || typeof title !== "string") {
|
|
@@ -2509,7 +2598,7 @@ data: ${message}
|
|
|
2509
2598
|
return;
|
|
2510
2599
|
}
|
|
2511
2600
|
issue = localIssue;
|
|
2512
|
-
targetFile =
|
|
2601
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2513
2602
|
}
|
|
2514
2603
|
const { title, type, priority, status, description, parent, relatedTo } = req.body;
|
|
2515
2604
|
const updates = {};
|
|
@@ -2637,7 +2726,7 @@ data: ${message}
|
|
|
2637
2726
|
return;
|
|
2638
2727
|
}
|
|
2639
2728
|
issue = localIssue;
|
|
2640
|
-
targetFile =
|
|
2729
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2641
2730
|
}
|
|
2642
2731
|
if (issue.status === "closed") {
|
|
2643
2732
|
res.status(400).json({ error: "Issue is already closed" });
|
|
@@ -2711,7 +2800,7 @@ data: ${message}
|
|
|
2711
2800
|
}
|
|
2712
2801
|
} else {
|
|
2713
2802
|
const pebbleDir = getOrCreatePebbleDir();
|
|
2714
|
-
appendEventToFile(autoCloseEvent,
|
|
2803
|
+
appendEventToFile(autoCloseEvent, path3.join(pebbleDir, "issues.jsonl"));
|
|
2715
2804
|
}
|
|
2716
2805
|
autoClosed = { id: targetIssue.id, title: targetIssue.title };
|
|
2717
2806
|
}
|
|
@@ -2751,7 +2840,7 @@ data: ${message}
|
|
|
2751
2840
|
return;
|
|
2752
2841
|
}
|
|
2753
2842
|
issue = localIssue;
|
|
2754
|
-
targetFile =
|
|
2843
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2755
2844
|
}
|
|
2756
2845
|
if (issue.status !== "closed") {
|
|
2757
2846
|
res.status(400).json({ error: "Issue is not closed" });
|
|
@@ -2799,7 +2888,7 @@ data: ${message}
|
|
|
2799
2888
|
return;
|
|
2800
2889
|
}
|
|
2801
2890
|
issue = localIssue;
|
|
2802
|
-
targetFile =
|
|
2891
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2803
2892
|
}
|
|
2804
2893
|
const { text, author } = req.body;
|
|
2805
2894
|
if (!text || typeof text !== "string" || text.trim() === "") {
|
|
@@ -2851,7 +2940,7 @@ data: ${message}
|
|
|
2851
2940
|
return;
|
|
2852
2941
|
}
|
|
2853
2942
|
issue = localIssue;
|
|
2854
|
-
targetFile =
|
|
2943
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2855
2944
|
}
|
|
2856
2945
|
const { blockerId } = req.body;
|
|
2857
2946
|
if (!blockerId) {
|
|
@@ -2925,7 +3014,7 @@ data: ${message}
|
|
|
2925
3014
|
return;
|
|
2926
3015
|
}
|
|
2927
3016
|
issue = localIssue;
|
|
2928
|
-
targetFile =
|
|
3017
|
+
targetFile = path3.join(pebbleDir, "issues.jsonl");
|
|
2929
3018
|
}
|
|
2930
3019
|
let resolvedBlockerId;
|
|
2931
3020
|
if (isMultiWorktree()) {
|
|
@@ -2963,12 +3052,12 @@ data: ${message}
|
|
|
2963
3052
|
res.status(500).json({ error: error.message });
|
|
2964
3053
|
}
|
|
2965
3054
|
});
|
|
2966
|
-
const
|
|
2967
|
-
const
|
|
2968
|
-
const uiPath =
|
|
3055
|
+
const __filename3 = fileURLToPath(import.meta.url);
|
|
3056
|
+
const __dirname3 = path3.dirname(__filename3);
|
|
3057
|
+
const uiPath = path3.resolve(__dirname3, "../ui");
|
|
2969
3058
|
app.use(express.static(uiPath));
|
|
2970
3059
|
app.get("*", (_req, res) => {
|
|
2971
|
-
res.sendFile(
|
|
3060
|
+
res.sendFile(path3.join(uiPath, "index.html"));
|
|
2972
3061
|
});
|
|
2973
3062
|
const requestedPort = parseInt(options.port, 10);
|
|
2974
3063
|
const actualPort = await findAvailablePort(requestedPort);
|
|
@@ -2992,7 +3081,7 @@ data: ${message}
|
|
|
2992
3081
|
// src/cli/commands/import.ts
|
|
2993
3082
|
import * as fs3 from "fs";
|
|
2994
3083
|
import * as readline from "readline";
|
|
2995
|
-
import * as
|
|
3084
|
+
import * as path4 from "path";
|
|
2996
3085
|
function importCommand(program2) {
|
|
2997
3086
|
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
3087
|
const pretty = program2.opts().pretty ?? false;
|
|
@@ -3005,7 +3094,7 @@ function importCommand(program2) {
|
|
|
3005
3094
|
console.log(pretty ? "No issues found in file." : formatJson({ imported: 0 }));
|
|
3006
3095
|
return;
|
|
3007
3096
|
}
|
|
3008
|
-
const prefix = options.prefix ?? derivePrefix(
|
|
3097
|
+
const prefix = options.prefix ?? derivePrefix(path4.basename(process.cwd()));
|
|
3009
3098
|
const { events, idMap, stats } = convertToPebbleEvents(beadsIssues, prefix);
|
|
3010
3099
|
if (options.dryRun) {
|
|
3011
3100
|
if (pretty) {
|
|
@@ -3183,7 +3272,7 @@ function generateSuffix() {
|
|
|
3183
3272
|
|
|
3184
3273
|
// src/cli/commands/merge.ts
|
|
3185
3274
|
import * as fs4 from "fs";
|
|
3186
|
-
import * as
|
|
3275
|
+
import * as path5 from "path";
|
|
3187
3276
|
function mergeEvents(filePaths) {
|
|
3188
3277
|
const seen = /* @__PURE__ */ new Map();
|
|
3189
3278
|
for (const filePath of filePaths) {
|
|
@@ -3226,7 +3315,7 @@ function mergeCommand(program2) {
|
|
|
3226
3315
|
const pretty = program2.opts().pretty ?? false;
|
|
3227
3316
|
const filePaths = [];
|
|
3228
3317
|
for (const file of files) {
|
|
3229
|
-
const resolved =
|
|
3318
|
+
const resolved = path5.resolve(process.cwd(), file);
|
|
3230
3319
|
if (!fs4.existsSync(resolved)) {
|
|
3231
3320
|
console.error(`Error: File not found: ${file}`);
|
|
3232
3321
|
process.exit(1);
|
|
@@ -3628,7 +3717,7 @@ function verificationsCommand(program2) {
|
|
|
3628
3717
|
}
|
|
3629
3718
|
|
|
3630
3719
|
// src/cli/commands/init.ts
|
|
3631
|
-
import * as
|
|
3720
|
+
import * as path6 from "path";
|
|
3632
3721
|
function initCommand(program2) {
|
|
3633
3722
|
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
3723
|
const existing = discoverPebbleDir();
|
|
@@ -3644,17 +3733,26 @@ function initCommand(program2) {
|
|
|
3644
3733
|
console.log(JSON.stringify({
|
|
3645
3734
|
initialized: true,
|
|
3646
3735
|
path: pebbleDir,
|
|
3647
|
-
configPath:
|
|
3648
|
-
issuesPath:
|
|
3736
|
+
configPath: path6.join(pebbleDir, "config.json"),
|
|
3737
|
+
issuesPath: path6.join(pebbleDir, "issues.jsonl")
|
|
3649
3738
|
}));
|
|
3650
3739
|
});
|
|
3651
3740
|
}
|
|
3652
3741
|
|
|
3653
3742
|
// src/cli/index.ts
|
|
3743
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3744
|
+
var __dirname2 = dirname2(__filename2);
|
|
3745
|
+
var packageJson = JSON.parse(readFileSync2(join3(__dirname2, "../../package.json"), "utf-8"));
|
|
3654
3746
|
var program = new Command();
|
|
3655
|
-
program.name("pebble").description("A lightweight JSONL-based issue tracker").version(
|
|
3747
|
+
program.name("pebble").description("A lightweight JSONL-based issue tracker").version(packageJson.version);
|
|
3656
3748
|
program.option("-P, --pretty", "Human-readable output (default: JSON)");
|
|
3657
3749
|
program.option("--json", "JSON output (this is the default, flag not needed)");
|
|
3750
|
+
program.option("--local", "Use local .pebble directory even in a git worktree");
|
|
3751
|
+
program.hook("preAction", () => {
|
|
3752
|
+
if (program.opts().local) {
|
|
3753
|
+
process.env.PEBBLE_LOCAL = "1";
|
|
3754
|
+
}
|
|
3755
|
+
});
|
|
3658
3756
|
createCommand(program);
|
|
3659
3757
|
updateCommand(program);
|
|
3660
3758
|
closeCommand(program);
|