@yemi33/minions 0.1.2362 → 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 CHANGED
@@ -14113,8 +14113,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14113
14113
  path.join(MINIONS_DIR, 'pull-requests.json'),
14114
14114
  ];
14115
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.
14116
14123
  for (const prPath of prPaths) {
14117
- if (found) break;
14118
14124
  // Issue #384: use a tombstone (userDeleted:true) instead of splice so
14119
14125
  // the next poller cycle cannot re-add the PR. The entry stays in the
14120
14126
  // file but is filtered from API responses and skipped by pollers.
@@ -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
- const targetPath = project ? shared.projectPrPath(project) : path.join(MINIONS_DIR, 'pull-requests.json');
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
@@ -7930,6 +7930,23 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
7930
7930
  delete target.userDeletedAt;
7931
7931
  resurrected = true;
7932
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 */ }
7949
+ }
7933
7950
  // P-e9f0a2b4 — When findPrRecord returns null but multiple records share the
7934
7951
  // same prNumber (e.g. cross-scope contamination or a project-config change
7935
7952
  // that shifted canonical IDs), collapse the duplicates before inserting.
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.2362",
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"