clay-server 4.0.0-beta.1 → 4.0.0-beta.11
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/lib/background-task-timing.js +105 -0
- package/lib/config.js +25 -1
- package/lib/daemon.js +22 -0
- package/lib/git-cli.js +94 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/project-connection.js +17 -0
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-mcp-server.js +314 -0
- package/lib/project-logs-query.js +167 -0
- package/lib/project-logs-root.js +67 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +357 -0
- package/lib/project-logs-snapshot.js +202 -0
- package/lib/project-logs-store.js +498 -0
- package/lib/project-logs-versioning.js +128 -0
- package/lib/project-logs.js +348 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-pair-lifecycle.js +461 -0
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +124 -111
- package/lib/project-sessions.js +46 -6
- package/lib/project-user-message.js +41 -4
- package/lib/project-vendor-login.js +414 -0
- package/lib/project-worker-permission.js +429 -0
- package/lib/project-worker-proposal.js +16 -1
- package/lib/project.js +164 -4
- package/lib/public/app.js +49 -20
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/input.css +79 -25
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +234 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/sticky-notes.css +231 -226
- package/lib/public/index.html +12 -0
- package/lib/public/modules/app-connection.js +90 -4
- package/lib/public/modules/app-messages.js +54 -19
- package/lib/public/modules/app-notifications.js +15 -124
- package/lib/public/modules/app-panels.js +43 -2
- package/lib/public/modules/app-projects.js +2 -2
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/pane-bridge.js +9 -0
- package/lib/public/modules/project-logs-ambient.js +238 -0
- package/lib/public/modules/project-logs-render.js +359 -0
- package/lib/public/modules/project-logs.js +329 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/sidebar-mobile.js +5 -9
- package/lib/public/modules/sidebar-sessions.js +25 -1
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-view.js +7 -0
- package/lib/public/modules/sticky-notes-browser.js +324 -0
- package/lib/public/modules/sticky-notes-card.js +358 -0
- package/lib/public/modules/sticky-notes-editor.js +288 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +168 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tui-attention.js +10 -0
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/vendor-login.js +287 -0
- package/lib/public/modules/worker-pane-lock.js +174 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +77 -10
- package/lib/sdk-message-processor.js +24 -7
- package/lib/server.js +61 -6
- package/lib/session-driver-eligibility.js +183 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +233 -0
- package/lib/session-pair-mcp-server.js +45 -5
- package/lib/session-pair-prompts.js +60 -0
- package/lib/sessions.js +4 -1
- package/lib/tools-registry.js +11 -1
- package/lib/update-snooze.js +437 -0
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/ws-schema.js +28 -3
- package/lib/yoke/adapters/claude.js +69 -4
- package/lib/yoke/adapters/codex.js +42 -5
- package/lib/yoke/codex-app-server.js +20 -2
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/package.json +3 -3
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Background task list shaping shared by the server-side consumers:
|
|
2
|
+
// start-time tracking, plus the ambient/user split every activity indicator
|
|
3
|
+
// needs.
|
|
4
|
+
//
|
|
5
|
+
// Vendors report background tasks as a full replacement list on every change
|
|
6
|
+
// and none of them carries a reliable start time today (see
|
|
7
|
+
// normalizeBackgroundTasks in yoke/adapters/claude.js and mapTerminals in
|
|
8
|
+
// yoke/codex-background-tasks.js). The session's previous list is the only
|
|
9
|
+
// place that knows when a task was first seen, so the merge belongs here,
|
|
10
|
+
// where that list is maintained, rather than in an adapter that rebuilds the
|
|
11
|
+
// array from scratch each time.
|
|
12
|
+
//
|
|
13
|
+
// A vendor-supplied timestamp always wins when present; otherwise a task is
|
|
14
|
+
// stamped the first time it appears and keeps that stamp for its whole life.
|
|
15
|
+
|
|
16
|
+
// Accepts epoch milliseconds, epoch seconds, or an ISO date string, since
|
|
17
|
+
// vendors are not consistent. Returns null for anything unusable so the
|
|
18
|
+
// caller falls back to first-seen stamping instead of rendering 1970.
|
|
19
|
+
function normalizeTimestamp(value) {
|
|
20
|
+
if (typeof value === "number" && isFinite(value) && value > 0) {
|
|
21
|
+
// Values this small are epoch seconds, not milliseconds.
|
|
22
|
+
return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === "string" && value) {
|
|
25
|
+
var parsed = Date.parse(value);
|
|
26
|
+
if (!isNaN(parsed)) return parsed;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function vendorStartedAt(task) {
|
|
32
|
+
if (!task) return null;
|
|
33
|
+
return normalizeTimestamp(task.started_at)
|
|
34
|
+
|| normalizeTimestamp(task.startedAt)
|
|
35
|
+
|| normalizeTimestamp(task.createdAt)
|
|
36
|
+
|| normalizeTimestamp(task.created_at);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return `nextTasks` with a `started_at` (epoch ms) on every entry.
|
|
41
|
+
* Tasks already present in `previousTasks` keep their original stamp so the
|
|
42
|
+
* elapsed time does not reset every time the list is re-emitted.
|
|
43
|
+
*/
|
|
44
|
+
function mergeStartTimes(previousTasks, nextTasks, now) {
|
|
45
|
+
if (!Array.isArray(nextTasks)) return [];
|
|
46
|
+
var stampedAt = typeof now === "number" ? now : Date.now();
|
|
47
|
+
// Stamps we wrote on a previous pass are already epoch milliseconds, so they
|
|
48
|
+
// are read as-is. Only vendor-supplied values go through normalizeTimestamp,
|
|
49
|
+
// whose seconds-vs-milliseconds heuristic would otherwise be re-applied to
|
|
50
|
+
// our own output.
|
|
51
|
+
var known = {};
|
|
52
|
+
var previous = Array.isArray(previousTasks) ? previousTasks : [];
|
|
53
|
+
for (var p = 0; p < previous.length; p++) {
|
|
54
|
+
var seen = previous[p];
|
|
55
|
+
if (!seen || !seen.task_id) continue;
|
|
56
|
+
if (typeof seen.started_at === "number" && isFinite(seen.started_at) && seen.started_at > 0) {
|
|
57
|
+
known[seen.task_id] = seen.started_at;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var merged = [];
|
|
62
|
+
for (var i = 0; i < nextTasks.length; i++) {
|
|
63
|
+
var task = nextTasks[i];
|
|
64
|
+
if (!task) continue;
|
|
65
|
+
var startedAt = vendorStartedAt(task) || known[task.task_id] || stampedAt;
|
|
66
|
+
merged.push(Object.assign({}, task, { started_at: startedAt }));
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Ambient tasks are the CLI's own housekeeping (every skip_transcript task
|
|
72
|
+
// plus auto-started live-update watchers). The SDK asks hosts to keep them out
|
|
73
|
+
// of activity indicators, so every count that drives a badge, a summary, or a
|
|
74
|
+
// change notification must go through here rather than reading `.length`.
|
|
75
|
+
// Absent means false: vendors without an ambient concept (Codex terminals)
|
|
76
|
+
// and older CLI builds report only real user work.
|
|
77
|
+
function isAmbientTask(task) {
|
|
78
|
+
return !!(task && task.ambient === true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function userTasks(tasks) {
|
|
82
|
+
if (!Array.isArray(tasks)) return [];
|
|
83
|
+
return tasks.filter(function (task) {
|
|
84
|
+
return task && !isAmbientTask(task);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ambientTasks(tasks) {
|
|
89
|
+
if (!Array.isArray(tasks)) return [];
|
|
90
|
+
return tasks.filter(isAmbientTask);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The count that represents "user-visible background work is running".
|
|
94
|
+
function countUserTasks(tasks) {
|
|
95
|
+
return userTasks(tasks).length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
mergeStartTimes: mergeStartTimes,
|
|
100
|
+
normalizeTimestamp: normalizeTimestamp,
|
|
101
|
+
isAmbientTask: isAmbientTask,
|
|
102
|
+
userTasks: userTasks,
|
|
103
|
+
ambientTasks: ambientTasks,
|
|
104
|
+
countUserTasks: countUserTasks,
|
|
105
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -148,12 +148,36 @@ function saveConfig(config) {
|
|
|
148
148
|
chmodSafe(configPath(), 0o600);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// Does a process with this pid exist? Existence only — never ownership.
|
|
152
|
+
//
|
|
153
|
+
// `process.kill(pid, 0)` sends no signal; it just asks the kernel. Three
|
|
154
|
+
// answers matter, and conflating them has caused real damage:
|
|
155
|
+
//
|
|
156
|
+
// success -> the process exists and we may signal it
|
|
157
|
+
// EPERM -> the process EXISTS but belongs to another user. This is alive.
|
|
158
|
+
// Reading it as dead is how a daemon started by another OS user
|
|
159
|
+
// (or under OS-user isolation) got its config cleared and its
|
|
160
|
+
// socket unlinked by a second daemon, which then started
|
|
161
|
+
// alongside it.
|
|
162
|
+
// ESRCH -> no such process. This is the only "dead".
|
|
163
|
+
//
|
|
164
|
+
// Any other error is a question we could not answer, so we answer "alive":
|
|
165
|
+
// every caller uses this to gate a destructive or duplicating action
|
|
166
|
+
// (clearStaleConfig unlinks the live socket; the migration lock would be
|
|
167
|
+
// stolen), and doing nothing is always the recoverable choice.
|
|
168
|
+
//
|
|
169
|
+
// The pid is validated first, so a malformed value never reaches the kernel
|
|
170
|
+
// call. That matters beyond hygiene: process.kill accepts 0 and negative pids
|
|
171
|
+
// as process-GROUP selectors and returns success for them, so an unvalidated
|
|
172
|
+
// 0 or -1 in a config file used to report a live daemon that does not exist.
|
|
151
173
|
function isPidAlive(pid) {
|
|
174
|
+
if (typeof pid !== "number" || !isFinite(pid) || Math.floor(pid) !== pid || pid <= 0) return false;
|
|
152
175
|
try {
|
|
153
176
|
process.kill(pid, 0);
|
|
154
177
|
return true;
|
|
155
178
|
} catch (e) {
|
|
156
|
-
return false;
|
|
179
|
+
if (e && e.code === "ESRCH") return false;
|
|
180
|
+
return true;
|
|
157
181
|
}
|
|
158
182
|
}
|
|
159
183
|
|
package/lib/daemon.js
CHANGED
|
@@ -34,6 +34,7 @@ var { checkAclSupport, grantProjectAccess, revokeProjectAccess, provisionAllUser
|
|
|
34
34
|
var usersModule = require("./users");
|
|
35
35
|
var { createWorktree, removeWorktree, isWorktree } = require("./worktree");
|
|
36
36
|
var mates = require("./mates");
|
|
37
|
+
var mateKnowledgeMigration = require("./mate-knowledge-migration");
|
|
37
38
|
var { isWorktreeSlug, scanAndRegisterWorktrees, rescanWorktrees, cleanupWorktreesForParent, getFilteredRemovedProjects, registerWorktreeSlug, unregisterWorktreeSlug } = require("./daemon-projects");
|
|
38
39
|
|
|
39
40
|
var daemonVersion = require("../package.json").version;
|
|
@@ -1294,6 +1295,27 @@ for (var i = 0; i < projects.length; i++) {
|
|
|
1294
1295
|
// Migrate legacy mates storage before registration
|
|
1295
1296
|
mates.migrateLegacyMates();
|
|
1296
1297
|
|
|
1298
|
+
// Import durable Mate knowledge into the Clay-wide Knowledge backend. Runs on
|
|
1299
|
+
// every start, imports nothing when there is nothing new, and never touches
|
|
1300
|
+
// the legacy files that remain the live source for existing read paths.
|
|
1301
|
+
mateKnowledgeMigration.runAtStartup({
|
|
1302
|
+
extraRoots: (function () {
|
|
1303
|
+
var roots = [];
|
|
1304
|
+
try {
|
|
1305
|
+
if (!usersModule.isMultiUser()) return roots;
|
|
1306
|
+
var all = usersModule.getAllUsers() || [];
|
|
1307
|
+
for (var mi = 0; mi < all.length; mi++) {
|
|
1308
|
+
if (!all[mi] || !all[mi].linuxUser) continue;
|
|
1309
|
+
roots.push({
|
|
1310
|
+
userId: all[mi].id,
|
|
1311
|
+
matesRoot: mates.resolveMatesRoot({ userId: all[mi].id, multiUser: true, linuxUser: all[mi].linuxUser }),
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
} catch (e) {}
|
|
1315
|
+
return roots;
|
|
1316
|
+
})(),
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1297
1319
|
// Register existing mates as projects
|
|
1298
1320
|
if (usersModule.isMultiUser()) {
|
|
1299
1321
|
// Multi-user mode: iterate over all users and load each user's mates
|
package/lib/git-cli.js
CHANGED
|
@@ -6,6 +6,7 @@ var { execFile, execFileSync } = require("child_process");
|
|
|
6
6
|
var { wrapSpawnAsUser } = require("./os-users");
|
|
7
7
|
|
|
8
8
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
9
|
+
var MAX_REMOTE_LABEL = 64;
|
|
9
10
|
|
|
10
11
|
function gitEnvironment(osUserInfo) {
|
|
11
12
|
var overrides = { GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -157,6 +158,24 @@ function normalizeGitPath(cwd, value) {
|
|
|
157
158
|
return path.normalize(path.isAbsolute(value) ? value : path.resolve(cwd, value));
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
// Whether this checkout is a linked worktree. The two directories have to be
|
|
162
|
+
// compared through realpath: git answers --absolute-git-dir already resolved
|
|
163
|
+
// but --git-common-dir relative to cwd, so on any platform where the working
|
|
164
|
+
// directory sits behind a symlink (macOS /var -> /private/var, for one) a
|
|
165
|
+
// plain string compare reports every ordinary repository as a worktree.
|
|
166
|
+
function isLinkedWorktree(gitDir, commonDir) {
|
|
167
|
+
if (!gitDir || !commonDir) return false;
|
|
168
|
+
return realGitPath(gitDir) !== realGitPath(commonDir);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function realGitPath(value) {
|
|
172
|
+
try {
|
|
173
|
+
return fs.realpathSync(value);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
160
179
|
function parseWorktrees(raw) {
|
|
161
180
|
var blocks = String(raw || "").trim().split(/\n\n+/);
|
|
162
181
|
var result = [];
|
|
@@ -219,7 +238,7 @@ function getStatus(cwd, osUserInfo) {
|
|
|
219
238
|
origin: origin,
|
|
220
239
|
gitDir: gitDir,
|
|
221
240
|
commonDir: commonDir,
|
|
222
|
-
isWorktree:
|
|
241
|
+
isWorktree: isLinkedWorktree(gitDir, commonDir),
|
|
223
242
|
mainWorktree: worktrees.length > 0 ? worktrees[0].path : root,
|
|
224
243
|
worktrees: worktrees,
|
|
225
244
|
files: parsed.files,
|
|
@@ -227,6 +246,78 @@ function getStatus(cwd, osUserInfo) {
|
|
|
227
246
|
};
|
|
228
247
|
}
|
|
229
248
|
|
|
249
|
+
// A bounded, credential-free remote label. The raw remote URL can carry an
|
|
250
|
+
// embedded token, so the userinfo segment is dropped before the label is
|
|
251
|
+
// built rather than after.
|
|
252
|
+
function shortRemoteLabel(origin) {
|
|
253
|
+
if (!origin) return null;
|
|
254
|
+
var label = String(origin).trim()
|
|
255
|
+
.replace(/^git@([^:]+):/, "$1/")
|
|
256
|
+
.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "")
|
|
257
|
+
.replace(/^[^@/]*@/, "")
|
|
258
|
+
.replace(/\.git$/, "")
|
|
259
|
+
.replace(/\/+$/, "");
|
|
260
|
+
if (label.length > MAX_REMOTE_LABEL) label = label.slice(0, MAX_REMOTE_LABEL - 1) + "…";
|
|
261
|
+
return label || null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function countChangedFiles(files) {
|
|
265
|
+
var counts = { changed: files.length, staged: 0, unstaged: 0, untracked: 0, conflicted: 0 };
|
|
266
|
+
for (var i = 0; i < files.length; i++) {
|
|
267
|
+
var file = files[i];
|
|
268
|
+
if (file.conflicted) counts.conflicted++;
|
|
269
|
+
if (file.untracked) counts.untracked++;
|
|
270
|
+
if (file.staged) counts.staged++;
|
|
271
|
+
if (file.unstaged && !file.untracked) counts.unstaged++;
|
|
272
|
+
}
|
|
273
|
+
return counts;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Cheap always-visible repository placard data. Deliberately narrower than
|
|
277
|
+
// getStatus: three git invocations instead of six, no per-file payload, no
|
|
278
|
+
// session attribution, and no filesystem path in the result. Identity is
|
|
279
|
+
// derived here so the client never resolves or renders a repository path.
|
|
280
|
+
function getSummary(cwd, osUserInfo) {
|
|
281
|
+
var probe;
|
|
282
|
+
try {
|
|
283
|
+
probe = runGitSync(cwd, [
|
|
284
|
+
"rev-parse", "--is-inside-work-tree", "--show-toplevel",
|
|
285
|
+
"--absolute-git-dir", "--git-common-dir",
|
|
286
|
+
], null, osUserInfo);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return { isRepository: false };
|
|
289
|
+
}
|
|
290
|
+
var lines = String(probe || "").trim().split("\n");
|
|
291
|
+
if (String(lines[0] || "").trim() !== "true" || !lines[1]) return { isRepository: false };
|
|
292
|
+
|
|
293
|
+
var root = String(lines[1]).trim();
|
|
294
|
+
var gitDir = lines[2] ? normalizeGitPath(cwd, String(lines[2]).trim()) : null;
|
|
295
|
+
var commonDir = lines[3] ? normalizeGitPath(cwd, String(lines[3]).trim()) : null;
|
|
296
|
+
var parsed = parsePorcelainV2(runGitSync(root, ["status", "--porcelain=v2", "--branch", "-z"], null, osUserInfo));
|
|
297
|
+
var origin = null;
|
|
298
|
+
try { origin = runGitSync(cwd, ["remote", "get-url", "origin"], null, osUserInfo).trim() || null; } catch (e) {}
|
|
299
|
+
|
|
300
|
+
var detached = parsed.branch === "(detached)";
|
|
301
|
+
var counts = countChangedFiles(parsed.files);
|
|
302
|
+
return {
|
|
303
|
+
isRepository: true,
|
|
304
|
+
name: path.basename(root),
|
|
305
|
+
branch: detached ? null : parsed.branch,
|
|
306
|
+
detached: detached,
|
|
307
|
+
shortOid: parsed.oid && parsed.oid !== "(initial)" ? String(parsed.oid).slice(0, 8) : null,
|
|
308
|
+
isWorktree: isLinkedWorktree(gitDir, commonDir),
|
|
309
|
+
hasUpstream: !!parsed.upstream,
|
|
310
|
+
ahead: parsed.ahead,
|
|
311
|
+
behind: parsed.behind,
|
|
312
|
+
changed: counts.changed,
|
|
313
|
+
staged: counts.staged,
|
|
314
|
+
unstaged: counts.unstaged,
|
|
315
|
+
untracked: counts.untracked,
|
|
316
|
+
conflicted: counts.conflicted,
|
|
317
|
+
remote: shortRemoteLabel(origin),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
230
321
|
function resolveChangedPaths(status, requestedPaths) {
|
|
231
322
|
if (!Array.isArray(requestedPaths) || requestedPaths.length === 0 || requestedPaths.length > 200) {
|
|
232
323
|
throw new Error("Select at least one changed file");
|
|
@@ -363,8 +454,10 @@ module.exports = {
|
|
|
363
454
|
getFileAtCommit: getFileAtCommit,
|
|
364
455
|
getFileDiff: getFileDiff,
|
|
365
456
|
getStatus: getStatus,
|
|
457
|
+
getSummary: getSummary,
|
|
366
458
|
parsePorcelainV2: parsePorcelainV2,
|
|
367
459
|
parseWorktrees: parseWorktrees,
|
|
368
460
|
readWorkingTreeFile: readWorkingTreeFile,
|
|
369
461
|
runAction: runAction,
|
|
462
|
+
shortRemoteLabel: shortRemoteLabel,
|
|
370
463
|
};
|