@moberg_hr/work-tree 1.5.0 → 1.5.1
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/README.md +158 -52
- package/dist/bin.js +578 -500
- package/dist/bin.js.map +1 -1
- package/dist/wd-bin.js +572 -494
- package/dist/wd-bin.js.map +1 -1
- package/dist/web/assets/index-8WP9kHU6.css +1 -0
- package/dist/web/assets/index-C5ddmssO.js +106 -0
- package/dist/web/assets/index-C5ddmssO.js.map +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/index-Quf37ngR.css +0 -1
- package/dist/web/assets/index-vD0svqIi.js +0 -106
- package/dist/web/assets/index-vD0svqIi.js.map +0 -1
package/dist/wd-bin.js
CHANGED
|
@@ -307,10 +307,15 @@ function fetchRemote(cwd) {
|
|
|
307
307
|
}
|
|
308
308
|
async function fetchRemoteAsync(cwd) {
|
|
309
309
|
await new Promise((resolve, reject) => {
|
|
310
|
-
execFile(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
310
|
+
execFile(
|
|
311
|
+
"git",
|
|
312
|
+
["fetch", "--quiet"],
|
|
313
|
+
{ cwd, timeout: 3e4, windowsHide: true },
|
|
314
|
+
(err) => {
|
|
315
|
+
if (err) reject(err);
|
|
316
|
+
else resolve();
|
|
317
|
+
}
|
|
318
|
+
);
|
|
314
319
|
});
|
|
315
320
|
if (!getDefaultBranch(cwd)) {
|
|
316
321
|
git(["remote", "set-head", "origin", "--auto"], cwd);
|
|
@@ -409,7 +414,8 @@ function generateGroupClaudeMd(groupName, repoAliases, config) {
|
|
|
409
414
|
const result = spawn2.sync("claude", ["-p"], {
|
|
410
415
|
input: prompt,
|
|
411
416
|
encoding: "utf-8",
|
|
412
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
417
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
418
|
+
windowsHide: true
|
|
413
419
|
});
|
|
414
420
|
let content;
|
|
415
421
|
if (result.status !== 0 || !result.stdout?.trim()) {
|
|
@@ -871,7 +877,11 @@ function getWindowsDocumentsFolder() {
|
|
|
871
877
|
const result = spawn4.sync(
|
|
872
878
|
exe,
|
|
873
879
|
["-NoProfile", "-Command", "[Environment]::GetFolderPath('MyDocuments')"],
|
|
874
|
-
{
|
|
880
|
+
{
|
|
881
|
+
encoding: "utf-8",
|
|
882
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
883
|
+
windowsHide: true
|
|
884
|
+
}
|
|
875
885
|
);
|
|
876
886
|
if (result.status === 0 && result.stdout) {
|
|
877
887
|
const dir = result.stdout.toString().trim();
|
|
@@ -884,7 +894,8 @@ function executableExists(name) {
|
|
|
884
894
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
885
895
|
const result = spawn4.sync(cmd, [name], {
|
|
886
896
|
encoding: "utf-8",
|
|
887
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
897
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
898
|
+
windowsHide: true
|
|
888
899
|
});
|
|
889
900
|
return result.status === 0;
|
|
890
901
|
}
|
|
@@ -2793,10 +2804,8 @@ function collectPrunable(config, options = {}) {
|
|
|
2793
2804
|
for (const [alias, repoPath] of Object.entries(config.repos)) {
|
|
2794
2805
|
if (!fs19.existsSync(repoPath)) continue;
|
|
2795
2806
|
if (skipAliases.has(alias)) continue;
|
|
2796
|
-
const worktrees = parseWorktreeList(repoPath);
|
|
2797
|
-
const normalizedRepoPath = path16.resolve(repoPath);
|
|
2807
|
+
const worktrees = parseWorktreeList(repoPath).slice(1);
|
|
2798
2808
|
for (const wt of worktrees) {
|
|
2799
|
-
if (path16.resolve(wt.path) === normalizedRepoPath) continue;
|
|
2800
2809
|
if (!wt.branch) continue;
|
|
2801
2810
|
if (groupCoveredKeys.has(`${alias}:${wt.branch}`)) continue;
|
|
2802
2811
|
const { merged, into, confidence } = isBranchMerged(wt.branch, repoPath);
|
|
@@ -3126,10 +3135,15 @@ import spawn7 from "cross-spawn";
|
|
|
3126
3135
|
import { execFile as execFile2 } from "child_process";
|
|
3127
3136
|
function execAsync(cmd, args, cwd, timeout) {
|
|
3128
3137
|
return new Promise((resolve, reject) => {
|
|
3129
|
-
execFile2(
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3138
|
+
execFile2(
|
|
3139
|
+
cmd,
|
|
3140
|
+
args,
|
|
3141
|
+
{ cwd, encoding: "utf-8", timeout, windowsHide: true },
|
|
3142
|
+
(err, stdout) => {
|
|
3143
|
+
if (err) reject(err);
|
|
3144
|
+
else resolve(stdout ?? "");
|
|
3145
|
+
}
|
|
3146
|
+
);
|
|
3133
3147
|
});
|
|
3134
3148
|
}
|
|
3135
3149
|
function parsePrJson(stdout, repoAlias, currentUser) {
|
|
@@ -3245,10 +3259,15 @@ async function isGhAvailable() {
|
|
|
3245
3259
|
import { execFile as execFile3 } from "child_process";
|
|
3246
3260
|
function execAsync2(cmd, args, timeout) {
|
|
3247
3261
|
return new Promise((resolve, reject) => {
|
|
3248
|
-
execFile3(
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3262
|
+
execFile3(
|
|
3263
|
+
cmd,
|
|
3264
|
+
args,
|
|
3265
|
+
{ encoding: "utf-8", timeout, windowsHide: true },
|
|
3266
|
+
(err, stdout) => {
|
|
3267
|
+
if (err) reject(err);
|
|
3268
|
+
else resolve(stdout ?? "");
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
3252
3271
|
});
|
|
3253
3272
|
}
|
|
3254
3273
|
async function isAcliAvailable() {
|
|
@@ -4663,7 +4682,7 @@ function generateSlug(summary) {
|
|
|
4663
4682
|
const child = execFile4(
|
|
4664
4683
|
"claude",
|
|
4665
4684
|
["-p", "--model", "haiku"],
|
|
4666
|
-
{ encoding: "utf-8", timeout: 1e4 },
|
|
4685
|
+
{ encoding: "utf-8", timeout: 1e4, windowsHide: true },
|
|
4667
4686
|
(err, stdout) => {
|
|
4668
4687
|
const result = stdout?.trim().toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/^-|-$/g, "");
|
|
4669
4688
|
resolve(result || fallback);
|
|
@@ -6316,18 +6335,16 @@ var hydrateCommand = {
|
|
|
6316
6335
|
};
|
|
6317
6336
|
|
|
6318
6337
|
// src/commands/diff.ts
|
|
6319
|
-
import
|
|
6338
|
+
import fs35 from "fs";
|
|
6320
6339
|
import os9 from "os";
|
|
6321
|
-
import
|
|
6340
|
+
import path32 from "path";
|
|
6322
6341
|
import { spawn as childSpawn } from "child_process";
|
|
6323
6342
|
import chalk21 from "chalk";
|
|
6324
6343
|
|
|
6325
6344
|
// src/core/diff-pipeline.ts
|
|
6326
|
-
import
|
|
6327
|
-
import
|
|
6328
|
-
import
|
|
6329
|
-
import crypto from "crypto";
|
|
6330
|
-
import spawn8 from "cross-spawn";
|
|
6345
|
+
import fs28 from "fs";
|
|
6346
|
+
import path25 from "path";
|
|
6347
|
+
import spawn9 from "cross-spawn";
|
|
6331
6348
|
|
|
6332
6349
|
// src/core/diff-parse.ts
|
|
6333
6350
|
var HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/;
|
|
@@ -6619,19 +6636,65 @@ function coverageLookup(root, relPaths) {
|
|
|
6619
6636
|
return { byPath: out, lcovMtimeMs: read.mtimeMs };
|
|
6620
6637
|
}
|
|
6621
6638
|
|
|
6639
|
+
// src/core/git-tree-snapshot.ts
|
|
6640
|
+
import fs27 from "fs";
|
|
6641
|
+
import os7 from "os";
|
|
6642
|
+
import path24 from "path";
|
|
6643
|
+
import crypto from "crypto";
|
|
6644
|
+
import spawn8 from "cross-spawn";
|
|
6645
|
+
function writeTempTree(repoRoot, opts = {}) {
|
|
6646
|
+
const includeWorkingTree = opts.includeWorkingTree ?? true;
|
|
6647
|
+
const tmpIndex = path24.join(
|
|
6648
|
+
os7.tmpdir(),
|
|
6649
|
+
`wd-tree-${process.pid}-${crypto.randomBytes(6).toString("hex")}.idx`
|
|
6650
|
+
);
|
|
6651
|
+
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
6652
|
+
const run2 = (args) => spawn8.sync("git", args, {
|
|
6653
|
+
cwd: repoRoot,
|
|
6654
|
+
encoding: "utf-8",
|
|
6655
|
+
env,
|
|
6656
|
+
windowsHide: true,
|
|
6657
|
+
maxBuffer: 64 * 1024 * 1024
|
|
6658
|
+
});
|
|
6659
|
+
try {
|
|
6660
|
+
const headSha = (spawn8.sync("git", ["rev-parse", "--verify", "HEAD"], {
|
|
6661
|
+
cwd: repoRoot,
|
|
6662
|
+
encoding: "utf-8",
|
|
6663
|
+
windowsHide: true
|
|
6664
|
+
}).stdout ?? "").trim() || null;
|
|
6665
|
+
if (headSha) {
|
|
6666
|
+
const r = run2(["read-tree", "HEAD"]);
|
|
6667
|
+
if (r.status !== 0) return null;
|
|
6668
|
+
}
|
|
6669
|
+
if (includeWorkingTree) {
|
|
6670
|
+
const add = run2(["add", "-A"]);
|
|
6671
|
+
if (add.status !== 0) return null;
|
|
6672
|
+
}
|
|
6673
|
+
const wt = run2(["write-tree"]);
|
|
6674
|
+
if (wt.status !== 0 || !wt.stdout) return null;
|
|
6675
|
+
return { treeSha: wt.stdout.trim(), headSha };
|
|
6676
|
+
} finally {
|
|
6677
|
+
try {
|
|
6678
|
+
if (fs27.existsSync(tmpIndex)) fs27.unlinkSync(tmpIndex);
|
|
6679
|
+
} catch {
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6622
6684
|
// src/core/diff-pipeline.ts
|
|
6685
|
+
var RENAME_DETECT = "-M";
|
|
6623
6686
|
var MARKDOWN_EXT_RE = /\.(md|markdown|mdx)$/i;
|
|
6624
6687
|
var MARKDOWN_SIZE_CAP = 256 * 1024;
|
|
6625
6688
|
function isMarkdownPath(p) {
|
|
6626
6689
|
return p !== "/dev/null" && MARKDOWN_EXT_RE.test(p);
|
|
6627
6690
|
}
|
|
6628
6691
|
function isInsideRoot(root, rel) {
|
|
6629
|
-
const resolvedRoot =
|
|
6630
|
-
const resolvedTarget =
|
|
6631
|
-
const r =
|
|
6692
|
+
const resolvedRoot = path25.resolve(root);
|
|
6693
|
+
const resolvedTarget = path25.resolve(resolvedRoot, rel);
|
|
6694
|
+
const r = path25.relative(resolvedRoot, resolvedTarget);
|
|
6632
6695
|
if (r === "") return true;
|
|
6633
6696
|
if (r.startsWith("..")) return false;
|
|
6634
|
-
if (
|
|
6697
|
+
if (path25.isAbsolute(r)) return false;
|
|
6635
6698
|
return true;
|
|
6636
6699
|
}
|
|
6637
6700
|
function readMarkdownContent(root, file, fromRef, toRef) {
|
|
@@ -6640,7 +6703,7 @@ function readMarkdownContent(root, file, fromRef, toRef) {
|
|
|
6640
6703
|
return void 0;
|
|
6641
6704
|
}
|
|
6642
6705
|
const showAt = (ref, p) => {
|
|
6643
|
-
const r =
|
|
6706
|
+
const r = spawn9.sync("git", ["show", `${ref}:${p}`], {
|
|
6644
6707
|
cwd: root,
|
|
6645
6708
|
encoding: "utf-8",
|
|
6646
6709
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -6656,12 +6719,12 @@ function readMarkdownContent(root, file, fromRef, toRef) {
|
|
|
6656
6719
|
if (file.status !== "deleted" && isMarkdownPath(file.newPath) && isInsideRoot(root, file.newPath)) {
|
|
6657
6720
|
if (toRef === "working") {
|
|
6658
6721
|
try {
|
|
6659
|
-
const absPath =
|
|
6660
|
-
const realRoot2 =
|
|
6661
|
-
const realPath =
|
|
6662
|
-
const sep =
|
|
6722
|
+
const absPath = path25.join(root, file.newPath);
|
|
6723
|
+
const realRoot2 = fs28.realpathSync(path25.resolve(root));
|
|
6724
|
+
const realPath = fs28.realpathSync(absPath);
|
|
6725
|
+
const sep = path25.sep;
|
|
6663
6726
|
if (realPath === realRoot2 || realPath.startsWith(realRoot2 + sep)) {
|
|
6664
|
-
result.after =
|
|
6727
|
+
result.after = fs28.readFileSync(absPath, "utf-8");
|
|
6665
6728
|
}
|
|
6666
6729
|
} catch {
|
|
6667
6730
|
}
|
|
@@ -6679,39 +6742,7 @@ function readMarkdownContent(root, file, fromRef, toRef) {
|
|
|
6679
6742
|
return result;
|
|
6680
6743
|
}
|
|
6681
6744
|
function workingTreeTreeSha(root) {
|
|
6682
|
-
|
|
6683
|
-
os7.tmpdir(),
|
|
6684
|
-
`wd-diff-${process.pid}-${crypto.randomBytes(6).toString("hex")}.idx`
|
|
6685
|
-
);
|
|
6686
|
-
const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
|
|
6687
|
-
const run2 = (args) => spawn8.sync("git", args, {
|
|
6688
|
-
cwd: root,
|
|
6689
|
-
encoding: "utf-8",
|
|
6690
|
-
env,
|
|
6691
|
-
windowsHide: true,
|
|
6692
|
-
maxBuffer: 64 * 1024 * 1024
|
|
6693
|
-
});
|
|
6694
|
-
try {
|
|
6695
|
-
const headExists = (spawn8.sync("git", ["rev-parse", "--verify", "HEAD"], {
|
|
6696
|
-
cwd: root,
|
|
6697
|
-
encoding: "utf-8",
|
|
6698
|
-
windowsHide: true
|
|
6699
|
-
}).stdout ?? "").trim().length > 0;
|
|
6700
|
-
if (headExists) {
|
|
6701
|
-
const r = run2(["read-tree", "HEAD"]);
|
|
6702
|
-
if (r.status !== 0) return null;
|
|
6703
|
-
}
|
|
6704
|
-
const add = run2(["add", "-A"]);
|
|
6705
|
-
if (add.status !== 0) return null;
|
|
6706
|
-
const wt = run2(["write-tree"]);
|
|
6707
|
-
if (wt.status !== 0 || !wt.stdout) return null;
|
|
6708
|
-
return wt.stdout.trim();
|
|
6709
|
-
} finally {
|
|
6710
|
-
try {
|
|
6711
|
-
if (fs27.existsSync(tmpIndex)) fs27.unlinkSync(tmpIndex);
|
|
6712
|
-
} catch {
|
|
6713
|
-
}
|
|
6714
|
-
}
|
|
6745
|
+
return writeTempTree(root, { includeWorkingTree: true })?.treeSha ?? null;
|
|
6715
6746
|
}
|
|
6716
6747
|
function isBinaryContent(buffer) {
|
|
6717
6748
|
const len = Math.min(buffer.length, 8192);
|
|
@@ -6721,10 +6752,10 @@ function isBinaryContent(buffer) {
|
|
|
6721
6752
|
return false;
|
|
6722
6753
|
}
|
|
6723
6754
|
function synthesizeUntrackedDiff(root, relPath) {
|
|
6724
|
-
const absPath =
|
|
6755
|
+
const absPath = path25.join(root, relPath);
|
|
6725
6756
|
let buffer;
|
|
6726
6757
|
try {
|
|
6727
|
-
buffer =
|
|
6758
|
+
buffer = fs28.readFileSync(absPath);
|
|
6728
6759
|
} catch {
|
|
6729
6760
|
return "";
|
|
6730
6761
|
}
|
|
@@ -6760,11 +6791,13 @@ new file mode 100644
|
|
|
6760
6791
|
}
|
|
6761
6792
|
function computeDiff(opts) {
|
|
6762
6793
|
const { root, diffArg } = opts;
|
|
6763
|
-
const trackedResult =
|
|
6794
|
+
const trackedResult = spawn9.sync(
|
|
6764
6795
|
"git",
|
|
6765
6796
|
// -w (ignore-all-space) hides pure whitespace changes so a reformat
|
|
6766
|
-
// of indentation doesn't drown out the real changes.
|
|
6767
|
-
|
|
6797
|
+
// of indentation doesn't drown out the real changes. RENAME_DETECT turns
|
|
6798
|
+
// on rename detection so a `git mv` (+ edits) shows as one renamed entry
|
|
6799
|
+
// with its inline diff instead of a separate delete + add pair.
|
|
6800
|
+
["diff", "--no-color", "--no-ext-diff", "-w", RENAME_DETECT, diffArg],
|
|
6768
6801
|
{
|
|
6769
6802
|
cwd: root,
|
|
6770
6803
|
encoding: "utf-8",
|
|
@@ -6810,7 +6843,7 @@ function attachCoverage(root, files) {
|
|
|
6810
6843
|
f.coverageMtimeMs = lcovMtimeMs;
|
|
6811
6844
|
let srcMtimeMs = null;
|
|
6812
6845
|
try {
|
|
6813
|
-
srcMtimeMs =
|
|
6846
|
+
srcMtimeMs = fs28.statSync(path25.join(root, f.path)).mtimeMs;
|
|
6814
6847
|
} catch {
|
|
6815
6848
|
srcMtimeMs = null;
|
|
6816
6849
|
}
|
|
@@ -6828,9 +6861,12 @@ function computeRangeDiff(opts) {
|
|
|
6828
6861
|
}
|
|
6829
6862
|
const wtTreeSha = workingTreeTreeSha(root);
|
|
6830
6863
|
if (!wtTreeSha) return [];
|
|
6831
|
-
const result2 =
|
|
6864
|
+
const result2 = spawn9.sync(
|
|
6832
6865
|
"git",
|
|
6833
|
-
|
|
6866
|
+
// RENAME_DETECT: detect renames between the checkpoint tree and the
|
|
6867
|
+
// working tree so a rename (with or without edits) renders as one
|
|
6868
|
+
// entry, matching the HEAD-vs-working path above.
|
|
6869
|
+
["diff-tree", "-r", "-p", RENAME_DETECT, "--no-color", "--no-ext-diff", fromRef, wtTreeSha],
|
|
6834
6870
|
{
|
|
6835
6871
|
cwd: root,
|
|
6836
6872
|
encoding: "utf-8",
|
|
@@ -6849,9 +6885,11 @@ function computeRangeDiff(opts) {
|
|
|
6849
6885
|
}
|
|
6850
6886
|
return parsed;
|
|
6851
6887
|
}
|
|
6852
|
-
const result =
|
|
6888
|
+
const result = spawn9.sync(
|
|
6853
6889
|
"git",
|
|
6854
|
-
|
|
6890
|
+
// RENAME_DETECT: rename detection between two checkpoint commits (no -w
|
|
6891
|
+
// here — see the note above on why range diffs keep whitespace changes).
|
|
6892
|
+
["diff", "--no-color", "--no-ext-diff", RENAME_DETECT, fromRef, toRef],
|
|
6855
6893
|
{
|
|
6856
6894
|
cwd: root,
|
|
6857
6895
|
encoding: "utf-8",
|
|
@@ -6872,69 +6910,82 @@ function computeRangeDiff(opts) {
|
|
|
6872
6910
|
}
|
|
6873
6911
|
|
|
6874
6912
|
// src/core/repo-spec.ts
|
|
6875
|
-
import
|
|
6876
|
-
import
|
|
6913
|
+
import fs29 from "fs";
|
|
6914
|
+
import path26 from "path";
|
|
6877
6915
|
import os8 from "os";
|
|
6878
6916
|
import crypto2 from "crypto";
|
|
6879
|
-
function
|
|
6917
|
+
function scopeHashFor(keyPaths) {
|
|
6880
6918
|
const key = keyPaths.slice().sort().join("|");
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6919
|
+
return crypto2.createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
6920
|
+
}
|
|
6921
|
+
function stableDiffPath(keyPaths) {
|
|
6922
|
+
const dir = path26.join(os8.homedir(), ".work", "diffs");
|
|
6923
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
6924
|
+
return path26.join(dir, scopeHashFor(keyPaths));
|
|
6885
6925
|
}
|
|
6886
6926
|
|
|
6887
6927
|
// src/core/diff-scope.ts
|
|
6888
|
-
import
|
|
6928
|
+
import fs30 from "fs";
|
|
6929
|
+
import path27 from "path";
|
|
6889
6930
|
import chalk18 from "chalk";
|
|
6890
6931
|
function normPath(p) {
|
|
6891
|
-
return
|
|
6932
|
+
return path27.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
6933
|
+
}
|
|
6934
|
+
function realNorm(p) {
|
|
6935
|
+
try {
|
|
6936
|
+
return normPath(fs30.realpathSync(p));
|
|
6937
|
+
} catch {
|
|
6938
|
+
return normPath(p);
|
|
6939
|
+
}
|
|
6892
6940
|
}
|
|
6893
6941
|
function resolveScope(cwd) {
|
|
6894
6942
|
const normCwd = normPath(cwd);
|
|
6895
6943
|
const sessions = loadHistory();
|
|
6944
|
+
const top = git(["rev-parse", "--show-toplevel"], cwd);
|
|
6945
|
+
const toplevel = top.exitCode === 0 && top.stdout ? top.stdout : null;
|
|
6946
|
+
const realTop = toplevel ? realNorm(toplevel) : null;
|
|
6896
6947
|
for (const s of sessions) {
|
|
6897
6948
|
for (const p of s.paths) {
|
|
6898
6949
|
const np = normPath(p);
|
|
6899
6950
|
if (normCwd === np || normCwd.startsWith(np + "/")) {
|
|
6951
|
+
if (realTop && realNorm(p) !== realTop) continue;
|
|
6900
6952
|
if (s.isGroup) {
|
|
6901
6953
|
return {
|
|
6902
6954
|
isGroup: true,
|
|
6903
6955
|
session: s,
|
|
6904
|
-
repos: s.paths.map((rp) => ({ name:
|
|
6905
|
-
activeRepoName:
|
|
6956
|
+
repos: s.paths.map((rp) => ({ name: path27.basename(rp), root: rp })),
|
|
6957
|
+
activeRepoName: path27.basename(p)
|
|
6906
6958
|
};
|
|
6907
6959
|
}
|
|
6908
6960
|
return {
|
|
6909
6961
|
isGroup: false,
|
|
6910
6962
|
session: s,
|
|
6911
|
-
repos: [{ name:
|
|
6912
|
-
activeRepoName:
|
|
6963
|
+
repos: [{ name: path27.basename(p), root: p }],
|
|
6964
|
+
activeRepoName: path27.basename(p)
|
|
6913
6965
|
};
|
|
6914
6966
|
}
|
|
6915
6967
|
}
|
|
6916
6968
|
}
|
|
6917
6969
|
for (const s of sessions) {
|
|
6918
6970
|
if (!s.isGroup || s.paths.length === 0) continue;
|
|
6919
|
-
const parents = s.paths.map((p) => normPath(
|
|
6971
|
+
const parents = s.paths.map((p) => normPath(path27.dirname(p)));
|
|
6920
6972
|
const groupRoot = parents[0];
|
|
6921
6973
|
if (!parents.every((par) => par === groupRoot)) continue;
|
|
6922
6974
|
if (normCwd === groupRoot || normCwd.startsWith(groupRoot + "/")) {
|
|
6923
6975
|
return {
|
|
6924
6976
|
isGroup: true,
|
|
6925
6977
|
session: s,
|
|
6926
|
-
repos: s.paths.map((rp) => ({ name:
|
|
6978
|
+
repos: s.paths.map((rp) => ({ name: path27.basename(rp), root: rp })),
|
|
6927
6979
|
activeRepoName: null
|
|
6928
6980
|
};
|
|
6929
6981
|
}
|
|
6930
6982
|
}
|
|
6931
|
-
|
|
6932
|
-
if (toplevel.exitCode !== 0 || !toplevel.stdout) return null;
|
|
6983
|
+
if (!toplevel) return null;
|
|
6933
6984
|
return {
|
|
6934
6985
|
isGroup: false,
|
|
6935
6986
|
session: null,
|
|
6936
|
-
repos: [{ name:
|
|
6937
|
-
activeRepoName:
|
|
6987
|
+
repos: [{ name: path27.basename(toplevel), root: toplevel }],
|
|
6988
|
+
activeRepoName: path27.basename(toplevel)
|
|
6938
6989
|
};
|
|
6939
6990
|
}
|
|
6940
6991
|
function findAnyParentBranch(cwd) {
|
|
@@ -7002,7 +7053,7 @@ function resolveRepoDiff(root, base, sessionBaseBranch) {
|
|
|
7002
7053
|
if (base === "uncommitted") {
|
|
7003
7054
|
return { resolvedBase: "HEAD", diffArg: "HEAD" };
|
|
7004
7055
|
}
|
|
7005
|
-
const parent = sessionBaseBranch ?? findAnyParentBranch(root);
|
|
7056
|
+
const parent = sessionBaseBranch ?? detectParentBranch(root) ?? findAnyParentBranch(root);
|
|
7006
7057
|
if (!parent) return { resolvedBase: "HEAD", diffArg: "HEAD" };
|
|
7007
7058
|
let diffArg = "HEAD";
|
|
7008
7059
|
const mb = git(["merge-base", parent, "HEAD"], root);
|
|
@@ -7097,36 +7148,86 @@ import { serve } from "@hono/node-server";
|
|
|
7097
7148
|
import { streamSSE } from "hono/streaming";
|
|
7098
7149
|
|
|
7099
7150
|
// src/core/fs-watcher.ts
|
|
7100
|
-
import
|
|
7151
|
+
import fs31 from "fs";
|
|
7152
|
+
import path28 from "path";
|
|
7101
7153
|
import chalk19 from "chalk";
|
|
7102
7154
|
import chokidar from "chokidar";
|
|
7155
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
7156
|
+
".git",
|
|
7157
|
+
"node_modules",
|
|
7158
|
+
"bin",
|
|
7159
|
+
// .NET build output
|
|
7160
|
+
"obj",
|
|
7161
|
+
// .NET build output
|
|
7162
|
+
"dist",
|
|
7163
|
+
"build",
|
|
7164
|
+
"out",
|
|
7165
|
+
"target",
|
|
7166
|
+
// Rust / JVM
|
|
7167
|
+
".next",
|
|
7168
|
+
".nuxt",
|
|
7169
|
+
".svelte-kit",
|
|
7170
|
+
".turbo",
|
|
7171
|
+
".gradle",
|
|
7172
|
+
"coverage",
|
|
7173
|
+
".vs",
|
|
7174
|
+
".idea"
|
|
7175
|
+
]);
|
|
7176
|
+
function isIgnoredWatchPath(roots, filePath) {
|
|
7177
|
+
for (const root of roots) {
|
|
7178
|
+
const rel = path28.relative(root, filePath).replace(/\\/g, "/");
|
|
7179
|
+
if (rel === "" || rel.startsWith("../")) continue;
|
|
7180
|
+
if (rel.split("/").some((seg) => IGNORED_DIRS.has(seg))) return true;
|
|
7181
|
+
}
|
|
7182
|
+
return false;
|
|
7183
|
+
}
|
|
7184
|
+
var SUPPORTS_RECURSIVE_WATCH = process.platform === "darwin" || process.platform === "win32";
|
|
7185
|
+
function logWatchError(err) {
|
|
7186
|
+
process.stderr.write(
|
|
7187
|
+
chalk19.yellow("[watcher] fs error: ") + err.message + "\n"
|
|
7188
|
+
);
|
|
7189
|
+
}
|
|
7103
7190
|
function createFsWatcher(opts) {
|
|
7104
7191
|
const debounceMs = opts.debounceMs ?? 150;
|
|
7105
7192
|
let debounceTimer = null;
|
|
7106
|
-
const
|
|
7107
|
-
ignored: (filePath) => {
|
|
7108
|
-
for (const root of opts.roots) {
|
|
7109
|
-
const rel = path27.relative(root, filePath).replace(/\\/g, "/");
|
|
7110
|
-
if (rel === ".git" || rel.startsWith(".git/")) return true;
|
|
7111
|
-
}
|
|
7112
|
-
return false;
|
|
7113
|
-
},
|
|
7114
|
-
ignoreInitial: true,
|
|
7115
|
-
persistent: true,
|
|
7116
|
-
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 }
|
|
7117
|
-
});
|
|
7118
|
-
watcher.on("all", () => {
|
|
7193
|
+
const fire = () => {
|
|
7119
7194
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
7120
7195
|
debounceTimer = setTimeout(() => {
|
|
7121
7196
|
debounceTimer = null;
|
|
7122
7197
|
opts.onChange();
|
|
7123
7198
|
}, debounceMs);
|
|
7199
|
+
};
|
|
7200
|
+
if (SUPPORTS_RECURSIVE_WATCH) {
|
|
7201
|
+
const watchers = opts.roots.map((root) => {
|
|
7202
|
+
const w = fs31.watch(root, { recursive: true }, (_event, filename) => {
|
|
7203
|
+
if (filename && isIgnoredWatchPath([root], path28.join(root, filename.toString()))) {
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
fire();
|
|
7207
|
+
});
|
|
7208
|
+
w.on("error", logWatchError);
|
|
7209
|
+
return w;
|
|
7210
|
+
});
|
|
7211
|
+
return {
|
|
7212
|
+
stop() {
|
|
7213
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
7214
|
+
for (const w of watchers) {
|
|
7215
|
+
try {
|
|
7216
|
+
w.close();
|
|
7217
|
+
} catch {
|
|
7218
|
+
}
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7221
|
+
};
|
|
7222
|
+
}
|
|
7223
|
+
const watcher = chokidar.watch(opts.roots, {
|
|
7224
|
+
ignored: (filePath) => isIgnoredWatchPath(opts.roots, filePath),
|
|
7225
|
+
ignoreInitial: true,
|
|
7226
|
+
persistent: true,
|
|
7227
|
+
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 }
|
|
7124
7228
|
});
|
|
7125
|
-
watcher.on("
|
|
7126
|
-
|
|
7127
|
-
chalk19.yellow("[watcher] fs error: ") + err.message + "\n"
|
|
7128
|
-
);
|
|
7129
|
-
});
|
|
7229
|
+
watcher.on("all", fire);
|
|
7230
|
+
watcher.on("error", logWatchError);
|
|
7130
7231
|
return {
|
|
7131
7232
|
stop() {
|
|
7132
7233
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -7137,30 +7238,30 @@ function createFsWatcher(opts) {
|
|
|
7137
7238
|
}
|
|
7138
7239
|
|
|
7139
7240
|
// src/core/web-static.ts
|
|
7140
|
-
import
|
|
7141
|
-
import
|
|
7241
|
+
import fs32 from "fs";
|
|
7242
|
+
import path29 from "path";
|
|
7142
7243
|
import { fileURLToPath } from "url";
|
|
7143
7244
|
function resolveWebRoot() {
|
|
7144
|
-
const entryDir =
|
|
7145
|
-
const moduleDir =
|
|
7245
|
+
const entryDir = path29.dirname(process.argv[1] ?? "");
|
|
7246
|
+
const moduleDir = path29.dirname(fileURLToPath(import.meta.url));
|
|
7146
7247
|
const candidates = [
|
|
7147
|
-
|
|
7248
|
+
path29.join(entryDir, "web"),
|
|
7148
7249
|
// bundled: this module is inlined into dist/<bin>.js, so dist/web is a
|
|
7149
7250
|
// sibling of the bundle. Works even when argv[1] is an npm bin symlink
|
|
7150
7251
|
// (which is not realpath'd, so the entryDir candidate above misses).
|
|
7151
|
-
|
|
7252
|
+
path29.join(moduleDir, "web"),
|
|
7152
7253
|
// dev/tsx fallback: walk up from src/core to repo root then into dist/web.
|
|
7153
|
-
|
|
7254
|
+
path29.resolve(moduleDir, "../../dist/web")
|
|
7154
7255
|
];
|
|
7155
7256
|
for (const c of candidates) {
|
|
7156
|
-
if (
|
|
7257
|
+
if (fs32.existsSync(path29.join(c, "index.html"))) return c;
|
|
7157
7258
|
}
|
|
7158
7259
|
return null;
|
|
7159
7260
|
}
|
|
7160
7261
|
|
|
7161
7262
|
// src/core/spa-handler.ts
|
|
7162
|
-
import
|
|
7163
|
-
import
|
|
7263
|
+
import fs33 from "fs";
|
|
7264
|
+
import path30 from "path";
|
|
7164
7265
|
var MIME = {
|
|
7165
7266
|
".html": "text/html; charset=utf-8",
|
|
7166
7267
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -7174,18 +7275,18 @@ var MIME = {
|
|
|
7174
7275
|
function readFile(root, relPath) {
|
|
7175
7276
|
const clean = relPath.split("?")[0];
|
|
7176
7277
|
const requested = clean === "/" ? "/index.html" : clean;
|
|
7177
|
-
const filePath =
|
|
7178
|
-
const norm =
|
|
7179
|
-
if (!norm.startsWith(
|
|
7278
|
+
const filePath = path30.join(root, requested);
|
|
7279
|
+
const norm = path30.normalize(filePath);
|
|
7280
|
+
if (!norm.startsWith(path30.normalize(root))) return null;
|
|
7180
7281
|
let stat;
|
|
7181
7282
|
try {
|
|
7182
|
-
stat =
|
|
7283
|
+
stat = fs33.statSync(norm);
|
|
7183
7284
|
} catch {
|
|
7184
7285
|
return null;
|
|
7185
7286
|
}
|
|
7186
7287
|
if (!stat.isFile()) return null;
|
|
7187
|
-
const ext =
|
|
7188
|
-
return { body:
|
|
7288
|
+
const ext = path30.extname(norm).toLowerCase();
|
|
7289
|
+
return { body: fs33.readFileSync(norm), ext };
|
|
7189
7290
|
}
|
|
7190
7291
|
function serveSpa(c, webRoot) {
|
|
7191
7292
|
const url = new URL(c.req.url);
|
|
@@ -7220,15 +7321,23 @@ async function startDiffServer(opts) {
|
|
|
7220
7321
|
for (const cb of sseListeners) cb(payload);
|
|
7221
7322
|
}
|
|
7222
7323
|
const app = new Hono();
|
|
7223
|
-
app.get(
|
|
7224
|
-
|
|
7225
|
-
|
|
7324
|
+
app.get("/api/context", (c) => {
|
|
7325
|
+
const primaryRoot = opts.repos[0]?.root;
|
|
7326
|
+
let headBranch;
|
|
7327
|
+
if (primaryRoot) {
|
|
7328
|
+
const head = git(["rev-parse", "--abbrev-ref", "HEAD"], primaryRoot);
|
|
7329
|
+
if (head.exitCode === 0 && head.stdout && head.stdout !== "HEAD") {
|
|
7330
|
+
headBranch = head.stdout;
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
return c.json({
|
|
7226
7334
|
mode: "review",
|
|
7227
7335
|
scopeLabel: opts.scopeLabel,
|
|
7228
7336
|
repos: opts.repos.map((r) => ({ name: r.name })),
|
|
7229
|
-
readOnly: !!opts.readOnly
|
|
7230
|
-
|
|
7231
|
-
|
|
7337
|
+
readOnly: !!opts.readOnly,
|
|
7338
|
+
headBranch
|
|
7339
|
+
});
|
|
7340
|
+
});
|
|
7232
7341
|
app.get("/api/diff", (c) => {
|
|
7233
7342
|
const base = c.req.query("base") === "branch" ? "branch" : "uncommitted";
|
|
7234
7343
|
try {
|
|
@@ -7419,16 +7528,6 @@ async function startCommentServer(opts) {
|
|
|
7419
7528
|
stop: () => server.stop()
|
|
7420
7529
|
};
|
|
7421
7530
|
}
|
|
7422
|
-
async function startReadOnlyDiffServer(opts) {
|
|
7423
|
-
const server = await startDiffServer({
|
|
7424
|
-
repos: opts.repos,
|
|
7425
|
-
scopeLabel: opts.scopeLabel,
|
|
7426
|
-
sessionBaseBranch: opts.sessionBaseBranch,
|
|
7427
|
-
watchDebounceMs: opts.watchDebounceMs,
|
|
7428
|
-
readOnly: true
|
|
7429
|
-
});
|
|
7430
|
-
return { url: server.url, stop: () => server.stop() };
|
|
7431
|
-
}
|
|
7432
7531
|
function formatSingleComment(c) {
|
|
7433
7532
|
const bodyLines = c.body.split("\n").map((l) => `> ${l}`).join("\n");
|
|
7434
7533
|
const header = c.side === "general" ? `**General review comment**` : `**${c.repo}/${c.file}** : line ${c.line} (${c.side})`;
|
|
@@ -7460,8 +7559,8 @@ function diffReviewSnapshot(snapshot, seen) {
|
|
|
7460
7559
|
}
|
|
7461
7560
|
|
|
7462
7561
|
// src/core/static-renderer.ts
|
|
7463
|
-
import
|
|
7464
|
-
import
|
|
7562
|
+
import fs34 from "fs";
|
|
7563
|
+
import path31 from "path";
|
|
7465
7564
|
function buildDiff(specs, resolvedBase) {
|
|
7466
7565
|
return {
|
|
7467
7566
|
repos: specs.map((r) => ({
|
|
@@ -7477,8 +7576,8 @@ function renderStatic(opts) {
|
|
|
7477
7576
|
if (!webRoot) {
|
|
7478
7577
|
throw new Error("Could not find dist/web/. Run `npm run build` first.");
|
|
7479
7578
|
}
|
|
7480
|
-
const shellPath =
|
|
7481
|
-
let shell =
|
|
7579
|
+
const shellPath = path31.join(webRoot, "index.html");
|
|
7580
|
+
let shell = fs34.readFileSync(shellPath, "utf-8");
|
|
7482
7581
|
const uncommitted = buildDiff(opts.uncommitted, "HEAD");
|
|
7483
7582
|
const branch = opts.branch ? buildDiff(opts.branch.specs, opts.branch.resolvedBase) : void 0;
|
|
7484
7583
|
const initialBase = opts.initialBase ?? "uncommitted";
|
|
@@ -7527,10 +7626,10 @@ function escapeForScriptTag(json) {
|
|
|
7527
7626
|
}
|
|
7528
7627
|
function readAsset(webRoot, urlPath) {
|
|
7529
7628
|
const clean = urlPath.split("?")[0].replace(/^\//, "");
|
|
7530
|
-
const full =
|
|
7531
|
-
if (!
|
|
7629
|
+
const full = path31.join(webRoot, clean);
|
|
7630
|
+
if (!path31.normalize(full).startsWith(path31.normalize(webRoot))) return null;
|
|
7532
7631
|
try {
|
|
7533
|
-
return
|
|
7632
|
+
return fs34.readFileSync(full, "utf-8");
|
|
7534
7633
|
} catch {
|
|
7535
7634
|
return null;
|
|
7536
7635
|
}
|
|
@@ -7540,108 +7639,85 @@ function readAsset(webRoot, urlPath) {
|
|
|
7540
7639
|
function info(message) {
|
|
7541
7640
|
process.stderr.write(message + "\n");
|
|
7542
7641
|
}
|
|
7543
|
-
function
|
|
7642
|
+
function scopePathStem(repoSpecs) {
|
|
7643
|
+
return stableDiffPath(repoSpecs.map((r) => r.root));
|
|
7644
|
+
}
|
|
7645
|
+
async function runStop(repoSpecs) {
|
|
7646
|
+
const webUrl = readWebUrl();
|
|
7647
|
+
if (!webUrl) {
|
|
7648
|
+
info(chalk21.gray("No work web running \u2014 nothing to stop."));
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7651
|
+
const hash = stableDiffPath(repoSpecs.map((r) => r.root)).split(/[\\/]/).pop();
|
|
7544
7652
|
try {
|
|
7545
|
-
|
|
7546
|
-
|
|
7653
|
+
const res = await fetch(`${webUrl}api/scopes/${hash}`, {
|
|
7654
|
+
method: "DELETE"
|
|
7655
|
+
});
|
|
7656
|
+
if (res.ok) {
|
|
7657
|
+
info(chalk21.gray("De-registered this scope from work web."));
|
|
7658
|
+
} else {
|
|
7659
|
+
info(
|
|
7660
|
+
chalk21.yellow(
|
|
7661
|
+
`work web responded ${res.status} \u2014 scope may not have been registered.`
|
|
7662
|
+
)
|
|
7663
|
+
);
|
|
7664
|
+
}
|
|
7665
|
+
} catch (err) {
|
|
7666
|
+
console.error(
|
|
7667
|
+
chalk21.red("Could not reach work web:"),
|
|
7668
|
+
err.message
|
|
7669
|
+
);
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
function webUrlFilePath() {
|
|
7673
|
+
return path32.join(os9.homedir(), ".work", "web.url");
|
|
7674
|
+
}
|
|
7675
|
+
function resolveWorkBinPath(selfArgv1) {
|
|
7676
|
+
let real = selfArgv1;
|
|
7677
|
+
try {
|
|
7678
|
+
real = fs35.realpathSync(selfArgv1);
|
|
7547
7679
|
} catch {
|
|
7548
|
-
return false;
|
|
7549
7680
|
}
|
|
7681
|
+
if (real.endsWith("wd-bin.js")) {
|
|
7682
|
+
return path32.join(path32.dirname(real), "bin.js");
|
|
7683
|
+
}
|
|
7684
|
+
return real;
|
|
7550
7685
|
}
|
|
7551
|
-
function
|
|
7686
|
+
function readWebUrl() {
|
|
7552
7687
|
try {
|
|
7553
|
-
const
|
|
7554
|
-
|
|
7555
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
7688
|
+
const v = fs35.readFileSync(webUrlFilePath(), "utf-8").trim();
|
|
7689
|
+
return v || null;
|
|
7556
7690
|
} catch {
|
|
7557
7691
|
return null;
|
|
7558
7692
|
}
|
|
7559
7693
|
}
|
|
7560
|
-
function
|
|
7561
|
-
const
|
|
7694
|
+
async function ensureWorkWebRunning() {
|
|
7695
|
+
const existing = readWebUrl();
|
|
7696
|
+
if (existing) return existing;
|
|
7697
|
+
const workBin = resolveWorkBinPath(process.argv[1]);
|
|
7698
|
+
const out = fs35.openSync(
|
|
7699
|
+
path32.join(os9.homedir(), ".work", "web-autostart.log"),
|
|
7700
|
+
"a"
|
|
7701
|
+
);
|
|
7562
7702
|
const child = childSpawn(
|
|
7563
7703
|
process.execPath,
|
|
7564
|
-
[
|
|
7704
|
+
[workBin, "web", "--lean", "--no-open"],
|
|
7565
7705
|
{
|
|
7566
7706
|
detached: true,
|
|
7567
7707
|
stdio: ["ignore", out, out],
|
|
7568
|
-
windowsHide: true
|
|
7569
|
-
cwd
|
|
7708
|
+
windowsHide: true
|
|
7709
|
+
// Inherit cwd doesn't matter for work web — its file-watches use
|
|
7710
|
+
// ~/.work paths exclusively.
|
|
7570
7711
|
}
|
|
7571
7712
|
);
|
|
7572
7713
|
child.unref();
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
function pathsForScope(repoSpecs) {
|
|
7577
|
-
const base = stableDiffPath(repoSpecs.map((r) => r.root));
|
|
7578
|
-
return {
|
|
7579
|
-
base,
|
|
7580
|
-
pid: `${base}.pid`,
|
|
7581
|
-
log: `${base}.log`,
|
|
7582
|
-
url: `${base}.url`
|
|
7583
|
-
};
|
|
7584
|
-
}
|
|
7585
|
-
function runStop(paths) {
|
|
7586
|
-
const pid = readPid(paths.pid);
|
|
7587
|
-
if (pid && isPidAlive(pid)) {
|
|
7588
|
-
try {
|
|
7589
|
-
process.kill(pid);
|
|
7590
|
-
info(chalk21.gray(`Stopped watcher (PID ${pid}).`));
|
|
7591
|
-
} catch (err) {
|
|
7592
|
-
console.error(chalk21.red("Failed to stop watcher:"), err.message);
|
|
7593
|
-
}
|
|
7594
|
-
} else {
|
|
7595
|
-
info(chalk21.gray("No watcher running for this scope."));
|
|
7596
|
-
}
|
|
7597
|
-
try {
|
|
7598
|
-
fs32.unlinkSync(paths.pid);
|
|
7599
|
-
} catch {
|
|
7600
|
-
}
|
|
7601
|
-
try {
|
|
7602
|
-
fs32.unlinkSync(paths.url);
|
|
7603
|
-
} catch {
|
|
7604
|
-
}
|
|
7605
|
-
}
|
|
7606
|
-
async function runDaemon(ctx) {
|
|
7607
|
-
fs32.writeFileSync(ctx.paths.pid, String(process.pid));
|
|
7608
|
-
const handle = await startReadOnlyDiffServer({
|
|
7609
|
-
repos: ctx.repoSpecs,
|
|
7610
|
-
scopeLabel: ctx.scopeLabel,
|
|
7611
|
-
sessionBaseBranch: ctx.scope.session?.baseBranch
|
|
7612
|
-
});
|
|
7613
|
-
fs32.writeFileSync(ctx.paths.url, handle.url);
|
|
7614
|
-
info(
|
|
7615
|
-
chalk21.gray(
|
|
7616
|
-
`[live] watcher started, pid=${process.pid}, repos=${ctx.repoSpecs.map((r) => r.name).join(",")}, base=${ctx.base}, url=${handle.url}`
|
|
7617
|
-
)
|
|
7618
|
-
);
|
|
7619
|
-
const shutdown = () => {
|
|
7620
|
-
handle.stop();
|
|
7621
|
-
try {
|
|
7622
|
-
fs32.unlinkSync(ctx.paths.pid);
|
|
7623
|
-
} catch {
|
|
7624
|
-
}
|
|
7625
|
-
try {
|
|
7626
|
-
fs32.unlinkSync(ctx.paths.url);
|
|
7627
|
-
} catch {
|
|
7628
|
-
}
|
|
7629
|
-
process.exit(0);
|
|
7630
|
-
};
|
|
7631
|
-
process.on("SIGINT", shutdown);
|
|
7632
|
-
process.on("SIGTERM", shutdown);
|
|
7633
|
-
await new Promise(() => {
|
|
7634
|
-
});
|
|
7714
|
+
fs35.closeSync(out);
|
|
7715
|
+
const url = await waitForUrlFile(webUrlFilePath(), 5e3);
|
|
7716
|
+
return url;
|
|
7635
7717
|
}
|
|
7636
7718
|
async function tryRegisterWithWorkWeb(ctx, routeKind) {
|
|
7637
|
-
const
|
|
7638
|
-
|
|
7639
|
-
try {
|
|
7640
|
-
webUrl = fs32.readFileSync(webUrlFile, "utf-8").trim();
|
|
7641
|
-
if (!webUrl) return null;
|
|
7642
|
-
} catch {
|
|
7643
|
-
return null;
|
|
7644
|
-
}
|
|
7719
|
+
const webUrl = await ensureWorkWebRunning();
|
|
7720
|
+
if (!webUrl) return null;
|
|
7645
7721
|
try {
|
|
7646
7722
|
const res = await fetch(`${webUrl}api/scopes`, {
|
|
7647
7723
|
method: "POST",
|
|
@@ -7662,42 +7738,19 @@ async function tryRegisterWithWorkWeb(ctx, routeKind) {
|
|
|
7662
7738
|
async function runLauncher(ctx) {
|
|
7663
7739
|
const webRouteUrl = await tryRegisterWithWorkWeb(ctx, "diff");
|
|
7664
7740
|
if (webRouteUrl) {
|
|
7665
|
-
info(chalk21.gray(`Opening
|
|
7741
|
+
info(chalk21.gray(`Opening: ${webRouteUrl}`));
|
|
7666
7742
|
openUrl(webRouteUrl);
|
|
7667
7743
|
return;
|
|
7668
7744
|
}
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
try {
|
|
7679
|
-
fs32.unlinkSync(ctx.paths.pid);
|
|
7680
|
-
} catch {
|
|
7681
|
-
}
|
|
7682
|
-
try {
|
|
7683
|
-
fs32.unlinkSync(ctx.paths.url);
|
|
7684
|
-
} catch {
|
|
7685
|
-
}
|
|
7686
|
-
const passthrough = process.argv.slice(2).filter((a) => a !== "--stop" && a !== "--watch");
|
|
7687
|
-
const pid = spawnDaemon(passthrough, ctx.paths.log);
|
|
7688
|
-
info(chalk21.gray(`Started watcher (PID ${pid}). Log: ${ctx.paths.log}`));
|
|
7689
|
-
info(chalk21.gray("Stop with: wd --stop"));
|
|
7690
|
-
url = await waitForUrlFile(ctx.paths.url, 3e3);
|
|
7691
|
-
}
|
|
7692
|
-
if (!url) {
|
|
7693
|
-
console.error(
|
|
7694
|
-
chalk21.red("Watcher did not report a URL \u2014 check the log:"),
|
|
7695
|
-
ctx.paths.log
|
|
7696
|
-
);
|
|
7697
|
-
return;
|
|
7698
|
-
}
|
|
7699
|
-
info(chalk21.gray(`URL: ${url}`));
|
|
7700
|
-
openUrl(url);
|
|
7745
|
+
console.error(
|
|
7746
|
+
chalk21.red("Could not start or reach work web.")
|
|
7747
|
+
);
|
|
7748
|
+
console.error(
|
|
7749
|
+
chalk21.gray(
|
|
7750
|
+
`Tail ~/.work/web-autostart.log for diagnostics, or run \`work web\` in another shell to inspect startup directly.`
|
|
7751
|
+
)
|
|
7752
|
+
);
|
|
7753
|
+
process.exitCode = 1;
|
|
7701
7754
|
}
|
|
7702
7755
|
function runStatic(ctx, initialBranch) {
|
|
7703
7756
|
const uncommitted = buildRepoSpecs(ctx.scope, "HEAD");
|
|
@@ -7725,8 +7778,8 @@ function runStatic(ctx, initialBranch) {
|
|
|
7725
7778
|
branch,
|
|
7726
7779
|
initialBase: initialBranch && branch ? "branch" : "uncommitted"
|
|
7727
7780
|
});
|
|
7728
|
-
const filePath = `${ctx.
|
|
7729
|
-
|
|
7781
|
+
const filePath = `${ctx.scopeStem}.html`;
|
|
7782
|
+
fs35.writeFileSync(filePath, html, "utf-8");
|
|
7730
7783
|
info(chalk21.gray(`Wrote ${filePath}`));
|
|
7731
7784
|
openUrl(`file:///${filePath.replace(/\\/g, "/")}`);
|
|
7732
7785
|
}
|
|
@@ -7735,7 +7788,7 @@ function waitForUrlFile(filePath, timeoutMs) {
|
|
|
7735
7788
|
const start = Date.now();
|
|
7736
7789
|
const tick = () => {
|
|
7737
7790
|
try {
|
|
7738
|
-
const v =
|
|
7791
|
+
const v = fs35.readFileSync(filePath, "utf-8").trim();
|
|
7739
7792
|
if (v) return resolve(v);
|
|
7740
7793
|
} catch {
|
|
7741
7794
|
}
|
|
@@ -7746,14 +7799,8 @@ function waitForUrlFile(filePath, timeoutMs) {
|
|
|
7746
7799
|
});
|
|
7747
7800
|
}
|
|
7748
7801
|
async function tryReviewViaWorkWeb(ctx) {
|
|
7749
|
-
const
|
|
7750
|
-
|
|
7751
|
-
try {
|
|
7752
|
-
webUrl = fs32.readFileSync(webUrlFile, "utf-8").trim();
|
|
7753
|
-
if (!webUrl) return false;
|
|
7754
|
-
} catch {
|
|
7755
|
-
return false;
|
|
7756
|
-
}
|
|
7802
|
+
const webUrl = await ensureWorkWebRunning();
|
|
7803
|
+
if (!webUrl) return false;
|
|
7757
7804
|
let hash;
|
|
7758
7805
|
try {
|
|
7759
7806
|
const res = await fetch(`${webUrl}api/scopes`, {
|
|
@@ -7942,12 +7989,7 @@ var diffCommand = {
|
|
|
7942
7989
|
}).option("stop", {
|
|
7943
7990
|
type: "boolean",
|
|
7944
7991
|
default: false,
|
|
7945
|
-
describe: "
|
|
7946
|
-
}).option("watch-daemon", {
|
|
7947
|
-
type: "boolean",
|
|
7948
|
-
default: false,
|
|
7949
|
-
hidden: true,
|
|
7950
|
-
describe: "Internal: run the foreground watcher loop."
|
|
7992
|
+
describe: "De-register this scope from work web. The work web server itself keeps running; use `work web --stop` to terminate the server."
|
|
7951
7993
|
}).option("comments", {
|
|
7952
7994
|
type: "boolean",
|
|
7953
7995
|
alias: "c",
|
|
@@ -7965,7 +8007,8 @@ var diffCommand = {
|
|
|
7965
8007
|
branch: argv.branch
|
|
7966
8008
|
});
|
|
7967
8009
|
const repoSpecs = buildRepoSpecs(scope, base);
|
|
7968
|
-
const
|
|
8010
|
+
const scopeStem = scopePathStem(repoSpecs);
|
|
8011
|
+
if (argv.stop) return runStop(repoSpecs);
|
|
7969
8012
|
if (base === "HEAD") {
|
|
7970
8013
|
info(chalk21.gray("Showing uncommitted changes vs HEAD."));
|
|
7971
8014
|
} else {
|
|
@@ -7981,11 +8024,9 @@ var diffCommand = {
|
|
|
7981
8024
|
base,
|
|
7982
8025
|
baseSource,
|
|
7983
8026
|
repoSpecs,
|
|
7984
|
-
|
|
8027
|
+
scopeStem,
|
|
7985
8028
|
scopeLabel
|
|
7986
8029
|
};
|
|
7987
|
-
if (argv.stop) return runStop(ctx.paths);
|
|
7988
|
-
if (argv["watch-daemon"]) return runDaemon(ctx);
|
|
7989
8030
|
if (argv.comments) return runReview(ctx);
|
|
7990
8031
|
if (argv.static) return runStatic(ctx, !!argv.branch);
|
|
7991
8032
|
return runLauncher(ctx);
|
|
@@ -7993,21 +8034,21 @@ var diffCommand = {
|
|
|
7993
8034
|
};
|
|
7994
8035
|
|
|
7995
8036
|
// src/commands/web.ts
|
|
7996
|
-
import
|
|
8037
|
+
import fs40 from "fs";
|
|
7997
8038
|
import os13 from "os";
|
|
7998
|
-
import
|
|
8039
|
+
import path41 from "path";
|
|
7999
8040
|
import chalk23 from "chalk";
|
|
8000
8041
|
|
|
8001
8042
|
// src/core/web-server.ts
|
|
8002
|
-
import
|
|
8043
|
+
import fs39 from "fs";
|
|
8003
8044
|
import os12 from "os";
|
|
8004
|
-
import
|
|
8045
|
+
import path40 from "path";
|
|
8005
8046
|
import chalk22 from "chalk";
|
|
8006
8047
|
import { Hono as Hono3 } from "hono";
|
|
8007
8048
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
8008
8049
|
|
|
8009
8050
|
// src/core/web-state.ts
|
|
8010
|
-
import
|
|
8051
|
+
import path33 from "path";
|
|
8011
8052
|
import crypto4 from "crypto";
|
|
8012
8053
|
import chokidar2 from "chokidar";
|
|
8013
8054
|
function sessionIdFor(s) {
|
|
@@ -8028,7 +8069,7 @@ function subscribeSession(sessionId, onChange) {
|
|
|
8028
8069
|
const watcher = chokidar2.watch(roots, {
|
|
8029
8070
|
ignored: (filePath) => {
|
|
8030
8071
|
for (const r of roots) {
|
|
8031
|
-
const rel =
|
|
8072
|
+
const rel = path33.relative(r, filePath).replace(/\\/g, "/");
|
|
8032
8073
|
if (rel === ".git" || rel.startsWith(".git/")) return true;
|
|
8033
8074
|
}
|
|
8034
8075
|
return false;
|
|
@@ -8142,24 +8183,24 @@ function disposeAllPtys() {
|
|
|
8142
8183
|
}
|
|
8143
8184
|
|
|
8144
8185
|
// src/core/comment-file-store.ts
|
|
8145
|
-
import
|
|
8146
|
-
import
|
|
8186
|
+
import fs36 from "fs";
|
|
8187
|
+
import path34 from "path";
|
|
8147
8188
|
import os10 from "os";
|
|
8148
8189
|
function commentsDir() {
|
|
8149
|
-
return
|
|
8190
|
+
return path34.join(os10.homedir(), ".work", "comments");
|
|
8150
8191
|
}
|
|
8151
8192
|
function ensureDir() {
|
|
8152
|
-
|
|
8193
|
+
fs36.mkdirSync(commentsDir(), { recursive: true });
|
|
8153
8194
|
}
|
|
8154
8195
|
function commentsFileFor(sessionId) {
|
|
8155
|
-
return
|
|
8196
|
+
return path34.join(commentsDir(), `${sessionId}.json`);
|
|
8156
8197
|
}
|
|
8157
8198
|
function pathFor(sessionId) {
|
|
8158
8199
|
return commentsFileFor(sessionId);
|
|
8159
8200
|
}
|
|
8160
8201
|
function readDisk(sessionId) {
|
|
8161
8202
|
try {
|
|
8162
|
-
const raw =
|
|
8203
|
+
const raw = fs36.readFileSync(pathFor(sessionId), "utf-8");
|
|
8163
8204
|
const parsed = JSON.parse(raw);
|
|
8164
8205
|
return Array.isArray(parsed) ? parsed : [];
|
|
8165
8206
|
} catch {
|
|
@@ -8231,29 +8272,29 @@ function clearCommentStoreCache() {
|
|
|
8231
8272
|
}
|
|
8232
8273
|
|
|
8233
8274
|
// src/core/pending-delivery.ts
|
|
8234
|
-
import
|
|
8235
|
-
import
|
|
8275
|
+
import fs37 from "fs";
|
|
8276
|
+
import path35 from "path";
|
|
8236
8277
|
function pathFor2(sessionId) {
|
|
8237
8278
|
return {
|
|
8238
8279
|
comments: commentsFileFor(sessionId),
|
|
8239
|
-
delivered:
|
|
8280
|
+
delivered: path35.join(commentsDir(), `${sessionId}.delivered.json`)
|
|
8240
8281
|
};
|
|
8241
8282
|
}
|
|
8242
8283
|
function readJson(filePath, fallback) {
|
|
8243
8284
|
try {
|
|
8244
|
-
return JSON.parse(
|
|
8285
|
+
return JSON.parse(fs37.readFileSync(filePath, "utf8"));
|
|
8245
8286
|
} catch {
|
|
8246
8287
|
return fallback;
|
|
8247
8288
|
}
|
|
8248
8289
|
}
|
|
8249
8290
|
function writeAtomic2(filePath, content) {
|
|
8250
|
-
|
|
8291
|
+
fs37.mkdirSync(path35.dirname(filePath), { recursive: true });
|
|
8251
8292
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
8252
|
-
|
|
8253
|
-
|
|
8293
|
+
fs37.writeFileSync(tmp, content, "utf-8");
|
|
8294
|
+
fs37.renameSync(tmp, filePath);
|
|
8254
8295
|
}
|
|
8255
8296
|
function normalize(p) {
|
|
8256
|
-
return
|
|
8297
|
+
return path35.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
8257
8298
|
}
|
|
8258
8299
|
function findSessionForCwd(cwd) {
|
|
8259
8300
|
const norm = normalize(cwd);
|
|
@@ -8274,13 +8315,40 @@ function findSessionForCwd(cwd) {
|
|
|
8274
8315
|
}
|
|
8275
8316
|
return best?.session ?? null;
|
|
8276
8317
|
}
|
|
8318
|
+
function isPendingFor(delivered) {
|
|
8319
|
+
return (c) => c.status === "published" && c.author === "user" && !delivered.has(c.id);
|
|
8320
|
+
}
|
|
8277
8321
|
function readPendingForSession(sessionId) {
|
|
8278
8322
|
const paths = pathFor2(sessionId);
|
|
8279
8323
|
const comments = getCommentFileStore(sessionId).snapshot();
|
|
8280
8324
|
const delivered = new Set(readJson(paths.delivered, []));
|
|
8281
|
-
return comments.filter(
|
|
8282
|
-
|
|
8325
|
+
return comments.filter(isPendingFor(delivered));
|
|
8326
|
+
}
|
|
8327
|
+
function scopeStoreIdsForPaths(paths) {
|
|
8328
|
+
const resolved = paths.map((p) => path35.resolve(p));
|
|
8329
|
+
const ids = /* @__PURE__ */ new Set();
|
|
8330
|
+
ids.add(`scope-${scopeHashFor(resolved)}`);
|
|
8331
|
+
for (const p of resolved) ids.add(`scope-${scopeHashFor([p])}`);
|
|
8332
|
+
return [...ids];
|
|
8333
|
+
}
|
|
8334
|
+
function readPendingForWorktree(session) {
|
|
8335
|
+
const sessionId = sessionIdFor(session);
|
|
8336
|
+
const delivered = new Set(
|
|
8337
|
+
readJson(pathFor2(sessionId).delivered, [])
|
|
8283
8338
|
);
|
|
8339
|
+
const pending = isPendingFor(delivered);
|
|
8340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8341
|
+
const out = [];
|
|
8342
|
+
const collect = (storeId) => {
|
|
8343
|
+
for (const c of getCommentFileStore(storeId).snapshot()) {
|
|
8344
|
+
if (seen.has(c.id) || !pending(c)) continue;
|
|
8345
|
+
seen.add(c.id);
|
|
8346
|
+
out.push(c);
|
|
8347
|
+
}
|
|
8348
|
+
};
|
|
8349
|
+
collect(sessionId);
|
|
8350
|
+
for (const storeId of scopeStoreIdsForPaths(session.paths)) collect(storeId);
|
|
8351
|
+
return out;
|
|
8284
8352
|
}
|
|
8285
8353
|
function markDelivered(sessionId, ids) {
|
|
8286
8354
|
if (ids.length === 0) return;
|
|
@@ -8580,8 +8648,8 @@ function mountPanesRoutes(app, opts) {
|
|
|
8580
8648
|
}
|
|
8581
8649
|
|
|
8582
8650
|
// src/core/worktree-routes.ts
|
|
8583
|
-
import
|
|
8584
|
-
import { spawn as
|
|
8651
|
+
import path36 from "path";
|
|
8652
|
+
import { spawn as spawn10 } from "child_process";
|
|
8585
8653
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
8586
8654
|
import { z as z3 } from "zod";
|
|
8587
8655
|
function mountWorktreeRoutes(app, opts) {
|
|
@@ -8704,10 +8772,10 @@ function mountWorktreeRoutes(app, opts) {
|
|
|
8704
8772
|
const id = c.req.param("id");
|
|
8705
8773
|
const session = findSession2(id);
|
|
8706
8774
|
if (!session) return c.json({ error: "unknown session" }, 404);
|
|
8707
|
-
const target = session.isGroup ?
|
|
8775
|
+
const target = session.isGroup ? path36.dirname(session.paths[0]) : session.paths[0];
|
|
8708
8776
|
try {
|
|
8709
8777
|
const cmd = process.platform === "win32" ? "code.cmd" : "code";
|
|
8710
|
-
const child =
|
|
8778
|
+
const child = spawn10(cmd, [target], {
|
|
8711
8779
|
detached: true,
|
|
8712
8780
|
stdio: "ignore",
|
|
8713
8781
|
shell: false
|
|
@@ -8724,28 +8792,27 @@ function mountWorktreeRoutes(app, opts) {
|
|
|
8724
8792
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
8725
8793
|
import { z as z4 } from "zod";
|
|
8726
8794
|
import { EventEmitter } from "events";
|
|
8727
|
-
import
|
|
8728
|
-
import
|
|
8795
|
+
import path39 from "path";
|
|
8796
|
+
import spawn12 from "cross-spawn";
|
|
8729
8797
|
|
|
8730
8798
|
// src/core/checkpoint.ts
|
|
8731
|
-
import
|
|
8799
|
+
import fs38 from "fs";
|
|
8732
8800
|
import os11 from "os";
|
|
8733
|
-
import
|
|
8734
|
-
import
|
|
8735
|
-
import spawn10 from "cross-spawn";
|
|
8801
|
+
import path37 from "path";
|
|
8802
|
+
import spawn11 from "cross-spawn";
|
|
8736
8803
|
function manifestPath(scopeHash) {
|
|
8737
|
-
const dir =
|
|
8738
|
-
|
|
8739
|
-
return
|
|
8804
|
+
const dir = path37.join(os11.homedir(), ".work", "diffs");
|
|
8805
|
+
fs38.mkdirSync(dir, { recursive: true });
|
|
8806
|
+
return path37.join(dir, `${scopeHash}.checkpoints.json`);
|
|
8740
8807
|
}
|
|
8741
8808
|
function emptyManifest(scopeHash) {
|
|
8742
8809
|
return { version: 1, scopeHash, entries: [] };
|
|
8743
8810
|
}
|
|
8744
8811
|
function loadManifest(scopeHash) {
|
|
8745
8812
|
const file = manifestPath(scopeHash);
|
|
8746
|
-
if (!
|
|
8813
|
+
if (!fs38.existsSync(file)) return emptyManifest(scopeHash);
|
|
8747
8814
|
try {
|
|
8748
|
-
const raw =
|
|
8815
|
+
const raw = fs38.readFileSync(file, "utf-8");
|
|
8749
8816
|
const parsed = JSON.parse(raw);
|
|
8750
8817
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
8751
8818
|
return emptyManifest(scopeHash);
|
|
@@ -8759,68 +8826,37 @@ function loadManifest(scopeHash) {
|
|
|
8759
8826
|
return emptyManifest(scopeHash);
|
|
8760
8827
|
}
|
|
8761
8828
|
}
|
|
8762
|
-
function snapshotRepo(repoRoot, scopeHash, id) {
|
|
8763
|
-
const
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
const
|
|
8829
|
+
function snapshotRepo(repoRoot, scopeHash, id, includeWorkingTree = true) {
|
|
8830
|
+
const tree = writeTempTree(repoRoot, { includeWorkingTree });
|
|
8831
|
+
if (!tree) return null;
|
|
8832
|
+
const { treeSha, headSha } = tree;
|
|
8833
|
+
const commitArgs = ["commit-tree", treeSha, "-m", "wd checkpoint"];
|
|
8834
|
+
if (headSha) commitArgs.push("-p", headSha);
|
|
8835
|
+
const commitEnv = {
|
|
8836
|
+
...process.env,
|
|
8837
|
+
GIT_AUTHOR_NAME: "wd",
|
|
8838
|
+
GIT_AUTHOR_EMAIL: "wd@local",
|
|
8839
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
8840
|
+
GIT_COMMITTER_NAME: "wd",
|
|
8841
|
+
GIT_COMMITTER_EMAIL: "wd@local",
|
|
8842
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
8843
|
+
};
|
|
8844
|
+
const commit = spawn11.sync("git", commitArgs, {
|
|
8769
8845
|
cwd: repoRoot,
|
|
8770
8846
|
encoding: "utf-8",
|
|
8771
|
-
env,
|
|
8772
|
-
windowsHide: true
|
|
8773
|
-
maxBuffer: 64 * 1024 * 1024
|
|
8847
|
+
env: commitEnv,
|
|
8848
|
+
windowsHide: true
|
|
8774
8849
|
});
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
}
|
|
8786
|
-
const add = run2(["add", "-A"]);
|
|
8787
|
-
if (add.status !== 0) return null;
|
|
8788
|
-
const wt = run2(["write-tree"]);
|
|
8789
|
-
if (wt.status !== 0 || !wt.stdout) return null;
|
|
8790
|
-
const treeSha = wt.stdout.trim();
|
|
8791
|
-
const commitArgs = ["commit-tree", treeSha, "-m", "wd checkpoint"];
|
|
8792
|
-
if (hasHead) commitArgs.push("-p", headSha);
|
|
8793
|
-
const commitEnv = {
|
|
8794
|
-
...process.env,
|
|
8795
|
-
GIT_AUTHOR_NAME: "wd",
|
|
8796
|
-
GIT_AUTHOR_EMAIL: "wd@local",
|
|
8797
|
-
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
8798
|
-
GIT_COMMITTER_NAME: "wd",
|
|
8799
|
-
GIT_COMMITTER_EMAIL: "wd@local",
|
|
8800
|
-
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
8801
|
-
};
|
|
8802
|
-
const commit = spawn10.sync("git", commitArgs, {
|
|
8803
|
-
cwd: repoRoot,
|
|
8804
|
-
encoding: "utf-8",
|
|
8805
|
-
env: commitEnv,
|
|
8806
|
-
windowsHide: true
|
|
8807
|
-
});
|
|
8808
|
-
if (commit.status !== 0 || !commit.stdout) return null;
|
|
8809
|
-
const commitSha = commit.stdout.trim();
|
|
8810
|
-
const refName = `refs/wd/${scopeHash}/${id}`;
|
|
8811
|
-
const updateRef = spawn10.sync(
|
|
8812
|
-
"git",
|
|
8813
|
-
["update-ref", refName, commitSha],
|
|
8814
|
-
{ cwd: repoRoot, encoding: "utf-8", windowsHide: true }
|
|
8815
|
-
);
|
|
8816
|
-
if (updateRef.status !== 0) return null;
|
|
8817
|
-
return commitSha;
|
|
8818
|
-
} finally {
|
|
8819
|
-
try {
|
|
8820
|
-
if (fs35.existsSync(tmpIndex)) fs35.unlinkSync(tmpIndex);
|
|
8821
|
-
} catch {
|
|
8822
|
-
}
|
|
8823
|
-
}
|
|
8850
|
+
if (commit.status !== 0 || !commit.stdout) return null;
|
|
8851
|
+
const commitSha = commit.stdout.trim();
|
|
8852
|
+
const refName = `refs/wd/${scopeHash}/${id}`;
|
|
8853
|
+
const updateRef = spawn11.sync("git", ["update-ref", refName, commitSha], {
|
|
8854
|
+
cwd: repoRoot,
|
|
8855
|
+
encoding: "utf-8",
|
|
8856
|
+
windowsHide: true
|
|
8857
|
+
});
|
|
8858
|
+
if (updateRef.status !== 0) return null;
|
|
8859
|
+
return commitSha;
|
|
8824
8860
|
}
|
|
8825
8861
|
async function takeCheckpoint(scopeHash, repos, opts = {}) {
|
|
8826
8862
|
const file = manifestPath(scopeHash);
|
|
@@ -8831,12 +8867,17 @@ async function takeCheckpoint(scopeHash, repos, opts = {}) {
|
|
|
8831
8867
|
const nextId = isFirst ? 0 : manifest.entries[manifest.entries.length - 1].id + 1;
|
|
8832
8868
|
const captured = {};
|
|
8833
8869
|
for (const repo of repos) {
|
|
8834
|
-
captured[repo.name] = snapshotRepo(
|
|
8870
|
+
captured[repo.name] = snapshotRepo(
|
|
8871
|
+
repo.root,
|
|
8872
|
+
scopeHash,
|
|
8873
|
+
nextId,
|
|
8874
|
+
!isFirst
|
|
8875
|
+
);
|
|
8835
8876
|
}
|
|
8836
8877
|
const rollbackRefs = () => {
|
|
8837
8878
|
const refName = `refs/wd/${scopeHash}/${nextId}`;
|
|
8838
8879
|
for (const repo of repos) {
|
|
8839
|
-
|
|
8880
|
+
spawn11.sync("git", ["update-ref", "-d", refName], {
|
|
8840
8881
|
cwd: repo.root,
|
|
8841
8882
|
encoding: "utf-8",
|
|
8842
8883
|
windowsHide: true
|
|
@@ -8849,10 +8890,22 @@ async function takeCheckpoint(scopeHash, repos, opts = {}) {
|
|
|
8849
8890
|
}
|
|
8850
8891
|
if (!opts.force && !isFirst) {
|
|
8851
8892
|
const prev = manifest.entries[manifest.entries.length - 1];
|
|
8852
|
-
const
|
|
8853
|
-
(
|
|
8854
|
-
|
|
8855
|
-
|
|
8893
|
+
const treeOf = (root, commitSha) => {
|
|
8894
|
+
if (!commitSha) return null;
|
|
8895
|
+
const r = spawn11.sync(
|
|
8896
|
+
"git",
|
|
8897
|
+
["rev-parse", `${commitSha}^{tree}`],
|
|
8898
|
+
{ cwd: root, encoding: "utf-8", windowsHide: true }
|
|
8899
|
+
);
|
|
8900
|
+
if (r.status !== 0 || typeof r.stdout !== "string") return null;
|
|
8901
|
+
return r.stdout.trim() || null;
|
|
8902
|
+
};
|
|
8903
|
+
const allTreesMatch = repos.every((repo) => {
|
|
8904
|
+
const curTree = treeOf(repo.root, captured[repo.name]);
|
|
8905
|
+
const prevTree = treeOf(repo.root, prev.repos[repo.name] ?? null);
|
|
8906
|
+
return curTree !== null && curTree === prevTree;
|
|
8907
|
+
});
|
|
8908
|
+
if (allTreesMatch) {
|
|
8856
8909
|
rollbackRefs();
|
|
8857
8910
|
return null;
|
|
8858
8911
|
}
|
|
@@ -8874,7 +8927,7 @@ function clearCheckpoints(scopeHash, repoRoots) {
|
|
|
8874
8927
|
for (const root of repoRoots) {
|
|
8875
8928
|
for (const entry of manifest.entries) {
|
|
8876
8929
|
const refName = `refs/wd/${scopeHash}/${entry.id}`;
|
|
8877
|
-
|
|
8930
|
+
spawn11.sync("git", ["update-ref", "-d", refName], {
|
|
8878
8931
|
cwd: root,
|
|
8879
8932
|
encoding: "utf-8",
|
|
8880
8933
|
windowsHide: true
|
|
@@ -8882,21 +8935,21 @@ function clearCheckpoints(scopeHash, repoRoots) {
|
|
|
8882
8935
|
}
|
|
8883
8936
|
}
|
|
8884
8937
|
const file = manifestPath(scopeHash);
|
|
8885
|
-
if (
|
|
8938
|
+
if (fs38.existsSync(file)) {
|
|
8886
8939
|
try {
|
|
8887
|
-
|
|
8940
|
+
fs38.unlinkSync(file);
|
|
8888
8941
|
} catch {
|
|
8889
8942
|
}
|
|
8890
8943
|
}
|
|
8891
8944
|
}
|
|
8892
8945
|
|
|
8893
8946
|
// src/core/scope-manager.ts
|
|
8894
|
-
import
|
|
8895
|
-
import
|
|
8947
|
+
import path38 from "path";
|
|
8948
|
+
import crypto5 from "crypto";
|
|
8896
8949
|
var scopes = /* @__PURE__ */ new Map();
|
|
8897
8950
|
function hashFor(paths) {
|
|
8898
8951
|
const key = paths.slice().sort().join("|");
|
|
8899
|
-
return
|
|
8952
|
+
return crypto5.createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
8900
8953
|
}
|
|
8901
8954
|
var ScopePathRejectedError = class extends Error {
|
|
8902
8955
|
constructor(rejected) {
|
|
@@ -8908,7 +8961,7 @@ var ScopePathRejectedError = class extends Error {
|
|
|
8908
8961
|
}
|
|
8909
8962
|
};
|
|
8910
8963
|
function normaliseForCompare(p) {
|
|
8911
|
-
return
|
|
8964
|
+
return path38.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
8912
8965
|
}
|
|
8913
8966
|
function rejectedPaths(normalised) {
|
|
8914
8967
|
const config = loadConfig();
|
|
@@ -8926,7 +8979,7 @@ function rejectedPaths(normalised) {
|
|
|
8926
8979
|
});
|
|
8927
8980
|
}
|
|
8928
8981
|
function registerScope(paths, label2) {
|
|
8929
|
-
const normalised = paths.map((p) =>
|
|
8982
|
+
const normalised = paths.map((p) => path38.resolve(p));
|
|
8930
8983
|
const rejected = rejectedPaths(normalised);
|
|
8931
8984
|
if (rejected.length > 0) throw new ScopePathRejectedError(rejected);
|
|
8932
8985
|
const hash = hashFor(normalised);
|
|
@@ -8940,7 +8993,7 @@ function registerScope(paths, label2) {
|
|
|
8940
8993
|
const scope = {
|
|
8941
8994
|
hash,
|
|
8942
8995
|
paths: normalised,
|
|
8943
|
-
label: label2 ??
|
|
8996
|
+
label: label2 ?? path38.basename(normalised[0]),
|
|
8944
8997
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8945
8998
|
ended: false
|
|
8946
8999
|
};
|
|
@@ -9013,7 +9066,7 @@ function mountScopeRoutes(app, opts) {
|
|
|
9013
9066
|
function workingTreeFingerprint(paths) {
|
|
9014
9067
|
const parts = [];
|
|
9015
9068
|
for (const p of paths) {
|
|
9016
|
-
const r =
|
|
9069
|
+
const r = spawn12.sync(
|
|
9017
9070
|
"git",
|
|
9018
9071
|
["status", "--porcelain", "--no-renames", "-z"],
|
|
9019
9072
|
{ cwd: p, encoding: "utf-8", windowsHide: true }
|
|
@@ -9137,7 +9190,7 @@ function mountScopeRoutes(app, opts) {
|
|
|
9137
9190
|
const fromSha = fromEntry.repos[p] ?? "HEAD";
|
|
9138
9191
|
const toSha = toEntry === void 0 ? "working" : toEntry.repos[p] ?? "HEAD";
|
|
9139
9192
|
return {
|
|
9140
|
-
name:
|
|
9193
|
+
name: path39.basename(p),
|
|
9141
9194
|
root: p,
|
|
9142
9195
|
files: computeRangeDiff({ root: p, fromRef: fromSha, toRef: toSha })
|
|
9143
9196
|
};
|
|
@@ -9153,13 +9206,21 @@ function mountScopeRoutes(app, opts) {
|
|
|
9153
9206
|
}
|
|
9154
9207
|
const resolved = scope.paths.map((p) => resolveRepoDiff(p, base));
|
|
9155
9208
|
const repos = scope.paths.map((p, i) => ({
|
|
9156
|
-
name:
|
|
9209
|
+
name: path39.basename(p),
|
|
9157
9210
|
root: p,
|
|
9158
9211
|
resolvedBase: resolved[i].resolvedBase,
|
|
9159
9212
|
files: computeDiff({ root: p, diffArg: resolved[i].diffArg })
|
|
9160
9213
|
}));
|
|
9161
9214
|
const resolvedBase = resolved[0]?.resolvedBase ?? "HEAD";
|
|
9162
|
-
|
|
9215
|
+
const head = git(["rev-parse", "--abbrev-ref", "HEAD"], scope.paths[0]);
|
|
9216
|
+
const headBranch = head.exitCode === 0 && head.stdout && head.stdout !== "HEAD" ? head.stdout : void 0;
|
|
9217
|
+
return c.json({
|
|
9218
|
+
scopeHash: scope.hash,
|
|
9219
|
+
base,
|
|
9220
|
+
resolvedBase,
|
|
9221
|
+
headBranch,
|
|
9222
|
+
repos
|
|
9223
|
+
});
|
|
9163
9224
|
} catch (err) {
|
|
9164
9225
|
return c.json({ error: err.message }, 500);
|
|
9165
9226
|
}
|
|
@@ -9394,14 +9455,15 @@ function sessionToWire(s) {
|
|
|
9394
9455
|
function computeSessionDiff(s, base) {
|
|
9395
9456
|
const resolved = s.paths.map((p) => resolveRepoDiff(p, base, s.baseBranch));
|
|
9396
9457
|
const repos = s.paths.map((p, i) => ({
|
|
9397
|
-
name:
|
|
9458
|
+
name: path40.basename(p),
|
|
9398
9459
|
root: p,
|
|
9399
9460
|
resolvedBase: resolved[i].resolvedBase,
|
|
9400
9461
|
files: computeDiff({ root: p, diffArg: resolved[i].diffArg })
|
|
9401
9462
|
}));
|
|
9402
9463
|
return { repos, resolvedBase: resolved[0]?.resolvedBase ?? "HEAD" };
|
|
9403
9464
|
}
|
|
9404
|
-
async function startWebServer() {
|
|
9465
|
+
async function startWebServer(opts = {}) {
|
|
9466
|
+
const { lean = false } = opts;
|
|
9405
9467
|
const webRoot = resolveWebRoot();
|
|
9406
9468
|
if (!webRoot) {
|
|
9407
9469
|
throw new Error(
|
|
@@ -9413,25 +9475,27 @@ async function startWebServer() {
|
|
|
9413
9475
|
for (const cb of sseListeners) cb({ event, data });
|
|
9414
9476
|
};
|
|
9415
9477
|
const home = os12.homedir();
|
|
9416
|
-
const historyPath =
|
|
9478
|
+
const historyPath = path40.join(home, ".work", "history.json");
|
|
9417
9479
|
const onHistoryChange = () => broadcast("sessions-changed", { ts: Date.now() });
|
|
9418
|
-
|
|
9419
|
-
const tasksPath =
|
|
9480
|
+
fs39.watchFile(historyPath, { interval: 1e3 }, onHistoryChange);
|
|
9481
|
+
const tasksPath = path40.join(home, ".work", "tasks.json");
|
|
9420
9482
|
const onTasksChange = () => broadcast("tasks-changed", { ts: Date.now() });
|
|
9421
|
-
|
|
9483
|
+
fs39.watchFile(tasksPath, { interval: 1e3 }, onTasksChange);
|
|
9422
9484
|
const projectsRoot = claudeProjectsRoot();
|
|
9423
9485
|
let activityWatcher = null;
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9486
|
+
if (!lean) {
|
|
9487
|
+
try {
|
|
9488
|
+
if (fs39.existsSync(projectsRoot)) {
|
|
9489
|
+
activityWatcher = createFsWatcher({
|
|
9490
|
+
roots: [projectsRoot],
|
|
9491
|
+
debounceMs: 250,
|
|
9492
|
+
onChange: () => broadcast("sessions-changed", { ts: Date.now() })
|
|
9493
|
+
});
|
|
9494
|
+
}
|
|
9495
|
+
} catch {
|
|
9431
9496
|
}
|
|
9432
|
-
} catch {
|
|
9433
9497
|
}
|
|
9434
|
-
const decayTick = setInterval(
|
|
9498
|
+
const decayTick = lean ? null : setInterval(
|
|
9435
9499
|
() => broadcast("sessions-changed", { ts: Date.now() }),
|
|
9436
9500
|
1e4
|
|
9437
9501
|
);
|
|
@@ -9496,9 +9560,9 @@ async function startWebServer() {
|
|
|
9496
9560
|
url: handle.url,
|
|
9497
9561
|
port: handle.port,
|
|
9498
9562
|
stop: async () => {
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
clearInterval(decayTick);
|
|
9563
|
+
fs39.unwatchFile(historyPath, onHistoryChange);
|
|
9564
|
+
fs39.unwatchFile(tasksPath, onTasksChange);
|
|
9565
|
+
if (decayTick) clearInterval(decayTick);
|
|
9502
9566
|
activityWatcher?.stop();
|
|
9503
9567
|
disposeAllWatchers();
|
|
9504
9568
|
for (const scope of listScopes()) {
|
|
@@ -9559,12 +9623,12 @@ function info2(message) {
|
|
|
9559
9623
|
process.stderr.write(message + "\n");
|
|
9560
9624
|
}
|
|
9561
9625
|
function urlFilePath() {
|
|
9562
|
-
return
|
|
9626
|
+
return path41.join(os13.homedir(), ".work", "web.url");
|
|
9563
9627
|
}
|
|
9564
9628
|
function pidFilePath() {
|
|
9565
|
-
return
|
|
9629
|
+
return path41.join(os13.homedir(), ".work", "web.pid");
|
|
9566
9630
|
}
|
|
9567
|
-
function
|
|
9631
|
+
function isPidAlive(pid) {
|
|
9568
9632
|
try {
|
|
9569
9633
|
process.kill(pid, 0);
|
|
9570
9634
|
return true;
|
|
@@ -9572,9 +9636,9 @@ function isPidAlive2(pid) {
|
|
|
9572
9636
|
return false;
|
|
9573
9637
|
}
|
|
9574
9638
|
}
|
|
9575
|
-
function
|
|
9639
|
+
function readPid() {
|
|
9576
9640
|
try {
|
|
9577
|
-
const raw =
|
|
9641
|
+
const raw = fs40.readFileSync(pidFilePath(), "utf-8").trim();
|
|
9578
9642
|
const n = Number(raw);
|
|
9579
9643
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
9580
9644
|
} catch {
|
|
@@ -9583,7 +9647,7 @@ function readPid2() {
|
|
|
9583
9647
|
}
|
|
9584
9648
|
function readUrl() {
|
|
9585
9649
|
try {
|
|
9586
|
-
const v =
|
|
9650
|
+
const v = fs40.readFileSync(urlFilePath(), "utf-8").trim();
|
|
9587
9651
|
return v || null;
|
|
9588
9652
|
} catch {
|
|
9589
9653
|
return null;
|
|
@@ -9601,19 +9665,19 @@ async function pingsAlive(url, timeoutMs = 500) {
|
|
|
9601
9665
|
}
|
|
9602
9666
|
}
|
|
9603
9667
|
function stopExisting() {
|
|
9604
|
-
const pid =
|
|
9668
|
+
const pid = readPid();
|
|
9605
9669
|
if (!pid) {
|
|
9606
9670
|
info2(chalk23.gray("No work web running."));
|
|
9607
9671
|
return false;
|
|
9608
9672
|
}
|
|
9609
|
-
if (!
|
|
9673
|
+
if (!isPidAlive(pid)) {
|
|
9610
9674
|
info2(chalk23.gray(`Stale PID ${pid} \u2014 cleaning up.`));
|
|
9611
9675
|
try {
|
|
9612
|
-
|
|
9676
|
+
fs40.unlinkSync(pidFilePath());
|
|
9613
9677
|
} catch {
|
|
9614
9678
|
}
|
|
9615
9679
|
try {
|
|
9616
|
-
|
|
9680
|
+
fs40.unlinkSync(urlFilePath());
|
|
9617
9681
|
} catch {
|
|
9618
9682
|
}
|
|
9619
9683
|
return false;
|
|
@@ -9622,11 +9686,11 @@ function stopExisting() {
|
|
|
9622
9686
|
process.kill(pid);
|
|
9623
9687
|
info2(chalk23.gray(`Stopped work web (PID ${pid}).`));
|
|
9624
9688
|
try {
|
|
9625
|
-
|
|
9689
|
+
fs40.unlinkSync(pidFilePath());
|
|
9626
9690
|
} catch {
|
|
9627
9691
|
}
|
|
9628
9692
|
try {
|
|
9629
|
-
|
|
9693
|
+
fs40.unlinkSync(urlFilePath());
|
|
9630
9694
|
} catch {
|
|
9631
9695
|
}
|
|
9632
9696
|
return true;
|
|
@@ -9646,14 +9710,19 @@ var webCommand = {
|
|
|
9646
9710
|
type: "boolean",
|
|
9647
9711
|
default: false,
|
|
9648
9712
|
describe: "Stop a running work web instance and exit."
|
|
9713
|
+
}).option("lean", {
|
|
9714
|
+
type: "boolean",
|
|
9715
|
+
default: false,
|
|
9716
|
+
hidden: true,
|
|
9717
|
+
describe: "Internal: start without dashboard-only features (Claude activity watcher + hooks). Used by `wd` when it auto-starts work web for a diff-only session."
|
|
9649
9718
|
}),
|
|
9650
9719
|
handler: async (argv) => {
|
|
9651
9720
|
if (argv.stop) {
|
|
9652
9721
|
stopExisting();
|
|
9653
9722
|
process.exit(0);
|
|
9654
9723
|
}
|
|
9655
|
-
const existingPid =
|
|
9656
|
-
if (existingPid &&
|
|
9724
|
+
const existingPid = readPid();
|
|
9725
|
+
if (existingPid && isPidAlive(existingPid)) {
|
|
9657
9726
|
const url = readUrl();
|
|
9658
9727
|
if (url && await pingsAlive(url)) {
|
|
9659
9728
|
info2(
|
|
@@ -9672,55 +9741,64 @@ var webCommand = {
|
|
|
9672
9741
|
process.exit(1);
|
|
9673
9742
|
}
|
|
9674
9743
|
try {
|
|
9675
|
-
|
|
9744
|
+
fs40.unlinkSync(pidFilePath());
|
|
9676
9745
|
} catch {
|
|
9677
9746
|
}
|
|
9678
9747
|
try {
|
|
9679
|
-
|
|
9748
|
+
fs40.unlinkSync(urlFilePath());
|
|
9680
9749
|
} catch {
|
|
9681
9750
|
}
|
|
9682
|
-
const
|
|
9751
|
+
const lean = !!argv.lean || process.env.WORK_WEB_LEAN === "1";
|
|
9752
|
+
const handle = await startWebServer({ lean });
|
|
9683
9753
|
try {
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9754
|
+
fs40.mkdirSync(path41.dirname(urlFilePath()), { recursive: true });
|
|
9755
|
+
fs40.writeFileSync(urlFilePath(), handle.url);
|
|
9756
|
+
fs40.writeFileSync(pidFilePath(), String(process.pid));
|
|
9687
9757
|
} catch {
|
|
9688
9758
|
}
|
|
9689
|
-
info2(
|
|
9759
|
+
info2(
|
|
9760
|
+
chalk23.gray(
|
|
9761
|
+
`work web running at ${handle.url}${lean ? " (lean \u2014 diff-only mode)" : ""}`
|
|
9762
|
+
)
|
|
9763
|
+
);
|
|
9690
9764
|
info2(chalk23.gray("Press Ctrl+C to stop. Or: `work web --stop` from another shell."));
|
|
9691
9765
|
if (argv.open) openUrl(handle.url);
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9766
|
+
if (!lean) {
|
|
9767
|
+
await Promise.all([
|
|
9768
|
+
installCommandHook({
|
|
9769
|
+
owner: "web",
|
|
9770
|
+
event: "UserPromptSubmit",
|
|
9771
|
+
command: "work hook prompt-submit",
|
|
9772
|
+
timeoutSec: 5
|
|
9773
|
+
}),
|
|
9774
|
+
installCommandHook({
|
|
9775
|
+
owner: "web",
|
|
9776
|
+
event: "Stop",
|
|
9777
|
+
command: "work hook stop",
|
|
9778
|
+
timeoutSec: 5
|
|
9779
|
+
})
|
|
9780
|
+
]).catch(() => {
|
|
9781
|
+
});
|
|
9782
|
+
}
|
|
9707
9783
|
const shutdown = () => {
|
|
9708
9784
|
info2(chalk23.gray("\nStopping work web."));
|
|
9709
9785
|
try {
|
|
9710
|
-
|
|
9711
|
-
} catch {
|
|
9712
|
-
}
|
|
9713
|
-
try {
|
|
9714
|
-
fs37.unlinkSync(pidFilePath());
|
|
9786
|
+
fs40.unlinkSync(urlFilePath());
|
|
9715
9787
|
} catch {
|
|
9716
9788
|
}
|
|
9717
9789
|
try {
|
|
9718
|
-
|
|
9790
|
+
fs40.unlinkSync(pidFilePath());
|
|
9719
9791
|
} catch {
|
|
9720
9792
|
}
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9793
|
+
if (!lean) {
|
|
9794
|
+
try {
|
|
9795
|
+
removeCommandHookSync("web", "UserPromptSubmit");
|
|
9796
|
+
} catch {
|
|
9797
|
+
}
|
|
9798
|
+
try {
|
|
9799
|
+
removeCommandHookSync("web", "Stop");
|
|
9800
|
+
} catch {
|
|
9801
|
+
}
|
|
9724
9802
|
}
|
|
9725
9803
|
handle.stop();
|
|
9726
9804
|
process.exit(0);
|
|
@@ -9729,11 +9807,11 @@ var webCommand = {
|
|
|
9729
9807
|
process.on("SIGTERM", shutdown);
|
|
9730
9808
|
process.on("exit", () => {
|
|
9731
9809
|
try {
|
|
9732
|
-
|
|
9810
|
+
fs40.unlinkSync(pidFilePath());
|
|
9733
9811
|
} catch {
|
|
9734
9812
|
}
|
|
9735
9813
|
try {
|
|
9736
|
-
|
|
9814
|
+
fs40.unlinkSync(urlFilePath());
|
|
9737
9815
|
} catch {
|
|
9738
9816
|
}
|
|
9739
9817
|
});
|
|
@@ -9749,7 +9827,7 @@ function computeHookOutput(input2) {
|
|
|
9749
9827
|
const activity = readSessionActivity(session);
|
|
9750
9828
|
if (activity.state === "stale") return null;
|
|
9751
9829
|
const sessionId = sessionIdFor(session);
|
|
9752
|
-
const pending =
|
|
9830
|
+
const pending = readPendingForWorktree(session);
|
|
9753
9831
|
if (pending.length === 0) return null;
|
|
9754
9832
|
const text = formatPendingForPrompt(pending);
|
|
9755
9833
|
if (!text) return null;
|
|
@@ -9808,7 +9886,7 @@ var hookCommand = {
|
|
|
9808
9886
|
|
|
9809
9887
|
// src/commands/run.ts
|
|
9810
9888
|
import chalk24 from "chalk";
|
|
9811
|
-
import
|
|
9889
|
+
import spawn13 from "cross-spawn";
|
|
9812
9890
|
|
|
9813
9891
|
// src/core/fleet.ts
|
|
9814
9892
|
function selectSessions(sessions, filter) {
|
|
@@ -9850,7 +9928,7 @@ function killAllChildren(signal = "SIGTERM") {
|
|
|
9850
9928
|
function runInPath(unit, cmd, prefix) {
|
|
9851
9929
|
const { bin, args } = shellInvocation(cmd);
|
|
9852
9930
|
return new Promise((resolve) => {
|
|
9853
|
-
const child =
|
|
9931
|
+
const child = spawn13(bin, args, {
|
|
9854
9932
|
cwd: unit.path,
|
|
9855
9933
|
stdio: prefix ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
9856
9934
|
shell: false
|
|
@@ -10082,15 +10160,15 @@ var runCommand = {
|
|
|
10082
10160
|
};
|
|
10083
10161
|
|
|
10084
10162
|
// src/commands/broadcast.ts
|
|
10085
|
-
import
|
|
10163
|
+
import fs42 from "fs";
|
|
10086
10164
|
import chalk25 from "chalk";
|
|
10087
10165
|
|
|
10088
10166
|
// src/core/broadcast.ts
|
|
10089
|
-
import
|
|
10090
|
-
import
|
|
10167
|
+
import crypto6 from "crypto";
|
|
10168
|
+
import fs41 from "fs";
|
|
10091
10169
|
function readComments(file) {
|
|
10092
10170
|
try {
|
|
10093
|
-
const parsed = JSON.parse(
|
|
10171
|
+
const parsed = JSON.parse(fs41.readFileSync(file, "utf-8"));
|
|
10094
10172
|
return Array.isArray(parsed) ? parsed : [];
|
|
10095
10173
|
} catch {
|
|
10096
10174
|
return [];
|
|
@@ -10098,10 +10176,10 @@ function readComments(file) {
|
|
|
10098
10176
|
}
|
|
10099
10177
|
async function appendLocked(sessionId, body) {
|
|
10100
10178
|
const file = commentsFileFor(sessionId);
|
|
10101
|
-
|
|
10179
|
+
fs41.mkdirSync(commentsDir(), { recursive: true });
|
|
10102
10180
|
ensureFile(file, "[]");
|
|
10103
10181
|
const comment = {
|
|
10104
|
-
id:
|
|
10182
|
+
id: crypto6.randomBytes(8).toString("hex"),
|
|
10105
10183
|
repo: "",
|
|
10106
10184
|
file: "",
|
|
10107
10185
|
line: 0,
|
|
@@ -10135,7 +10213,7 @@ async function broadcastPrompt(sessions, filter, prompt) {
|
|
|
10135
10213
|
// src/commands/broadcast.ts
|
|
10136
10214
|
function readStdin() {
|
|
10137
10215
|
try {
|
|
10138
|
-
return
|
|
10216
|
+
return fs42.readFileSync(0, "utf-8");
|
|
10139
10217
|
} catch {
|
|
10140
10218
|
return "";
|
|
10141
10219
|
}
|
|
@@ -10210,8 +10288,8 @@ var broadcastCommand = {
|
|
|
10210
10288
|
};
|
|
10211
10289
|
|
|
10212
10290
|
// src/completions/index.ts
|
|
10213
|
-
import
|
|
10214
|
-
import
|
|
10291
|
+
import fs43 from "fs";
|
|
10292
|
+
import path42 from "path";
|
|
10215
10293
|
function completionHandler(current, argv, done) {
|
|
10216
10294
|
const rawArgs = argv._;
|
|
10217
10295
|
const args = rawArgs.slice(1, -1);
|
|
@@ -10315,7 +10393,7 @@ function completeTreeRemoveList(command, args, current, config, done) {
|
|
|
10315
10393
|
}
|
|
10316
10394
|
function completeRepoBranches(alias, current, config, done) {
|
|
10317
10395
|
const repoPath = config.repos[alias];
|
|
10318
|
-
if (!repoPath || !
|
|
10396
|
+
if (!repoPath || !fs43.existsSync(repoPath)) {
|
|
10319
10397
|
done([]);
|
|
10320
10398
|
return;
|
|
10321
10399
|
}
|
|
@@ -10324,19 +10402,19 @@ function completeRepoBranches(alias, current, config, done) {
|
|
|
10324
10402
|
done(branches);
|
|
10325
10403
|
}
|
|
10326
10404
|
function completeGroupBranches(groupName, repoAliases, current, config, done) {
|
|
10327
|
-
const groupDir =
|
|
10328
|
-
if (!
|
|
10405
|
+
const groupDir = path42.join(config.worktreesRoot, groupName);
|
|
10406
|
+
if (!fs43.existsSync(groupDir)) {
|
|
10329
10407
|
done([]);
|
|
10330
10408
|
return;
|
|
10331
10409
|
}
|
|
10332
10410
|
try {
|
|
10333
|
-
const branchDirs =
|
|
10334
|
-
const bdPath =
|
|
10411
|
+
const branchDirs = fs43.readdirSync(groupDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
10412
|
+
const bdPath = path42.join(groupDir, d.name);
|
|
10335
10413
|
for (const alias of repoAliases) {
|
|
10336
10414
|
const repoPath = config.repos[alias];
|
|
10337
10415
|
if (!repoPath) continue;
|
|
10338
|
-
const subPath =
|
|
10339
|
-
if (
|
|
10416
|
+
const subPath = path42.join(bdPath, path42.basename(repoPath));
|
|
10417
|
+
if (fs43.existsSync(subPath)) {
|
|
10340
10418
|
const branch = getCurrentBranch(subPath);
|
|
10341
10419
|
if (branch) return branch;
|
|
10342
10420
|
}
|
|
@@ -10350,7 +10428,7 @@ function completeGroupBranches(groupName, repoAliases, current, config, done) {
|
|
|
10350
10428
|
}
|
|
10351
10429
|
|
|
10352
10430
|
// src/version.ts
|
|
10353
|
-
var VERSION = true ? "1.5.
|
|
10431
|
+
var VERSION = true ? "1.5.1" : "dev";
|
|
10354
10432
|
|
|
10355
10433
|
// src/cli.ts
|
|
10356
10434
|
function showHelp() {
|