@yemi33/minions 0.1.2144 → 0.1.2145

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/engine/shared.js CHANGED
@@ -2906,6 +2906,51 @@ function _resetLegacyCcModelMigrationFlag() {
2906
2906
  _legacyCcModelMigrationLogged = false;
2907
2907
  }
2908
2908
 
2909
+ /**
2910
+ * One-time force-on for the CC worker pool.
2911
+ *
2912
+ * The pool has been the resolved default for copilot CC since PR #2492
2913
+ * (`resolveCcUseWorkerPool` returns true when the config has no explicit
2914
+ * value). But configs that carried an explicit `ccUseWorkerPool: false` —
2915
+ * set before the default flipped, or copied from an old template — stay
2916
+ * opted out forever. This migration flips those explicit-false opt-outs to
2917
+ * `true` ONCE, on both surfaces the value lives in (`engine.ccUseWorkerPool`
2918
+ * which the resolver reads, and `features.ccUseWorkerPool` which drives the
2919
+ * Settings toggle / `isFeatureOn`), so the two stay consistent.
2920
+ *
2921
+ * Idempotency: records `engine._ccPoolForcedOnV1` after running. Once that
2922
+ * marker is set the migration never touches the value again — so an operator
2923
+ * who *deliberately* turns the pool off afterward is never re-forced. The
2924
+ * marker is persisted even when nothing needed flipping, which is what lets a
2925
+ * later opt-out be distinguished from a never-migrated config.
2926
+ *
2927
+ * Pure + idempotent: safe to call in-memory then re-apply to the on-disk copy
2928
+ * under a lock (mirrors backfillProjectWorkSourceDefaults). Returns
2929
+ * `{ changed, flipped }` — `changed` true means the config was mutated (marker
2930
+ * set and/or values flipped) and should be persisted; `flipped` lists which
2931
+ * surfaces ('engine'/'features') were actually turned on.
2932
+ */
2933
+ function applyCcWorkerPoolForceOnMigration(config) {
2934
+ const result = { changed: false, flipped: [] };
2935
+ if (!config || typeof config !== 'object') return result;
2936
+ const engine = (config.engine && typeof config.engine === 'object') ? config.engine : null;
2937
+ if (!engine) return result; // no engine section — retry next start, harmless
2938
+ if (engine._ccPoolForcedOnV1) return result; // already forced once; respect later opt-out
2939
+
2940
+ if (engine.ccUseWorkerPool === false) {
2941
+ engine.ccUseWorkerPool = true;
2942
+ result.flipped.push('engine');
2943
+ }
2944
+ const features = (config.features && typeof config.features === 'object') ? config.features : null;
2945
+ if (features && features.ccUseWorkerPool === false) {
2946
+ features.ccUseWorkerPool = true;
2947
+ result.flipped.push('features');
2948
+ }
2949
+ engine._ccPoolForcedOnV1 = true; // marker — never force again
2950
+ result.changed = true; // marker always needs persisting on first run
2951
+ return result;
2952
+ }
2953
+
2909
2954
  // ─── Runtime Config Preflight Warnings ──────────────────────────────────────
2910
2955
  //
2911
2956
  // Emit non-fatal warnings about runtime/CLI configuration drift. Consumed by
@@ -5547,39 +5592,17 @@ function normalizePrLinkItems(value) {
5547
5592
  return [...new Set(items.filter(item => typeof item === 'string' && item))];
5548
5593
  }
5549
5594
 
