@yemi33/minions 0.1.2361 → 0.1.2363
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 +23 -3
- package/engine/lifecycle.js +14 -1
- package/engine/queries.js +14 -1
- package/engine/shared.js +33 -4
- package/engine.js +2 -1
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -1373,6 +1373,11 @@ function linkPullRequestForTracking({ url, title, project: projectName, contextO
|
|
|
1373
1373
|
}, {
|
|
1374
1374
|
project: targetProject,
|
|
1375
1375
|
itemId: linkedWorkItemId,
|
|
1376
|
+
// W-mrdutzdr000t22f4 — a POST /api/pull-requests/link call is deliberate
|
|
1377
|
+
// user intent to (re)track this PR. Unlike passive re-enrollment paths
|
|
1378
|
+
// (pollers, enrollPrFromCanonicalId), this call site should clear a
|
|
1379
|
+
// tombstone left by a prior explicit delete rather than silently no-op.
|
|
1380
|
+
allowResurrect: true,
|
|
1376
1381
|
});
|
|
1377
1382
|
return { ...result, prPath, targetProject, projectResolution, prNum };
|
|
1378
1383
|
}
|
|
@@ -13844,16 +13849,25 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13844
13849
|
} catch (e) {
|
|
13845
13850
|
return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
|
|
13846
13851
|
}
|
|
13847
|
-
const { id: prId, prPath, prNum, created, linked, targetProject, projectResolution } = linkResult;
|
|
13852
|
+
const { id: prId, prPath, prNum, created, linked, targetProject, projectResolution, skipped, resurrected } = linkResult;
|
|
13848
13853
|
invalidateStatusCache();
|
|
13854
|
+
// W-mrdutzdr000t22f4 — upsertPullRequestRecord can leave the record
|
|
13855
|
+
// untouched (`skipped: true`) for reasons other than a resurrect, e.g.
|
|
13856
|
+
// a legitimate cross-scope tombstone match it still refuses to touch.
|
|
13857
|
+
// Surface that instead of always claiming success so a caller relying
|
|
13858
|
+
// on "PR linked." isn't misled into thinking the record changed.
|
|
13849
13859
|
jsonReply(res, 200, {
|
|
13850
13860
|
ok: true,
|
|
13851
13861
|
id: prId,
|
|
13852
13862
|
created,
|
|
13853
13863
|
linked,
|
|
13864
|
+
skipped: skipped === true,
|
|
13865
|
+
resurrected: resurrected === true,
|
|
13854
13866
|
project: targetProject?.name || 'central',
|
|
13855
13867
|
projectResolution,
|
|
13856
|
-
message:
|
|
13868
|
+
message: skipped
|
|
13869
|
+
? 'PR was not linked: an existing tracked record blocked the update.'
|
|
13870
|
+
: (projectResolution?.message || 'PR linked.'),
|
|
13857
13871
|
});
|
|
13858
13872
|
|
|
13859
13873
|
// Async-enrich: fetch title, description, branch, author from GitHub/ADO API
|
|
@@ -14099,8 +14113,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14099
14113
|
path.join(MINIONS_DIR, 'pull-requests.json'),
|
|
14100
14114
|
];
|
|
14101
14115
|
let found = false;
|
|
14116
|
+
// Issue #803: tombstone EVERY scope that has a copy of this PR id, not
|
|
14117
|
+
// just the first one found. When enrollPrFromCanonicalId (issue #802)
|
|
14118
|
+
// has left a stray cross-scope duplicate (e.g. a legit per-project row
|
|
14119
|
+
// plus a stray central-scope copy), stopping at the first match leaves
|
|
14120
|
+
// the other scope's copy fully untouched — queries.js#getPullRequests
|
|
14121
|
+
// then de-dupes by picking that untouched duplicate as the "winner"
|
|
14122
|
+
// and silently resurrects the "deleted" PR on the next read.
|
|
14102
14123
|
for (const prPath of prPaths) {
|
|
14103
|
-
if (found) break;
|
|
14104
14124
|
// Issue #384: use a tombstone (userDeleted:true) instead of splice so
|
|
14105
14125
|
// the next poller cycle cannot re-add the PR. The entry stays in the
|
|
14106
14126
|
// file but is filtered from API responses and skipped by pollers.
|
package/engine/lifecycle.js
CHANGED
|
@@ -1378,7 +1378,20 @@ async function enrollPrFromCanonicalId(canonicalPrId, project, opts = {}) {
|
|
|
1378
1378
|
// pr-links stays consistent (#779 pattern).
|
|
1379
1379
|
if (opts.itemId && Array.isArray(existing.prdItems) && !existing.prdItems.includes(opts.itemId)) {
|
|
1380
1380
|
try {
|
|
1381
|
-
|
|
1381
|
+
// W-mrduef3q000m2172 — derive targetPath from the scope the store
|
|
1382
|
+
// actually returned `existing` under (existing._scope), NOT from the
|
|
1383
|
+
// current call's `project` argument. When the two differ (e.g. the
|
|
1384
|
+
// record lives under 'central' but this enrollment call was made
|
|
1385
|
+
// with a specific project), re-deriving from `project` sends the
|
|
1386
|
+
// upsert to the WRONG scope's file/array. upsertPullRequestRecord's
|
|
1387
|
+
// tombstone guard then finds no matching record in that (wrong)
|
|
1388
|
+
// scope and falls through to insert-as-new, silently clearing any
|
|
1389
|
+
// userDeleted tombstone on the real record. See issue writeup for
|
|
1390
|
+
// the confirmed repro.
|
|
1391
|
+
const store = require('./pull-requests-store');
|
|
1392
|
+
const targetPath = existing._scope
|
|
1393
|
+
? store._filePathForScope(existing._scope)
|
|
1394
|
+
: (project ? shared.projectPrPath(project) : path.join(MINIONS_DIR, 'pull-requests.json'));
|
|
1382
1395
|
shared.upsertPullRequestRecord(targetPath, {
|
|
1383
1396
|
id: normalizedCanonicalId,
|
|
1384
1397
|
prNumber,
|
package/engine/queries.js
CHANGED
|
@@ -995,12 +995,25 @@ function getPullRequests(config) {
|
|
|
995
995
|
// Then merge non-conflicting fields from the loser into the winner so we
|
|
996
996
|
// never lose prdItems, sourcePlan, itemType, minionsReview, or
|
|
997
997
|
// _automationFixCauses if only the loser carried them.
|
|
998
|
+
// Issue #803 — defense in depth: if a canonical PR id has a tombstoned
|
|
999
|
+
// (userDeleted:true) copy in ANY scope, treat the whole id as deleted, not
|
|
1000
|
+
// just that one record. Without this, a delete that (for whatever reason)
|
|
1001
|
+
// only tombstones one scope's copy would still have its untouched
|
|
1002
|
+
// cross-scope duplicate survive grouping below and get picked as the
|
|
1003
|
+
// dedupe "winner", silently resurrecting a PR the user just deleted.
|
|
1004
|
+
const tombstonedIds = new Set();
|
|
1005
|
+
for (const pr of sqlPrs) {
|
|
1006
|
+
if (pr && pr.id && pr.userDeleted === true) tombstonedIds.add(pr.id);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
998
1009
|
const groups = [];
|
|
999
1010
|
const groupsById = new Map();
|
|
1000
1011
|
for (const pr of sqlPrs) {
|
|
1001
1012
|
if (!pr || !pr.id) continue;
|
|
1002
|
-
// Issue #384: skip tombstoned records (user explicitly deleted)
|
|
1013
|
+
// Issue #384/#803: skip tombstoned records (user explicitly deleted), and
|
|
1014
|
+
// skip ANY record sharing an id that has a tombstoned copy elsewhere.
|
|
1003
1015
|
if (pr.userDeleted === true) continue;
|
|
1016
|
+
if (tombstonedIds.has(pr.id)) continue;
|
|
1004
1017
|
if (!groupsById.has(pr.id)) {
|
|
1005
1018
|
const g = [];
|
|
1006
1019
|
groupsById.set(pr.id, g);
|
package/engine/shared.js
CHANGED
|
@@ -7873,7 +7873,7 @@ function collapseDuplicatePrRecords(prPath, { project = null } = {}) {
|
|
|
7873
7873
|
* manually records a PR for a work item must use this helper so the PR record
|
|
7874
7874
|
* and the canonical work-item attachment are created together and idempotently.
|
|
7875
7875
|
*/
|
|
7876
|
-
function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null, itemIds = null, beforeInsert = null } = {}) {
|
|
7876
|
+
function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null, itemIds = null, beforeInsert = null, allowResurrect = false } = {}) {
|
|
7877
7877
|
if (!prPath) throw new Error('prPath required');
|
|
7878
7878
|
if (!entry || typeof entry !== 'object') throw new Error('entry required');
|
|
7879
7879
|
|
|
@@ -7906,6 +7906,7 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
7906
7906
|
let created = false;
|
|
7907
7907
|
let linked = false;
|
|
7908
7908
|
let skipped = false;
|
|
7909
|
+
let resurrected = false;
|
|
7909
7910
|
let record = null;
|
|
7910
7911
|
|
|
7911
7912
|
mutatePullRequests(prPath, (prs) => {
|
|
@@ -7914,9 +7915,37 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
7914
7915
|
// Issue #384: tombstoned records (userDeleted:true) must not be re-activated
|
|
7915
7916
|
// by any poller path. Return skipped so reconcilePrs / shared-branch-reconcile
|
|
7916
7917
|
// cannot resurrect a PR the user explicitly removed.
|
|
7918
|
+
//
|
|
7919
|
+
// W-mrdutzdr000t22f4 — passive re-enrollment (pollers, enrollPrFromCanonicalId)
|
|
7920
|
+
// must keep respecting the tombstone, but a deliberate user-initiated relink
|
|
7921
|
+
// via POST /api/pull-requests/link is explicit intent to bring the PR back.
|
|
7922
|
+
// Callers that want that behavior opt in with `allowResurrect: true`, which
|
|
7923
|
+
// clears the tombstone here and lets the normal field-merge logic below run.
|
|
7917
7924
|
if (target && target.userDeleted === true) {
|
|
7918
|
-
|
|
7919
|
-
|
|
7925
|
+
if (!allowResurrect) {
|
|
7926
|
+
skipped = true;
|
|
7927
|
+
return prs;
|
|
7928
|
+
}
|
|
7929
|
+
delete target.userDeleted;
|
|
7930
|
+
delete target.userDeletedAt;
|
|
7931
|
+
resurrected = true;
|
|
7932
|
+
}
|
|
7933
|
+
// W-mrduef3q000m2172 — defense-in-depth: `prs` here is scoped to a single
|
|
7934
|
+
// scope's array (mutatePullRequests routes by prPath). If a caller
|
|
7935
|
+
// resolved the wrong scope for an already-tombstoned canonical id (the
|
|
7936
|
+
// record actually lives in a DIFFERENT scope's file), the above check
|
|
7937
|
+
// never sees it and would otherwise fall through to insert-as-new,
|
|
7938
|
+
// silently clearing the tombstone. Check globally before inserting.
|
|
7939
|
+
if (!target) {
|
|
7940
|
+
try {
|
|
7941
|
+
const store = require('./pull-requests-store');
|
|
7942
|
+
const globalMatch = (store.readAllPullRequests() || [])
|
|
7943
|
+
.find(pr => pr && pr.id === canonicalId && pr.userDeleted === true);
|
|
7944
|
+
if (globalMatch) {
|
|
7945
|
+
skipped = true;
|
|
7946
|
+
return prs;
|
|
7947
|
+
}
|
|
7948
|
+
} catch { /* best-effort global tombstone check — fall through on failure */ }
|
|
7920
7949
|
}
|
|
7921
7950
|
// P-e9f0a2b4 — When findPrRecord returns null but multiple records share the
|
|
7922
7951
|
// same prNumber (e.g. cross-scope contamination or a project-config change
|
|
@@ -7980,7 +8009,7 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
7980
8009
|
}
|
|
7981
8010
|
}
|
|
7982
8011
|
|
|
7983
|
-
return { id: canonicalId, prNumber, created, linked, skipped, record };
|
|
8012
|
+
return { id: canonicalId, prNumber, created, linked, skipped, resurrected, record };
|
|
7984
8013
|
}
|
|
7985
8014
|
|
|
7986
8015
|
// ─── PR Reference → URL Derivation ───────────────────────────────────────────
|
package/engine.js
CHANGED
|
@@ -28,13 +28,14 @@ require('./engine/stdio-timestamps').installIfNotInstalled();
|
|
|
28
28
|
|
|
29
29
|
const fs = require('fs');
|
|
30
30
|
const path = require('path');
|
|
31
|
+
const os = require('os');
|
|
31
32
|
const crypto = require('crypto');
|
|
32
33
|
const shared = require('./engine/shared');
|
|
33
34
|
const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS,
|
|
34
35
|
WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, REVIEW_STATUS, DISPATCH_RESULT, AGENT_STATUS,
|
|
35
36
|
FAILURE_CLASS, resolvePollFlag } = shared;
|
|
36
37
|
const { resolveRuntime } = require('./engine/runtimes');
|
|
37
|
-
const { assertStaleHeadOk } = require('./engine/spawn-agent');
|
|
38
|
+
const { assertStaleHeadOk, preApproveWorkspaceMcps, computeAddDirs } = require('./engine/spawn-agent');
|
|
38
39
|
const adoGitAuth = require('./engine/ado-git-auth');
|
|
39
40
|
const queries = require('./engine/queries');
|
|
40
41
|
const dispatchEvents = require('./engine/dispatch-events');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2363",
|
|
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"
|