@yemi33/minions 0.1.2352 → 0.1.2354
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 +71 -19
- package/docs/README.md +2 -0
- package/docs/kb-dedup-duplicate-pair-investigation.md +138 -0
- package/docs/kb-sweep.md +13 -1
- package/docs/live-checkout-mode.md +7 -4
- package/docs/shared-lifecycle-module-map.md +675 -0
- package/engine/cli.js +4 -0
- package/engine/lifecycle.js +55 -22
- package/engine/live-checkout.js +64 -5
- package/engine/playbook.js +1 -0
- package/engine/pr-action.js +12 -7
- package/engine/process-utils.js +700 -0
- package/engine/shared.js +39 -666
- package/engine.js +78 -0
- package/package.json +1 -1
- package/playbooks/plan-to-prd.md +2 -0
package/engine/cli.js
CHANGED
|
@@ -1653,6 +1653,10 @@ const commands = {
|
|
|
1653
1653
|
return;
|
|
1654
1654
|
}
|
|
1655
1655
|
const targetProject = target?.project || (projects.length > 0 ? projects[0] : null);
|
|
1656
|
+
if (!targetProject) {
|
|
1657
|
+
console.log('No projects configured. Add a project before running work.');
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1656
1660
|
const wiPath = projectWorkItemsPath(targetProject);
|
|
1657
1661
|
const archivePath = wiPath.replace(/\.json$/, '-archive.json');
|
|
1658
1662
|
|
package/engine/lifecycle.js
CHANGED
|
@@ -8,7 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const shared = require('./shared');
|
|
10
10
|
const { safeRead, safeJson, safeJsonArr, safeJsonNoRestore, safeWrite, mutateJsonFileLocked, mutateWorkItems, execAsync, projectPrPath, getPrLinks,
|
|
11
|
-
log, ts, dateStamp, WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PR_STATUS, REVIEW_STATUS, RETRY_DELAY_MS, DISPATCH_RESULT,
|
|
11
|
+
log, ts, dateStamp, WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, REVIEW_STATUS, RETRY_DELAY_MS, DISPATCH_RESULT,
|
|
12
12
|
ENGINE_DEFAULTS, DEFAULT_AGENT_METRICS, FAILURE_CLASS } = shared;
|
|
13
13
|
const { resolveRuntime } = require('./runtimes');
|
|
14
14
|
const adoGitAuth = require('./ado-git-auth');
|
|
@@ -38,27 +38,6 @@ function checkPlanCompletion(meta, config) {
|
|
|
38
38
|
// Collect work items from ALL projects + central (PRD items can be in either)
|
|
39
39
|
const allWorkItems = queries.getWorkItems(config);
|
|
40
40
|
const planItems = allWorkItems.filter(w => w.sourcePlan === planFile && w.itemType !== 'pr' && w.itemType !== 'verify');
|
|
41
|
-
if (planItems.length === 0) return;
|
|
42
|
-
|
|
43
|
-
// W-mqk5ld3p: verify-creation must be a pure function of work-item state,
|
|
44
|
-
// independent of who/what set `status: completed`. We do NOT short-circuit on
|
|
45
|
-
// the raw `_completionNotified` boolean — a PRD can land on disk pre-completed
|
|
46
|
-
// with the flag already set (out-of-band write by the plan-to-prd / pipeline
|
|
47
|
-
// path), and the legacy top-of-function `if (_completionNotified) return` then
|
|
48
|
-
// permanently skipped the aggregate build/test verify gate. Instead the flag
|
|
49
|
-
// gates ONLY the one-shot completion summary (below); control always falls
|
|
50
|
-
// through to the verify-creation block, which re-checks for an existing verify
|
|
51
|
-
// WI under the file lock and is therefore safe to reach every scan. The one
|
|
52
|
-
// exception is the REOPEN sub-path (terminal verify → re-open): that is a
|
|
53
|
-
// plan-modification concern (the dashboard clears `_completionNotified` when it
|
|
54
|
-
// re-opens a plan), so it is gated on `!alreadyNotified` to avoid bouncing an
|
|
55
|
-
// already-done verify on every steady-state scan.
|
|
56
|
-
const alreadyNotified = plan.status === PLAN_STATUS.COMPLETED && !!plan._completionNotified;
|
|
57
|
-
|
|
58
|
-
// Hard completion gate: every PRD feature ID must have a corresponding work item in a terminal state.
|
|
59
|
-
const planFeatureIds = new Set((plan.missing_features || []).map(f => f.id).filter(Boolean));
|
|
60
|
-
const workItemById = {};
|
|
61
|
-
for (const w of planItems) { if (w.id) workItemById[w.id] = w; }
|
|
62
41
|
|
|
63
42
|
// Self-heal stale completion. checkPlanCompletion only ever ADVANCED a plan to
|
|
64
43
|
// `completed` — it never reverted. So a PRD that was completed and then gained
|
|
@@ -71,6 +50,8 @@ function checkPlanCompletion(meta, config) {
|
|
|
71
50
|
// materializer recreates the missing WI and the normal completion flow resumes.
|
|
72
51
|
// Idempotent: once reverted (status active, flag cleared) the guard no-ops, so
|
|
73
52
|
// it cannot flap on subsequent scans while the item is genuinely incomplete.
|
|
53
|
+
// Defined BEFORE the `planItems.length === 0` early return (below) so the
|
|
54
|
+
// zero-materialized-items dead end (W-mrboq1g900096f04) can call it too.
|
|
74
55
|
const reopenStaleCompletedPlan = (reason) => {
|
|
75
56
|
if (plan.status !== PLAN_STATUS.COMPLETED && !plan._completionNotified) return false;
|
|
76
57
|
log('warn', `Plan ${planFile}: re-opening stale completed PRD — ${reason}`);
|
|
@@ -83,6 +64,50 @@ function checkPlanCompletion(meta, config) {
|
|
|
83
64
|
return true;
|
|
84
65
|
};
|
|
85
66
|
|
|
67
|
+
if (planItems.length === 0) {
|
|
68
|
+
// W-mrboq1g900096f04: a PRD that reaches `completed` with ZERO materialized
|
|
69
|
+
// work items (agent authoring bug, race, or manual/API misuse writing
|
|
70
|
+
// status:'completed' directly instead of 'awaiting-approval') was a
|
|
71
|
+
// permanent, silent dead end — this function used to hard-return here
|
|
72
|
+
// before ever reaching reopenStaleCompletedPlan above, and
|
|
73
|
+
// materializePlansAsWorkItems unconditionally skips COMPLETED plans, so
|
|
74
|
+
// nothing ever re-triggered materialization. Self-heal: if the PRD is
|
|
75
|
+
// COMPLETED and still has feature IDs stuck at a non-terminal PRD-level
|
|
76
|
+
// status ('missing'/'updated', i.e. PRD_MATERIALIZABLE) with no
|
|
77
|
+
// corresponding work item, reopen it so the materializer picks it up.
|
|
78
|
+
// Do NOT reopen a genuinely-complete PRD (no missing_features, or every
|
|
79
|
+
// feature already at a terminal PRD-level status such as 'done').
|
|
80
|
+
if (plan.status === PLAN_STATUS.COMPLETED) {
|
|
81
|
+
const stuckIds = (plan.missing_features || [])
|
|
82
|
+
.filter(f => f?.id && PRD_MATERIALIZABLE.has(f.status))
|
|
83
|
+
.map(f => f.id);
|
|
84
|
+
if (stuckIds.length > 0) {
|
|
85
|
+
reopenStaleCompletedPlan(`${stuckIds.length} item(s) stuck at PRD-level status with zero materialized work items: ${stuckIds.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// W-mqk5ld3p: verify-creation must be a pure function of work-item state,
|
|
92
|
+
// independent of who/what set `status: completed`. We do NOT short-circuit on
|
|
93
|
+
// the raw `_completionNotified` boolean — a PRD can land on disk pre-completed
|
|
94
|
+
// with the flag already set (out-of-band write by the plan-to-prd / pipeline
|
|
95
|
+
// path), and the legacy top-of-function `if (_completionNotified) return` then
|
|
96
|
+
// permanently skipped the aggregate build/test verify gate. Instead the flag
|
|
97
|
+
// gates ONLY the one-shot completion summary (below); control always falls
|
|
98
|
+
// through to the verify-creation block, which re-checks for an existing verify
|
|
99
|
+
// WI under the file lock and is therefore safe to reach every scan. The one
|
|
100
|
+
// exception is the REOPEN sub-path (terminal verify → re-open): that is a
|
|
101
|
+
// plan-modification concern (the dashboard clears `_completionNotified` when it
|
|
102
|
+
// re-opens a plan), so it is gated on `!alreadyNotified` to avoid bouncing an
|
|
103
|
+
// already-done verify on every steady-state scan.
|
|
104
|
+
const alreadyNotified = plan.status === PLAN_STATUS.COMPLETED && !!plan._completionNotified;
|
|
105
|
+
|
|
106
|
+
// Hard completion gate: every PRD feature ID must have a corresponding work item in a terminal state.
|
|
107
|
+
const planFeatureIds = new Set((plan.missing_features || []).map(f => f.id).filter(Boolean));
|
|
108
|
+
const workItemById = {};
|
|
109
|
+
for (const w of planItems) { if (w.id) workItemById[w.id] = w; }
|
|
110
|
+
|
|
86
111
|
// Check 1: every feature must have a work item (materialized)
|
|
87
112
|
// Fallback: also accept features marked done directly in the PRD JSON (resolved externally)
|
|
88
113
|
const unmaterialized = [...planFeatureIds].filter(id => {
|
|
@@ -5647,9 +5672,16 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
5647
5672
|
const w = data.find(i => i.id === meta.item.id);
|
|
5648
5673
|
if (!w) return data;
|
|
5649
5674
|
const retries = w._retryCount || 0;
|
|
5675
|
+
// #893 gate failure reason — surfaced into the next dispatch's prompt via
|
|
5676
|
+
// buildWorkItemDispatchVars()'s retry_context var (W-mrbq53ra000lf2e5) so the
|
|
5677
|
+
// retried agent knows the PRD file was never found instead of silently
|
|
5678
|
+
// repeating the same mistake.
|
|
5679
|
+
const gateReason = `Validation gate (#893): no PRD JSON file was found on disk for planFile "${meta.item.planFile || w.planFile || '(unknown)'}" (expected "${expectedFile || '(unset)'}") after the previous attempt completed. Write the PRD JSON to the exact path specified in the playbook before finishing.`;
|
|
5650
5680
|
if (retries < ENGINE_DEFAULTS.maxRetries) {
|
|
5651
5681
|
w.status = WI_STATUS.PENDING;
|
|
5652
5682
|
w._retryCount = retries + 1;
|
|
5683
|
+
w._lastRetryReason = gateReason;
|
|
5684
|
+
w._lastRetryAt = ts();
|
|
5653
5685
|
// W-mpmwxn1j — bump per-agent counter so a planner that never
|
|
5654
5686
|
// writes the PRD gets reassigned after maxRetriesPerAgent hits.
|
|
5655
5687
|
const failedAgent = meta?._agentId || w.dispatched_to;
|
|
@@ -5661,6 +5693,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
5661
5693
|
w.status = WI_STATUS.FAILED;
|
|
5662
5694
|
w.failReason = 'PRD file not written after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
|
|
5663
5695
|
w.failedAt = ts();
|
|
5696
|
+
w._lastRetryReason = gateReason;
|
|
5664
5697
|
log('warn', `plan-to-prd ${meta.item.id} failed — no PRD file after ${ENGINE_DEFAULTS.maxRetries} retries`);
|
|
5665
5698
|
}
|
|
5666
5699
|
return data;
|
package/engine/live-checkout.js
CHANGED
|
@@ -1168,6 +1168,40 @@ async function prepareLiveCheckout(opts = {}) {
|
|
|
1168
1168
|
// auth-less GVFS blob fetches that fail on partial clones in headless mode.
|
|
1169
1169
|
const failedCreate = await runCheckout(['checkout', '-b', branchName], { creating: true });
|
|
1170
1170
|
if (failedCreate) return failedCreate;
|
|
1171
|
+
// ── originalRef contamination invariant (P-mrb8yg9x001gf984). ───────────
|
|
1172
|
+
// Defense-in-depth mirror of the existing-branch (4b) invariant above
|
|
1173
|
+
// (W-mrb6an8300058fd8). Ordinarily the WRONG-BASE GUARD earlier in this
|
|
1174
|
+
// path (Step 4d-i) already normalizes originalRef to mainRef whenever HEAD
|
|
1175
|
+
// started off a non-mainRef branch and !allowNonMainBase — but that
|
|
1176
|
+
// normalization only runs inside the `headBranch !== mainRef` branch of
|
|
1177
|
+
// that guard. If some future change to that guard (or to the
|
|
1178
|
+
// auto-base-repair / STALE-BASE divergence logic sitting between it and
|
|
1179
|
+
// here) leaves originalRef still pointing at a leftover engine-managed
|
|
1180
|
+
// branch instead of a sane restore target, it would otherwise flow straight
|
|
1181
|
+
// through to the return below and propagate into
|
|
1182
|
+
// restoreLiveCheckoutAtDispatchEnd at dispatch end — the exact
|
|
1183
|
+
// contamination class W-mrb6an8300058fd8 fixed on the 4b path. Apply the
|
|
1184
|
+
// SAME `_looksLikeAgentBranch` check here (same `!allowNonMainBase` gate —
|
|
1185
|
+
// an explicit PR-continuation / shared-branch dispatch still intentionally
|
|
1186
|
+
// bypasses this, exactly as it does on the 4b path) so the new-branch-fork
|
|
1187
|
+
// path can never silently regress that invariant.
|
|
1188
|
+
if (!allowNonMainBase && originalRefType === 'branch' && originalRef && originalRef !== mainRef &&
|
|
1189
|
+
_looksLikeAgentBranch(originalRef)) {
|
|
1190
|
+
logFn(
|
|
1191
|
+
`[live-checkout] originalRef anomaly: HEAD's captured originalRef '${originalRef}' still looks like an engine-managed branch after forking '${branchName}' — this looks like leftover contamination from an earlier dispatch that should have been corrected to '${mainRef}' already. Attempting to correct it now.`,
|
|
1192
|
+
'warn'
|
|
1193
|
+
);
|
|
1194
|
+
const corrected = await _resolveMainRefForRestore({ git, baseOpts, mainRef, autoBaseRepair, _resolveAutoBaseRepair, localPath, logFn });
|
|
1195
|
+
if (corrected.ref) {
|
|
1196
|
+
originalRef = corrected.ref;
|
|
1197
|
+
originalRefType = corrected.type;
|
|
1198
|
+
} else {
|
|
1199
|
+
logFn(
|
|
1200
|
+
`[live-checkout] originalRef anomaly: could not resolve '${mainRef}' as a corrected restore target (no local ref, auto-base-repair disabled, or 'origin/${mainRef}' missing) — leaving originalRef as the contaminated '${originalRef}'; a human should verify the operator checkout manually.`,
|
|
1201
|
+
'warn'
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1171
1205
|
return { ok: true, branch: branchName, created: true, originalRef, originalRefType };
|
|
1172
1206
|
}
|
|
1173
1207
|
|
|
@@ -1451,7 +1485,10 @@ async function restoreLiveCheckoutAtDispatchEnd(opts = {}) {
|
|
|
1451
1485
|
* and the per-agent cli/model override pattern):
|
|
1452
1486
|
* 1. If `project.liveCheckoutAutoStash` is an explicit boolean, use it.
|
|
1453
1487
|
* 2. Else if `engine.liveCheckoutAutoStash` is an explicit boolean, use it.
|
|
1454
|
-
* 3. Else
|
|
1488
|
+
* 3. Else fall back to `shared.ENGINE_DEFAULTS.liveCheckoutAutoStash`
|
|
1489
|
+
* (default true as of W-mrawiym8000c42c9 — non-destructive, recoverable
|
|
1490
|
+
* via `git stash pop`, and now the first line of defense in the
|
|
1491
|
+
* dirty-tree recovery flow now that `liveCheckoutAutoReset` defaults off).
|
|
1455
1492
|
*
|
|
1456
1493
|
* Pure — no I/O. Tested in test/unit/live-checkout-auto-stash.test.js.
|
|
1457
1494
|
*
|
|
@@ -1466,7 +1503,7 @@ function resolveLiveCheckoutAutoStash(opts = {}) {
|
|
|
1466
1503
|
if (engine && typeof engine.liveCheckoutAutoStash === 'boolean') {
|
|
1467
1504
|
return engine.liveCheckoutAutoStash;
|
|
1468
1505
|
}
|
|
1469
|
-
return
|
|
1506
|
+
return shared.ENGINE_DEFAULTS.liveCheckoutAutoStash;
|
|
1470
1507
|
}
|
|
1471
1508
|
|
|
1472
1509
|
/**
|
|
@@ -1536,11 +1573,29 @@ async function performLiveCheckoutAutoStash(opts = {}) {
|
|
|
1536
1573
|
*
|
|
1537
1574
|
* Returns a discriminated outcome the caller acts on:
|
|
1538
1575
|
* - `{ outcome:'unchanged', liveResult }` — not dirty, disabled, or stash failed
|
|
1539
|
-
* (caller falls through to the existing retry-once-then-fail dirty handling
|
|
1576
|
+
* (caller falls through to the existing retry-once-then-fail dirty handling,
|
|
1577
|
+
* or — W-mrawgw4q000a6bff — to an explicit `liveCheckoutAutoReset` fallback
|
|
1578
|
+
* when that policy also resolves true; see engine.js spawnAgent).
|
|
1540
1579
|
* - `{ outcome:'stashed', liveResult }` — stashed + re-preflighted; proceed.
|
|
1541
1580
|
* - `{ outcome:'threw', error }` — the re-preflight threw; caller must
|
|
1542
1581
|
* fail the dispatch as LIVE_CHECKOUT_FAILED (engine-specific completion).
|
|
1543
1582
|
*
|
|
1583
|
+
* W-mrawgw4q000a6bff — stash-before-reset ordering + no auto-sync-to-origin.
|
|
1584
|
+
* The re-preflight (step 2) is called with `autoReset:false` so a still-dirty
|
|
1585
|
+
* tree after a successful stash NEVER silently falls through to a destructive
|
|
1586
|
+
* reset here — reset is only ever triggered explicitly by the caller as a
|
|
1587
|
+
* documented last-resort fallback. Also, by design, this function does NOT
|
|
1588
|
+
* follow up a successful stash with a `git merge --ff-only origin/<mainRef>`
|
|
1589
|
+
* to re-sync the branch: `liveCheckoutAutoStash`'s contract (see
|
|
1590
|
+
* `resolveLiveCheckoutAutoStash` above) has only ever been "park the dirty
|
|
1591
|
+
* changes so dispatch can proceed", never "keep the branch in sync with
|
|
1592
|
+
* origin" — that stronger guarantee is what `liveCheckoutAutoReset` is for,
|
|
1593
|
+
* and it is still available as an explicit opt-in for dispatches that need it
|
|
1594
|
+
* (with stash getting first crack at the tree, non-destructively, per the new
|
|
1595
|
+
* ordering). Adding an implicit ff-only sync here would blur that line and
|
|
1596
|
+
* risk a "successful" stash silently leaving a merge commit or a still-dirty
|
|
1597
|
+
* tree if the ff fails — so it was deliberately left out.
|
|
1598
|
+
*
|
|
1544
1599
|
* @param {object} opts
|
|
1545
1600
|
* @returns {Promise<{outcome:'unchanged'|'stashed'|'threw', liveResult?:object, error?:Error}>}
|
|
1546
1601
|
*/
|
|
@@ -1549,6 +1604,7 @@ async function applyLiveCheckoutAutoStash(opts = {}) {
|
|
|
1549
1604
|
liveResult, project, engine, localPath, branchName, mainRef,
|
|
1550
1605
|
gitOpts, dispatchId, wiId, log,
|
|
1551
1606
|
clearDirtyStamp, writeStashNote,
|
|
1607
|
+
_git, // private injection for testing — forwarded to performLiveCheckoutAutoStash + the re-preflight prepareLiveCheckout call
|
|
1552
1608
|
} = opts;
|
|
1553
1609
|
const logFn = (typeof log === 'function') ? log : () => {};
|
|
1554
1610
|
const reason = (liveResult && liveResult.ok === false) ? liveResult.reason : null;
|
|
@@ -1557,7 +1613,7 @@ async function applyLiveCheckoutAutoStash(opts = {}) {
|
|
|
1557
1613
|
}
|
|
1558
1614
|
let stashResult;
|
|
1559
1615
|
try {
|
|
1560
|
-
stashResult = await performLiveCheckoutAutoStash({ localPath, dispatchId, gitOpts, log });
|
|
1616
|
+
stashResult = await performLiveCheckoutAutoStash({ localPath, dispatchId, gitOpts, log, _git });
|
|
1561
1617
|
} catch (stashErr) {
|
|
1562
1618
|
stashResult = { ok: false, error: stashErr && stashErr.message };
|
|
1563
1619
|
}
|
|
@@ -1567,7 +1623,10 @@ async function applyLiveCheckoutAutoStash(opts = {}) {
|
|
|
1567
1623
|
}
|
|
1568
1624
|
let newLive;
|
|
1569
1625
|
try {
|
|
1570
|
-
|
|
1626
|
+
// autoReset:false — see the function doc above; a still-dirty tree after a
|
|
1627
|
+
// successful stash must never silently fall through to a destructive reset
|
|
1628
|
+
// inside this re-preflight.
|
|
1629
|
+
newLive = await prepareLiveCheckout({ localPath, branchName, mainRef, gitOpts, dispatchId, wiId, log, autoReset: false, _git });
|
|
1571
1630
|
} catch (stashLiveErr) {
|
|
1572
1631
|
return { outcome: 'threw', error: stashLiveErr };
|
|
1573
1632
|
}
|
package/engine/playbook.js
CHANGED
|
@@ -307,6 +307,7 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
307
307
|
'references', // only set when item.references has entries
|
|
308
308
|
'acceptance_criteria', // only set when item.acceptanceCriteria has entries
|
|
309
309
|
'checkpoint_context', // only set when resuming from a prior timeout
|
|
310
|
+
'retry_context', // only set when retrying after a validation-gate failure (#893)
|
|
310
311
|
// Host-specific identifiers — always one of {ado_*, github_*} is empty
|
|
311
312
|
// (project is either ADO or GitHub, never both). Keeping them as warns
|
|
312
313
|
// produces ~96 noise lines per day on a single-host project.
|
package/engine/pr-action.js
CHANGED
|
@@ -85,16 +85,20 @@ const PR_ACTION_FOLLOWUPS = Object.freeze([
|
|
|
85
85
|
{ kind: 'track-auto-fix', label: 'Track for auto-fix' },
|
|
86
86
|
]);
|
|
87
87
|
|
|
88
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Canonical `host:slug#number` id for a record/ref, or '' when unknowable.
|
|
90
|
+
*
|
|
91
|
+
* Thin wrapper over prResolve.prIdFromRef: normalizes the heterogeneous
|
|
92
|
+
* record shapes buildPrActionFollowups is called with — an explicit
|
|
93
|
+
* `record.prId`, a `record.ref` ref object, or a bare ref-shaped object
|
|
94
|
+
* (`{host, slug, number}`/`{id}`) — into the ref shape prIdFromRef expects,
|
|
95
|
+
* then delegates so the `host:slug#number` template literal has one owner.
|
|
96
|
+
*/
|
|
89
97
|
function _prIdForRecord(record) {
|
|
90
98
|
if (!record) return '';
|
|
91
99
|
if (record.prId) return record.prId;
|
|
92
100
|
const ref = record.ref || record;
|
|
93
|
-
|
|
94
|
-
if (ref && ref.host && ref.slug && (ref.number || ref.number === 0)) {
|
|
95
|
-
return `${ref.host}:${ref.slug}#${ref.number}`;
|
|
96
|
-
}
|
|
97
|
-
return '';
|
|
101
|
+
return prResolve.prIdFromRef(ref) || '';
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
/**
|
|
@@ -364,7 +368,8 @@ function buildPrActionPrompt(action, payload) {
|
|
|
364
368
|
number: payload && payload.number,
|
|
365
369
|
id: payload && payload.prId,
|
|
366
370
|
};
|
|
367
|
-
const prId =
|
|
371
|
+
const prId = prResolve.prIdFromRef(ref)
|
|
372
|
+
|| (payload && payload.slug ? `${payload.slug}#${payload.number}` : 'the pull request');
|
|
368
373
|
const guidance = PR_ACTION_GUIDANCE[act] || PR_ACTION_GUIDANCE.review;
|
|
369
374
|
|
|
370
375
|
const sections = [
|