5550
- /**
5551
- * Single source of truth for "should the engine dispatch review/fix for this PR?"
5552
- *
5553
- * Used by `discoverFromPrs` in engine.js to gate review + fix dispatch, and by
5554
- * `upsertPullRequestRecord` to decide whether to preserve auto-managed metadata
5555
- * on merge. Dispatch-loop callers MUST go through this helper — do NOT inline
5556
- * variants like `knownAgents.has(pr.agent) || pr.prdItems?.length || pr._manual`
5557
- * because they silently drop the `_autoObserve` case (PR linked with
5558
- * autoObserve=true but no prdItems and no configured-agent author) and the
5559
- * sourcePlan/itemType cases. See W-mq5rs2eq000da8a9 for the bug repro.
5560
- *
5561
- * A PR is auto-managed when ANY of the following hold (and `_contextOnly` is
5562
- * not explicitly true — context-only always wins):
5563
- * - It carries one or more `prdItems` (linked to work items)
5564
- * - It was explicitly opted-in via `_autoObserve: true` (manual link with
5565
- * autoObserve, or per-row toggle via POST /api/pull-requests/observe)
5566
- * - It was created from a plan or has an itemType (`sourcePlan`/`itemType`)
5567
- * - It has a non-empty agent author other than the literal string `'human'`
5568
- * (the helper deliberately does NOT require the agent be in
5569
- * `config.agents` — once `_autoObserve` is the explicit opt-in for
5570
- * non-agent authors, the loosened agent-string check has no operational
5571
- * downside for managed flows and avoids the silent-skip bug.)
5572
- *
5573
- * @param {object} pr Pull request record
5574
- * @returns {boolean} true when the engine should drive review/fix for this PR
5575
- */
5595
+ // W-mq5s5ttx000j7ab8-a — canonical `contextOnly` gate. The 6-clause legacy
5596
+ // body (prdItems / _autoObserve / sourcePlan / itemType / agent-heuristic) is
5597
+ // replaced by a single read of the canonical field. The one-shot boot
5598
+ // migration (migratePrGateFlags) projects the legacy signals onto
5599
+ // `contextOnly` so every on-disk record has the canonical value before any
5600
+ // tick fires. Anything new written via upsertPullRequestRecord (and the
5601
+ // dashboard link/observe paths consolidated in PR #3137 / `-c`) also lands
5602
+ // on `contextOnly` directly. See W-mq5rs2eq000da8a9 for the bug repro that
5603
+ // motivated consolidating onto a single helper.
5576
5604
  function isAutoManagedPrRecord(pr) {
5577
- if (!pr || typeof pr !== 'object' || pr._contextOnly === true) return false;
5578
- if (normalizePrLinkItems(pr.prdItems).length > 0) return true;
5579
- if (pr._autoObserve === true) return true;
5580
- if (pr.sourcePlan || pr.itemType) return true;
5581
- const agent = String(pr.agent || '').trim().toLowerCase();
5582
- return !!agent && agent !== 'human';
5605
+ return !!pr && typeof pr === 'object' && pr.contextOnly !== true;
5583
5606
  }
5584
5607
 
5585
5608
  function mergePrLinkItems(links, prId, itemIds) {
@@ -5757,12 +5780,24 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
5757
5780
  target[key] = normalizedEntry[key];
5758
5781
  }
5759
5782
  }
5760
- for (const key of ['_manual', '_autoObserve', '_context', '_projectResolution']) {
5783
+ // W-mq5s5ttx000j7ab8-a `_manual` and `_autoObserve` are no longer
5784
+ // copied through; the engine reads gate state from the canonical
5785
+ // `contextOnly` field (see isAutoManagedPrRecord above + the boot
5786
+ // migration migratePrGateFlags). `_context`/`_projectResolution` are
5787
+ // unrelated breadcrumbs and stay.
5788
+ for (const key of ['_context', '_projectResolution']) {
5761
5789
  if (normalizedEntry[key] != null) target[key] = normalizedEntry[key];
5762
5790
  }
5763
- if (normalizedEntry._contextOnly != null) {
5764
- const wouldDemoteManagedPr = normalizedEntry._contextOnly === true && targetWasAutoManaged;
5765
- if (!wouldDemoteManagedPr) target._contextOnly = normalizedEntry._contextOnly;
5791
+ // Accept either the canonical `contextOnly` (preferred) or the legacy
5792
+ // `_contextOnly` from callers that haven't been migrated yet
5793
+ // (dashboard.js manual-link path, lifecycle.js oneShot tagging — items
5794
+ // (b)/(c) of the decomposition). Persist as canonical `contextOnly`.
5795
+ const incomingContextOnly = normalizedEntry.contextOnly != null
5796
+ ? normalizedEntry.contextOnly
5797
+ : normalizedEntry._contextOnly;
5798
+ if (incomingContextOnly != null) {
5799
+ const wouldDemoteManagedPr = incomingContextOnly === true && targetWasAutoManaged;
5800
+ if (!wouldDemoteManagedPr) target.contextOnly = incomingContextOnly === true;
5766
5801
  }
5767
5802
  }
5768
5803
  target.prdItems = normalizePrLinkItems(target.prdItems || []);
@@ -5785,6 +5820,105 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
5785
5820
  return { id: canonicalId, prNumber, created, linked, skipped, record };
5786
5821
  }
