dsh-git-ui 0.0.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.
@@ -0,0 +1,53 @@
1
+ import type { GitRunner } from './git.ts';
2
+ import type { GitSnapshotResult } from './types.ts';
3
+ /** Resolved plugin config (already normalized; see normalizeConfig). */
4
+ export interface GitStatusConfig {
5
+ readonly timeoutMs: number;
6
+ readonly maxStatusBytes: number;
7
+ readonly maxChanges: number;
8
+ readonly defaultRefreshIntervalMs: number;
9
+ }
10
+ /** Session identity lookup: live first, persisted fallback. */
11
+ export interface SessionLookup {
12
+ /** Live session cwd; undefined when the session is cold or absent in memory. */
13
+ liveCwd(sessionId: string): string | undefined;
14
+ /**
15
+ * Persisted session metadata; resolves to undefined when no persisted
16
+ * session exists, and to `{ cwd }` (cwd possibly undefined) otherwise.
17
+ */
18
+ persistedMeta(sessionId: string): Promise<{
19
+ readonly cwd?: string;
20
+ } | undefined>;
21
+ }
22
+ /** Filesystem primitives (node:fs/promises slices). */
23
+ export interface FsLike {
24
+ realpath(path: string): Promise<string>;
25
+ stat(path: string): Promise<{
26
+ isDirectory(): boolean;
27
+ }>;
28
+ }
29
+ /** Everything the snapshot flow needs beyond the session lookup. */
30
+ export interface SnapshotDeps {
31
+ readonly run: GitRunner;
32
+ readonly fs: FsLike;
33
+ readonly sessions: SessionLookup;
34
+ /** Injectable clock for deterministic tests. */
35
+ readonly now?: () => number;
36
+ /** Caller-side cancellation (Remote `signal` slot): aborts in-flight git runs. */
37
+ readonly signal?: AbortSignal;
38
+ }
39
+ /** Defaults applied by normalizeConfig when a value is absent or invalid. */
40
+ export declare const DEFAULT_CONFIG: GitStatusConfig;
41
+ /** Coerce a raw patch config value into a validated GitStatusConfig. */
42
+ export declare function normalizeConfig(raw: unknown): GitStatusConfig;
43
+ /**
44
+ * Build one frozen GitSnapshot for a session working directory.
45
+ * Command sequence (all read-only; every command after the first runs with
46
+ * the repository root as cwd):
47
+ * 1. `git rev-parse --show-toplevel` — repo detection (exit 128 → not-a-git-repo)
48
+ * 2. `git branch --show-current` — null when detached
49
+ * 3. `git rev-parse --short HEAD` — null + unborn when the repo has no commits
50
+ * 4. `git status --porcelain=v1 -z --branch`
51
+ * 5. `git log -n 5 --format=%H%x1f%h%x1f%s%x1f%an%x1f%aI`
52
+ */
53
+ export declare function snapshotForSession(deps: SnapshotDeps, config: GitStatusConfig, sessionId: string): Promise<GitSnapshotResult>;
@@ -0,0 +1,89 @@
1
+ /** One collected stream disposition (matches the host SubprocessCollect). */
2
+ interface CollectDisposition {
3
+ readonly collect: {
4
+ readonly maxBytes: number;
5
+ /**
6
+ * Spill disposition: when the stream overflows the in-memory tail, the
7
+ * host appends the COMPLETE stream to a private spill file (up to this
8
+ * cap) and `readFrom` reports its path. Without it, only the tail is
9
+ * ever retained and the head (and its change counts) is lost.
10
+ */
11
+ readonly spill?: {
12
+ readonly maxBytes: number;
13
+ };
14
+ };
15
+ }
16
+ /** Structural slice of the host subprocess spawn spec. */
17
+ interface SpawnSpec {
18
+ readonly argv: readonly string[];
19
+ readonly cwd: string;
20
+ readonly stdio: {
21
+ readonly stdout: CollectDisposition;
22
+ readonly stderr: CollectDisposition;
23
+ };
24
+ readonly graceMs: number;
25
+ readonly signal?: AbortSignal;
26
+ }
27
+ /** Structural slice of the host subprocess handle (collect-mode output). */
28
+ interface SpawnHandle {
29
+ readonly done: Promise<{
30
+ readonly exitCode: number | null;
31
+ readonly signal: NodeJS.Signals | null;
32
+ }>;
33
+ readonly collected: {
34
+ readonly stdout?: {
35
+ readFrom(fromByte: number): {
36
+ readonly text: string;
37
+ readonly lossy: boolean;
38
+ readonly spillPath?: string;
39
+ };
40
+ };
41
+ readonly stderr?: {
42
+ readFrom(fromByte: number): {
43
+ readonly text: string;
44
+ readonly lossy: boolean;
45
+ readonly spillPath?: string;
46
+ };
47
+ };
48
+ };
49
+ }
50
+ /** Minimal subprocess-service face the adapter consumes. */
51
+ export interface SubprocessLike {
52
+ spawn(spec: SpawnSpec): SpawnHandle;
53
+ }
54
+ /** One git command outcome. */
55
+ export interface GitRunResult {
56
+ /** Process exit code; null when terminated by a signal. */
57
+ readonly exitCode: number | null;
58
+ readonly stdout: string;
59
+ readonly stderr: string;
60
+ /** True when the run was killed by our timeout (or the caller's signal). */
61
+ readonly timedOut: boolean;
62
+ /**
63
+ * True when the final stdout text is still incomplete: the collected
64
+ * output overflowed its byte cap AND the spill file was unavailable (no
65
+ * spill configured on the host, or the spill cap also overflowed).
66
+ */
67
+ readonly stdoutLossy: boolean;
68
+ }
69
+ /** The run primitive the snapshot orchestration uses. */
70
+ export interface GitRunner {
71
+ run(argv: readonly string[], opts: {
72
+ readonly cwd: string;
73
+ readonly signal?: AbortSignal;
74
+ }): Promise<GitRunResult>;
75
+ }
76
+ /**
77
+ * Adapt the host subprocess service into a `GitRunner` with a per-command
78
+ * timeout. A timed-out run resolves (never rejects) with `timedOut: true`;
79
+ * only spawn-level failures (e.g. git not installed) reject.
80
+ *
81
+ * Overflow handling: stdout/stderr collect with a spill cap of
82
+ * `maxBytes * 16` (default 4 MiB memory tail → 64 MiB spill file). When the
83
+ * tail overflowed but the spill file holds the complete stream, the runner
84
+ * reads the file and reports `stdoutLossy: false` — the change COUNTS stay
85
+ * exact. `stdoutLossy: true` is reserved for the doubly-overflowed case
86
+ * (spill also exceeded), where the head is genuinely lost.
87
+ */
88
+ export declare function createGitRunner(subprocess: SubprocessLike, timeoutMs: number, maxBytes: number): GitRunner;
89
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * dsh-git-ui host half: the `gitInfo` Remote service.
3
+ *
4
+ * Cordis shell only — every behavior lives in `core.ts` behind injected
5
+ * structural faces, so tests never need a cordis runtime. The class is a
6
+ * plugin in its own right (class form), mounted by the bundle patch row with
7
+ * the package name; the gateway exposes `gitInfo/snapshot` through SRC
8
+ * discovery (`typertRemote` binding + `@Remote` marker).
9
+ */
10
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { GitSnapshotRequest, GitSnapshotResult } from './types.ts';
13
+ export type { GitSnapshot, GitSnapshotResult, GitSnapshotFailure, GitSnapshotRequest, GitCommit, GitChange } from './types.ts';
14
+ export { normalizeConfig, DEFAULT_CONFIG } from './core.ts';
15
+ export { parseStatusOutput, parseLogOutput, parseBranchOutput } from './parser.ts';
16
+ /** The `gitInfo` service: one `snapshot` Remote endpoint. */
17
+ export declare class GitStatusService extends TypertRemoteService {
18
+ static inject: string[];
19
+ private readonly config;
20
+ constructor(ctx: Context, config: unknown);
21
+ snapshot(request: GitSnapshotRequest, signal?: AbortSignal): Promise<GitSnapshotResult>;
22
+ }
23
+ export default GitStatusService;
@@ -0,0 +1,380 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
11
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
12
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
13
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
14
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
15
+ var __runInitializers = (array, flags, self, value) => {
16
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
17
+ return value;
18
+ };
19
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
20
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
21
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
22
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
23
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
24
+ return __privateGet(this, extra);
25
+ }, set [name](x) {
26
+ return __privateSet(this, extra, x);
27
+ } }, name));
28
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
29
+ for (var i = decorators.length - 1; i >= 0; i--) {
30
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
31
+ if (k) {
32
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
33
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
34
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
35
+ }
36
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
37
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
38
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
39
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
40
+ }
41
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
42
+ };
43
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
44
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
45
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
46
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
47
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
48
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
49
+
50
+ // src/host/index.ts
51
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
52
+ import { realpath, stat } from "node:fs/promises";
53
+
54
+ // src/host/git.ts
55
+ import { readFile } from "node:fs/promises";
56
+ function createGitRunner(subprocess, timeoutMs, maxBytes) {
57
+ const spillMaxBytes = maxBytes * 16;
58
+ return {
59
+ async run(argv, opts) {
60
+ const controller = new AbortController();
61
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
62
+ try {
63
+ const signal = opts.signal === void 0 ? controller.signal : AbortSignal.any([controller.signal, opts.signal]);
64
+ const handle = subprocess.spawn({
65
+ argv,
66
+ cwd: opts.cwd,
67
+ stdio: {
68
+ stdout: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } },
69
+ stderr: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } }
70
+ },
71
+ graceMs: 200,
72
+ signal
73
+ });
74
+ let outcome;
75
+ try {
76
+ outcome = await handle.done;
77
+ } catch (error) {
78
+ if (controller.signal.aborted || opts.signal?.aborted === true) {
79
+ return { exitCode: null, stdout: "", stderr: "", timedOut: true, stdoutLossy: false };
80
+ }
81
+ throw error;
82
+ }
83
+ const stdout = handle.collected.stdout?.readFrom(0);
84
+ const stderr = handle.collected.stderr?.readFrom(0);
85
+ const stdoutResolved = await resolveStdout(stdout);
86
+ return {
87
+ exitCode: outcome.exitCode,
88
+ stdout: stdoutResolved.text,
89
+ stderr: stderr?.text ?? "",
90
+ timedOut: controller.signal.aborted || opts.signal?.aborted === true,
91
+ stdoutLossy: stdoutResolved.lossy
92
+ };
93
+ } finally {
94
+ clearTimeout(timer);
95
+ }
96
+ }
97
+ };
98
+ }
99
+ async function resolveStdout(read) {
100
+ if (read === void 0) return { text: "", lossy: false };
101
+ if (!read.lossy || read.spillPath === void 0) return { text: read.text, lossy: read.lossy };
102
+ try {
103
+ return { text: await readFile(read.spillPath, "utf8"), lossy: false };
104
+ } catch {
105
+ return { text: read.text, lossy: true };
106
+ }
107
+ }
108
+
109
+ // src/host/parser.ts
110
+ var NUL = "\0";
111
+ var LOG_SEP = "";
112
+ function parseStatusHeader(line) {
113
+ const body = line.startsWith("## ") ? line.slice(3) : line;
114
+ if (body === "") return { branch: null, unborn: false, ahead: 0, behind: 0 };
115
+ const unbornMatch = /^(?:No commits yet on|Initial commit on)\s+(.+)$/.exec(body);
116
+ if (unbornMatch !== null) {
117
+ return { branch: unbornMatch[1] ?? null, unborn: true, ahead: 0, behind: 0 };
118
+ }
119
+ const detached = /^HEAD(?:\s+\([^)]*\))?$/.exec(body);
120
+ if (detached !== null) {
121
+ return { branch: null, unborn: false, ahead: 0, behind: 0 };
122
+ }
123
+ const bracketMatch = /^(.*?)\s*\[([^\]]+)\]$/.exec(body);
124
+ const core = bracketMatch?.[1] ?? body;
125
+ let ahead = 0;
126
+ let behind = 0;
127
+ if (bracketMatch?.[2] !== void 0) {
128
+ for (const part of bracketMatch[2].split(",")) {
129
+ const trimmed = part.trim();
130
+ const aheadMatch = /^ahead (\d+)$/.exec(trimmed);
131
+ const behindMatch = /^behind (\d+)$/.exec(trimmed);
132
+ if (aheadMatch !== null) ahead = Number(aheadMatch[1]);
133
+ if (behindMatch !== null) behind = Number(behindMatch[1]);
134
+ }
135
+ }
136
+ const branch = core.split("...", 1)[0] ?? core;
137
+ return { branch: branch === "" ? null : branch, unborn: false, ahead, behind };
138
+ }
139
+ function changeStatus(x, y) {
140
+ if (x === "?" && y === "?") return "untracked";
141
+ if (x === "U" || y === "U" || x !== " " && y !== " ") return "conflicted";
142
+ switch (x) {
143
+ case "A":
144
+ return "added";
145
+ case "M":
146
+ return "modified";
147
+ case "D":
148
+ return "deleted";
149
+ case "R":
150
+ return "renamed";
151
+ case "T":
152
+ return "typechange";
153
+ case "C":
154
+ return "added";
155
+ default:
156
+ return "modified";
157
+ }
158
+ }
159
+ function parseStatusOutput(output, maxChanges) {
160
+ const raw = output.split(NUL);
161
+ const segments = raw[raw.length - 1] === "" ? raw.slice(0, -1) : raw;
162
+ const header = parseStatusHeader(segments[0] ?? "");
163
+ let staged = 0;
164
+ let modified = 0;
165
+ let untracked = 0;
166
+ const changes = [];
167
+ let truncated = false;
168
+ for (let index = 1; index < segments.length; index += 1) {
169
+ const entry = segments[index] ?? "";
170
+ const x = entry[0] ?? " ";
171
+ const y = entry[1] ?? " ";
172
+ const path = entry.slice(3);
173
+ if (x === " " && y === " ") continue;
174
+ if (x === "R" || x === "C") {
175
+ index += 1;
176
+ }
177
+ if (x === "?" && y === "?") {
178
+ untracked += 1;
179
+ } else {
180
+ if (x !== " " && x !== "?") staged += 1;
181
+ if (y !== " " && y !== "?") modified += 1;
182
+ }
183
+ if (changes.length < maxChanges) {
184
+ changes.push({ path, status: changeStatus(x, y), staged: x !== " " && x !== "?" });
185
+ } else {
186
+ truncated = true;
187
+ }
188
+ }
189
+ return {
190
+ branch: header.branch,
191
+ unborn: header.unborn,
192
+ staged,
193
+ modified,
194
+ untracked,
195
+ ahead: header.ahead,
196
+ behind: header.behind,
197
+ changes,
198
+ truncated
199
+ };
200
+ }
201
+ function parseLogOutput(output) {
202
+ const commits = [];
203
+ for (const line of output.split("\n")) {
204
+ if (line === "") continue;
205
+ const [hash, shortHash, subject, author, dateIso] = line.split(LOG_SEP);
206
+ if (hash === void 0 || hash === "") continue;
207
+ commits.push({
208
+ hash,
209
+ shortHash: shortHash ?? "",
210
+ subject: subject ?? "",
211
+ author: author ?? "",
212
+ dateIso: dateIso ?? ""
213
+ });
214
+ }
215
+ return commits;
216
+ }
217
+ function parseBranchOutput(output) {
218
+ const trimmed = output.trim();
219
+ return trimmed === "" ? null : trimmed;
220
+ }
221
+
222
+ // src/host/core.ts
223
+ var DEFAULT_CONFIG = {
224
+ timeoutMs: 5e3,
225
+ maxStatusBytes: 4 * 1024 * 1024,
226
+ maxChanges: 100,
227
+ defaultRefreshIntervalMs: 3e4
228
+ };
229
+ function normalizeConfig(raw) {
230
+ const value = raw ?? {};
231
+ const numberOr = (key, fallback) => {
232
+ const candidate = value[key];
233
+ return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0 ? candidate : fallback;
234
+ };
235
+ return {
236
+ timeoutMs: numberOr("timeoutMs", DEFAULT_CONFIG.timeoutMs) || DEFAULT_CONFIG.timeoutMs,
237
+ maxStatusBytes: numberOr("maxStatusBytes", DEFAULT_CONFIG.maxStatusBytes) || DEFAULT_CONFIG.maxStatusBytes,
238
+ maxChanges: Math.floor(numberOr("maxChanges", DEFAULT_CONFIG.maxChanges) || DEFAULT_CONFIG.maxChanges),
239
+ defaultRefreshIntervalMs: numberOr("defaultRefreshIntervalMs", DEFAULT_CONFIG.defaultRefreshIntervalMs)
240
+ };
241
+ }
242
+ async function resolveCwd(sessions, sessionId) {
243
+ const live = sessions.liveCwd(sessionId);
244
+ if (live !== void 0) return { ok: true, cwd: live };
245
+ const persisted = await sessions.persistedMeta(sessionId);
246
+ if (persisted === void 0) return { ok: false, error: { code: "session-not-found", sessionId } };
247
+ if (persisted.cwd === void 0) return { ok: false, error: { code: "cwd-unavailable", sessionId } };
248
+ return { ok: true, cwd: persisted.cwd };
249
+ }
250
+ function runFailure(result, detail) {
251
+ return result.timedOut ? { code: "timeout" } : { code: "git-unavailable", detail };
252
+ }
253
+ async function runCommand(runner, argv, cwd, label, signal) {
254
+ try {
255
+ return { run: await runner.run(argv, { cwd, ...signal === void 0 ? {} : { signal } }) };
256
+ } catch (error) {
257
+ return { failure: { code: "git-unavailable", detail: `${label}: ${error instanceof Error ? error.message : String(error)}` } };
258
+ }
259
+ }
260
+ async function snapshotForSession(deps, config, sessionId) {
261
+ const resolved = await resolveCwd(deps.sessions, sessionId);
262
+ if (!resolved.ok) return { ok: false, error: resolved.error };
263
+ let realCwd;
264
+ try {
265
+ realCwd = await deps.fs.realpath(resolved.cwd);
266
+ const stat2 = await deps.fs.stat(realCwd);
267
+ if (!stat2.isDirectory()) {
268
+ return { ok: false, error: { code: "path-not-found", path: realCwd } };
269
+ }
270
+ } catch {
271
+ return { ok: false, error: { code: "path-not-found", path: resolved.cwd } };
272
+ }
273
+ const toplevel = await runCommand(deps.run, ["git", "rev-parse", "--show-toplevel"], realCwd, "rev-parse", deps.signal);
274
+ if ("failure" in toplevel) return { ok: false, error: toplevel.failure };
275
+ if (toplevel.run.timedOut) return { ok: false, error: { code: "timeout" } };
276
+ if (toplevel.run.exitCode !== 0) {
277
+ const stderr = toplevel.run.stderr;
278
+ if (!stderr.includes("not a git repository")) {
279
+ return { ok: false, error: runFailure(toplevel.run, `git rev-parse failed: ${stderr.trim() || `exit ${String(toplevel.run.exitCode)}`}`) };
280
+ }
281
+ return { ok: false, error: { code: "not-a-git-repo" } };
282
+ }
283
+ const root = toplevel.run.stdout.trim();
284
+ if (root === "") return { ok: false, error: { code: "not-a-git-repo" } };
285
+ const branchRun = await runCommand(deps.run, ["git", "branch", "--show-current"], root, "branch", deps.signal);
286
+ if ("failure" in branchRun) return { ok: false, error: branchRun.failure };
287
+ if (branchRun.run.timedOut) return { ok: false, error: { code: "timeout" } };
288
+ const branch = branchRun.run.exitCode === 0 ? parseBranchOutput(branchRun.run.stdout) : null;
289
+ const headRun = await runCommand(deps.run, ["git", "rev-parse", "--short", "HEAD"], root, "rev-parse HEAD", deps.signal);
290
+ if ("failure" in headRun) return { ok: false, error: headRun.failure };
291
+ if (headRun.run.timedOut) return { ok: false, error: { code: "timeout" } };
292
+ const head = headRun.run.exitCode === 0 ? headRun.run.stdout.trim() || null : null;
293
+ const status = await runCommand(deps.run, ["git", "status", "--porcelain=v1", "-z", "--branch"], root, "status", deps.signal);
294
+ if ("failure" in status) return { ok: false, error: status.failure };
295
+ if (status.run.timedOut) return { ok: false, error: { code: "timeout" } };
296
+ if (status.run.exitCode !== 0) {
297
+ return { ok: false, error: runFailure(status.run, `git status exited ${String(status.run.exitCode)}`) };
298
+ }
299
+ const parsed = parseStatusOutput(status.run.stdout, config.maxChanges);
300
+ const log = await runCommand(deps.run, ["git", "log", "-n", "5", "--format=%H%x1f%h%x1f%s%x1f%an%x1f%aI"], root, "log", deps.signal);
301
+ if ("failure" in log) return { ok: false, error: log.failure };
302
+ if (log.run.timedOut) return { ok: false, error: { code: "timeout" } };
303
+ const recentCommits = log.run.exitCode === 0 ? parseLogOutput(log.run.stdout) : [];
304
+ const checkedAt = deps.now?.() ?? Date.now();
305
+ const snapshot = {
306
+ root,
307
+ branch,
308
+ head,
309
+ unborn: parsed.unborn,
310
+ dirty: parsed.staged + parsed.modified + parsed.untracked > 0,
311
+ staged: parsed.staged,
312
+ modified: parsed.modified,
313
+ untracked: parsed.untracked,
314
+ ahead: parsed.ahead,
315
+ behind: parsed.behind,
316
+ lastCommit: recentCommits[0] ?? null,
317
+ recentCommits,
318
+ changes: parsed.changes,
319
+ truncated: parsed.truncated || "run" in status && status.run.stdoutLossy,
320
+ refreshIntervalMs: config.defaultRefreshIntervalMs,
321
+ checkedAt
322
+ };
323
+ return { ok: true, value: snapshot };
324
+ }
325
+
326
+ // src/host/index.ts
327
+ var _snapshot_dec, _a, _init;
328
+ var GitStatusService = class extends (_a = TypertRemoteService, _snapshot_dec = [Remote("snapshot")], _a) {
329
+ constructor(ctx, config) {
330
+ super(ctx, "gitInfo");
331
+ __runInitializers(_init, 5, this);
332
+ __publicField(this, "config");
333
+ this.config = normalizeConfig(config);
334
+ }
335
+ async snapshot(request, signal) {
336
+ const subprocess = this.ctx.get("subprocess");
337
+ if (subprocess === void 0) {
338
+ return { ok: false, error: { code: "git-unavailable", detail: "subprocess service unavailable" } };
339
+ }
340
+ const sessions = this.ctx.get("sessions");
341
+ const persistence = this.ctx.get("sessionPersistence");
342
+ const runner = createGitRunner(subprocess, this.config.timeoutMs, this.config.maxStatusBytes);
343
+ return snapshotForSession(
344
+ {
345
+ run: runner,
346
+ fs: { realpath, stat },
347
+ sessions: {
348
+ liveCwd: (id) => sessions?.get(id)?.header?.cwd,
349
+ persistedMeta: async (id) => {
350
+ if (persistence === void 0) return void 0;
351
+ try {
352
+ const inspection = await persistence.inspect(id);
353
+ return { cwd: inspection.meta.cwd };
354
+ } catch {
355
+ return void 0;
356
+ }
357
+ }
358
+ },
359
+ signal
360
+ },
361
+ this.config,
362
+ request.sessionId
363
+ );
364
+ }
365
+ };
366
+ _init = __decoratorStart(_a);
367
+ __decorateElement(_init, 1, "snapshot", _snapshot_dec, GitStatusService);
368
+ __decoratorMetadata(_init, GitStatusService);
369
+ __publicField(GitStatusService, "inject", ["subprocess", "sessions", "sessionPersistence"]);
370
+ var index_default = GitStatusService;
371
+ export {
372
+ DEFAULT_CONFIG,
373
+ GitStatusService,
374
+ index_default as default,
375
+ normalizeConfig,
376
+ parseBranchOutput,
377
+ parseLogOutput,
378
+ parseStatusOutput
379
+ };
380
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/host/index.ts", "../../src/host/git.ts", "../../src/host/parser.ts", "../../src/host/core.ts"],
4
+ "sourcesContent": ["/**\n * dsh-git-ui host half: the `gitInfo` Remote service.\n *\n * Cordis shell only \u2014 every behavior lives in `core.ts` behind injected\n * structural faces, so tests never need a cordis runtime. The class is a\n * plugin in its own right (class form), mounted by the bundle patch row with\n * the package name; the gateway exposes `gitInfo/snapshot` through SRC\n * discovery (`typertRemote` binding + `@Remote` marker).\n */\nimport { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { realpath, stat } from 'node:fs/promises'\nimport { createGitRunner, type SubprocessLike } from './git.ts'\nimport { normalizeConfig, snapshotForSession, type GitStatusConfig } from './core.ts'\nimport type { GitSnapshotRequest, GitSnapshotResult } from './types.ts'\n\nexport type { GitSnapshot, GitSnapshotResult, GitSnapshotFailure, GitSnapshotRequest, GitCommit, GitChange } from './types.ts'\nexport { normalizeConfig, DEFAULT_CONFIG } from './core.ts'\nexport { parseStatusOutput, parseLogOutput, parseBranchOutput } from './parser.ts'\n\n/** Structural face of a live session header. */\ninterface SessionLike {\n readonly header?: { readonly cwd?: string }\n}\n\n/** Structural face of the sessions service. */\ninterface SessionsLike {\n get(id: string): SessionLike | undefined\n}\n\n/** Structural face of the session-persistence service. */\ninterface SessionPersistenceLike {\n inspect(id: string): Promise<{ readonly meta: { readonly cwd?: string } }>\n}\n\n/** The `gitInfo` service: one `snapshot` Remote endpoint. */\nexport class GitStatusService extends TypertRemoteService {\n static inject = ['subprocess', 'sessions', 'sessionPersistence']\n\n private readonly config: GitStatusConfig\n\n constructor(ctx: Context, config: unknown) {\n super(ctx, 'gitInfo')\n this.config = normalizeConfig(config)\n }\n\n @Remote('snapshot')\n async snapshot(request: GitSnapshotRequest, signal?: AbortSignal): Promise<GitSnapshotResult> {\n const subprocess = this.ctx.get('subprocess') as SubprocessLike | undefined\n if (subprocess === undefined) {\n return { ok: false, error: { code: 'git-unavailable', detail: 'subprocess service unavailable' } }\n }\n const sessions = this.ctx.get('sessions') as SessionsLike | undefined\n const persistence = this.ctx.get('sessionPersistence') as SessionPersistenceLike | undefined\n const runner = createGitRunner(subprocess, this.config.timeoutMs, this.config.maxStatusBytes)\n return snapshotForSession(\n {\n run: runner,\n fs: { realpath, stat },\n sessions: {\n liveCwd: (id) => sessions?.get(id)?.header?.cwd,\n persistedMeta: async (id) => {\n if (persistence === undefined) return undefined\n try {\n const inspection = await persistence.inspect(id)\n return { cwd: inspection.meta.cwd }\n } catch {\n return undefined\n }\n },\n },\n signal,\n },\n this.config,\n request.sessionId,\n )\n }\n}\n\nexport default GitStatusService\n", "/**\n * Git command execution adapter over the host subprocess service.\n *\n * The widget only needs a tiny slice of the subprocess contract; declaring it\n * structurally here (instead of depending on the npm package, whose registry\n * chain is incomplete) keeps the plugin buildable standalone while remaining\n * wire-compatible with the host's `subprocess` service.\n */\nimport { readFile } from 'node:fs/promises'\n\n/** One collected stream disposition (matches the host SubprocessCollect). */\ninterface CollectDisposition {\n readonly collect: {\n readonly maxBytes: number\n /**\n * Spill disposition: when the stream overflows the in-memory tail, the\n * host appends the COMPLETE stream to a private spill file (up to this\n * cap) and `readFrom` reports its path. Without it, only the tail is\n * ever retained and the head (and its change counts) is lost.\n */\n readonly spill?: { readonly maxBytes: number }\n }\n}\n\n/** Structural slice of the host subprocess spawn spec. */\ninterface SpawnSpec {\n readonly argv: readonly string[]\n readonly cwd: string\n readonly stdio: {\n readonly stdout: CollectDisposition\n readonly stderr: CollectDisposition\n }\n readonly graceMs: number\n readonly signal?: AbortSignal\n}\n\n/** Structural slice of the host subprocess handle (collect-mode output). */\ninterface SpawnHandle {\n readonly done: Promise<{ readonly exitCode: number | null; readonly signal: NodeJS.Signals | null }>\n readonly collected: {\n readonly stdout?: {\n readFrom(fromByte: number): { readonly text: string; readonly lossy: boolean; readonly spillPath?: string }\n }\n readonly stderr?: {\n readFrom(fromByte: number): { readonly text: string; readonly lossy: boolean; readonly spillPath?: string }\n }\n }\n}\n\n/** Minimal subprocess-service face the adapter consumes. */\nexport interface SubprocessLike {\n spawn(spec: SpawnSpec): SpawnHandle\n}\n\n/** One git command outcome. */\nexport interface GitRunResult {\n /** Process exit code; null when terminated by a signal. */\n readonly exitCode: number | null\n readonly stdout: string\n readonly stderr: string\n /** True when the run was killed by our timeout (or the caller's signal). */\n readonly timedOut: boolean\n /**\n * True when the final stdout text is still incomplete: the collected\n * output overflowed its byte cap AND the spill file was unavailable (no\n * spill configured on the host, or the spill cap also overflowed).\n */\n readonly stdoutLossy: boolean\n}\n\n/** The run primitive the snapshot orchestration uses. */\nexport interface GitRunner {\n run(argv: readonly string[], opts: { readonly cwd: string; readonly signal?: AbortSignal }): Promise<GitRunResult>\n}\n\n/**\n * Adapt the host subprocess service into a `GitRunner` with a per-command\n * timeout. A timed-out run resolves (never rejects) with `timedOut: true`;\n * only spawn-level failures (e.g. git not installed) reject.\n *\n * Overflow handling: stdout/stderr collect with a spill cap of\n * `maxBytes * 16` (default 4 MiB memory tail \u2192 64 MiB spill file). When the\n * tail overflowed but the spill file holds the complete stream, the runner\n * reads the file and reports `stdoutLossy: false` \u2014 the change COUNTS stay\n * exact. `stdoutLossy: true` is reserved for the doubly-overflowed case\n * (spill also exceeded), where the head is genuinely lost.\n */\nexport function createGitRunner(subprocess: SubprocessLike, timeoutMs: number, maxBytes: number): GitRunner {\n const spillMaxBytes = maxBytes * 16\n return {\n async run(argv, opts) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const signal = opts.signal === undefined\n ? controller.signal\n : AbortSignal.any([controller.signal, opts.signal])\n const handle = subprocess.spawn({\n argv,\n cwd: opts.cwd,\n stdio: {\n stdout: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } },\n stderr: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } },\n },\n graceMs: 200,\n signal,\n })\n let outcome: Awaited<SpawnHandle['done']>\n try {\n // `done` rejects for spawn-level failures; an abort-triggered\n // rejection is the timeout path and resolves as timedOut.\n outcome = await handle.done\n } catch (error) {\n if (controller.signal.aborted || opts.signal?.aborted === true) {\n return { exitCode: null, stdout: '', stderr: '', timedOut: true, stdoutLossy: false }\n }\n throw error\n }\n const stdout = handle.collected.stdout?.readFrom(0)\n const stderr = handle.collected.stderr?.readFrom(0)\n const stdoutResolved = await resolveStdout(stdout)\n return {\n exitCode: outcome.exitCode,\n stdout: stdoutResolved.text,\n stderr: stderr?.text ?? '',\n timedOut: controller.signal.aborted || opts.signal?.aborted === true,\n stdoutLossy: stdoutResolved.lossy,\n }\n } finally {\n clearTimeout(timer)\n }\n },\n }\n}\n\n/**\n * Resolve the stdout text from a collect read: the in-memory tail, or \u2014 when\n * the read is lossy and the host spilled the complete stream to a file \u2014 the\n * spill file contents (so change COUNTS stay exact). A failed spill read\n * falls back to the tail and keeps `lossy: true` (head genuinely lost).\n */\nasync function resolveStdout(\n read: { readonly text: string; readonly lossy: boolean; readonly spillPath?: string } | undefined,\n): Promise<{ readonly text: string; readonly lossy: boolean }> {\n if (read === undefined) return { text: '', lossy: false }\n if (!read.lossy || read.spillPath === undefined) return { text: read.text, lossy: read.lossy }\n try {\n return { text: await readFile(read.spillPath, 'utf8'), lossy: false }\n } catch {\n return { text: read.text, lossy: true }\n }\n}\n", "/**\n * Pure parsers for the git porcelain/log output shapes used by the widget.\n * No side effects and no I/O \u2014 fully unit-testable against literal fixtures\n * (verified against real `git status --porcelain=v1 -z --branch` output).\n */\nimport type { GitChange, GitChangeStatus, GitCommit } from './types.ts'\n\n/** Parsed status counts plus the (possibly capped) change list. */\nexport interface ParsedStatus {\n readonly branch: string | null\n readonly unborn: boolean\n readonly staged: number\n readonly modified: number\n readonly untracked: number\n readonly ahead: number\n readonly behind: number\n readonly changes: readonly GitChange[]\n readonly truncated: boolean\n}\n\n/** The NUL byte separating porcelain v1 -z entries. */\nconst NUL = '\\u0000'\n/** The unit separator used by the log --format payload. */\nconst LOG_SEP = '\\u001f'\n\ninterface StatusHeader {\n readonly branch: string | null\n readonly unborn: boolean\n readonly ahead: number\n readonly behind: number\n}\n\n/**\n * Parse the `## ` header line of `git status --porcelain=v1 -z --branch`.\n * Recognized shapes (verified against git 2.x):\n * `## main`\n * `## main...origin/main`\n * `## main...origin/main [ahead 1]`\n * `## main...origin/main [behind 2]`\n * `## main...origin/main [ahead 1, behind 2]`\n * `## HEAD (no branch)` (detached)\n * `## HEAD (detached at <hash>)` (detached, older git)\n * `## No commits yet on main` (unborn)\n * `## Initial commit on main` (unborn, older git)\n */\nexport function parseStatusHeader(line: string): StatusHeader {\n const body = line.startsWith('## ') ? line.slice(3) : line\n if (body === '') return { branch: null, unborn: false, ahead: 0, behind: 0 }\n\n const unbornMatch = /^(?:No commits yet on|Initial commit on)\\s+(.+)$/.exec(body)\n if (unbornMatch !== null) {\n return { branch: unbornMatch[1] ?? null, unborn: true, ahead: 0, behind: 0 }\n }\n\n const detached = /^HEAD(?:\\s+\\([^)]*\\))?$/.exec(body)\n if (detached !== null) {\n return { branch: null, unborn: false, ahead: 0, behind: 0 }\n }\n\n const bracketMatch = /^(.*?)\\s*\\[([^\\]]+)\\]$/.exec(body)\n const core = bracketMatch?.[1] ?? body\n let ahead = 0\n let behind = 0\n if (bracketMatch?.[2] !== undefined) {\n for (const part of bracketMatch[2].split(',')) {\n const trimmed = part.trim()\n const aheadMatch = /^ahead (\\d+)$/.exec(trimmed)\n const behindMatch = /^behind (\\d+)$/.exec(trimmed)\n if (aheadMatch !== null) ahead = Number(aheadMatch[1])\n if (behindMatch !== null) behind = Number(behindMatch[1])\n }\n }\n // The core is `<branch>...<upstream>` \u2014 the branch never contains `...`.\n const branch = core.split('...', 1)[0] ?? core\n return { branch: branch === '' ? null : branch, unborn: false, ahead, behind }\n}\n\n/** Map one porcelain XY pair to a change status. */\nfunction changeStatus(x: string, y: string): GitChangeStatus {\n if (x === '?' && y === '?') return 'untracked'\n if (x === 'U' || y === 'U' || (x !== ' ' && y !== ' ')) return 'conflicted'\n switch (x) {\n case 'A': return 'added'\n case 'M': return 'modified'\n case 'D': return 'deleted'\n case 'R': return 'renamed'\n case 'T': return 'typechange'\n case 'C': return 'added'\n default: return 'modified'\n }\n}\n\n/**\n * Parse the full `git status --porcelain=v1 -z --branch` output.\n * -z format: every entry (header and each `XY path`) is NUL-terminated; a\n * rename/copy entry emits `R <new>\\0<old>\\0` so the following item is the\n * source path and must be consumed without becoming a change itself.\n */\nexport function parseStatusOutput(output: string, maxChanges: number): ParsedStatus {\n const raw = output.split(NUL)\n // Trailing NUL produces a final empty segment; drop it.\n const segments = raw[raw.length - 1] === '' ? raw.slice(0, -1) : raw\n const header = parseStatusHeader(segments[0] ?? '')\n\n let staged = 0\n let modified = 0\n let untracked = 0\n const changes: GitChange[] = []\n let truncated = false\n\n for (let index = 1; index < segments.length; index += 1) {\n const entry = segments[index] ?? ''\n const x = entry[0] ?? ' '\n const y = entry[1] ?? ' '\n const path = entry.slice(3)\n if (x === ' ' && y === ' ') continue\n if (x === 'R' || x === 'C') {\n // -z: the source path is the next segment \u2014 consume it.\n index += 1\n }\n if (x === '?' && y === '?') {\n untracked += 1\n } else {\n if (x !== ' ' && x !== '?') staged += 1\n if (y !== ' ' && y !== '?') modified += 1\n }\n if (changes.length < maxChanges) {\n changes.push({ path, status: changeStatus(x, y), staged: x !== ' ' && x !== '?' })\n } else {\n truncated = true\n }\n }\n\n return {\n branch: header.branch,\n unborn: header.unborn,\n staged,\n modified,\n untracked,\n ahead: header.ahead,\n behind: header.behind,\n changes,\n truncated,\n }\n}\n\n/**\n * Parse `git log -n 5 --format=%H%x1f%h%x1f%s%x1f%an%x1f%aI` output.\n * One commit per line, fields separated by the unit separator; empty output\n * (unborn repository) yields `[]`.\n */\nexport function parseLogOutput(output: string): readonly GitCommit[] {\n const commits: GitCommit[] = []\n for (const line of output.split('\\n')) {\n if (line === '') continue\n const [hash, shortHash, subject, author, dateIso] = line.split(LOG_SEP)\n if (hash === undefined || hash === '') continue\n commits.push({\n hash,\n shortHash: shortHash ?? '',\n subject: subject ?? '',\n author: author ?? '',\n dateIso: dateIso ?? '',\n })\n }\n return commits\n}\n\n/**\n * Parse `git branch --show-current` output: the branch name, or null when\n * empty (detached HEAD).\n */\nexport function parseBranchOutput(output: string): string | null {\n const trimmed = output.trim()\n return trimmed === '' ? null : trimmed\n}\n", "/**\n * Framework-free snapshot orchestration: session cwd resolution + git command\n * sequence + frozen GitSnapshot assembly. Every dependency is injected\n * structurally, so the whole flow is testable without a cordis runtime; the\n * cordis shell (GitStatusService) only adapts host services into these faces.\n */\nimport { parseBranchOutput, parseLogOutput, parseStatusOutput } from './parser.ts'\nimport type { GitRunner } from './git.ts'\nimport type { GitSnapshot, GitSnapshotResult } from './types.ts'\n\n/** Resolved plugin config (already normalized; see normalizeConfig). */\nexport interface GitStatusConfig {\n readonly timeoutMs: number\n readonly maxStatusBytes: number\n readonly maxChanges: number\n readonly defaultRefreshIntervalMs: number\n}\n\n/** Session identity lookup: live first, persisted fallback. */\nexport interface SessionLookup {\n /** Live session cwd; undefined when the session is cold or absent in memory. */\n liveCwd(sessionId: string): string | undefined\n /**\n * Persisted session metadata; resolves to undefined when no persisted\n * session exists, and to `{ cwd }` (cwd possibly undefined) otherwise.\n */\n persistedMeta(sessionId: string): Promise<{ readonly cwd?: string } | undefined>\n}\n\n/** Filesystem primitives (node:fs/promises slices). */\nexport interface FsLike {\n realpath(path: string): Promise<string>\n stat(path: string): Promise<{ isDirectory(): boolean }>\n}\n\n/** Everything the snapshot flow needs beyond the session lookup. */\nexport interface SnapshotDeps {\n readonly run: GitRunner\n readonly fs: FsLike\n readonly sessions: SessionLookup\n /** Injectable clock for deterministic tests. */\n readonly now?: () => number\n /** Caller-side cancellation (Remote `signal` slot): aborts in-flight git runs. */\n readonly signal?: AbortSignal\n}\n\n/** Defaults applied by normalizeConfig when a value is absent or invalid. */\nexport const DEFAULT_CONFIG: GitStatusConfig = {\n timeoutMs: 5000,\n maxStatusBytes: 4 * 1024 * 1024,\n maxChanges: 100,\n defaultRefreshIntervalMs: 30_000,\n}\n\n/** Coerce a raw patch config value into a validated GitStatusConfig. */\nexport function normalizeConfig(raw: unknown): GitStatusConfig {\n const value = (raw ?? {}) as Record<string, unknown>\n const numberOr = (key: string, fallback: number): number => {\n const candidate = value[key]\n return typeof candidate === 'number' && Number.isFinite(candidate) && candidate >= 0\n ? candidate\n : fallback\n }\n return {\n timeoutMs: numberOr('timeoutMs', DEFAULT_CONFIG.timeoutMs) || DEFAULT_CONFIG.timeoutMs,\n maxStatusBytes: numberOr('maxStatusBytes', DEFAULT_CONFIG.maxStatusBytes) || DEFAULT_CONFIG.maxStatusBytes,\n maxChanges: Math.floor(numberOr('maxChanges', DEFAULT_CONFIG.maxChanges) || DEFAULT_CONFIG.maxChanges),\n defaultRefreshIntervalMs: numberOr('defaultRefreshIntervalMs', DEFAULT_CONFIG.defaultRefreshIntervalMs),\n }\n}\n\n/** Outcome of the cwd resolution step. */\ntype CwdResolution =\n | { readonly ok: true; readonly cwd: string }\n | { readonly ok: false; readonly error: Extract<GitSnapshotResult, { ok: false }>['error'] }\n\nasync function resolveCwd(sessions: SessionLookup, sessionId: string): Promise<CwdResolution> {\n const live = sessions.liveCwd(sessionId)\n if (live !== undefined) return { ok: true, cwd: live }\n const persisted = await sessions.persistedMeta(sessionId)\n if (persisted === undefined) return { ok: false, error: { code: 'session-not-found', sessionId } }\n if (persisted.cwd === undefined) return { ok: false, error: { code: 'cwd-unavailable', sessionId } }\n return { ok: true, cwd: persisted.cwd }\n}\n\n/** Classify a failed run outcome into a snapshot failure. */\nfunction runFailure(result: { readonly timedOut: boolean }, detail: string): Extract<GitSnapshotResult, { ok: false }>['error'] {\n return result.timedOut ? { code: 'timeout' } : { code: 'git-unavailable', detail }\n}\n\n/** Run one command, mapping a spawn-level failure to a snapshot failure. */\nasync function runCommand(\n runner: GitRunner,\n argv: readonly string[],\n cwd: string,\n label: string,\n signal?: AbortSignal,\n): Promise<{ readonly run: Awaited<ReturnType<GitRunner['run']>> } | { readonly failure: Extract<GitSnapshotResult, { ok: false }>['error'] }> {\n try {\n return { run: await runner.run(argv, { cwd, ...(signal === undefined ? {} : { signal }) }) }\n } catch (error) {\n return { failure: { code: 'git-unavailable', detail: `${label}: ${error instanceof Error ? error.message : String(error)}` } }\n }\n}\n\n/**\n * Build one frozen GitSnapshot for a session working directory.\n * Command sequence (all read-only; every command after the first runs with\n * the repository root as cwd):\n * 1. `git rev-parse --show-toplevel` \u2014 repo detection (exit 128 \u2192 not-a-git-repo)\n * 2. `git branch --show-current` \u2014 null when detached\n * 3. `git rev-parse --short HEAD` \u2014 null + unborn when the repo has no commits\n * 4. `git status --porcelain=v1 -z --branch`\n * 5. `git log -n 5 --format=%H%x1f%h%x1f%s%x1f%an%x1f%aI`\n */\nexport async function snapshotForSession(\n deps: SnapshotDeps,\n config: GitStatusConfig,\n sessionId: string,\n): Promise<GitSnapshotResult> {\n const resolved = await resolveCwd(deps.sessions, sessionId)\n if (!resolved.ok) return { ok: false, error: resolved.error }\n\n let realCwd: string\n try {\n realCwd = await deps.fs.realpath(resolved.cwd)\n const stat = await deps.fs.stat(realCwd)\n if (!stat.isDirectory()) {\n return { ok: false, error: { code: 'path-not-found', path: realCwd } }\n }\n } catch {\n return { ok: false, error: { code: 'path-not-found', path: resolved.cwd } }\n }\n\n const toplevel = await runCommand(deps.run, ['git', 'rev-parse', '--show-toplevel'], realCwd, 'rev-parse', deps.signal)\n if ('failure' in toplevel) return { ok: false, error: toplevel.failure }\n if (toplevel.run.timedOut) return { ok: false, error: { code: 'timeout' } }\n if (toplevel.run.exitCode !== 0) {\n // exit 128 covers both \"not a git repository\" (plain directory) and\n // other git failures (dubious ownership, unreadable work tree, \u2026).\n // Only the former is a stable non-repo state; everything else surfaces\n // as git-unavailable with the actual reason instead of a misleading\n // \"no git repository\" pill.\n const stderr = toplevel.run.stderr\n if (!stderr.includes('not a git repository')) {\n return { ok: false, error: runFailure(toplevel.run, `git rev-parse failed: ${stderr.trim() || `exit ${String(toplevel.run.exitCode)}`}`) }\n }\n return { ok: false, error: { code: 'not-a-git-repo' } }\n }\n const root = toplevel.run.stdout.trim()\n if (root === '') return { ok: false, error: { code: 'not-a-git-repo' } }\n\n const branchRun = await runCommand(deps.run, ['git', 'branch', '--show-current'], root, 'branch', deps.signal)\n if ('failure' in branchRun) return { ok: false, error: branchRun.failure }\n if (branchRun.run.timedOut) return { ok: false, error: { code: 'timeout' } }\n const branch = branchRun.run.exitCode === 0 ? parseBranchOutput(branchRun.run.stdout) : null\n\n const headRun = await runCommand(deps.run, ['git', 'rev-parse', '--short', 'HEAD'], root, 'rev-parse HEAD', deps.signal)\n if ('failure' in headRun) return { ok: false, error: headRun.failure }\n if (headRun.run.timedOut) return { ok: false, error: { code: 'timeout' } }\n // A failed HEAD read (non-timeout) only nulls the hash: the authoritative\n // unborn flag comes from the status header below (`## No commits yet on\n // main`), so a corrupt repo is never misreported as \"no commits\".\n const head = headRun.run.exitCode === 0 ? (headRun.run.stdout.trim() || null) : null\n\n const status = await runCommand(deps.run, ['git', 'status', '--porcelain=v1', '-z', '--branch'], root, 'status', deps.signal)\n if ('failure' in status) return { ok: false, error: status.failure }\n if (status.run.timedOut) return { ok: false, error: { code: 'timeout' } }\n if (status.run.exitCode !== 0) {\n return { ok: false, error: runFailure(status.run, `git status exited ${String(status.run.exitCode)}`) }\n }\n const parsed = parseStatusOutput(status.run.stdout, config.maxChanges)\n\n const log = await runCommand(deps.run, ['git', 'log', '-n', '5', '--format=%H%x1f%h%x1f%s%x1f%an%x1f%aI'], root, 'log', deps.signal)\n if ('failure' in log) return { ok: false, error: log.failure }\n if (log.run.timedOut) return { ok: false, error: { code: 'timeout' } }\n const recentCommits = log.run.exitCode === 0 ? parseLogOutput(log.run.stdout) : []\n\n const checkedAt = deps.now?.() ?? Date.now()\n const snapshot: GitSnapshot = {\n root,\n branch,\n head,\n unborn: parsed.unborn,\n dirty: parsed.staged + parsed.modified + parsed.untracked > 0,\n staged: parsed.staged,\n modified: parsed.modified,\n untracked: parsed.untracked,\n ahead: parsed.ahead,\n behind: parsed.behind,\n lastCommit: recentCommits[0] ?? null,\n recentCommits,\n changes: parsed.changes,\n truncated: parsed.truncated || ('run' in status && status.run.stdoutLossy),\n refreshIntervalMs: config.defaultRefreshIntervalMs,\n checkedAt,\n }\n return { ok: true, value: snapshot }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["stat"]
7
+ }