@yemi33/minions 0.1.2223 → 0.1.2225
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 +81 -6
- package/docs/branch-derivation.md +34 -0
- package/docs/deprecated.json +8 -8
- package/engine/ado.js +20 -0
- package/engine/github.js +18 -0
- package/engine/shared.js +34 -5
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -512,7 +512,64 @@ function inferActionPrRecord(action, prs, project = null) {
|
|
|
512
512
|
return findPrRecordReferencedByText(prs, text, project);
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
|
|
515
|
+
// issue #246 — verify a LOOSE (description/title-scanned) PR ref points at a
|
|
516
|
+
// real pull request before `copyWorkItemPrFields` promotes it to structured
|
|
517
|
+
// targetPr/pr_id/prNumber fields. GitHub & ADO issues share the PR number
|
|
518
|
+
// namespace, so a bare `owner/repo#244` / `#244` can be an ISSUE. Stamping an
|
|
519
|
+
// issue mints a phantom pull-requests.json record (agent 'human', goes
|
|
520
|
+
// abandoned) AND routes the dispatch through the PR-fix path instead of
|
|
521
|
+
// implement-and-open-a-fresh-PR.
|
|
522
|
+
//
|
|
523
|
+
// Resolution order (cheap → expensive, fail-safe):
|
|
524
|
+
// 1. An already-tracked PR record for the ref ⇒ it's a known PR ⇒ true (no net).
|
|
525
|
+
// 2. Host existence check (gh/ado prExists) ⇒ true | false | null.
|
|
526
|
+
// Returns true only when a PR is positively confirmed; false / null / unknown
|
|
527
|
+
// all mean "do not stamp" (prefer a fresh PR over a phantom one).
|
|
528
|
+
const _PR_REF_VERIFY_TTL_MS = 5 * 60 * 1000;
|
|
529
|
+
const _prRefVerifyCache = new Map(); // cacheKey → { value: boolean, at: ms }
|
|
530
|
+
let _prRefVerifierOverride = null; // test seam
|
|
531
|
+
|
|
532
|
+
// Test seam (issue #246) — inject a fake verifier so handler tests can assert
|
|
533
|
+
// the stamp decision without a live gh/ado call. The override receives
|
|
534
|
+
// (prRef, project) and returns true | false | null.
|
|
535
|
+
function _setPrRefVerifierForTest(fn) { _prRefVerifierOverride = (typeof fn === 'function') ? fn : null; }
|
|
536
|
+
|
|
537
|
+
function _findTrackedPrRecord(prRef, project) {
|
|
538
|
+
if (!project) return null;
|
|
539
|
+
try {
|
|
540
|
+
const prs = shared.safeJsonArr(shared.projectPrPath(project));
|
|
541
|
+
return shared.findPrRecord(prs, prRef, project);
|
|
542
|
+
} catch { return null; }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function verifyLoosePrRefIsPr(prRef, project) {
|
|
546
|
+
if (_prRefVerifierOverride) {
|
|
547
|
+
try { return (await _prRefVerifierOverride(prRef, project)) === true; }
|
|
548
|
+
catch { return false; }
|
|
549
|
+
}
|
|
550
|
+
// 1. Known PR record ⇒ definitely a PR (no network call needed).
|
|
551
|
+
if (_findTrackedPrRecord(prRef, project)) return true;
|
|
552
|
+
// 2. Host existence check.
|
|
553
|
+
const info = shared.classifyPrRefForVerification(prRef, project);
|
|
554
|
+
if (!info) return false; // can't resolve host/number → fail-safe, do not stamp
|
|
555
|
+
const cacheKey = info.host === 'github'
|
|
556
|
+
? `github:${info.slug}#${info.prNumber}`
|
|
557
|
+
: `ado:${info.adoOrg}/${info.adoProject}/${info.adoRepo}#${info.prNumber}`;
|
|
558
|
+
const cached = _prRefVerifyCache.get(cacheKey);
|
|
559
|
+
if (cached && (Date.now() - cached.at) < _PR_REF_VERIFY_TTL_MS) return cached.value;
|
|
560
|
+
let verdict = null;
|
|
561
|
+
try {
|
|
562
|
+
verdict = info.host === 'github'
|
|
563
|
+
? await gh.prExists(info.slug, info.prNumber)
|
|
564
|
+
: await ado.prExists(info.adoOrg, info.adoProject, info.adoRepo, info.prNumber);
|
|
565
|
+
} catch { verdict = null; }
|
|
566
|
+
const value = verdict === true;
|
|
567
|
+
// Only cache a definitive answer; an unknown (null) may resolve next time.
|
|
568
|
+
if (verdict === true || verdict === false) _prRefVerifyCache.set(cacheKey, { value, at: Date.now() });
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function copyWorkItemPrFields(item, input, pr = null, opts = {}) {
|
|
516
573
|
// W-mqbaby2a000pa8ee: Gate the LOOSE description/title scan in
|
|
517
574
|
// `shared.extractWorkItemPrRef` on `type: "fix"`. Without this gate,
|
|
518
575
|
// an implement/explore/test WI that merely mentions an existing PR
|
|
@@ -526,13 +583,30 @@ function copyWorkItemPrFields(item, input, pr = null) {
|
|
|
526
583
|
// operator intent — they stamp on EVERY type. An explicit `pr` record
|
|
527
584
|
// (caller already resolved the PR) also bypasses the gate. Only the
|
|
528
585
|
// last-resort description/title regex scan is type-gated.
|
|
529
|
-
|
|
586
|
+
//
|
|
587
|
+
// issue #246: a loose `github:owner/repo#NNN` / `#NNN` token can be an
|
|
588
|
+
// ISSUE — issues and PRs share one number namespace. Before stamping a
|
|
589
|
+
// loose ref we verify it points at a real PR (already-tracked record or a
|
|
590
|
+
// gh/ado existence check); structured refs are trusted without a network
|
|
591
|
+
// call. Fail-safe: if existence can't be confirmed, do NOT stamp — the WI
|
|
592
|
+
// then dispatches as a normal fix that opens a fresh PR.
|
|
593
|
+
let prRef;
|
|
594
|
+
if (pr) {
|
|
595
|
+
prRef = getWorkItemPrRef(input);
|
|
596
|
+
} else {
|
|
530
597
|
const structuredRef = shared.extractStructuredWorkItemPrRef(input);
|
|
531
|
-
if (
|
|
532
|
-
|
|
598
|
+
if (structuredRef) {
|
|
599
|
+
prRef = structuredRef; // explicit operator intent — trusted, no verification
|
|
600
|
+
} else {
|
|
601
|
+
if (String(item?.type || '').toLowerCase() !== WORK_TYPE.FIX) return;
|
|
602
|
+
const looseRef = getWorkItemPrRef(input);
|
|
603
|
+
if (!looseRef) return;
|
|
604
|
+
// issue #246 — confirm the loose ref is a PR (not an issue) before stamping.
|
|
605
|
+
const confirmed = await verifyLoosePrRefIsPr(looseRef, opts.project || null);
|
|
606
|
+
if (!confirmed) return; // issue / unverifiable → dispatch as a fresh fix
|
|
607
|
+
prRef = looseRef;
|
|
533
608
|
}
|
|
534
609
|
}
|
|
535
|
-
const prRef = getWorkItemPrRef(input);
|
|
536
610
|
if (!prRef && !pr) return;
|
|
537
611
|
const prNumber = pr ? shared.getPrNumber(pr) : shared.getPrNumber(prRef);
|
|
538
612
|
item.targetPr = pr?.id || prRef;
|
|
@@ -6361,7 +6435,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
6361
6435
|
const originWi = extractMinionsOriginWiHeader(req);
|
|
6362
6436
|
if (originAgent) item._originAgent = originAgent;
|
|
6363
6437
|
if (originWi) item._originWi = originWi;
|
|
6364
|
-
copyWorkItemPrFields(item, body);
|
|
6438
|
+
await copyWorkItemPrFields(item, body, null, { project: targetProject });
|
|
6365
6439
|
// W-mq5wfh1v000e0da9 — Auto-enroll the PR into pull-requests.json when
|
|
6366
6440
|
// this is a `type: fix` WI carrying a structured PR pointer and the PR
|
|
6367
6441
|
// isn't tracked yet. Without this, the engine's `pr_not_found` gate
|
|
@@ -13857,6 +13931,7 @@ function _installCrashHandlers() {
|
|
|
13857
13931
|
// Production entry points use the closures directly; tests import via require('./dashboard').
|
|
13858
13932
|
module.exports = {
|
|
13859
13933
|
getMcpServers,
|
|
13934
|
+
_setPrRefVerifierForTest, // issue #246 — inject a fake loose-PR-ref verifier in handler tests
|
|
13860
13935
|
_parseClaudeMcpListLine,
|
|
13861
13936
|
_parseCopilotMcpListJson,
|
|
13862
13937
|
_readWorkspaceMcpServers,
|
|
@@ -56,6 +56,40 @@ PR-fix dispatch path, fail the PR-branch lookup, and stick in
|
|
|
56
56
|
(`targetPr` / `prUrl` / `references[].url` / etc.) still stamps on
|
|
57
57
|
every type — only the loose regex scan is gated.
|
|
58
58
|
|
|
59
|
+
## Loose refs are PR-verified before stamping (issue #246)
|
|
60
|
+
|
|
61
|
+
A loose `github:owner/repo#NNN` / `ado:org/proj/repo#NNN` / bare `#NNN` token
|
|
62
|
+
carries **no issue-vs-PR discriminator** — GitHub issues and PRs share one
|
|
63
|
+
number namespace, and ADO work items share theirs. Before this guard, a
|
|
64
|
+
`type: "fix"` WI titled `Fix github:owner/repo#244: …` where **#244 is an
|
|
65
|
+
issue** got `targetPr` / `pr_id` / `prNumber` stamped by
|
|
66
|
+
`dashboard.js#copyWorkItemPrFields`, which then (a) minted a phantom
|
|
67
|
+
`pull-requests.json` record (`agent: 'human'`, went abandoned, polluted the PR
|
|
68
|
+
count) via `shared.autoEnrollPrFromFixWorkItem`, and (b) routed the dispatch
|
|
69
|
+
through the PR-fix path (`targetPr`/`pr_id`/`prNumber`) instead of
|
|
70
|
+
implement-and-open-a-fresh-PR.
|
|
71
|
+
|
|
72
|
+
`copyWorkItemPrFields` now verifies a **loose** ref points at a real PR before
|
|
73
|
+
stamping:
|
|
74
|
+
|
|
75
|
+
1. **Structured refs are trusted** — `targetPr` / `prUrl` / `references[].url`
|
|
76
|
+
/ `meta.pr_followup.parent_pr_url` are explicit operator intent and stamp
|
|
77
|
+
with **no** network call.
|
|
78
|
+
2. **Loose (description/title-scanned) refs on a fix WI** are confirmed via:
|
|
79
|
+
- an already-tracked `pull-requests.json` record for the ref (cheap, no
|
|
80
|
+
network), else
|
|
81
|
+
- a host existence check — `engine/github.js#prExists` (`gh api
|
|
82
|
+
repos/:owner/:repo/pulls/NNN`; 404 ⇒ issue) or `engine/ado.js#prExists`
|
|
83
|
+
(`…/pullrequests/NNN`), routed via `engine/gh-token.js` per-account token.
|
|
84
|
+
`shared.classifyPrRefForVerification(prRef, project)` does the pure host/slug
|
|
85
|
+
/number routing; the result is TTL-cached.
|
|
86
|
+
3. **Fail-safe:** when existence can't be confirmed (issue, 404, network/auth
|
|
87
|
+
error, unresolvable host) the ref is **not** stamped — the WI dispatches as a
|
|
88
|
+
normal fix that opens a NEW PR. Prefer a fresh PR over a phantom one.
|
|
89
|
+
|
|
90
|
+
The `type: "fix"` gate from W-mqbaby2a000pa8ee still applies first: non-fix WIs
|
|
91
|
+
are never stamped from loose prose.
|
|
92
|
+
|
|
59
93
|
## Structured-vs-loose split (W-mq18ec6h000p7b87)
|
|
60
94
|
|
|
61
95
|
The PR-ref extractor has **two** variants — pick the right one for the
|
package/docs/deprecated.json
CHANGED
|
@@ -119,8 +119,8 @@
|
|
|
119
119
|
{ "file": "engine.js", "note": "discoverFromPrs reads canonical `pr.contextOnly` only." }
|
|
120
120
|
],
|
|
121
121
|
"deprecated": "2026-06-08",
|
|
122
|
-
"targetRemovalDate": "2026-06-
|
|
123
|
-
"notes": "The write side already shipped out — there is no dual-write to retire. What remains is the read-fallback (`_contextOnly === true`) plus the one-shot boot migration migratePrGateFlags (engine/shared.js:6833) that copies + deletes the alias from any pre-migration on-disk record. Pair this entry's removal with `pr-record-autoObserve-underscore-alias`, `pr-record-manual-underscore-alias`, and `pr-link-autoObserve-body-param` — they share the same boot migration. Removal scope (DEFERRED — gated on a positive signal that every live pull-requests.json has been swept of underscore keys, not on the expired calendar date): delete the `_contextOnly` read-fallback in engine/shared.js and the migratePrGateFlags handling of it."
|
|
122
|
+
"targetRemovalDate": "2026-06-25",
|
|
123
|
+
"notes": "The write side already shipped out — there is no dual-write to retire. What remains is the read-fallback (`_contextOnly === true`) plus the one-shot boot migration migratePrGateFlags (engine/shared.js:6833) that copies + deletes the alias from any pre-migration on-disk record. Pair this entry's removal with `pr-record-autoObserve-underscore-alias`, `pr-record-manual-underscore-alias`, and `pr-link-autoObserve-body-param` — they share the same boot migration. Removal scope (DEFERRED — gated on a positive signal that every live pull-requests.json has been swept of underscore keys, not on the expired calendar date): delete the `_contextOnly` read-fallback in engine/shared.js and the migratePrGateFlags handling of it. On-disk sweep confirmed CLEAN on 2026-06-18: grep of every live projects/*/pull-requests.json (budget-test-project, constellation, minions-opg, office-bohemia) for the `_contextOnly`/`_autoObserve`/`_manual` record keys returned zero matches, so the deferral gate is now cleared and a deliberate, human-reviewed code removal can be scheduled (this calendar bump 2026-06-16 → 2026-06-25 is the docs-only consistency half; the MEDIUM/>3-file code removal stays a separate PR)."
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
"id": "pr-record-autoObserve-underscore-alias",
|
|
@@ -130,8 +130,8 @@
|
|
|
130
130
|
{ "file": "dashboard.js", "note": "Zero `_autoObserve` references — link/observe handlers accept the canonical `contextOnly` body param and write no underscore alias onto the record." }
|
|
131
131
|
],
|
|
132
132
|
"deprecated": "2026-06-08",
|
|
133
|
-
"targetRemovalDate": "2026-06-
|
|
134
|
-
"notes": "The write side already shipped out — there is no dual-write to retire. Removal is paired with `pr-record-contextOnly-underscore-alias` — same boot migration, same read-bridge. Removal scope (DEFERRED — gated on a sweep confirming no live record retains `_autoObserve`, not on the expired calendar date): delete the `_autoObserve === true` read in _prRecordIsLegacyManaged (engine/shared.js:6823) and the migratePrGateFlags handling of it."
|
|
133
|
+
"targetRemovalDate": "2026-06-25",
|
|
134
|
+
"notes": "The write side already shipped out — there is no dual-write to retire. Removal is paired with `pr-record-contextOnly-underscore-alias` — same boot migration, same read-bridge. Removal scope (DEFERRED — gated on a sweep confirming no live record retains `_autoObserve`, not on the expired calendar date): delete the `_autoObserve === true` read in _prRecordIsLegacyManaged (engine/shared.js:6823) and the migratePrGateFlags handling of it. On-disk sweep confirmed CLEAN on 2026-06-18: grep of every live projects/*/pull-requests.json (budget-test-project, constellation, minions-opg, office-bohemia) for the `_contextOnly`/`_autoObserve`/`_manual` record keys returned zero matches, so the deferral gate is now cleared and a deliberate, human-reviewed code removal can be scheduled (this calendar bump 2026-06-16 → 2026-06-25 is the docs-only consistency half; the MEDIUM/>3-file code removal stays a separate PR)."
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
"id": "pr-record-manual-underscore-alias",
|
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
{ "file": "dashboard.js", "note": "Zero `_manual` references — the link handler no longer writes the provenance alias onto the record." }
|
|
143
143
|
],
|
|
144
144
|
"deprecated": "2026-06-08",
|
|
145
|
-
"targetRemovalDate": "2026-06-
|
|
146
|
-
"notes": "Disambiguation: this entry covers ONLY the PR-record `_manual` flag (engine/<scope>/pull-requests.json). Other `_manual`-named flags elsewhere in the engine (work items, dispatch records, etc.) are NOT covered by this entry and remain in active use. The write side already shipped out — there is no write site or dashboard badge left to remove. Removal scope (DEFERRED — gated on a sweep confirming no live record retains `_manual`, not on the expired calendar date): drop the `_manual` handling in normalizePrRecord and migratePrGateFlags (engine/shared.js) once the on-disk sweep is confirmed."
|
|
145
|
+
"targetRemovalDate": "2026-06-25",
|
|
146
|
+
"notes": "Disambiguation: this entry covers ONLY the PR-record `_manual` flag (engine/<scope>/pull-requests.json). Other `_manual`-named flags elsewhere in the engine (work items, dispatch records, etc.) are NOT covered by this entry and remain in active use. The write side already shipped out — there is no write site or dashboard badge left to remove. Removal scope (DEFERRED — gated on a sweep confirming no live record retains `_manual`, not on the expired calendar date): drop the `_manual` handling in normalizePrRecord and migratePrGateFlags (engine/shared.js) once the on-disk sweep is confirmed. On-disk sweep confirmed CLEAN on 2026-06-18: grep of every live projects/*/pull-requests.json (budget-test-project, constellation, minions-opg, office-bohemia) for the `_contextOnly`/`_autoObserve`/`_manual` record keys returned zero matches, so the deferral gate is now cleared and a deliberate, human-reviewed code removal can be scheduled (this calendar bump 2026-06-16 → 2026-06-25 is the docs-only consistency half; the MEDIUM/>3-file code removal stays a separate PR)."
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"id": "pr-link-autoObserve-body-param",
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
{ "file": "dashboard.js", "note": "linkPullRequestForTracking resolves `contextOnly` from `body.contextOnly` when boolean, else `autoObserve === undefined ? false : !autoObserve` (dashboard.js:1055-1057). Route registry params string still lists `autoObserve?` (dashboard.js:12680)." }
|
|
153
153
|
],
|
|
154
154
|
"deprecated": "2026-06-08",
|
|
155
|
-
"targetRemovalDate": "2026-06-
|
|
156
|
-
"notes": "Unlike the three record-field aliases, nothing is written here — this is purely an input read-fallback. Removal scope (DEFERRED — gated on confirming no client still POSTs `autoObserve`, not on the expired calendar date): drop the `!body.autoObserve` fallback in the link handler in dashboard.js, drop `autoObserve?` from the route registry `params` string, and update any client (dashboard JS, ops scripts) that still POSTs `autoObserve`. After removal, callers that still send `autoObserve` will see their value silently ignored."
|
|
155
|
+
"targetRemovalDate": "2026-06-25",
|
|
156
|
+
"notes": "Unlike the three record-field aliases, nothing is written here — this is purely an input read-fallback. Removal scope (DEFERRED — gated on confirming no client still POSTs `autoObserve`, not on the expired calendar date): drop the `!body.autoObserve` fallback in the link handler in dashboard.js, drop `autoObserve?` from the route registry `params` string, and update any client (dashboard JS, ops scripts) that still POSTs `autoObserve`. After removal, callers that still send `autoObserve` will see their value silently ignored. On-disk sweep confirmed CLEAN on 2026-06-18: grep of every live projects/*/pull-requests.json (budget-test-project, constellation, minions-opg, office-bohemia) for the `_contextOnly`/`_autoObserve`/`_manual` record keys returned zero matches, so the deferral gate is now cleared and a deliberate, human-reviewed removal can be scheduled (this calendar bump 2026-06-16 → 2026-06-25 is the docs-only consistency half; the actual code removal stays a separate human-reviewed PR)."
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"id": "worktreemode-field-rename",
|
package/engine/ado.js
CHANGED
|
@@ -2511,6 +2511,25 @@ async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo) {
|
|
|
2511
2511
|
};
|
|
2512
2512
|
}
|
|
2513
2513
|
|
|
2514
|
+
/**
|
|
2515
|
+
* issue #246 — confirm a number points at a real ADO PULL REQUEST. ADO work
|
|
2516
|
+
* items (issues) live in a different namespace, so a non-PR number 404s on the
|
|
2517
|
+
* pullrequests endpoint. `adoFetch` can't distinguish 404 from auth/network
|
|
2518
|
+
* failure, so a missing/erroring PR returns null (not false) — and the caller
|
|
2519
|
+
* treats both false and null as "do not stamp" (fail-safe). Returns true only
|
|
2520
|
+
* on a positively-fetched PR.
|
|
2521
|
+
*/
|
|
2522
|
+
async function prExists(adoOrg, adoProject, adoRepo, prNumber) {
|
|
2523
|
+
const n = parseInt(prNumber, 10);
|
|
2524
|
+
if (!adoOrg || !adoProject || !adoRepo || !Number.isInteger(n) || n <= 0) return null;
|
|
2525
|
+
try {
|
|
2526
|
+
const meta = await fetchAdoPrMetadata(n, adoOrg, adoProject, adoRepo);
|
|
2527
|
+
return meta ? true : null;
|
|
2528
|
+
} catch {
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2514
2533
|
/**
|
|
2515
2534
|
* Fetch live PR and build status for a single PR number.
|
|
2516
2535
|
* Used by engine/ado-status.js so agents can check CI without raw curl calls.
|
|
@@ -2918,6 +2937,7 @@ module.exports = {
|
|
|
2918
2937
|
getAdoThrottleState,
|
|
2919
2938
|
getAdoThrottleStateAll,
|
|
2920
2939
|
fetchAdoPrMetadata,
|
|
2940
|
+
prExists, // issue #246 — confirm a loose ref points at a PR (not a work item) before stamping
|
|
2921
2941
|
fetchSinglePrBuildStatus,
|
|
2922
2942
|
findOpenPrOnBranch,
|
|
2923
2943
|
applyAdoPrMetadata, // #3079 — exported for unit tests of targetRefName + retarget reset
|
package/engine/github.js
CHANGED
|
@@ -400,6 +400,23 @@ async function ghApi(endpoint, slug, opts = {}) {
|
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
/**
|
|
404
|
+
* issue #246 — confirm a number points at a real PULL REQUEST (GitHub issues
|
|
405
|
+
* and PRs share one number namespace, so `owner/repo#244` may be an issue).
|
|
406
|
+
* Hits GET /repos/{slug}/pulls/{n}, which 404s for issue numbers. Returns:
|
|
407
|
+
* true — the PR exists
|
|
408
|
+
* false — 404 (the number is an issue or doesn't exist)
|
|
409
|
+
* null — could not determine (network / auth / throttle) → caller fail-safe
|
|
410
|
+
*/
|
|
411
|
+
async function prExists(slug, prNumber, opts = {}) {
|
|
412
|
+
const n = parseInt(prNumber, 10);
|
|
413
|
+
if (!slug || !Number.isInteger(n) || n <= 0) return null;
|
|
414
|
+
const result = await ghApi(`/pulls/${n}`, slug, opts);
|
|
415
|
+
if (result === GH_NOT_FOUND) return false;
|
|
416
|
+
if (result && typeof result === 'object' && result._notFound !== true) return true;
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
|
|
403
420
|
const BUILD_ERROR_LOG_MAX_LINES = 150;
|
|
404
421
|
|
|
405
422
|
/**
|
|
@@ -1885,6 +1902,7 @@ module.exports = {
|
|
|
1885
1902
|
isGhThrottled,
|
|
1886
1903
|
getGhThrottleState,
|
|
1887
1904
|
ghApi, // P-8c1b6e45 — used by engine/shared-branch-pr-reconcile.js to list open PRs on a feature branch
|
|
1905
|
+
prExists, // issue #246 — confirm a loose ref points at a PR (not an issue) before stamping
|
|
1888
1906
|
// Exported for testing
|
|
1889
1907
|
isGitHub,
|
|
1890
1908
|
getRepoSlug,
|
package/engine/shared.js
CHANGED
|
@@ -6163,15 +6163,11 @@ function parsePrUrl(url) {
|
|
|
6163
6163
|
// `isPrTargeted` paths intact and prevents PR-feedback fix WIs from being
|
|
6164
6164
|
// dispatched on a fresh `work/<wi-id>` branch when only the description /
|
|
6165
6165
|
// references[] carried the PR pointer (issue #2999).
|
|
6166
|
-
function _trimTrailingPrRefPunctuation(value) {
|
|
6167
|
-
return String(value || '').replace(/[),.;:]+$/g, '');
|
|
6168
|
-
}
|
|
6169
|
-
|
|
6170
6166
|
function extractPrRefFromText(value) {
|
|
6171
6167
|
const text = String(value || '');
|
|
6172
6168
|
if (!text.trim()) return null;
|
|
6173
6169
|
const urlMatch = text.match(/https?:\/\/[^\s<>()]+(?:\/pull\/\d+|\/pullrequest\/\d+)[^\s<>()]*/i);
|
|
6174
|
-
if (urlMatch) return
|
|
6170
|
+
if (urlMatch) return String(urlMatch[0] || '').replace(/[),.;:]+$/g, '');
|
|
6175
6171
|
const canonicalMatch = text.match(/\b(?:github|ado):[^\s#]+#\d+\b/i);
|
|
6176
6172
|
if (canonicalMatch) return canonicalMatch[0];
|
|
6177
6173
|
const legacyMatch = text.match(/\bPR-(\d+)\b/i);
|
|
@@ -7053,6 +7049,38 @@ function deriveUrlForPrRef(prRef, project) {
|
|
|
7053
7049
|
return null;
|
|
7054
7050
|
}
|
|
7055
7051
|
|
|
7052
|
+
// issue #246 — classify a PR ref into the host + identifiers needed to verify
|
|
7053
|
+
// it actually points at a PULL REQUEST (not an ISSUE, which shares the number
|
|
7054
|
+
// namespace on GitHub and is a distinct endpoint on ADO). Pure: no network.
|
|
7055
|
+
//
|
|
7056
|
+
// Resolution: prefer the scope embedded in the ref (canonical `github:…#N` /
|
|
7057
|
+
// `ado:…#N` id, or a PR URL); fall back to the project's own scope for a bare
|
|
7058
|
+
// `#N` / `PR-N` / number ref. Returns null when the ref can't be resolved to a
|
|
7059
|
+
// concrete host + number — the caller treats null as "cannot verify" and, per
|
|
7060
|
+
// the fail-safe contract, declines to stamp.
|
|
7061
|
+
//
|
|
7062
|
+
// Returns one of:
|
|
7063
|
+
// { host: 'github', slug: 'owner/repo', prNumber }
|
|
7064
|
+
// { host: 'ado', adoOrg, adoProject, adoRepo, prNumber }
|
|
7065
|
+
// null
|
|
7066
|
+
function classifyPrRefForVerification(prRef, project = null) {
|
|
7067
|
+
const scoped = getPrScopeInfo(prRef);
|
|
7068
|
+
let scope = scoped?.scope || '';
|
|
7069
|
+
const prNumber = scoped?.prNumber ?? getPrNumber(prRef);
|
|
7070
|
+
if (!scope && project) scope = getProjectPrScope(project);
|
|
7071
|
+
if (!scope || prNumber == null) return null;
|
|
7072
|
+
if (scope.startsWith('github:')) {
|
|
7073
|
+
const slug = scope.slice('github:'.length);
|
|
7074
|
+
return slug.includes('/') ? { host: 'github', slug, prNumber } : null;
|
|
7075
|
+
}
|
|
7076
|
+
if (scope.startsWith('ado:')) {
|
|
7077
|
+
const parts = scope.slice('ado:'.length).split('/');
|
|
7078
|
+
if (parts.length < 3 || !parts[0] || !parts[1] || !parts[2]) return null;
|
|
7079
|
+
return { host: 'ado', adoOrg: parts[0], adoProject: parts[1], adoRepo: parts[2], prNumber };
|
|
7080
|
+
}
|
|
7081
|
+
return null;
|
|
7082
|
+
}
|
|
7083
|
+
|
|
7056
7084
|
// W-mq5wfh1v000e0da9 — Auto-enroll a PR into pull-requests.json when a
|
|
7057
7085
|
// `type: fix` work item is created with a structured PR pointer. Without
|
|
7058
7086
|
// this, fix WIs against untracked PRs bypass the polling / review / build
|
|
@@ -8775,6 +8803,7 @@ module.exports = {
|
|
|
8775
8803
|
migratePrGateFlags, // W-mq5s5ttx000j7ab8-a — boot migration wired from engine/cli.js
|
|
8776
8804
|
autoEnrollPrFromFixWorkItem,
|
|
8777
8805
|
deriveUrlForPrRef, // exported for testing
|
|
8806
|
+
classifyPrRefForVerification, // issue #246 — host routing for loose PR-ref verification
|
|
8778
8807
|
nextWorkItemId,
|
|
8779
8808
|
getProjectOrg,
|
|
8780
8809
|
getAdoOrgBase,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2225",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|