@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
|
|
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 =
|
|
68
|
-
const root =
|
|
122
|
+
let currentDir = path2.resolve(startDir);
|
|
123
|
+
const root = path2.parse(currentDir).root;
|
|
69
124
|
while (currentDir !== root) {
|
|
70
|
-
const pebbleDir =
|
|
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 =
|
|
129
|
+
currentDir = path2.dirname(currentDir);
|
|
75
130
|
}
|
|
76
|
-
const rootPebble =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (
|
|
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,
|
|
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 (
|
|
805
|
-
|
|
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,
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2968
|
-
const uiPath =
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
3648
|
-
issuesPath:
|
|
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);
|