specrails-desktop 2.24.0 → 2.24.2
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 +5 -4
- package/docs/guide/de/integrations/3-jira-integration.md +3 -2
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/en/integrations/3-jira-integration.md +4 -3
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/es/integrations/3-jira-integration.md +3 -2
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/fr/integrations/3-jira-integration.md +3 -2
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/it/integrations/3-jira-integration.md +3 -2
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/ja/integrations/3-jira-integration.md +3 -2
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/pt/integrations/3-jira-integration.md +3 -2
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/docs/guide/zh/integrations/3-jira-integration.md +3 -2
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +5 -5
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +6 -4
- package/package.json +1 -1
- package/server/dist/active-pr-continuation.js +171 -0
- package/server/dist/agent-operator-prompt.js +9 -0
- package/server/dist/claude-trust.js +32 -25
- package/server/dist/docs-router.js +75 -10
- package/server/dist/integration-branch.js +80 -0
- package/server/dist/jira/jira-adf.js +8 -0
- package/server/dist/jira/jira-sync-manager.js +50 -11
- package/server/dist/mcp/guide.js +9 -0
- package/server/dist/mcp/tools/rails.js +2 -1
- package/server/dist/pr-publisher.js +40 -2
- package/server/dist/rail-isolated-launch.js +107 -7
- package/server/dist/vitest-setup.js +28 -0
- package/server/dist/worktree-manager.js +5 -1
|
@@ -23,10 +23,18 @@ exports.publishDraftPr = publishDraftPr;
|
|
|
23
23
|
*/
|
|
24
24
|
const child_process_1 = require("child_process");
|
|
25
25
|
const git_guardrails_1 = require("./git-guardrails");
|
|
26
|
+
const win_spawn_1 = require("./util/win-spawn");
|
|
26
27
|
exports.defaultExec = {
|
|
27
28
|
run(cmd, args, cwd) {
|
|
28
29
|
return new Promise((resolve) => {
|
|
29
|
-
(
|
|
30
|
+
// `windowsSpawnEnv()` backfills SystemRoot / ComSpec / USERPROFILE, which a
|
|
31
|
+
// GUI-launched / pkg-stripped Windows sidecar can lack — without them the
|
|
32
|
+
// `git`/`gh` child (and the `sh -c` the `!gh …` credential helper runs) can
|
|
33
|
+
// fail to start. NOT `GIT_EXEC_ENV`: that hardened env disables credentials
|
|
34
|
+
// (GIT_ASKPASS=echo / GIT_TERMINAL_PROMPT=0), which the push must keep. No-op
|
|
35
|
+
// on POSIX (returns process.env), so mac/Linux behaviour is byte-identical.
|
|
36
|
+
const env = (0, win_spawn_1.windowsSpawnEnv)();
|
|
37
|
+
(0, child_process_1.execFile)(cmd, args, { cwd, env, maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
|
|
30
38
|
const code = err && typeof err.code === 'number' ? err.code : err ? 1 : 0;
|
|
31
39
|
resolve({ code, stdout: stdout?.toString() ?? '', stderr: stderr?.toString() ?? '' });
|
|
32
40
|
});
|
|
@@ -45,7 +53,22 @@ async function publishDraftPr(exec, input) {
|
|
|
45
53
|
// Guardrail: never force-push, never push the integration branch itself.
|
|
46
54
|
const pushArgs = ['push', '-u', remote, branch];
|
|
47
55
|
(0, git_guardrails_1.assertGitAllowed)('git', pushArgs, { protectedBranch: baseBranch });
|
|
48
|
-
|
|
56
|
+
let push = await exec.run('git', pushArgs, repoDir);
|
|
57
|
+
if (push.code !== 0 && isCredentialFailure(push)) {
|
|
58
|
+
// The push failed authenticating over HTTPS — typically the machine's
|
|
59
|
+
// configured credential helper (e.g. git-credential-osxkeychain) is not on
|
|
60
|
+
// the app's PATH (GUI-launch PATH divergence: the sidecar inherits launchd's
|
|
61
|
+
// PATH, not the user's login shell). Retry borrowing gh's credentials — the
|
|
62
|
+
// SAME auth we use for `gh pr create` below, so if the PR can be created the
|
|
63
|
+
// push can succeed — instead of depending on a machine credential helper.
|
|
64
|
+
// `-c credential.helper=` first RESETS the (broken) inherited helper list,
|
|
65
|
+
// then adds gh's. Harmless for SSH remotes (git ignores credential.helper).
|
|
66
|
+
// The semantic push (refspec/remote) is unchanged, so the guardrail asserted
|
|
67
|
+
// above still holds; no-op when gh is absent (retry fails the same way).
|
|
68
|
+
const retry = await exec.run('git', ['-c', 'credential.helper=', '-c', 'credential.helper=!gh auth git-credential', ...pushArgs], repoDir);
|
|
69
|
+
if (retry.code === 0)
|
|
70
|
+
push = retry;
|
|
71
|
+
}
|
|
49
72
|
if (push.code !== 0) {
|
|
50
73
|
return { state: 'local-only', branch, reason: reasonFrom(push) };
|
|
51
74
|
}
|
|
@@ -66,3 +89,18 @@ function reasonFrom(r) {
|
|
|
66
89
|
const msg = (r.stderr.trim() || r.stdout.trim()).split('\n')[0];
|
|
67
90
|
return msg || `exit ${r.code}`;
|
|
68
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* True when a failed `git push` looks like an HTTPS credential/authentication
|
|
94
|
+
* problem (as opposed to a rejected ref, missing remote, network, etc.) — the
|
|
95
|
+
* only class worth retrying through gh's credential helper. Matches the
|
|
96
|
+
* canonical git strings, including the "'credential-<helper>' is not a git
|
|
97
|
+
* command" case that happens when the configured helper binary isn't on PATH.
|
|
98
|
+
*/
|
|
99
|
+
function isCredentialFailure(r) {
|
|
100
|
+
const s = `${r.stderr}\n${r.stdout}`.toLowerCase();
|
|
101
|
+
return (s.includes('could not read username') ||
|
|
102
|
+
s.includes('could not read password') ||
|
|
103
|
+
s.includes('authentication failed') ||
|
|
104
|
+
s.includes('credential-') ||
|
|
105
|
+
s.includes('terminal prompts disabled'));
|
|
106
|
+
}
|
|
@@ -80,6 +80,8 @@ const feature_flags_1 = require("./feature-flags");
|
|
|
80
80
|
const file_provenance_1 = require("./file-provenance");
|
|
81
81
|
const file_story_1 = require("./file-story");
|
|
82
82
|
const providers_1 = require("./providers");
|
|
83
|
+
const pr_publisher_1 = require("./pr-publisher");
|
|
84
|
+
const active_pr_continuation_1 = require("./active-pr-continuation");
|
|
83
85
|
/**
|
|
84
86
|
* Ticket data for the conventional branch name (pr-naming): title/labels from
|
|
85
87
|
* the ticket-spec reader, the Jira key resolved with the authoritative
|
|
@@ -106,6 +108,18 @@ function unitNamingInput(ctx, ticketId) {
|
|
|
106
108
|
}
|
|
107
109
|
return { ticketId, title: spec?.title ?? null, labels: spec?.labels ?? null, jiraKey };
|
|
108
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Commit messages are consumed by GitHub↔Jira development panels. Branch names
|
|
113
|
+
* already carry the Jira key for new work; commits must too, especially when
|
|
114
|
+
* continuing an existing PR whose branch name may predate the current ticket
|
|
115
|
+
* key. Keep the local id as a fallback/cross-reference for Specrails.
|
|
116
|
+
*/
|
|
117
|
+
function worktreeCommitMessage(ctx, ticketId, runId, partial = false) {
|
|
118
|
+
const input = unitNamingInput(ctx, ticketId);
|
|
119
|
+
const ref = (0, pr_naming_1.ticketRef)(input);
|
|
120
|
+
const subjectRef = ref === String(ticketId) ? `ticket-${ticketId}` : `${ref} ticket-${ticketId}`;
|
|
121
|
+
return `specrails: ${subjectRef}${partial ? ' partial' : ''} (run ${runId})`;
|
|
122
|
+
}
|
|
109
123
|
/**
|
|
110
124
|
* Launch the rail's tickets in isolated worktrees + schedule the merge-back.
|
|
111
125
|
* Returns the loop run ids. Throws if worktree allocation fails (the caller then
|
|
@@ -115,6 +129,7 @@ function unitNamingInput(ctx, ticketId) {
|
|
|
115
129
|
async function launchIsolatedRail(input, io = {}) {
|
|
116
130
|
const { ctx, railIndex, ticketIds, loopId, loopName, loopGraph, provider, model, effort } = input;
|
|
117
131
|
const git = io.git ?? worktree_manager_1.defaultGitRunner;
|
|
132
|
+
const exec = io.exec ?? pr_publisher_1.defaultExec;
|
|
118
133
|
const create = io.create ?? worktree_manager_1.createWorktree;
|
|
119
134
|
const remove = io.remove ?? worktree_manager_1.removeWorktree;
|
|
120
135
|
const overlay = io.overlay ?? worktree_overlay_1.applyWorktreeOverlay;
|
|
@@ -209,8 +224,20 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
209
224
|
// Only allocation is locked — the fan-out (the actual AI runs) below
|
|
210
225
|
// stays fully parallel.
|
|
211
226
|
let integration;
|
|
227
|
+
let launchContinuation = null;
|
|
212
228
|
const allocated = [];
|
|
213
229
|
await (0, repo_lock_1.withRepoLock)(baseRepo, async () => {
|
|
230
|
+
// Bring the repo's remote-tracking refs up to date BEFORE resolving the
|
|
231
|
+
// integration branch or allocating any worktree — otherwise `git worktree
|
|
232
|
+
// add -b <branch> <path> <bare-name>` resolves against whatever (possibly
|
|
233
|
+
// stale) commit the user's LOCAL branch happens to be at. `fetchOrigin` only
|
|
234
|
+
// ever touches `refs/remotes/origin/*`; it never mutates the checked-out
|
|
235
|
+
// branch/working tree. De-duped per repo for a short TTL so a "Launch all"
|
|
236
|
+
// batch (N independent launch requests for the same repo, serialized by
|
|
237
|
+
// this same withRepoLock) performs one real fetch, not one per rail. A
|
|
238
|
+
// failed fetch (no network / no remote / auth error) never blocks the
|
|
239
|
+
// launch — resolveWorktreeBaseRef below degrades to the local ref.
|
|
240
|
+
const fetchResult = await (0, integration_branch_1.fetchOrigin)(git, baseRepo);
|
|
214
241
|
// Resolve the project's designated integration branch ONCE, and branch every
|
|
215
242
|
// ticket's worktree off it (not the ambient HEAD). Empty setting → auto-resolve
|
|
216
243
|
// (repo default → HEAD fallback). See server/integration-branch.ts.
|
|
@@ -218,13 +245,58 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
218
245
|
repoDir: baseRepo,
|
|
219
246
|
projectSetting: (0, db_1.getProjectSettings)(ctx.db).integrationBranch,
|
|
220
247
|
});
|
|
248
|
+
// Prefer the freshly-fetched remote-tracking ref (origin/<branch>) over the
|
|
249
|
+
// bare local name for repo-default/project-setting sources — see
|
|
250
|
+
// resolveWorktreeBaseRef for the exact fallback policy. `explicit` is left
|
|
251
|
+
// completely untouched (rare, launch-time-chosen override).
|
|
252
|
+
const worktreeBaseRef = await (0, integration_branch_1.resolveWorktreeBaseRef)(git, {
|
|
253
|
+
repoDir: baseRepo, integration, fetchOk: fetchResult.ok,
|
|
254
|
+
});
|
|
255
|
+
if (worktreeBaseRef.warning) {
|
|
256
|
+
console.warn(`[rail-isolated] ${worktreeBaseRef.warning} (repo ${baseRepo})`);
|
|
257
|
+
try {
|
|
258
|
+
ctx.broadcast({ type: 'rail.fetch_degraded', projectId: ctx.project.id, railIndex, warning: worktreeBaseRef.warning });
|
|
259
|
+
}
|
|
260
|
+
catch { /* non-fatal, mirrors notifyOverlayDegraded's broadcast guard below */ }
|
|
261
|
+
}
|
|
262
|
+
// Active-PR continuation: if a ticket is already parked on_review with a
|
|
263
|
+
// matching OPEN GitHub PR (or a Jira-linked in_progress ticket explicitly
|
|
264
|
+
// matches an open PR, covering unmapped Jira "Review" statuses), run the next
|
|
265
|
+
// pass directly on that PR's head branch. Fresh tickets stay on the normal
|
|
266
|
+
// integration-base path. Keep this deliberately single-target for now: one
|
|
267
|
+
// rail delivery row can represent one PR URL, so multi-PR batches continue to
|
|
268
|
+
// use the existing new-work flow instead of silently mixing PRs.
|
|
269
|
+
const continuationTargets = await (0, active_pr_continuation_1.resolveActivePrContinuationTargets)({
|
|
270
|
+
db: ctx.db,
|
|
271
|
+
git,
|
|
272
|
+
exec,
|
|
273
|
+
repoDir: baseRepo,
|
|
274
|
+
ticketIds: units.map((u) => u.ticketId),
|
|
275
|
+
integrationBranch: integration.branch,
|
|
276
|
+
fetchOk: fetchResult.ok,
|
|
277
|
+
getTicketSpec: (ticketId) => {
|
|
278
|
+
try {
|
|
279
|
+
return ctx.getTicketSpec(ticketId);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
const uniqueContinuationKeys = new Set(units
|
|
287
|
+
.map((u) => continuationTargets.get(u.ticketId))
|
|
288
|
+
.filter((t) => !!t)
|
|
289
|
+
.map((t) => `${t.prUrl ?? ''}\n${t.branch}`));
|
|
290
|
+
if (uniqueContinuationKeys.size === 1 && units.every((u) => continuationTargets.has(u.ticketId))) {
|
|
291
|
+
launchContinuation = continuationTargets.get(units[0].ticketId) ?? null;
|
|
292
|
+
}
|
|
221
293
|
if (prMode) {
|
|
222
294
|
prDeliveryId = (0, rail_pr_store_1.createPrDelivery)(ctx.db, {
|
|
223
295
|
railIndex,
|
|
224
296
|
loopId,
|
|
225
297
|
railKey: `${railIndex}-${loopId}`,
|
|
226
298
|
ticketIds: [...ticketIds],
|
|
227
|
-
baseBranch: integration.branch,
|
|
299
|
+
baseBranch: launchContinuation?.baseBranch ?? integration.branch,
|
|
228
300
|
loopName,
|
|
229
301
|
originSurface: input.originSurface ?? 'dashboard',
|
|
230
302
|
originConversationId: input.originConversationId ?? null,
|
|
@@ -241,6 +313,11 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
241
313
|
// launches see each other's just-created branches.
|
|
242
314
|
const takenBranches = await (0, worktree_manager_1.listLocalBranches)(git, baseRepo);
|
|
243
315
|
const unitBranchName = (ticketId) => {
|
|
316
|
+
const continuation = launchContinuation && continuationTargets.get(ticketId);
|
|
317
|
+
if (continuation) {
|
|
318
|
+
takenBranches.add(continuation.branch);
|
|
319
|
+
return continuation.branch;
|
|
320
|
+
}
|
|
244
321
|
const preferred = (0, pr_naming_1.ticketBranchName)(unitNamingInput(ctx, ticketId));
|
|
245
322
|
const branch = (0, pr_naming_1.resolveCollisionFreeName)(preferred, {
|
|
246
323
|
taken: (name) => takenBranches.has(name) && !(0, rail_worktrees_store_1.railWorktreeBranchExistsForTicket)(ctx.db, ticketId, name),
|
|
@@ -251,7 +328,15 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
251
328
|
};
|
|
252
329
|
try {
|
|
253
330
|
for (const unit of units) {
|
|
254
|
-
const
|
|
331
|
+
const continuationTarget = launchContinuation ? continuationTargets.get(unit.ticketId) ?? null : null;
|
|
332
|
+
const handle = await create(git, {
|
|
333
|
+
repoDir: baseRepo,
|
|
334
|
+
worktreesRoot,
|
|
335
|
+
slug,
|
|
336
|
+
ticketId: unit.ticketId,
|
|
337
|
+
baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
|
|
338
|
+
branch: unitBranchName(unit.ticketId),
|
|
339
|
+
});
|
|
255
340
|
// Per-run overlay: merge-link the framework surface the checkout didn't
|
|
256
341
|
// bring into the worktree (idempotent; resume-safe via its manifest).
|
|
257
342
|
let overlayExcludes = [];
|
|
@@ -290,7 +375,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
290
375
|
id: ledgerId, railIndex, ticketId: unit.ticketId, runId,
|
|
291
376
|
branch: handle.branch, worktreePath: handle.worktreePath,
|
|
292
377
|
});
|
|
293
|
-
allocated.push({ ticketId: unit.ticketId, ticketIds: unit.ticketIds, runId, ledgerId, handle, overlayExcludes, provenanceSnapshot });
|
|
378
|
+
allocated.push({ ticketId: unit.ticketId, ticketIds: unit.ticketIds, runId, ledgerId, handle, overlayExcludes, provenanceSnapshot, continuationTarget });
|
|
294
379
|
}
|
|
295
380
|
}
|
|
296
381
|
catch (err) {
|
|
@@ -354,7 +439,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
354
439
|
// Commit the run's work to its branch so the merge-back can integrate it
|
|
355
440
|
// (loops like autoloop-tdd don't commit) and a re-launch can resume it.
|
|
356
441
|
// Overlay scaffolding is excluded — it must never reach the branch/PR.
|
|
357
|
-
await (0, worktree_manager_1.commitWorktree)(git, a.handle.worktreePath,
|
|
442
|
+
await (0, worktree_manager_1.commitWorktree)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId), a.overlayExcludes);
|
|
358
443
|
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, succeeded ? 'built' : 'failed');
|
|
359
444
|
return { run: a, succeeded };
|
|
360
445
|
})
|
|
@@ -363,7 +448,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
363
448
|
ctx.onLoopRunFinished(a.runId, 'failed', runFinishedOpts);
|
|
364
449
|
recordRunProvenance(a);
|
|
365
450
|
// Commit partial work too → durable in git → resumable on re-launch.
|
|
366
|
-
await (0, worktree_manager_1.commitWorktree)(git, a.handle.worktreePath,
|
|
451
|
+
await (0, worktree_manager_1.commitWorktree)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, true), a.overlayExcludes);
|
|
367
452
|
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
|
|
368
453
|
return { run: a, succeeded: false };
|
|
369
454
|
});
|
|
@@ -402,8 +487,23 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
402
487
|
// → auto-close (per-run settle already reverted the tickets).
|
|
403
488
|
const worktreeIds = allocated.map((a) => a.ledgerId);
|
|
404
489
|
const anySucceeded = results.some((r) => r.succeeded);
|
|
405
|
-
const
|
|
406
|
-
|
|
490
|
+
const settledContinuation = anySucceeded ? launchContinuation : null;
|
|
491
|
+
const next = anySucceeded
|
|
492
|
+
? (settledContinuation
|
|
493
|
+
? (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft')
|
|
494
|
+
: 'on_review')
|
|
495
|
+
: 'discarded';
|
|
496
|
+
const patch = settledContinuation
|
|
497
|
+
? {
|
|
498
|
+
branches,
|
|
499
|
+
worktreeIds,
|
|
500
|
+
branch: settledContinuation.branch,
|
|
501
|
+
prUrl: settledContinuation.prUrl,
|
|
502
|
+
prNumber: settledContinuation.prNumber,
|
|
503
|
+
prState: 'pr-created',
|
|
504
|
+
}
|
|
505
|
+
: { branches, worktreeIds };
|
|
506
|
+
if (prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', next, patch)) {
|
|
407
507
|
broadcastPrState();
|
|
408
508
|
// Completion driver: refresh the origin conversation's card in place —
|
|
409
509
|
// on_review asks the question, discarded informs the outcome.
|
|
@@ -23,3 +23,31 @@ const path_1 = __importDefault(require("path"));
|
|
|
23
23
|
if (!process.env.SPECRAILS_REGISTRY_HOME) {
|
|
24
24
|
process.env.SPECRAILS_REGISTRY_HOME = (0, fs_1.mkdtempSync)(path_1.default.join(os_1.default.tmpdir(), 'specrails-desktop-test-home-'));
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Hermetic env: a test run must NEVER inherit relocation / desktop-mode env
|
|
28
|
+
* vars from the host process. When the suite runs INSIDE a Specrails rail spawn
|
|
29
|
+
* (e.g. a `/specrails:implement` job running the verification step), that parent
|
|
30
|
+
* process exports `SPECRAILS_REPO_DIR` etc., which leak straight through the
|
|
31
|
+
* legacy (non-relocated) spawn path — the managers spread `process.env` into the
|
|
32
|
+
* child env — and break the "LEGACY: no relocation env" assertions in
|
|
33
|
+
* chat-manager / queue-manager tests. No test relies on ambient inheritance: the
|
|
34
|
+
* relocated-branch tests activate relocation via the registry + populated
|
|
35
|
+
* workspace gate, and the path-resolver / setup-prerequisites tests set their
|
|
36
|
+
* own vars per-case in `beforeEach` (which run AFTER this file), so a
|
|
37
|
+
* blanket-delete here is safe and any per-case setter still wins. Only
|
|
38
|
+
* `SPECRAILS_REPO_DIR` breaks a test today; the rest are defense-in-depth (they
|
|
39
|
+
* would leak identically the moment a future test asserts on them). Do NOT
|
|
40
|
+
* delete `SPECRAILS_REGISTRY_HOME` — the block above sets it intentionally.
|
|
41
|
+
*/
|
|
42
|
+
for (const k of [
|
|
43
|
+
'SPECRAILS_REPO_DIR',
|
|
44
|
+
'SPECRAILS_IS_DESKTOP',
|
|
45
|
+
'SPECRAILS_BUNDLED_RUNTIMES_PATH',
|
|
46
|
+
'SPECRAILS_TICKETS_PATH',
|
|
47
|
+
'SPECRAILS_BACKLOG_CONFIG_PATH',
|
|
48
|
+
'SPECRAILS_WORKSPACE_DIR',
|
|
49
|
+
'SPECRAILS_PROFILES_DIR',
|
|
50
|
+
'SPECRAILS_STATE_DIR',
|
|
51
|
+
]) {
|
|
52
|
+
delete process.env[k];
|
|
53
|
+
}
|
|
@@ -59,10 +59,14 @@ exports.listWorktrees = listWorktrees;
|
|
|
59
59
|
*/
|
|
60
60
|
const child_process_1 = require("child_process");
|
|
61
61
|
const path = __importStar(require("path"));
|
|
62
|
+
const win_spawn_1 = require("./util/win-spawn");
|
|
62
63
|
exports.defaultGitRunner = {
|
|
63
64
|
run(args, cwd) {
|
|
64
65
|
return new Promise((resolve) => {
|
|
65
|
-
|
|
66
|
+
// SystemRoot/ComSpec backfill so worktree + PR-decision git ops don't fail
|
|
67
|
+
// to start under a pkg-stripped Windows sidecar env. No-op on POSIX.
|
|
68
|
+
const env = (0, win_spawn_1.windowsSpawnEnv)();
|
|
69
|
+
(0, child_process_1.execFile)('git', args, { cwd, env, maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
|
|
66
70
|
const code = err && typeof err.code === 'number' ? err.code : err ? 1 : 0;
|
|
67
71
|
resolve({ code, stdout: stdout?.toString() ?? '', stderr: stderr?.toString() ?? '' });
|
|
68
72
|
});
|