@yemi33/minions 0.1.2356 → 0.1.2357
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/dashboard/js/render-pipelines.js +12 -3
- package/dashboard/js/render-prs.js +55 -1
- package/dashboard.js +78 -1
- package/docs/README.md +1 -0
- package/docs/live-checkout-mode.md +6 -0
- package/docs/worktree-lifecycle.md +77 -1
- package/engine/ado.js +1 -1
- package/engine/cleanup.js +4 -4
- package/engine/lifecycle.js +72 -0
- package/engine/live-checkout.js +141 -0
- package/engine/pipeline.js +62 -8
- package/engine/playbook.js +6 -5
- package/engine/queries.js +34 -22
- package/engine/shared.js +41 -1
- package/engine.js +486 -80
- package/package.json +1 -1
package/engine/pipeline.js
CHANGED
|
@@ -501,11 +501,16 @@ function _findMeetingsInRun(run) {
|
|
|
501
501
|
return meetings;
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
-
// Check if a plan already exists for a given meeting (created manually via
|
|
505
|
-
|
|
504
|
+
// Check if a plan already exists for a given meeting (created manually via
|
|
505
|
+
// dashboard, or previously by this same pipeline stage). `pipelineSlugPrefix`
|
|
506
|
+
// (optional) is the slug a pipeline plan stage writes its files under
|
|
507
|
+
// (`slugify(stage.title)`) — matching on it catches pre-existing pipeline
|
|
508
|
+
// plans that predate the `**Source Meeting:**` marker below.
|
|
509
|
+
function _findExistingPlanForMeeting(meetingIds, plansDir, pipelineSlugPrefix) {
|
|
506
510
|
const files = safeReadDir(plansDir).filter(f => f.endsWith('.md'));
|
|
507
|
-
// Build slug prefixes for
|
|
511
|
+
// Build slug prefixes for dashboard, and (optionally) pipeline naming conventions
|
|
508
512
|
const slugPrefixes = [];
|
|
513
|
+
if (pipelineSlugPrefix) slugPrefixes.push(pipelineSlugPrefix);
|
|
509
514
|
for (const mid of meetingIds) {
|
|
510
515
|
const mtg = safeJson(path.join(MEETINGS_DIR, mid + '.json'));
|
|
511
516
|
if (mtg?.title) {
|
|
@@ -575,6 +580,18 @@ function _findExistingPrdForPlan(planFile, prdDir) {
|
|
|
575
580
|
return null;
|
|
576
581
|
}
|
|
577
582
|
|
|
583
|
+
// A well-formed plan (LLM path or fallback path) always starts with a markdown
|
|
584
|
+
// title and has substantive body content. Guards against `maxTurns: 1` LLM
|
|
585
|
+
// calls that spend their single turn on tool-oriented preamble/narration
|
|
586
|
+
// instead of the plan itself (W-mrbn0eex000265d7) — that kind of response is a
|
|
587
|
+
// short mid-task fragment, not a plan, and must not be written verbatim.
|
|
588
|
+
function _looksLikePlanContent(content) {
|
|
589
|
+
if (!content) return false;
|
|
590
|
+
const trimmed = content.trim();
|
|
591
|
+
if (trimmed.length < 200) return false;
|
|
592
|
+
return trimmed.startsWith('#');
|
|
593
|
+
}
|
|
594
|
+
|
|
578
595
|
async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
579
596
|
const plansDir = PLANS_DIR;
|
|
580
597
|
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
|
@@ -591,7 +608,7 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
591
608
|
// ── Reconciliation: check if a plan already exists for a meeting in this run ──
|
|
592
609
|
const meetingIds = _findMeetingsInRun(run);
|
|
593
610
|
if (meetingIds.length > 0) {
|
|
594
|
-
const existingPlanFile = _findExistingPlanForMeeting(meetingIds, plansDir);
|
|
611
|
+
const existingPlanFile = _findExistingPlanForMeeting(meetingIds, plansDir, slug);
|
|
595
612
|
if (existingPlanFile) {
|
|
596
613
|
log('info', `Pipeline ${run.pipelineId}: reconciling plan stage — adopting existing plan "${existingPlanFile}"`);
|
|
597
614
|
|
|
@@ -686,9 +703,11 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
686
703
|
});
|
|
687
704
|
if (result.missingRuntime) {
|
|
688
705
|
log('warn', `Pipeline plan LLM skipped: ${result.text || result.stderr || 'runtime unavailable'} — falling back to raw meeting context`);
|
|
689
|
-
} else if (result.text) {
|
|
706
|
+
} else if (result.text && _looksLikePlanContent(result.text)) {
|
|
690
707
|
content = result.text;
|
|
691
708
|
log('info', `Pipeline plan: LLM generated ${content.length} chars from meeting context`);
|
|
709
|
+
} else if (result.text) {
|
|
710
|
+
log('warn', `Pipeline plan LLM response looked like a fragment, not a plan (${result.text.length} chars, doesn't start with a title) — discarding and falling back to raw meeting context`);
|
|
692
711
|
}
|
|
693
712
|
} catch (e) { log('warn', `Pipeline plan LLM failed: ${e.message} — falling back to raw meeting context`); }
|
|
694
713
|
|
|
@@ -701,6 +720,14 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
701
720
|
if (stage.description) content += stage.description + '\n';
|
|
702
721
|
}
|
|
703
722
|
|
|
723
|
+
// Stamp every source meeting so a future/duplicate invocation for this run
|
|
724
|
+
// can find this plan via `_findExistingPlanForMeeting`'s content scan — the
|
|
725
|
+
// reconciliation guard above only matches on this literal marker or a slug
|
|
726
|
+
// prefix, and generated content (LLM or fallback) never included it before.
|
|
727
|
+
if (meetingIds.length > 0) {
|
|
728
|
+
content += '\n\n---\n\n' + meetingIds.map(mid => `**Source Meeting:** ${mid}`).join('\n') + '\n';
|
|
729
|
+
}
|
|
730
|
+
|
|
704
731
|
const filename = `${slug}-${dateStamp()}.md`;
|
|
705
732
|
const filePath = shared.uniquePath(path.join(plansDir, filename));
|
|
706
733
|
safeWrite(filePath, content);
|
|
@@ -1021,7 +1048,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
1021
1048
|
if (prIds.length === 0) return true; // nothing to merge
|
|
1022
1049
|
const projects = shared.getProjects(config);
|
|
1023
1050
|
for (const project of projects) {
|
|
1024
|
-
const prs =
|
|
1051
|
+
const prs = safeJsonArr(shared.projectPrPath(project));
|
|
1025
1052
|
for (const prId of prIds) {
|
|
1026
1053
|
const pr = shared.findPrRecord(prs, prId, project);
|
|
1027
1054
|
if (pr && pr.status !== PR_STATUS.MERGED && pr.status !== PR_STATUS.ABANDONED) return false;
|
|
@@ -1173,7 +1200,7 @@ async function discoverPipelineWork(config) {
|
|
|
1173
1200
|
if (meetingIds.length > 0) {
|
|
1174
1201
|
const plansDir = PLANS_DIR;
|
|
1175
1202
|
if (fs.existsSync(plansDir)) {
|
|
1176
|
-
const existingPlan = _findExistingPlanForMeeting(meetingIds, plansDir);
|
|
1203
|
+
const existingPlan = _findExistingPlanForMeeting(meetingIds, plansDir, slugify(nextPlanStage.title || 'pipeline-plan'));
|
|
1177
1204
|
if (existingPlan) {
|
|
1178
1205
|
updateRunStage(pipeline.id, activeRun.runId, stage.id, {
|
|
1179
1206
|
status: PIPELINE_STATUS.COMPLETED, completedAt: ts(),
|
|
@@ -1207,7 +1234,34 @@ async function discoverPipelineWork(config) {
|
|
|
1207
1234
|
stageState.status = PIPELINE_STATUS.FAILED;
|
|
1208
1235
|
anyFailed = true;
|
|
1209
1236
|
} else if (depsReady) {
|
|
1210
|
-
|
|
1237
|
+
// W-mrbn0eex000265d7: persist RUNNING BEFORE the (potentially slow,
|
|
1238
|
+
// up-to-120s LLM-backed) executeStage call resolves. Plan stages in
|
|
1239
|
+
// particular kick off an LLM call; the stage's persisted status
|
|
1240
|
+
// previously stayed PENDING for the whole call, so a concurrent or
|
|
1241
|
+
// retried invocation of discoverPipelineWork (overlapping tick, or
|
|
1242
|
+
// resumed run after an engine restart mid-await) would see PENDING
|
|
1243
|
+
// again and re-execute the stage, writing a second duplicate plan
|
|
1244
|
+
// file. Marking RUNNING first means a re-entrant call takes the
|
|
1245
|
+
// RUNNING branch above (isStageComplete) instead of re-executing.
|
|
1246
|
+
updateRunStage(pipeline.id, activeRun.runId, stage.id, { status: PIPELINE_STATUS.RUNNING, startedAt: ts() });
|
|
1247
|
+
stageState.status = PIPELINE_STATUS.RUNNING;
|
|
1248
|
+
// W-mrbn0eex000265d7 (review follow-up): the RUNNING status above is
|
|
1249
|
+
// already persisted, so if executeStage throws instead of resolving
|
|
1250
|
+
// (e.g. a lock-timeout in mutateWorkItems, an fs error), an uncaught
|
|
1251
|
+
// rejection would propagate out of discoverPipelineWork to the
|
|
1252
|
+
// engine.js tick-phase catch, which only logs a warning — leaving
|
|
1253
|
+
// this stage's persisted status stuck at RUNNING forever with no
|
|
1254
|
+
// follow-up transition (previously it stayed PENDING and simply
|
|
1255
|
+
// retried next tick). Catch here and route the failure through the
|
|
1256
|
+
// normal FAILED path so dependents fail fast instead of the run
|
|
1257
|
+
// stalling silently.
|
|
1258
|
+
let result;
|
|
1259
|
+
try {
|
|
1260
|
+
result = await executeStage(stage, activeRun, pipeline, config);
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
log('warn', `Pipeline ${pipeline.id}: stage ${stage.id} threw during execution: ${e.message}`);
|
|
1263
|
+
result = { status: PIPELINE_STATUS.FAILED, error: e.message };
|
|
1264
|
+
}
|
|
1211
1265
|
updateRunStage(pipeline.id, activeRun.runId, stage.id, { ...result, startedAt: ts() });
|
|
1212
1266
|
Object.assign(stageState, result, { startedAt: ts() });
|
|
1213
1267
|
if (result.status === PIPELINE_STATUS.RUNNING) {
|
package/engine/playbook.js
CHANGED
|
@@ -12,7 +12,7 @@ const queries = require('./queries');
|
|
|
12
12
|
const { wrapUntrusted, buildSource } = require('./untrusted-fence');
|
|
13
13
|
const { MINIONS_BRAND_URL } = require('./comment-format');
|
|
14
14
|
|
|
15
|
-
const {
|
|
15
|
+
const { safeJsonArr, safeRead, getProjects, log, ts, dateStamp, truncateTextBytes, ENGINE_DEFAULTS, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT, getProjectOrg } = shared;
|
|
16
16
|
const { getConfig, getDispatch, getNotes, getPrs, getKnowledgeBaseIndex, AGENTS_DIR } = queries;
|
|
17
17
|
|
|
18
18
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
@@ -1060,11 +1060,12 @@ function renderPlaybook(type, vars) {
|
|
|
1060
1060
|
// ─── Playbook Section Validator ──────────────────────────────────────────────
|
|
1061
1061
|
|
|
1062
1062
|
// Required structural section patterns — warn (do not throw) when absent.
|
|
1063
|
-
//
|
|
1063
|
+
// Only '## Your Task' is checked: it's the one section every playbook template
|
|
1064
|
+
// actually implements. '## Tools' / '## Constraints' were removed (#775) —
|
|
1065
|
+
// no template in playbooks/ defines those headers, so the checks always
|
|
1066
|
+
// failed and produced 100%-failure-rate warn-level noise in engine/log.json.
|
|
1064
1067
|
const _REQUIRED_PROMPT_SECTIONS = [
|
|
1065
1068
|
{ pattern: /^## Your Task\b/m, label: '## Your Task' },
|
|
1066
|
-
{ pattern: /^## Tools?\b/m, label: '## Tools' },
|
|
1067
|
-
{ pattern: /^## Constraints\b/m, label: '## Constraints' },
|
|
1068
1069
|
];
|
|
1069
1070
|
|
|
1070
1071
|
/**
|
|
@@ -1266,7 +1267,7 @@ function buildAgentContext(agentId, config, project) {
|
|
|
1266
1267
|
}
|
|
1267
1268
|
// Also check central pull-requests.json
|
|
1268
1269
|
try {
|
|
1269
|
-
const centralPrs =
|
|
1270
|
+
const centralPrs = safeJsonArr(path.join(MINIONS_DIR, 'pull-requests.json'));
|
|
1270
1271
|
for (const pr of centralPrs.filter(pr => pr.status === PR_STATUS.ACTIVE)) {
|
|
1271
1272
|
if (!allPrs.some(p => p.id === pr.id)) allPrs.push({ ...pr, _project: 'central' });
|
|
1272
1273
|
}
|
package/engine/queries.js
CHANGED
|
@@ -72,6 +72,25 @@ function _resetPrEnrichmentCacheForTest() {
|
|
|
72
72
|
_prEnrichmentInFlight.clear();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// W-mrcs8yqk001o6893 — shared enrichment-fallback resolver. Both the prLinks
|
|
76
|
+
// loop and the wi._pr fallback loop in getPrdInfo() need the SAME defensive
|
|
77
|
+
// behavior when the canonical PR id has no live record in pull-requests.json
|
|
78
|
+
// (e.g. it was merged and then swept by the merged-PR cleanup routine): check
|
|
79
|
+
// the enrichment cache first, else synthesize PR_STATUS.ACTIVE as a
|
|
80
|
+
// placeholder and kick off a fire-and-forget enrollment so the next poll
|
|
81
|
+
// (within PR_ENRICHMENT_TTL_MS) resolves the real status. Previously only the
|
|
82
|
+
// wi._pr loop did this (W-mq5uzmc6001d708f); the prLinks loop hardcoded
|
|
83
|
+
// `pr?.status || PR_STATUS.ACTIVE` with no self-heal, so PRs reached only via
|
|
84
|
+
// pr_links stayed 'active' forever once removed from pull-requests.json.
|
|
85
|
+
function _resolvePrStatusWithEnrichment(pr, canonicalPrId, project, itemId) {
|
|
86
|
+
if (pr?.status) return pr.status;
|
|
87
|
+
if (!canonicalPrId) return PR_STATUS.ACTIVE;
|
|
88
|
+
const cachedStatus = _getCachedPrEnrichment(canonicalPrId);
|
|
89
|
+
if (cachedStatus) return cachedStatus;
|
|
90
|
+
_scheduleAsyncPrEnrollment(canonicalPrId, project, itemId);
|
|
91
|
+
return PR_STATUS.ACTIVE;
|
|
92
|
+
}
|
|
93
|
+
|
|
75
94
|
/**
|
|
76
95
|
* Read the first `bytes` and last `bytes` of a file efficiently using byte offsets.
|
|
77
96
|
* For files <= 2*bytes, reads the whole file. Returns { head, tail } strings.
|
|
@@ -572,15 +591,6 @@ function _buildArchiveNotesByWiMap() {
|
|
|
572
591
|
return out;
|
|
573
592
|
}
|
|
574
593
|
|
|
575
|
-
// Test/maintenance hook — drop the archive notes cache (mirrors the other
|
|
576
|
-
// invalidate* helpers). Not normally needed: the cache self-heals via the
|
|
577
|
-
// dir-mtime gate + TTL.
|
|
578
|
-
function invalidateArchiveNotesByWiCache() {
|
|
579
|
-
_archiveNotesByWiCache = null;
|
|
580
|
-
_archiveNotesByWiCacheAt = 0;
|
|
581
|
-
_archiveNotesByWiCacheMtime = null;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
594
|
// Build a wiId → [filename, ...] map by scanning the inbox + archive dirs.
|
|
585
595
|
// Archive entries are prefixed with `archive:` so the dashboard renderer can
|
|
586
596
|
// tell them apart (matches the convention already used by _artifacts.notes).
|
|
@@ -2363,7 +2373,18 @@ function getPrdInfo(config) {
|
|
|
2363
2373
|
const url = buildPrUrlFromId(prId, pr, projects);
|
|
2364
2374
|
for (const itemId of (itemIds || [])) {
|
|
2365
2375
|
if (!prdToPr[itemId]) prdToPr[itemId] = [];
|
|
2366
|
-
|
|
2376
|
+
// W-mrcs8yqk001o6893 — when the pr_links mapping outlives the PR record
|
|
2377
|
+
// (merged-PR sweep removes it from pull-requests.json but pr_links is
|
|
2378
|
+
// never pruned), resolve the item's project so we can self-heal the
|
|
2379
|
+
// status the same way the wi._pr fallback loop below does.
|
|
2380
|
+
let itemProject = null;
|
|
2381
|
+
if (!pr) {
|
|
2382
|
+
const itemSource = (wiById[itemId] || allWiById[itemId] || {}).project
|
|
2383
|
+
|| (wiById[itemId] || allWiById[itemId] || {})._source || null;
|
|
2384
|
+
itemProject = itemSource ? shared.resolveProjectSource(itemSource, projects, { allowCentral: false }).project || null : null;
|
|
2385
|
+
}
|
|
2386
|
+
const status = _resolvePrStatusWithEnrichment(pr, prId, itemProject, itemId);
|
|
2387
|
+
prdToPr[itemId].push({ id: prId, url, title: pr?.title || '', status, _project: pr?._project || '' });
|
|
2367
2388
|
}
|
|
2368
2389
|
}
|
|
2369
2390
|
// Fallback: work item _pr field for anything still missing
|
|
@@ -2379,17 +2400,8 @@ function getPrdInfo(config) {
|
|
|
2379
2400
|
// exists (orphan _pr pointer), use a cached status from a prior async
|
|
2380
2401
|
// enrollment if available, else synthesize ACTIVE and schedule a
|
|
2381
2402
|
// fire-and-forget enrollment so the next status poll renders correctly.
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
const cachedStatus = _getCachedPrEnrichment(canonicalPrId);
|
|
2385
|
-
if (cachedStatus) {
|
|
2386
|
-
synthesizedStatus = cachedStatus;
|
|
2387
|
-
} else {
|
|
2388
|
-
synthesizedStatus = PR_STATUS.ACTIVE;
|
|
2389
|
-
_scheduleAsyncPrEnrollment(canonicalPrId, project, wi.id);
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
prdToPr[wi.id] = [{ id: pr?.id || canonicalPrId || wi._pr, url, title: pr?.title || '', status: synthesizedStatus || PR_STATUS.ACTIVE, _project: project?.name || '' }];
|
|
2403
|
+
const synthesizedStatus = _resolvePrStatusWithEnrichment(pr, canonicalPrId, project, wi.id);
|
|
2404
|
+
prdToPr[wi.id] = [{ id: pr?.id || canonicalPrId || wi._pr, url, title: pr?.title || '', status: synthesizedStatus, _project: project?.name || '' }];
|
|
2393
2405
|
}
|
|
2394
2406
|
// Aggregate sub-task PRs to decomposed parent (sub-tasks aren't PRD items but their PRs should show)
|
|
2395
2407
|
for (const pr of allPrs) {
|
|
@@ -3422,7 +3434,7 @@ module.exports = {
|
|
|
3422
3434
|
getKnowledgeBaseEntries, getKnowledgeBaseEntriesSnapshot, getKnowledgeBaseIndex,
|
|
3423
3435
|
|
|
3424
3436
|
// Work items & PRD
|
|
3425
|
-
getWorkItems, invalidateWorkItemsCache,
|
|
3437
|
+
getWorkItems, invalidateWorkItemsCache, getPrdInfo,
|
|
3426
3438
|
|
|
3427
3439
|
// W-mq5uzmc6001d708f — test hooks for the defensive PR enrichment cache.
|
|
3428
3440
|
_resetPrEnrichmentCacheForTest,
|
package/engine/shared.js
CHANGED
|
@@ -2306,8 +2306,47 @@ function runFile(file, args, opts = {}) {
|
|
|
2306
2306
|
return _spawn(file, args, { windowsHide: true, ...opts });
|
|
2307
2307
|
}
|
|
2308
2308
|
|
|
2309
|
+
// True when `p` is a UNC path (`\\server\share\...` or its forward-slash
|
|
2310
|
+
// form `//server/share/...`, e.g. `\\wsl.localhost\Ubuntu\...` / `\\wsl$\...`
|
|
2311
|
+
// for a WSL-hosted project). Windows' cmd.exe — the default shell
|
|
2312
|
+
// `execSync`/`exec` spawn under — refuses a UNC path as its working
|
|
2313
|
+
// directory ("UNC paths are not supported. Defaulting to Windows
|
|
2314
|
+
// directory."), which silently redirects the child's actual cwd elsewhere.
|
|
2315
|
+
function isUncPath(p) {
|
|
2316
|
+
return typeof p === 'string' && (p.startsWith('\\\\') || p.startsWith('//'));
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// Minimal argv tokenizer for the small, hardcoded `git ...` command strings
|
|
2320
|
+
// built elsewhere in this file — splits on whitespace, honoring
|
|
2321
|
+
// double-quoted segments (the only quoting these callers use, e.g. a branch
|
|
2322
|
+
// name interpolated as `"refs/heads/${branch}"`). Not a general shell
|
|
2323
|
+
// parser; only ever fed strings this codebase constructs itself.
|
|
2324
|
+
function _splitGitCommandLine(cmd) {
|
|
2325
|
+
const argv = [];
|
|
2326
|
+
const re = /"([^"]*)"|(\S+)/g;
|
|
2327
|
+
let m;
|
|
2328
|
+
while ((m = re.exec(cmd)) !== null) argv.push(m[1] !== undefined ? m[1] : m[2]);
|
|
2329
|
+
return argv;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2309
2332
|
function execSilent(cmd, opts = {}) {
|
|
2310
|
-
|
|
2333
|
+
const merged = { stdio: 'pipe', windowsHide: true, ...opts };
|
|
2334
|
+
// W-mrcv3ky7000c2338 / opg#772 — a `git ...` shellout whose `cwd` is a UNC
|
|
2335
|
+
// path (WSL-hosted project, or any other UNC-rooted checkout) would
|
|
2336
|
+
// otherwise run through cmd.exe, which can't chdir into a UNC path and
|
|
2337
|
+
// silently defaults to the Windows directory instead — the subsequent git
|
|
2338
|
+
// command then fails with "fatal: not a git repository", and callers like
|
|
2339
|
+
// cleanup.js's orphan/quarantine worktree-dir reaper have no ground truth
|
|
2340
|
+
// for which worktrees are registered, so quarantined worktree husks are
|
|
2341
|
+
// never reaped (unbounded disk growth). Re-parse into argv and run via
|
|
2342
|
+
// execFileSync (shell:false) instead — that bypasses cmd.exe entirely and
|
|
2343
|
+
// Windows' CreateProcess honors a UNC cwd correctly, exactly like
|
|
2344
|
+
// shared.shellSafeGitSync / shared.removeWorktree already do.
|
|
2345
|
+
if (typeof cmd === 'string' && /^git\b/.test(cmd.trim()) && isUncPath(merged.cwd)) {
|
|
2346
|
+
const argv = _splitGitCommandLine(cmd);
|
|
2347
|
+
return _execFileSync(argv[0], argv.slice(1), merged);
|
|
2348
|
+
}
|
|
2349
|
+
return _execSync(cmd, merged);
|
|
2311
2350
|
}
|
|
2312
2351
|
|
|
2313
2352
|
/**
|
|
@@ -9174,6 +9213,7 @@ module.exports = {
|
|
|
9174
9213
|
exec,
|
|
9175
9214
|
execAsync,
|
|
9176
9215
|
execSilent,
|
|
9216
|
+
isUncPath,
|
|
9177
9217
|
shellSafeGh,
|
|
9178
9218
|
shellSafeGit,
|
|
9179
9219
|
removeStaleIndexLock,
|