@dungle-scrubs/tallow 0.8.7 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +244 -54
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/sdk.d.ts +39 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +322 -13
- package/dist/sdk.js.map +1 -1
- package/dist/session-utils.d.ts +8 -0
- package/dist/session-utils.d.ts.map +1 -1
- package/dist/session-utils.js +38 -1
- package/dist/session-utils.js.map +1 -1
- package/extensions/__integration__/plan-rejection-feedback.test.ts +272 -0
- package/extensions/__integration__/worktree.test.ts +88 -0
- package/extensions/_icons/index.ts +2 -3
- package/extensions/_shared/inline-preview.ts +2 -4
- package/extensions/_shared/interop-events.ts +48 -0
- package/extensions/_shared/shell-policy.ts +2 -1
- package/extensions/_shared/tallow-paths.ts +48 -0
- package/extensions/agent-commands-tool/__tests__/parsing.test.ts +40 -1
- package/extensions/agent-commands-tool/index.ts +38 -6
- package/extensions/background-task-tool/index.ts +2 -2
- package/extensions/bash-tool-enhanced/index.ts +3 -4
- package/extensions/cd-tool/index.ts +3 -3
- package/extensions/claude-bridge/index.ts +2 -1
- package/extensions/command-prompt/__tests__/plugin-sources.test.ts +49 -0
- package/extensions/command-prompt/index.ts +36 -0
- package/extensions/context-files/index.ts +2 -1
- package/extensions/context-fork/index.ts +2 -1
- package/extensions/context-usage/__tests__/tool-result-memory.test.ts +62 -0
- package/extensions/context-usage/index.ts +169 -8
- package/extensions/debug/__tests__/analysis.test.ts +35 -2
- package/extensions/debug/__tests__/diagnostics-commands.test.ts +11 -2
- package/extensions/debug/analysis.ts +53 -16
- package/extensions/debug/index.ts +84 -10
- package/extensions/debug/logger.ts +2 -2
- package/extensions/health/index.ts +2 -2
- package/extensions/hooks/__tests__/claude-compat.test.ts +31 -0
- package/extensions/hooks/extension.json +3 -1
- package/extensions/hooks/hooks.schema.json +17 -1
- package/extensions/hooks/index.ts +52 -7
- package/extensions/lsp/index.ts +2 -7
- package/extensions/output-styles-tool/index.ts +2 -4
- package/extensions/plan-mode-tool/extension.json +1 -0
- package/extensions/plan-mode-tool/index.ts +119 -4
- package/extensions/prompt-suggestions/index.ts +2 -3
- package/extensions/random-spinner/index.ts +2 -3
- package/extensions/read-tool-enhanced/__tests__/notebook-read.test.ts +100 -0
- package/extensions/read-tool-enhanced/__tests__/notebook.test.ts +136 -0
- package/extensions/read-tool-enhanced/extension.json +4 -4
- package/extensions/read-tool-enhanced/index.ts +112 -10
- package/extensions/read-tool-enhanced/notebook.ts +526 -0
- package/extensions/rewind/__tests__/snapshots.test.ts +43 -1
- package/extensions/rewind/snapshots.ts +18 -6
- package/extensions/session-memory/index.ts +3 -2
- package/extensions/stats/stats-log.ts +2 -3
- package/extensions/subagent-tool/__tests__/discovery-defaults.test.ts +23 -0
- package/extensions/subagent-tool/__tests__/isolation-frontmatter.test.ts +100 -0
- package/extensions/subagent-tool/__tests__/model-router.test.ts +8 -0
- package/extensions/subagent-tool/agents.ts +77 -16
- package/extensions/subagent-tool/index.ts +213 -48
- package/extensions/subagent-tool/model-router.ts +3 -3
- package/extensions/subagent-tool/process.ts +233 -22
- package/extensions/subagent-tool/schema.ts +11 -0
- package/extensions/subagent-tool/widget.ts +15 -2
- package/extensions/tasks/__tests__/widget-subagents.test.ts +28 -7
- package/extensions/tasks/commands/register-tasks-extension.ts +15 -33
- package/extensions/tasks/state/index.ts +2 -2
- package/extensions/theme-selector/index.ts +3 -7
- package/extensions/worktree/__tests__/lifecycle.test.ts +115 -0
- package/extensions/worktree/extension.json +31 -0
- package/extensions/worktree/index.ts +149 -0
- package/extensions/worktree/lifecycle.ts +372 -0
- package/package.json +1 -1
- package/skills/tallow-expert/SKILL.md +1 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { type ExecFileSyncOptions, execFileSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
/** Managed worktree directory-name prefix in the system temp dir. */
|
|
8
|
+
export const TALLOW_WORKTREE_PREFIX = "tallow-worktree-";
|
|
9
|
+
|
|
10
|
+
/** Marker file written into managed worktrees for stale cleanup decisions. */
|
|
11
|
+
export const TALLOW_WORKTREE_MARKER_FILE = ".tallow-worktree.json";
|
|
12
|
+
|
|
13
|
+
/** Maximum runtime for git subprocess calls in milliseconds. */
|
|
14
|
+
const GIT_TIMEOUT_MS = 15_000;
|
|
15
|
+
|
|
16
|
+
/** Valid worktree isolation scopes. */
|
|
17
|
+
export type WorktreeScope = "session" | "subagent";
|
|
18
|
+
|
|
19
|
+
/** Options for creating a detached managed worktree. */
|
|
20
|
+
export interface CreateWorktreeOptions {
|
|
21
|
+
readonly scope?: WorktreeScope;
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
readonly agentId?: string;
|
|
24
|
+
readonly timestampMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Result from createWorktree. */
|
|
28
|
+
export interface CreatedWorktree {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly repoRoot: string;
|
|
31
|
+
readonly scope: WorktreeScope;
|
|
32
|
+
readonly timestampMs: number;
|
|
33
|
+
readonly worktreePath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Result from removeWorktree. */
|
|
37
|
+
export interface RemoveWorktreeResult {
|
|
38
|
+
readonly method: "filesystem" | "git" | "none";
|
|
39
|
+
readonly removed: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Aggregate stats from stale worktree cleanup. */
|
|
43
|
+
export interface WorktreeCleanupStats {
|
|
44
|
+
readonly removedCount: number;
|
|
45
|
+
readonly scannedCount: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** On-disk marker payload used to identify managed worktrees. */
|
|
49
|
+
interface WorktreeMarker {
|
|
50
|
+
readonly createdAt: string;
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly pid: number;
|
|
53
|
+
readonly repoRoot: string;
|
|
54
|
+
readonly scope: WorktreeScope;
|
|
55
|
+
agentId?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate that a working directory belongs to a git repository.
|
|
60
|
+
*
|
|
61
|
+
* @param cwd - Directory to validate
|
|
62
|
+
* @returns Resolved git repository root
|
|
63
|
+
* @throws {Error} When cwd is not inside a git repository
|
|
64
|
+
*/
|
|
65
|
+
export function validateGitRepo(cwd: string): { readonly repoRoot: string } {
|
|
66
|
+
const resolvedCwd = resolve(cwd);
|
|
67
|
+
const repoRoot = runGit(["-C", resolvedCwd, "rev-parse", "--show-toplevel"], resolvedCwd);
|
|
68
|
+
if (!repoRoot) {
|
|
69
|
+
throw new Error(`Not inside a git repository: ${resolvedCwd}`);
|
|
70
|
+
}
|
|
71
|
+
return { repoRoot: resolve(repoRoot) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a detached managed worktree for the given repository.
|
|
76
|
+
*
|
|
77
|
+
* @param repoRoot - Git repository root
|
|
78
|
+
* @param options - Optional scope and identifier overrides
|
|
79
|
+
* @returns Created worktree metadata
|
|
80
|
+
* @throws {Error} When creation fails
|
|
81
|
+
*/
|
|
82
|
+
export function createWorktree(
|
|
83
|
+
repoRoot: string,
|
|
84
|
+
options: CreateWorktreeOptions = {}
|
|
85
|
+
): CreatedWorktree {
|
|
86
|
+
const validatedRoot = validateGitRepo(repoRoot).repoRoot;
|
|
87
|
+
const scope = options.scope ?? "session";
|
|
88
|
+
const id = sanitizeSegment(options.id ?? randomUUID().slice(0, 8));
|
|
89
|
+
const timestampMs = options.timestampMs ?? Date.now();
|
|
90
|
+
const worktreePath = join(tmpdir(), `${TALLOW_WORKTREE_PREFIX}${scope}-${id}-${timestampMs}`);
|
|
91
|
+
|
|
92
|
+
runGit(["-C", validatedRoot, "worktree", "add", "--detach", worktreePath, "HEAD"], validatedRoot);
|
|
93
|
+
|
|
94
|
+
const marker: WorktreeMarker = {
|
|
95
|
+
createdAt: new Date(timestampMs).toISOString(),
|
|
96
|
+
id,
|
|
97
|
+
pid: process.pid,
|
|
98
|
+
repoRoot: validatedRoot,
|
|
99
|
+
scope,
|
|
100
|
+
};
|
|
101
|
+
if (options.agentId) {
|
|
102
|
+
marker.agentId = options.agentId;
|
|
103
|
+
}
|
|
104
|
+
writeMarkerFile(worktreePath, marker);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
repoRoot: validatedRoot,
|
|
109
|
+
scope,
|
|
110
|
+
timestampMs,
|
|
111
|
+
worktreePath,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove a managed worktree path.
|
|
117
|
+
*
|
|
118
|
+
* Prefers `git worktree remove --force`. Falls back to filesystem deletion
|
|
119
|
+
* plus `git worktree prune` when git removal fails.
|
|
120
|
+
*
|
|
121
|
+
* @param worktreePath - Worktree path to remove
|
|
122
|
+
* @returns Removal outcome and method used
|
|
123
|
+
*/
|
|
124
|
+
export function removeWorktree(worktreePath: string): RemoveWorktreeResult {
|
|
125
|
+
const absoluteWorktreePath = resolve(worktreePath);
|
|
126
|
+
if (!existsSync(absoluteWorktreePath)) {
|
|
127
|
+
return { method: "none", removed: false };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const marker = readMarkerFile(absoluteWorktreePath);
|
|
131
|
+
const repoRootCandidates = new Set<string>();
|
|
132
|
+
if (marker?.repoRoot) repoRootCandidates.add(resolve(marker.repoRoot));
|
|
133
|
+
const inferredRoot = inferRepoRootFromWorktree(absoluteWorktreePath);
|
|
134
|
+
if (inferredRoot) repoRootCandidates.add(inferredRoot);
|
|
135
|
+
|
|
136
|
+
for (const repoRoot of repoRootCandidates) {
|
|
137
|
+
try {
|
|
138
|
+
runGit(["-C", repoRoot, "worktree", "remove", "--force", absoluteWorktreePath], repoRoot);
|
|
139
|
+
pruneWorktrees(repoRoot);
|
|
140
|
+
return { method: "git", removed: !existsSync(absoluteWorktreePath) };
|
|
141
|
+
} catch {
|
|
142
|
+
// Fall through to filesystem fallback.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
rmSync(absoluteWorktreePath, { force: true, recursive: true });
|
|
148
|
+
} catch {
|
|
149
|
+
// Best-effort fallback path.
|
|
150
|
+
}
|
|
151
|
+
for (const repoRoot of repoRootCandidates) {
|
|
152
|
+
pruneWorktrees(repoRoot);
|
|
153
|
+
}
|
|
154
|
+
return { method: "filesystem", removed: !existsSync(absoluteWorktreePath) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Remove stale managed worktrees for a repository.
|
|
159
|
+
*
|
|
160
|
+
* A managed worktree is considered stale when either:
|
|
161
|
+
* - it is not listed in `git worktree list --porcelain`, or
|
|
162
|
+
* - its marker PID is not running anymore.
|
|
163
|
+
*
|
|
164
|
+
* @param repoRoot - Repository root to clean
|
|
165
|
+
* @returns Number of scanned and removed worktrees
|
|
166
|
+
*/
|
|
167
|
+
export function cleanupStaleWorktrees(repoRoot: string): WorktreeCleanupStats {
|
|
168
|
+
const validatedRoot = validateGitRepo(repoRoot).repoRoot;
|
|
169
|
+
pruneWorktrees(validatedRoot);
|
|
170
|
+
const activeWorktrees = listActiveWorktrees(validatedRoot);
|
|
171
|
+
const managedWorktrees = listManagedWorktreePaths();
|
|
172
|
+
|
|
173
|
+
let removedCount = 0;
|
|
174
|
+
let scannedCount = 0;
|
|
175
|
+
|
|
176
|
+
for (const worktreePath of managedWorktrees) {
|
|
177
|
+
const marker = readMarkerFile(worktreePath);
|
|
178
|
+
if (marker && resolve(marker.repoRoot) !== validatedRoot) continue;
|
|
179
|
+
|
|
180
|
+
scannedCount += 1;
|
|
181
|
+
const isActive = activeWorktrees.has(worktreePath);
|
|
182
|
+
const hasLiveOwnerPid = marker ? isProcessAlive(marker.pid) : false;
|
|
183
|
+
if (isActive && hasLiveOwnerPid) continue;
|
|
184
|
+
if (isActive && !marker) continue;
|
|
185
|
+
if (!marker && !isActive) continue;
|
|
186
|
+
|
|
187
|
+
const removal = removeWorktree(worktreePath);
|
|
188
|
+
if (removal.removed) {
|
|
189
|
+
removedCount += 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pruneWorktrees(validatedRoot);
|
|
194
|
+
return { removedCount, scannedCount };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* List active git worktrees for a repository.
|
|
199
|
+
*
|
|
200
|
+
* @param repoRoot - Repository root
|
|
201
|
+
* @returns Absolute worktree paths currently registered with git
|
|
202
|
+
*/
|
|
203
|
+
function listActiveWorktrees(repoRoot: string): Set<string> {
|
|
204
|
+
const output = runGit(["-C", repoRoot, "worktree", "list", "--porcelain"], repoRoot);
|
|
205
|
+
const active = new Set<string>();
|
|
206
|
+
for (const line of output.split("\n")) {
|
|
207
|
+
if (!line.startsWith("worktree ")) continue;
|
|
208
|
+
const value = line.slice("worktree ".length).trim();
|
|
209
|
+
if (!value) continue;
|
|
210
|
+
active.add(resolve(value));
|
|
211
|
+
}
|
|
212
|
+
return active;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Enumerate managed worktree directories in the system temp directory.
|
|
217
|
+
*
|
|
218
|
+
* @returns Absolute paths for managed worktree directories
|
|
219
|
+
*/
|
|
220
|
+
function listManagedWorktreePaths(): string[] {
|
|
221
|
+
const root = tmpdir();
|
|
222
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
223
|
+
const paths: string[] = [];
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (!entry.name.startsWith(TALLOW_WORKTREE_PREFIX)) continue;
|
|
226
|
+
const fullPath = join(root, entry.name);
|
|
227
|
+
const isDirectory =
|
|
228
|
+
entry.isDirectory() || (entry.isSymbolicLink() && safeIsDirectory(fullPath));
|
|
229
|
+
if (!isDirectory) continue;
|
|
230
|
+
paths.push(resolve(fullPath));
|
|
231
|
+
}
|
|
232
|
+
return paths;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check whether a path currently resolves to a directory.
|
|
237
|
+
*
|
|
238
|
+
* @param pathValue - Candidate directory path
|
|
239
|
+
* @returns True when the path is a directory
|
|
240
|
+
*/
|
|
241
|
+
function safeIsDirectory(pathValue: string): boolean {
|
|
242
|
+
try {
|
|
243
|
+
return statSync(pathValue).isDirectory();
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Parse a managed worktree marker file.
|
|
251
|
+
*
|
|
252
|
+
* @param worktreePath - Worktree directory path
|
|
253
|
+
* @returns Marker payload when present and valid
|
|
254
|
+
*/
|
|
255
|
+
function readMarkerFile(worktreePath: string): WorktreeMarker | undefined {
|
|
256
|
+
const markerPath = join(worktreePath, TALLOW_WORKTREE_MARKER_FILE);
|
|
257
|
+
if (!existsSync(markerPath)) return undefined;
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(readFileSync(markerPath, "utf-8")) as Partial<WorktreeMarker>;
|
|
260
|
+
if (!parsed || typeof parsed !== "object") return undefined;
|
|
261
|
+
if (parsed.scope !== "session" && parsed.scope !== "subagent") return undefined;
|
|
262
|
+
if (typeof parsed.repoRoot !== "string") return undefined;
|
|
263
|
+
if (typeof parsed.id !== "string") return undefined;
|
|
264
|
+
if (typeof parsed.pid !== "number" || !Number.isFinite(parsed.pid)) return undefined;
|
|
265
|
+
if (typeof parsed.createdAt !== "string") return undefined;
|
|
266
|
+
return {
|
|
267
|
+
agentId: typeof parsed.agentId === "string" ? parsed.agentId : undefined,
|
|
268
|
+
createdAt: parsed.createdAt,
|
|
269
|
+
id: parsed.id,
|
|
270
|
+
pid: Math.floor(parsed.pid),
|
|
271
|
+
repoRoot: resolve(parsed.repoRoot),
|
|
272
|
+
scope: parsed.scope,
|
|
273
|
+
};
|
|
274
|
+
} catch {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Write marker metadata into a managed worktree directory.
|
|
281
|
+
*
|
|
282
|
+
* @param worktreePath - Worktree directory path
|
|
283
|
+
* @param marker - Marker payload
|
|
284
|
+
*/
|
|
285
|
+
function writeMarkerFile(worktreePath: string, marker: WorktreeMarker): void {
|
|
286
|
+
const markerPath = join(worktreePath, TALLOW_WORKTREE_MARKER_FILE);
|
|
287
|
+
writeFileSync(markerPath, `${JSON.stringify(marker, null, 2)}\n`, "utf-8");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Infer repository root from a worktree path.
|
|
292
|
+
*
|
|
293
|
+
* @param worktreePath - Worktree directory path
|
|
294
|
+
* @returns Repository root when detectable
|
|
295
|
+
*/
|
|
296
|
+
function inferRepoRootFromWorktree(worktreePath: string): string | undefined {
|
|
297
|
+
try {
|
|
298
|
+
const output = runGit(["-C", worktreePath, "rev-parse", "--show-toplevel"], worktreePath);
|
|
299
|
+
return output ? resolve(output) : undefined;
|
|
300
|
+
} catch {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Run `git worktree prune` for a repository root.
|
|
307
|
+
*
|
|
308
|
+
* @param repoRoot - Repository root
|
|
309
|
+
*/
|
|
310
|
+
function pruneWorktrees(repoRoot: string): void {
|
|
311
|
+
try {
|
|
312
|
+
runGit(["-C", repoRoot, "worktree", "prune"], repoRoot);
|
|
313
|
+
} catch {
|
|
314
|
+
// Best-effort cleanup path.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check whether a PID appears alive.
|
|
320
|
+
*
|
|
321
|
+
* @param pid - Process identifier
|
|
322
|
+
* @returns True when the process is alive
|
|
323
|
+
*/
|
|
324
|
+
function isProcessAlive(pid: number): boolean {
|
|
325
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
326
|
+
try {
|
|
327
|
+
process.kill(pid, 0);
|
|
328
|
+
return true;
|
|
329
|
+
} catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Sanitize a user-provided segment for safe path construction.
|
|
336
|
+
*
|
|
337
|
+
* @param value - Raw segment value
|
|
338
|
+
* @returns Safe kebab-style segment
|
|
339
|
+
*/
|
|
340
|
+
function sanitizeSegment(value: string): string {
|
|
341
|
+
const normalized = value
|
|
342
|
+
.trim()
|
|
343
|
+
.toLowerCase()
|
|
344
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
345
|
+
.replace(/^-+|-+$/g, "");
|
|
346
|
+
if (!normalized) return "task";
|
|
347
|
+
return normalized.slice(0, 48);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Execute a git command and return trimmed stdout.
|
|
352
|
+
*
|
|
353
|
+
* @param args - Git command arguments
|
|
354
|
+
* @param cwd - Working directory
|
|
355
|
+
* @returns Trimmed command output
|
|
356
|
+
* @throws {Error} When git exits non-zero
|
|
357
|
+
*/
|
|
358
|
+
function runGit(args: string[], cwd: string): string {
|
|
359
|
+
const options: ExecFileSyncOptions = {
|
|
360
|
+
cwd,
|
|
361
|
+
encoding: "utf-8",
|
|
362
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
363
|
+
timeout: GIT_TIMEOUT_MS,
|
|
364
|
+
};
|
|
365
|
+
try {
|
|
366
|
+
const output = execFileSync("git", args, options) as string;
|
|
367
|
+
return output.trim();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
370
|
+
throw new Error(`git ${args.join(" ")} failed: ${message}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@ Relay that answer to the user.
|
|
|
34
34
|
| Component | Location |
|
|
35
35
|
|-----------|----------|
|
|
36
36
|
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust.ts, runtime-path-provider.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts) |
|
|
37
|
-
| Extensions | `extensions/` — extension.json + index.ts each (
|
|
37
|
+
| Extensions | `extensions/` — extension.json + index.ts each (51 bundled) |
|
|
38
38
|
| Skills | `skills/` — subdirs with SKILL.md |
|
|
39
39
|
| Agents | `agents/` — markdown with YAML frontmatter |
|
|
40
40
|
| Themes | `themes/` — JSON files (34 dark-only themes) |
|