@gtc6244/immediacy-daemon 0.1.0
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/src/actions.d.ts +22 -0
- package/dist/src/actions.js +44 -0
- package/dist/src/actions.js.map +1 -0
- package/dist/src/agent/agent.d.ts +89 -0
- package/dist/src/agent/agent.js +22 -0
- package/dist/src/agent/agent.js.map +1 -0
- package/dist/src/agent/claude.d.ts +70 -0
- package/dist/src/agent/claude.js +404 -0
- package/dist/src/agent/claude.js.map +1 -0
- package/dist/src/agent/codex.d.ts +44 -0
- package/dist/src/agent/codex.js +303 -0
- package/dist/src/agent/codex.js.map +1 -0
- package/dist/src/agent/mock.d.ts +31 -0
- package/dist/src/agent/mock.js +155 -0
- package/dist/src/agent/mock.js.map +1 -0
- package/dist/src/agent/model-error.d.ts +8 -0
- package/dist/src/agent/model-error.js +25 -0
- package/dist/src/agent/model-error.js.map +1 -0
- package/dist/src/agent/openrouter.d.ts +38 -0
- package/dist/src/agent/openrouter.js +60 -0
- package/dist/src/agent/openrouter.js.map +1 -0
- package/dist/src/agent/workspace-scope.d.ts +25 -0
- package/dist/src/agent/workspace-scope.js +71 -0
- package/dist/src/agent/workspace-scope.js.map +1 -0
- package/dist/src/allowlist.d.ts +56 -0
- package/dist/src/allowlist.js +141 -0
- package/dist/src/allowlist.js.map +1 -0
- package/dist/src/chatlog.d.ts +50 -0
- package/dist/src/chatlog.js +179 -0
- package/dist/src/chatlog.js.map +1 -0
- package/dist/src/cli/paths.d.ts +40 -0
- package/dist/src/cli/paths.js +76 -0
- package/dist/src/cli/paths.js.map +1 -0
- package/dist/src/cli/service.d.ts +20 -0
- package/dist/src/cli/service.js +245 -0
- package/dist/src/cli/service.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +227 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +33 -0
- package/dist/src/config.js +122 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/credentials.d.ts +38 -0
- package/dist/src/credentials.js +120 -0
- package/dist/src/credentials.js.map +1 -0
- package/dist/src/crypto.d.ts +49 -0
- package/dist/src/crypto.js +202 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/daemon-identity.d.ts +31 -0
- package/dist/src/daemon-identity.js +97 -0
- package/dist/src/daemon-identity.js.map +1 -0
- package/dist/src/envelope.d.ts +14 -0
- package/dist/src/envelope.js +28 -0
- package/dist/src/envelope.js.map +1 -0
- package/dist/src/events.d.ts +40 -0
- package/dist/src/events.js +24 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/forward.d.ts +51 -0
- package/dist/src/forward.js +170 -0
- package/dist/src/forward.js.map +1 -0
- package/dist/src/fs-scope.d.ts +17 -0
- package/dist/src/fs-scope.js +47 -0
- package/dist/src/fs-scope.js.map +1 -0
- package/dist/src/github.d.ts +70 -0
- package/dist/src/github.js +178 -0
- package/dist/src/github.js.map +1 -0
- package/dist/src/gitops.d.ts +113 -0
- package/dist/src/gitops.js +557 -0
- package/dist/src/gitops.js.map +1 -0
- package/dist/src/hub.d.ts +50 -0
- package/dist/src/hub.js +92 -0
- package/dist/src/hub.js.map +1 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +56 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/jobs/scheduler.d.ts +94 -0
- package/dist/src/jobs/scheduler.js +373 -0
- package/dist/src/jobs/scheduler.js.map +1 -0
- package/dist/src/jobs/store.d.ts +33 -0
- package/dist/src/jobs/store.js +111 -0
- package/dist/src/jobs/store.js.map +1 -0
- package/dist/src/log.d.ts +61 -0
- package/dist/src/log.js +166 -0
- package/dist/src/log.js.map +1 -0
- package/dist/src/migrate-state-dir.d.ts +1 -0
- package/dist/src/migrate-state-dir.js +47 -0
- package/dist/src/migrate-state-dir.js.map +1 -0
- package/dist/src/models.d.ts +36 -0
- package/dist/src/models.js +71 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/permission-gate.d.ts +48 -0
- package/dist/src/permission-gate.js +93 -0
- package/dist/src/permission-gate.js.map +1 -0
- package/dist/src/permission-scope.d.ts +77 -0
- package/dist/src/permission-scope.js +244 -0
- package/dist/src/permission-scope.js.map +1 -0
- package/dist/src/ports.d.ts +71 -0
- package/dist/src/ports.js +216 -0
- package/dist/src/ports.js.map +1 -0
- package/dist/src/projects.d.ts +83 -0
- package/dist/src/projects.js +205 -0
- package/dist/src/projects.js.map +1 -0
- package/dist/src/protocol.d.ts +1226 -0
- package/dist/src/protocol.js +16 -0
- package/dist/src/protocol.js.map +1 -0
- package/dist/src/repo-sync.d.ts +49 -0
- package/dist/src/repo-sync.js +83 -0
- package/dist/src/repo-sync.js.map +1 -0
- package/dist/src/server.d.ts +33 -0
- package/dist/src/server.js +973 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/terminal.d.ts +32 -0
- package/dist/src/terminal.js +92 -0
- package/dist/src/terminal.js.map +1 -0
- package/dist/src/titler.d.ts +15 -0
- package/dist/src/titler.js +72 -0
- package/dist/src/titler.js.map +1 -0
- package/dist/src/types.d.ts +99 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/usage-limit.d.ts +49 -0
- package/dist/src/usage-limit.js +83 -0
- package/dist/src/usage-limit.js.map +1 -0
- package/dist/src/usage.d.ts +23 -0
- package/dist/src/usage.js +63 -0
- package/dist/src/usage.js.map +1 -0
- package/dist/src/workspace.d.ts +303 -0
- package/dist/src/workspace.js +1086 -0
- package/dist/src/workspace.js.map +1 -0
- package/dist/src/worktree.d.ts +133 -0
- package/dist/src/worktree.js +561 -0
- package/dist/src/worktree.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git review-and-merge operations for a workspace: status, commit, push, PR.
|
|
3
|
+
*
|
|
4
|
+
* These power the Conductor-style "review the diff → commit → push → open a
|
|
5
|
+
* PR" path from a pure-front-end client. Everything runs inside the worktree;
|
|
6
|
+
* the PR goes through the `gh` CLI so its keychain auth is reused (same stance
|
|
7
|
+
* as github.ts — no tokens on the wire or in source).
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { simpleGit } from "simple-git";
|
|
12
|
+
import { diffStat } from "./worktree.js";
|
|
13
|
+
const exec = promisify(execFile);
|
|
14
|
+
/**
|
|
15
|
+
* `git remote get-url origin` for a repo, cached by path. Used to tag project /
|
|
16
|
+
* workspace summaries with an origin URL so a client connected to several
|
|
17
|
+
* daemons can match the "same" project across them (the basis for moving a
|
|
18
|
+
* workspace between daemons). A repo with no origin remote caches `undefined`.
|
|
19
|
+
*
|
|
20
|
+
* Origin rarely changes for a given repo path, so the cache avoids a git spawn
|
|
21
|
+
* per workspace on every list broadcast; call `forgetOriginUrl(repo)` if a
|
|
22
|
+
* project's remote is ever rewired.
|
|
23
|
+
*/
|
|
24
|
+
const originUrlCache = new Map();
|
|
25
|
+
export async function originUrl(repoPath) {
|
|
26
|
+
if (originUrlCache.has(repoPath))
|
|
27
|
+
return originUrlCache.get(repoPath);
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
const out = await simpleGit(repoPath).raw(["remote", "get-url", "origin"]);
|
|
31
|
+
url = out.trim() || undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
url = undefined; // no origin remote (blank/local project)
|
|
35
|
+
}
|
|
36
|
+
originUrlCache.set(repoPath, url);
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
/** Drop a cached origin URL (e.g. after a remote is added/changed). */
|
|
40
|
+
export function forgetOriginUrl(repoPath) {
|
|
41
|
+
originUrlCache.delete(repoPath);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True if `ref` resolves in this worktree (branch, tag, MERGE_HEAD, …). Must
|
|
45
|
+
* check the *output*, not just whether the call threw: `git rev-parse --verify
|
|
46
|
+
* --quiet <missing>` exits non-zero with empty stdout, and simple-git resolves
|
|
47
|
+
* (does not reject) that — so a missing ref comes back as an empty string.
|
|
48
|
+
*/
|
|
49
|
+
async function refExists(git, ref) {
|
|
50
|
+
try {
|
|
51
|
+
const out = await git.raw(["rev-parse", "--verify", "--quiet", ref]);
|
|
52
|
+
return out.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** A merge is unfinished exactly while MERGE_HEAD is present (incl. conflicts). */
|
|
59
|
+
async function mergeInProgress(git) {
|
|
60
|
+
return refExists(git, "MERGE_HEAD");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether the branch has an upstream configured (`branch.<name>.remote`, set by
|
|
64
|
+
* the first `push -u`). Unlike the remote-tracking ref, this config survives the
|
|
65
|
+
* remote branch being deleted — which GitHub does when a PR merges — so it is the
|
|
66
|
+
* reliable signal that the branch was ever pushed and may still carry a (now
|
|
67
|
+
* merged) PR worth querying.
|
|
68
|
+
*/
|
|
69
|
+
async function hasUpstream(git, branch) {
|
|
70
|
+
try {
|
|
71
|
+
// Scope to this repo's config: reading global/system config could report a
|
|
72
|
+
// false upstream (and trigger a needless `gh` call). `--get` parses the key
|
|
73
|
+
// as the segment after the final dot, so a `.`-containing branch name in the
|
|
74
|
+
// subsection is handled correctly.
|
|
75
|
+
const out = await git.raw([
|
|
76
|
+
"config",
|
|
77
|
+
"--local",
|
|
78
|
+
"--get",
|
|
79
|
+
`branch.${branch}.remote`,
|
|
80
|
+
]);
|
|
81
|
+
return out.trim().length > 0;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The ref this worktree is measured/reconciled against — the *remote* base
|
|
89
|
+
* (`origin/<base>`) when it exists, else the local base. Using the remote ref
|
|
90
|
+
* keeps ahead/behind pointed at the real merge-to branch, and it matches what
|
|
91
|
+
* `gitResolve` fetches + merges, so the behind count and the merge target can't
|
|
92
|
+
* drift into a "Resolve that does nothing" loop.
|
|
93
|
+
*/
|
|
94
|
+
async function baseCompareRef(git, baseBranch) {
|
|
95
|
+
return (await refExists(git, `refs/remotes/origin/${baseBranch}`))
|
|
96
|
+
? `origin/${baseBranch}`
|
|
97
|
+
: baseBranch;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Whether reconciling `mergeRef` into `headRef` would conflict — the signal that
|
|
101
|
+
* gates the "Resolve" affordance. Predicted with `git merge-tree --write-tree
|
|
102
|
+
* --quiet`, which performs the merge entirely in-memory (exit 0 = clean, 1 =
|
|
103
|
+
* conflicts) and never touches the worktree, so it is safe to run on every
|
|
104
|
+
* status poll. `mergeRef` is the same ref `gitResolve` fetches + merges
|
|
105
|
+
* (`baseCompareRef`), so the prediction matches what Resolve would actually do.
|
|
106
|
+
* Best effort: any failure other than the conflict exit (bad ref, ancient git)
|
|
107
|
+
* reports false, so a broken probe hides Resolve rather than misfiring.
|
|
108
|
+
*/
|
|
109
|
+
async function baseConflicts(worktreePath, headRef, mergeRef) {
|
|
110
|
+
try {
|
|
111
|
+
await exec("git", ["merge-tree", "--write-tree", "--quiet", headRef, mergeRef], {
|
|
112
|
+
cwd: worktreePath,
|
|
113
|
+
});
|
|
114
|
+
return false; // exit 0 — clean merge
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// execFile rejects with the process exit code on `.code`; merge-tree uses 1
|
|
118
|
+
// exclusively for "merge has conflicts". Any other code is an error we don't
|
|
119
|
+
// want to read as a conflict.
|
|
120
|
+
return err.code === 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Ahead/behind + dirty-file summary against the workspace's merge-to ref. */
|
|
124
|
+
export async function gitStatus(record) {
|
|
125
|
+
const git = simpleGit(record.worktreePath);
|
|
126
|
+
const compareRef = await baseCompareRef(git, record.baseBranch);
|
|
127
|
+
let ahead = 0;
|
|
128
|
+
let behind = 0;
|
|
129
|
+
try {
|
|
130
|
+
const raw = await git.raw([
|
|
131
|
+
"rev-list",
|
|
132
|
+
"--left-right",
|
|
133
|
+
"--count",
|
|
134
|
+
`${compareRef}...HEAD`,
|
|
135
|
+
]);
|
|
136
|
+
const [b, a] = raw.trim().split(/\s+/).map((n) => Number(n) || 0);
|
|
137
|
+
behind = b ?? 0;
|
|
138
|
+
ahead = a ?? 0;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* base ref may be gone (e.g. HEAD in a fresh clone) — report 0/0 */
|
|
142
|
+
}
|
|
143
|
+
// Unpushed = commits on HEAD the remote branch doesn't have yet. When the
|
|
144
|
+
// branch has never been pushed there is no `origin/<branch>` ref, so every
|
|
145
|
+
// commit ahead of the base still needs pushing — fall back to `ahead`.
|
|
146
|
+
const hasRemote = await refExists(git, `refs/remotes/origin/${record.branch}`);
|
|
147
|
+
let unpushed = ahead;
|
|
148
|
+
if (hasRemote) {
|
|
149
|
+
try {
|
|
150
|
+
const raw = await git.raw(["rev-list", "--count", `refs/remotes/origin/${record.branch}..HEAD`]);
|
|
151
|
+
unpushed = Number(raw.trim()) || 0;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
unpushed = ahead;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// A PR can only exist once the branch is pushed, so skip the `gh` call
|
|
158
|
+
// entirely for purely local branches. We gate on "ever pushed" — the
|
|
159
|
+
// remote-tracking ref OR an upstream config — rather than the remote-tracking
|
|
160
|
+
// ref alone: GitHub deletes the head branch when a PR merges, which prunes that
|
|
161
|
+
// ref, and gating on it there would blind us to the merged PR (dropping the
|
|
162
|
+
// "Archive" affordance). The upstream config outlives the branch deletion, so
|
|
163
|
+
// the merged PR is still queried. `hasPr` (an open PR) gates the "Create PR"
|
|
164
|
+
// button (hidden once one exists), "Merge", and "Resolve"; `prMerged` gates
|
|
165
|
+
// the "Archive" affordance once the PR has landed. `number`/`url` ride along so
|
|
166
|
+
// clients can surface a link to the PR (and the workspace label can follow the
|
|
167
|
+
// PR's head branch — see Workspace.gitStatus).
|
|
168
|
+
const everPushed = hasRemote || (await hasUpstream(git, record.branch));
|
|
169
|
+
const pr = everPushed ? await prInfo(record) : NO_PR;
|
|
170
|
+
const hasPr = pr.state === "OPEN";
|
|
171
|
+
const prMerged = pr.state === "MERGED";
|
|
172
|
+
// A conflict is only possible when the base has moved ahead of us; short-
|
|
173
|
+
// circuit otherwise so a merge-tree probe never runs on an up-to-date branch.
|
|
174
|
+
const conflictsWithBase = behind > 0 ? await baseConflicts(record.worktreePath, "HEAD", compareRef) : false;
|
|
175
|
+
const status = await git.status();
|
|
176
|
+
const merging = status.conflicted.length > 0 || (await mergeInProgress(git));
|
|
177
|
+
// diffStat runs `git add -AN` (intent-to-add) to count untracked lines; that
|
|
178
|
+
// mutates the index and, mid-merge, leaves conflicted entries in a state
|
|
179
|
+
// `git merge --abort` rejects ("Entry … not uptodate"). Skip it while a merge
|
|
180
|
+
// is in progress — line counts aren't shown in the conflict UI anyway.
|
|
181
|
+
const { added, removed } = merging
|
|
182
|
+
? { added: 0, removed: 0 }
|
|
183
|
+
: await diffStat(record.worktreePath, compareRef);
|
|
184
|
+
return {
|
|
185
|
+
ws: record.id,
|
|
186
|
+
branch: record.branch,
|
|
187
|
+
baseBranch: record.baseBranch,
|
|
188
|
+
ahead,
|
|
189
|
+
behind,
|
|
190
|
+
hasRemote,
|
|
191
|
+
unpushed,
|
|
192
|
+
merging,
|
|
193
|
+
hasPr,
|
|
194
|
+
prMerged,
|
|
195
|
+
...(pr.number != null ? { prNumber: pr.number } : {}),
|
|
196
|
+
...(pr.url ? { prUrl: pr.url } : {}),
|
|
197
|
+
conflictsWithBase,
|
|
198
|
+
conflicted: [...status.conflicted].sort(),
|
|
199
|
+
changed: status.files.map((f) => f.path).sort(),
|
|
200
|
+
added,
|
|
201
|
+
removed,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Stage everything and commit. Fails soft ("nothing to commit") into ok:false. */
|
|
205
|
+
export async function gitCommit(record, message) {
|
|
206
|
+
const ws = record.id;
|
|
207
|
+
const trimmed = message.trim();
|
|
208
|
+
if (!trimmed)
|
|
209
|
+
return { ws, op: "commit", ok: false, message: "commit message is empty" };
|
|
210
|
+
const git = simpleGit(record.worktreePath);
|
|
211
|
+
try {
|
|
212
|
+
await git.add(["-A"]);
|
|
213
|
+
const res = await git.commit(trimmed);
|
|
214
|
+
if (!res.commit) {
|
|
215
|
+
return { ws, op: "commit", ok: false, message: "nothing to commit" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
ws,
|
|
219
|
+
op: "commit",
|
|
220
|
+
ok: true,
|
|
221
|
+
message: `committed ${res.commit} (${res.summary.changes} change${res.summary.changes === 1 ? "" : "s"})`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
return { ws, op: "commit", ok: false, message: errText(err) };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Push the workspace branch. When `autoSetupRemote` is true (the default and the
|
|
230
|
+
* git.worktreePushAutoSetupRemote config default), sets the upstream on first
|
|
231
|
+
* push with `-u`; when false, pushes without configuring the upstream.
|
|
232
|
+
*/
|
|
233
|
+
export async function gitPush(record, autoSetupRemote = true) {
|
|
234
|
+
const ws = record.id;
|
|
235
|
+
const git = simpleGit(record.worktreePath);
|
|
236
|
+
try {
|
|
237
|
+
const args = autoSetupRemote
|
|
238
|
+
? ["-u", "origin", record.branch]
|
|
239
|
+
: ["origin", record.branch];
|
|
240
|
+
await git.push(args);
|
|
241
|
+
return { ws, op: "push", ok: true, message: `pushed ${record.branch} to origin` };
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
return { ws, op: "push", ok: false, message: errText(err) };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const NO_PR = { state: "NONE", number: null, url: null, headRef: null, title: null };
|
|
248
|
+
/** Default freshness window for a branch's cached PR state. */
|
|
249
|
+
export const DEFAULT_PR_STATE_TTL_MS = 60_000;
|
|
250
|
+
const prStateCache = new Map();
|
|
251
|
+
const prCacheKey = (record) => `${record.worktreePath}${record.branch}`;
|
|
252
|
+
async function fetchPrInfo(record) {
|
|
253
|
+
try {
|
|
254
|
+
const { stdout } = await exec("gh", ["pr", "view", record.branch, "--json", "state,number,url,headRefName,title"], { cwd: record.worktreePath });
|
|
255
|
+
const j = JSON.parse(stdout || "{}");
|
|
256
|
+
const s = j.state;
|
|
257
|
+
const state = s === "OPEN" || s === "MERGED" || s === "CLOSED" ? s : "NONE";
|
|
258
|
+
if (state === "NONE")
|
|
259
|
+
return NO_PR;
|
|
260
|
+
return {
|
|
261
|
+
state,
|
|
262
|
+
number: typeof j.number === "number" ? j.number : null,
|
|
263
|
+
url: j.url ?? null,
|
|
264
|
+
headRef: j.headRefName ?? null,
|
|
265
|
+
title: j.title ?? null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// No PR, `gh` unauthenticated/absent, or offline — treat as "no PR" so the
|
|
270
|
+
// buttons fall back to their pre-PR state rather than getting stuck.
|
|
271
|
+
return NO_PR;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The workspace branch's PR (state + number/url/head) via `gh pr view <branch>`
|
|
276
|
+
* (reuses keychain auth, same stance as prCreate), cached per branch to bound
|
|
277
|
+
* `gh` invocations on the status-poll path: at most one lookup per `ttlMs`, and
|
|
278
|
+
* concurrent callers for the same branch (a merged-check, a hasPr probe, and the
|
|
279
|
+
* label-reconcile in the same poll) ride a single in-flight request. Best effort
|
|
280
|
+
* throughout — a failed lookup resolves to {@link NO_PR} and the throttle still
|
|
281
|
+
* advances, so an unreachable remote isn't retried every poll.
|
|
282
|
+
*/
|
|
283
|
+
export async function prInfo(record, ttlMs = DEFAULT_PR_STATE_TTL_MS) {
|
|
284
|
+
const key = prCacheKey(record);
|
|
285
|
+
let e = prStateCache.get(key);
|
|
286
|
+
if (!e) {
|
|
287
|
+
e = { at: 0, inflight: null, value: NO_PR };
|
|
288
|
+
prStateCache.set(key, e);
|
|
289
|
+
}
|
|
290
|
+
if (e.inflight)
|
|
291
|
+
return e.inflight;
|
|
292
|
+
if (e.at !== 0 && Date.now() - e.at < ttlMs)
|
|
293
|
+
return e.value;
|
|
294
|
+
const entry = e;
|
|
295
|
+
entry.inflight = (async () => {
|
|
296
|
+
try {
|
|
297
|
+
entry.value = await fetchPrInfo(record);
|
|
298
|
+
return entry.value;
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
entry.at = Date.now();
|
|
302
|
+
entry.inflight = null;
|
|
303
|
+
}
|
|
304
|
+
})();
|
|
305
|
+
return entry.inflight;
|
|
306
|
+
}
|
|
307
|
+
/** Just the {@link prInfo} state enum — the common case for merge/archive gates. */
|
|
308
|
+
export async function prState(record, ttlMs = DEFAULT_PR_STATE_TTL_MS) {
|
|
309
|
+
return (await prInfo(record, ttlMs)).state;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Forget a branch's cached PR state so the next {@link prState} re-queries `gh`.
|
|
313
|
+
* Called after opening or merging a PR so the buttons re-key to the new state on
|
|
314
|
+
* the very next status poll instead of lingering until the TTL lapses.
|
|
315
|
+
*/
|
|
316
|
+
export function invalidatePrState(record) {
|
|
317
|
+
prStateCache.delete(prCacheKey(record));
|
|
318
|
+
}
|
|
319
|
+
/** Testing/reset hook: forget all cached PR state. */
|
|
320
|
+
export function resetPrStateCache() {
|
|
321
|
+
prStateCache.clear();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Whether the workspace branch's PR has been merged on the remote. Best effort:
|
|
325
|
+
* any failure (no PR, `gh` unauthenticated/absent, network) reads as not-merged
|
|
326
|
+
* so the caller simply doesn't auto-archive.
|
|
327
|
+
*/
|
|
328
|
+
export async function detectMerged(record) {
|
|
329
|
+
return (await prState(record)) === "MERGED";
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Open a PR for the workspace branch via `gh pr create`. If one already exists
|
|
333
|
+
* for this branch, surface its URL instead of failing.
|
|
334
|
+
*/
|
|
335
|
+
export async function prCreate(record, title, body) {
|
|
336
|
+
const ws = record.id;
|
|
337
|
+
const trimmed = title.trim();
|
|
338
|
+
if (!trimmed)
|
|
339
|
+
return { ws, op: "pr", ok: false, message: "PR title is empty" };
|
|
340
|
+
try {
|
|
341
|
+
const { stdout } = await exec("gh", [
|
|
342
|
+
"pr",
|
|
343
|
+
"create",
|
|
344
|
+
// Target the workspace's own base ref, not the repo default branch —
|
|
345
|
+
// a workspace branched off e.g. `release/2.0` must open its PR against
|
|
346
|
+
// `release/2.0`, matching how gitStatus computes ahead/behind.
|
|
347
|
+
"--base",
|
|
348
|
+
record.baseBranch,
|
|
349
|
+
"--head",
|
|
350
|
+
record.branch,
|
|
351
|
+
"--title",
|
|
352
|
+
trimmed,
|
|
353
|
+
"--body",
|
|
354
|
+
body ?? "",
|
|
355
|
+
], { cwd: record.worktreePath });
|
|
356
|
+
const url = stdout.trim().split("\n").pop() ?? "";
|
|
357
|
+
// A PR now exists — drop the cached "no PR" so the Create-PR button hides on
|
|
358
|
+
// the next status poll instead of waiting out the TTL.
|
|
359
|
+
invalidatePrState(record);
|
|
360
|
+
return { ws, op: "pr", ok: true, message: "pull request opened", url };
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
// `gh` exits non-zero when a PR for the branch already exists; recover its URL.
|
|
364
|
+
try {
|
|
365
|
+
const { stdout } = await exec("gh", ["pr", "view", record.branch, "--json", "url", "--jq", ".url"], { cwd: record.worktreePath });
|
|
366
|
+
const url = stdout.trim();
|
|
367
|
+
if (url) {
|
|
368
|
+
invalidatePrState(record);
|
|
369
|
+
return { ws, op: "pr", ok: true, message: "pull request already exists", url };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
/* fall through to the original failure */
|
|
374
|
+
}
|
|
375
|
+
return { ws, op: "pr", ok: false, message: errText(err) };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Merge the workspace branch's open PR on the remote via `gh pr merge --merge`.
|
|
380
|
+
* Backs the "Merge" button, which surfaces only for an open PR that is clean
|
|
381
|
+
* (no conflicts with the base). On success we drop the cached PR state so the
|
|
382
|
+
* next status poll sees the PR as merged and swaps Merge for the Archive
|
|
383
|
+
* affordance. Best effort on the error path, same keychain-auth stance as
|
|
384
|
+
* prCreate.
|
|
385
|
+
*/
|
|
386
|
+
export async function prMerge(record) {
|
|
387
|
+
const ws = record.id;
|
|
388
|
+
try {
|
|
389
|
+
await exec("gh", ["pr", "merge", record.branch, "--merge"], {
|
|
390
|
+
cwd: record.worktreePath,
|
|
391
|
+
});
|
|
392
|
+
invalidatePrState(record);
|
|
393
|
+
return { ws, op: "pr_merge", ok: true, message: "pull request merged" };
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
return { ws, op: "pr_merge", ok: false, message: errText(err) };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Reconcile the worktree with its merge-to branch. Used by the "Resolve"
|
|
401
|
+
* action, which surfaces only when the base has advanced past this branch
|
|
402
|
+
* (`behind > 0`) and the tree is clean.
|
|
403
|
+
*
|
|
404
|
+
* We fetch `origin/<base>` first so we reconcile against what's actually on the
|
|
405
|
+
* remote, then merge that ref (falling back to the local base when there is no
|
|
406
|
+
* remote-tracking ref, e.g. offline or base never pushed). A clean merge lands
|
|
407
|
+
* a merge commit. A conflicting merge is *left in progress on purpose*: the
|
|
408
|
+
* client drops into its in-app conflict flow (see `gitMerge`) to pick a side
|
|
409
|
+
* per file and commit, or abort. We refuse up front if the tree is dirty, since
|
|
410
|
+
* `git merge` would bail anyway with a less friendly message.
|
|
411
|
+
*/
|
|
412
|
+
export async function gitResolve(record) {
|
|
413
|
+
const ws = record.id;
|
|
414
|
+
const git = simpleGit(record.worktreePath);
|
|
415
|
+
try {
|
|
416
|
+
const pre = await git.status();
|
|
417
|
+
if (!pre.isClean()) {
|
|
418
|
+
return {
|
|
419
|
+
ws,
|
|
420
|
+
op: "resolve",
|
|
421
|
+
ok: false,
|
|
422
|
+
message: "commit or discard your changes before resolving",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
// Best-effort refresh of the remote base; offline falls back to last-known.
|
|
426
|
+
try {
|
|
427
|
+
await git.fetch(["origin", record.baseBranch]);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
/* offline / no remote — merge whatever we already have locally */
|
|
431
|
+
}
|
|
432
|
+
const mergeRef = await baseCompareRef(git, record.baseBranch);
|
|
433
|
+
// A conflicting merge may or may not reject (simple-git swallows some git
|
|
434
|
+
// non-zero exits), so the source of truth is whether conflicts are present
|
|
435
|
+
// afterward — not whether this threw.
|
|
436
|
+
let mergeErr = null;
|
|
437
|
+
try {
|
|
438
|
+
await git.raw(["merge", "--no-edit", mergeRef]);
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
mergeErr = e;
|
|
442
|
+
}
|
|
443
|
+
const conflicted = (await git.status()).conflicted;
|
|
444
|
+
if (conflicted.length > 0) {
|
|
445
|
+
const shown = conflicted.slice(0, 5).join(", ");
|
|
446
|
+
const more = conflicted.length > 5 ? ", …" : "";
|
|
447
|
+
return {
|
|
448
|
+
ws,
|
|
449
|
+
op: "resolve",
|
|
450
|
+
ok: false,
|
|
451
|
+
message: `merge has conflicts in ${conflicted.length} file(s): ${shown}${more} — resolve them below or abort`,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (mergeErr)
|
|
455
|
+
return { ws, op: "resolve", ok: false, message: errText(mergeErr) };
|
|
456
|
+
return {
|
|
457
|
+
ws,
|
|
458
|
+
op: "resolve",
|
|
459
|
+
ok: true,
|
|
460
|
+
message: `merged ${mergeRef} into ${record.branch}`,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
return { ws, op: "resolve", ok: false, message: errText(err) };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Drive an in-progress merge from the client's conflict UI:
|
|
469
|
+
* - `ours` — keep the workspace branch's version of the conflicting files
|
|
470
|
+
* - `theirs` — keep the merged-in base branch's version
|
|
471
|
+
* - `commit` — finalize the merge commit (refuses while conflicts remain)
|
|
472
|
+
* - `abort` — roll the merge back, restoring the pre-merge worktree
|
|
473
|
+
*
|
|
474
|
+
* `ours`/`theirs` default to every conflicting file; pass `paths` to resolve a
|
|
475
|
+
* subset (per-file resolution). After picking a side we `git add` the files so
|
|
476
|
+
* git marks them resolved.
|
|
477
|
+
*/
|
|
478
|
+
export async function gitMerge(record, action, paths) {
|
|
479
|
+
const ws = record.id;
|
|
480
|
+
const git = simpleGit(record.worktreePath);
|
|
481
|
+
try {
|
|
482
|
+
if (action === "abort") {
|
|
483
|
+
await git.raw(["merge", "--abort"]);
|
|
484
|
+
return { ws, op: "merge", ok: true, message: "merge aborted" };
|
|
485
|
+
}
|
|
486
|
+
if (action === "commit") {
|
|
487
|
+
const s = await git.status();
|
|
488
|
+
if (s.conflicted.length > 0) {
|
|
489
|
+
return {
|
|
490
|
+
ws,
|
|
491
|
+
op: "merge",
|
|
492
|
+
ok: false,
|
|
493
|
+
message: `resolve ${s.conflicted.length} conflicting file(s) first`,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (!(await mergeInProgress(git))) {
|
|
497
|
+
return { ws, op: "merge", ok: false, message: "no merge in progress" };
|
|
498
|
+
}
|
|
499
|
+
await git.raw(["commit", "--no-edit"]);
|
|
500
|
+
return { ws, op: "merge", ok: true, message: "merge committed" };
|
|
501
|
+
}
|
|
502
|
+
// ours / theirs: during a merge `--ours` is the workspace branch and
|
|
503
|
+
// `--theirs` is the base we merged in. Resolve the chosen files to one side.
|
|
504
|
+
const s = await git.status();
|
|
505
|
+
const targets = paths && paths.length > 0 ? paths : s.conflicted;
|
|
506
|
+
if (targets.length === 0) {
|
|
507
|
+
return { ws, op: "merge", ok: false, message: "no conflicts to resolve" };
|
|
508
|
+
}
|
|
509
|
+
await git.raw(["checkout", `--${action}`, "--", ...targets]);
|
|
510
|
+
await git.add(targets);
|
|
511
|
+
const left = (await git.status()).conflicted.length;
|
|
512
|
+
const side = action === "theirs" ? "base" : "your";
|
|
513
|
+
return {
|
|
514
|
+
ws,
|
|
515
|
+
op: "merge",
|
|
516
|
+
ok: true,
|
|
517
|
+
message: left > 0
|
|
518
|
+
? `kept ${side} version for ${targets.length} file(s); ${left} conflict(s) left`
|
|
519
|
+
: `kept ${side} version — all conflicts resolved, commit the merge`,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
return { ws, op: "merge", ok: false, message: errText(err) };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Reveal the worktree on this Mac: Finder, Terminal, or the user's editor.
|
|
528
|
+
* Editor prefers the VS Code CLI when installed, then falls back to `open -a`.
|
|
529
|
+
*/
|
|
530
|
+
export async function openWorktree(record, target) {
|
|
531
|
+
const path = record.worktreePath;
|
|
532
|
+
switch (target) {
|
|
533
|
+
case "finder":
|
|
534
|
+
await exec("open", [path]);
|
|
535
|
+
return;
|
|
536
|
+
case "terminal":
|
|
537
|
+
await exec("open", ["-a", "Terminal", path]);
|
|
538
|
+
return;
|
|
539
|
+
case "editor":
|
|
540
|
+
try {
|
|
541
|
+
await exec("code", [path]);
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
await exec("open", ["-a", "Visual Studio Code", path]);
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function errText(err) {
|
|
550
|
+
if (err instanceof Error) {
|
|
551
|
+
// execFile errors carry the CLI's stderr — usually the useful part.
|
|
552
|
+
const stderr = err.stderr;
|
|
553
|
+
return (stderr?.trim() || err.message).slice(0, 500);
|
|
554
|
+
}
|
|
555
|
+
return String(err).slice(0, 500);
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=gitops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitops.js","sourceRoot":"","sources":["../../src/gitops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection hub. Single-user, but more than one device may be attached at once
|
|
3
|
+
* (phone + tablet + a desktop client), so daemon→client events broadcast to all
|
|
4
|
+
* sockets.
|
|
5
|
+
*
|
|
6
|
+
* This is also the single send seam. With E2E on, each device pairs its own
|
|
7
|
+
* keypair, so there is no one global cipher: every socket carries its OWN codec
|
|
8
|
+
* (cleartext until it completes the `hello` handshake, then a NaCl box sealed to
|
|
9
|
+
* that device's key). Sends and broadcasts therefore encode per-socket.
|
|
10
|
+
*/
|
|
11
|
+
import { WebSocket } from "ws";
|
|
12
|
+
import type { Envelope, MessageType, PayloadFor } from "./protocol.js";
|
|
13
|
+
import { type Codec } from "./crypto.js";
|
|
14
|
+
export declare class Hub {
|
|
15
|
+
private requireSealed;
|
|
16
|
+
/** Each attached socket and the codec its frames are (de)coded with. */
|
|
17
|
+
private conns;
|
|
18
|
+
/**
|
|
19
|
+
* Sockets that asked to follow the daemon log (via `log.tail`). Live
|
|
20
|
+
* `log.event` frames go only here, so a phone that never opens a log view
|
|
21
|
+
* isn't fed a firehose. Membership is dropped when the socket detaches.
|
|
22
|
+
*/
|
|
23
|
+
private logSubscribers;
|
|
24
|
+
/**
|
|
25
|
+
* When true (E2E on), a socket that hasn't completed the `hello` handshake is
|
|
26
|
+
* still on the cleartext codec, so fanning a broadcast out to it would leak the
|
|
27
|
+
* payload to the Cloudflare edge in clear. In that mode we skip unsealed
|
|
28
|
+
* sockets on the broadcast paths — a device receives events only once it has
|
|
29
|
+
* sealed its channel. Point-to-point handshake replies still go via `send`.
|
|
30
|
+
*/
|
|
31
|
+
constructor(requireSealed?: boolean);
|
|
32
|
+
/** A socket whose channel is sealed to its device key (post-`hello`). */
|
|
33
|
+
private sealed;
|
|
34
|
+
add(socket: WebSocket): void;
|
|
35
|
+
remove(socket: WebSocket): void;
|
|
36
|
+
/** Follow the live daemon log on this socket (idempotent). */
|
|
37
|
+
subscribeLog(socket: WebSocket): void;
|
|
38
|
+
/** Push a daemon→client message to just the log subscribers. */
|
|
39
|
+
broadcastLog<T extends MessageType>(type: T, ws: string | null, payload: PayloadFor<T>): void;
|
|
40
|
+
/** The codec bound to a socket (cleartext if unknown/unpaired). */
|
|
41
|
+
codecFor(socket: WebSocket): Codec;
|
|
42
|
+
/** Bind a socket to a codec (called once the device's key is known). */
|
|
43
|
+
setCodec(socket: WebSocket, codec: Codec): void;
|
|
44
|
+
/** Send to one socket, sealed with that socket's own codec. */
|
|
45
|
+
send(socket: WebSocket, env: Envelope): void;
|
|
46
|
+
/** Build + send a daemon→client message to ONE socket (per-device streams). */
|
|
47
|
+
sendTo<T extends MessageType>(socket: WebSocket, type: T, ws: string | null, payload: PayloadFor<T>): void;
|
|
48
|
+
/** Build + broadcast a daemon→client message to every attached socket. */
|
|
49
|
+
broadcast<T extends MessageType>(type: T, ws: string | null, payload: PayloadFor<T>): void;
|
|
50
|
+
}
|