5787
5822
 
5823
+ // ─── PR Gate Migration (W-mq5s5ttx000j7ab8-a) ───────────────────────────────
5824
+ //
5825
+ // One-shot boot migration that projects the legacy gate signals
5826
+ // (`_contextOnly`, `_autoObserve`, `_manual`) onto the canonical
5827
+ // `contextOnly` field on every `projects/<name>/pull-requests.json` record.
5828
+ // Wired from `engine/cli.js#start()` before the first tick fires so the new
5829
+ // `isAutoManagedPrRecord` (which only reads `contextOnly`) returns the same
5830
+ // verdict as the legacy 6-clause helper for pre-existing data.
5831
+ //
5832
+ // Per-record decision:
5833
+ // 1. contextOnly := (_contextOnly === true)
5834
+ // 2. legacyManaged := prdItems.length > 0 || _autoObserve === true ||
5835
+ // sourcePlan || itemType || (agent is a Minions persona)
5836
+ // "Minions persona" = non-empty kebab-case identifier other than 'human'.
5837
+ // Anything with whitespace (human display names like "yemi shin", which
5838
+ // github poller writes into `agent` from `prData.user.login`) is treated
5839
+ // as a human author, not a managed agent.
5840
+ // 3. If !contextOnly && !legacyManaged, set contextOnly = true and stamp
5841
+ // `_migrationNote: 'auto-set-contextOnly-by-pr-gate-simplification'`.
5842
+ // 4. Persist `contextOnly`; delete `_autoObserve`, `_manual`, `_contextOnly`.
5843
+ //
5844
+ // Idempotent: records that already have `contextOnly` and none of the legacy
5845
+ // keys are skipped (no rewrite, no log line, JSON mtime unchanged).
5846
+ const _PR_GATE_MIGRATION_NOTE = 'auto-set-contextOnly-by-pr-gate-simplification';
5847
+ const _AGENT_PERSONA_RE = /^[a-z0-9_-]+$/;
5848
+
5849
+ function _prRecordHasLegacyGateKey(record) {
5850
+ return Object.prototype.hasOwnProperty.call(record, '_contextOnly')
5851
+ || Object.prototype.hasOwnProperty.call(record, '_autoObserve')
5852
+ || Object.prototype.hasOwnProperty.call(record, '_manual');
5853
+ }
5854
+
5855
+ function _prRecordIsLegacyManaged(record) {
5856
+ const prdItemsCount = Array.isArray(record.prdItems) ? record.prdItems.length : 0;
5857
+ if (prdItemsCount > 0) return true;
5858
+ if (record._autoObserve === true) return true;
5859
+ if (record.sourcePlan) return true;
5860
+ if (record.itemType) return true;
5861
+ if (typeof record.agent === 'string') {
5862
+ const agent = record.agent.trim().toLowerCase();
5863
+ if (agent && agent !== 'human' && _AGENT_PERSONA_RE.test(agent)) return true;
5864
+ }
5865
+ return false;
5866
+ }
5867
+
5868
+ function migratePrGateFlags(projectsRoot) {
5869
+ const summary = { projectsScanned: 0, projectsMigrated: 0, totalRecords: 0, totalMigrated: 0 };
5870
+ if (!projectsRoot || typeof projectsRoot !== 'string') return summary;
5871
+ let entries;
5872
+ try {
5873
+ entries = fs.readdirSync(projectsRoot, { withFileTypes: true });
5874
+ } catch {
5875
+ return summary;
5876
+ }
5877
+ for (const entry of entries) {
5878
+ if (!entry.isDirectory()) continue;
5879
+ const projectName = entry.name;
5880
+ const prPath = path.join(projectsRoot, projectName, 'pull-requests.json');
5881
+ if (!fs.existsSync(prPath)) continue;
5882
+ summary.projectsScanned++;
5883
+
5884
+ let migrated = 0;
5885
+ let stamped = 0;
5886
+ let alreadyMigrated = 0;
5887
+
5888
+ mutatePullRequests(prPath, (prs) => {
5889
+ if (!Array.isArray(prs)) return prs;
5890
+ for (const record of prs) {
5891
+ if (!record || typeof record !== 'object') continue;
5892
+ const hasLegacy = _prRecordHasLegacyGateKey(record);
5893
+ const hasCanonical = Object.prototype.hasOwnProperty.call(record, 'contextOnly');
5894
+ if (hasCanonical && !hasLegacy) { alreadyMigrated++; continue; }
5895
+
5896
+ let contextOnly = (record._contextOnly === true);
5897
+ if (!contextOnly && !_prRecordIsLegacyManaged(record)) {
5898
+ contextOnly = true;
5899
+ record._migrationNote = _PR_GATE_MIGRATION_NOTE;
5900
+ stamped++;
5901
+ }
5902
+ record.contextOnly = contextOnly;
5903
+ delete record._autoObserve;
5904
+ delete record._manual;
5905
+ delete record._contextOnly;
5906
+ migrated++;
5907
+ }
5908
+ return prs;
5909
+ });
5910
+
5911
+ summary.totalRecords += (migrated + alreadyMigrated);
5912
+ summary.totalMigrated += migrated;
5913
+ if (migrated > 0) {
5914
+ summary.projectsMigrated++;
5915
+ // One line per project that actually moved. Keep idempotent runs silent.
5916
+ console.log(`[pr-gate-migration] ${projectName}: migrated ${migrated} records (${stamped} stamped contextOnly, ${alreadyMigrated} already-migrated)`);
5917
+ }
5918
+ }
5919
+ return summary;
5920
+ }
5921
+
5788
5922
  // ─── Cross-Platform Process Kill Helpers ─────────────────────────────────────
5789
5923
 
5790
5924
  function normalizeKillPid(proc) {
@@ -6772,6 +6906,7 @@ module.exports = {
6772
6906
  resolveAgentCli, resolveCcCli, resolveCcUseWorkerPool, resolveAgentModel, resolveCcModel,
6773
6907
  resolveAgentMaxBudget, resolveAgentBareMode,
6774
6908
  applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
6909
+ applyCcWorkerPoolForceOnMigration,
6775
6910
  runtimeConfigWarnings,
6776
6911
  projectWorkSourceWarnings,
6777
6912
  backfillProjectWorkSourceDefaults,
@@ -6841,6 +6976,8 @@ module.exports = {
6841
6976
  mergePrLinkItems, // exported for testing
6842
6977
  isAutoManagedPrRecord,
6843
6978
  upsertPullRequestRecord,
6979
+ isAutoManagedPrRecord, // W-mq5s5ttx000j7ab8-a — exported for engine + watch-plugin gate consolidation
6980
+ migratePrGateFlags, // W-mq5s5ttx000j7ab8-a — boot migration wired from engine/cli.js
6844
6981
  nextWorkItemId,
6845
6982
  getProjectOrg,
6846
6983
  getAdoOrgBase,