@yemi33/minions 0.1.2268 → 0.1.2270

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.
@@ -103,7 +103,11 @@ function renderEngineStatus(engine) {
103
103
  // against a possibly-cached heartbeat — the bug pattern that produced false-
104
104
  // positive STALE banners after control.json was dropped from the mtime tracker.
105
105
  const staleMs = engine?.heartbeatAgeMs || 0;
106
- if (state === 'running' && engine?.heartbeatStale) state = 'stale';
106
+ // Map running→stale and degraded→stale when both heartbeat signals are stale.
107
+ // degraded is set by the dashboard watchdog (issue #423) when the engine PID
108
+ // is alive but the tick loop is frozen; treat it identically to stale so the
109
+ // Restart Engine banner fires in both cases.
110
+ if ((state === 'running' || state === 'degraded') && engine?.heartbeatStale) state = 'stale';
107
111
 
108
112
  // Clear restart grace as soon as the engine reports a fresh heartbeat — the
109
113
  // new engine has caught up, so STALE/restart banners should vanish.
package/dashboard.js CHANGED
@@ -5951,6 +5951,35 @@ function restartEngine() {
5951
5951
  return newPid;
5952
5952
  }
5953
5953
 
5954
+ // ── Frozen-engine detection (issue #423) ─────────────────────────────────────
5955
+ // Called by the 30s watchdog when the engine PID is still alive. If both
5956
+ // control.heartbeat AND control.lastTickAt have aged past their thresholds,
5957
+ // the tick loop is frozen (event-loop blocked). Flip state to 'degraded' so:
5958
+ // • The dashboard UI surfaces a hard warning with a Restart Engine button.
5959
+ // • When the engine eventually unhangs, tickInner reads state !== 'running'
5960
+ // and calls process.exit(0); the PID-dead watchdog then auto-restarts it.
5961
+ // The restarted engine reclaims state:'running' on its next boot.
5962
+ // Returns true if it wrote the degraded state, false otherwise.
5963
+ function _markEngineAsDegradedIfFrozen() {
5964
+ const control = getEngineState();
5965
+ if (control.state !== 'running' || !control.pid) return false;
5966
+
5967
+ const hbAge = control.heartbeat ? Date.now() - control.heartbeat : 0;
5968
+ const tickInterval = Number(CONFIG?.engine?.tickInterval) || shared.ENGINE_DEFAULTS.tickInterval;
5969
+ const tickStaleThresholdMs = Math.max(ENGINE_HEARTBEAT_STALE_MS, 2 * tickInterval);
5970
+ const tickAge = control.lastTickAt ? Date.now() - control.lastTickAt : Infinity;
5971
+
5972
+ const bothStale = !!(control.heartbeat
5973
+ && hbAge > ENGINE_HEARTBEAT_STALE_MS
5974
+ && tickAge > tickStaleThresholdMs);
5975
+ if (!bothStale) return false;
5976
+
5977
+ shared.mutateControl(c => ({ ...c, state: 'degraded' }));
5978
+ console.log(`[watchdog] Engine tick frozen (heartbeat ${Math.round(hbAge / 1000)}s old, last tick ${Math.round(tickAge / 1000)}s old) — marking state as degraded`);
5979
+ try { invalidateStatusCache(); } catch { /* best effort */ }
5980
+ return true;
5981
+ }
5982
+
5954
5983
  // -- Server --
5955
5984
 
5956
5985
  // Mutating HTTP methods that require Origin and Content-Type gating.
@@ -14415,6 +14444,8 @@ module.exports = {
14415
14444
  // staleness verdict it stamps on engine.heartbeatStale is the contract under
14416
14445
  // test. No production caller imports this; it is a test seam.
14417
14446
  _buildStatusFastState,
14447
+ // #423 — exported for unit testing the frozen-engine watchdog logic.
14448
+ _markEngineAsDegradedIfFrozen,
14418
14449
  // W-mq5xg5e9000nec0e — exported for direct unit testing of the slim shape
14419
14450
  // produced by GET /api/work-items. Production callers go through the
14420
14451
  // route's `builder` closure (getWorkItems().map(slimWorkItemForList)).
@@ -14643,6 +14674,8 @@ if (require.main === module) {
14643
14674
  if (!alive) {
14644
14675
  console.log(`[watchdog] Engine PID ${control.pid} is dead — auto-restarting...`);
14645
14676
  restartEngine();
14677
+ } else {
14678
+ _markEngineAsDegradedIfFrozen();
14646
14679
  }
14647
14680
  } catch (e) {
14648
14681
  console.error(`[watchdog] Error: ${e.message}`);
@@ -3,10 +3,26 @@
3
3
  "id": "agent-config-skills-field",
4
4
  "description": "Legacy per-agent descriptive-metadata array `agents.<id>.skills` in config.json, renamed to `agents.<id>.expertise` to remove the name collision with executable runtime/harness skills (SKILL.md). The field is metadata only (capability tags like `architecture`, `bug-fixes`); nothing in the dispatch path reads it for behavior. A read-compat shim honors the old key so operator configs still carrying `skills` (and no `expertise`) keep working.",
5
5
  "code": [
6
- { "file": "engine/playbook.js", "lines": "950", "note": "buildSystemPrompt reads `agent.expertise ?? agent.skills ?? []` for the `Expertise:` identity line" },
7
- { "file": "engine/lifecycle.js", "lines": "4620-4621", "note": "pickReReviewAgentHints reads `agent.expertise` with an `agent.skills` array fallback" },
8
- { "file": "engine/queries.js", "lines": "731", "note": "getAgents normalizes `expertise: a.expertise ?? a.skills ?? []` so the dashboard/settings UI always receives `expertise`" },
9
- { "file": "dashboard.js", "lines": "10708-10716", "note": "settings POST accepts a legacy `updates.skills` key, persists as `config.agents[id].expertise`, and deletes the old `skills` key" }
6
+ {
7
+ "file": "engine/playbook.js",
8
+ "lines": "950",
9
+ "note": "buildSystemPrompt reads `agent.expertise ?? agent.skills ?? []` for the `Expertise:` identity line"
10
+ },
11
+ {
12
+ "file": "engine/lifecycle.js",
13
+ "lines": "4620-4621",
14
+ "note": "pickReReviewAgentHints reads `agent.expertise` with an `agent.skills` array fallback"
15
+ },
16
+ {
17
+ "file": "engine/queries.js",
18
+ "lines": "731",
19
+ "note": "getAgents normalizes `expertise: a.expertise ?? a.skills ?? []` so the dashboard/settings UI always receives `expertise`"
20
+ },
21
+ {
22
+ "file": "dashboard.js",
23
+ "lines": "10708-10716",
24
+ "note": "settings POST accepts a legacy `updates.skills` key, persists as `config.agents[id].expertise`, and deletes the old `skills` key"
25
+ }
10
26
  ],
11
27
  "removalGate": "Telemetry / a config sweep across all known engines must show no persisted `config.agents.<id>.skills` key (only `expertise`) for >=30 consecutive days, confirming every operator config has been re-saved through the dashboard (which drops the legacy key) or hand-migrated.",
12
28
  "targetRemovalDate": "2026-09-17",
@@ -31,7 +47,10 @@
31
47
  {
32
48
  "id": "legacy-done-aliases",
33
49
  "location": "engine/cleanup.js:1165-1166",
34
- "constants": ["LEGACY_DONE_ALIASES", "LEGACY_NEEDS_REVIEW_STATUS"],
50
+ "constants": [
51
+ "LEGACY_DONE_ALIASES",
52
+ "LEGACY_NEEDS_REVIEW_STATUS"
53
+ ],
35
54
  "reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale on-disk values from old engine versions.",
36
55
  "targetRemovalDate": null,
37
56
  "notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :1165-1166; usage at :1168-1183 for work items and :1269-1272 for PRD missing_features) can be deleted. Total cost on disk today: 4 strings."
@@ -40,9 +59,21 @@
40
59
  "id": "config-claude-binary-override",
41
60
  "description": "Legacy `config.claude.binary` runtime-resolution override. Older `minions init` versions persisted a `config.claude.binary` field that pointed the Claude runtime at a specific binary path. The runtime adapter still honors this override on every Claude spawn; the engine emits a `deprecated-config-claude` warning at config-load time but does NOT delete the override, so the override branch in claude.js is load-bearing for any install that still carries a non-default value.",
42
61
  "code": [
43
- { "file": "engine/runtimes/claude.js", "lines": "82-86", "note": "resolveBinary() respects config.claude.binary on every Claude spawn (probes npm package dir or direct binary path)" },
44
- { "file": "engine/shared.js", "lines": "2482-2496", "note": "warnings.push({ id: 'deprecated-config-claude' }) — surface-only; never deletes the override" },
45
- { "file": "engine/shared.js", "lines": "3120-3124", "note": "DEFAULT_CLAUDE.binary baseline that the warning + prune logic compares against" }
62
+ {
63
+ "file": "engine/runtimes/claude.js",
64
+ "lines": "82-86",
65
+ "note": "resolveBinary() respects config.claude.binary on every Claude spawn (probes npm package dir or direct binary path)"
66
+ },
67
+ {
68
+ "file": "engine/shared.js",
69
+ "lines": "2482-2496",
70
+ "note": "warnings.push({ id: 'deprecated-config-claude' }) — surface-only; never deletes the override"
71
+ },
72
+ {
73
+ "file": "engine/shared.js",
74
+ "lines": "3120-3124",
75
+ "note": "DEFAULT_CLAUDE.binary baseline that the warning + prune logic compares against"
76
+ }
46
77
  ],
47
78
  "removalGate": "Telemetry: the `deprecated-config-claude` warning emitted at engine/shared.js:2492-2495 must report zero hits across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must show no `config.claude.binary` value that diverges from DEFAULT_CLAUDE.binary. Only then is the override branch in resolveBinary() (engine/runtimes/claude.js:82-86) removable, along with the `_deprecatedConfigClaudeFields` membership for `binary` and the warning emitter at engine/shared.js:2482-2496.",
48
79
  "targetRemovalDate": null,
@@ -52,13 +83,41 @@
52
83
  "id": "legacy-cc-model-migration",
53
84
  "description": "applyLegacyCcModelMigration: in-memory shim that promotes legacy `engine.ccModel` to `engine.defaultModel` when defaultModel is unset, so single-model installs keep working after the runtime fleet refactor (P-3b8e5f1d). No on-disk rewrite — the persisted config.json continues to carry the legacy `ccModel` field. Called unconditionally on every engine boot from cli.start().",
54
85
  "code": [
55
- { "file": "engine/shared.js", "lines": "2407", "note": "applyLegacyCcModelMigration definition (function signature + once-per-process flag via _resetLegacyCcModelMigrationFlag)" },
56
- { "file": "engine/cli.js", "lines": "477", "note": "Boot call site inside start(); wrapped in try/catch so a migration failure cannot block startup" },
57
- { "file": "CLAUDE.md", "lines": "316", "note": "Architectural documentation calling out the in-memory promotion contract" },
58
- { "file": "docs/slim-ux/concepts.md", "lines": "671", "note": "Surface-level concepts doc cross-reference" },
59
- { "file": "test/unit.test.js", "lines": "19801", "note": "Source-inspection test pinning the CLAUDE.md description against the function name" },
60
- { "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "209-254", "note": "Behavioural unit tests (promotion, no-op when defaultModel set, no-op when ccModel unset, empty-string handling, once-only logging, null-safety)" },
61
- { "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "500-505", "note": "Source-inspection test pinning the cli.js boot call site" }
86
+ {
87
+ "file": "engine/shared.js",
88
+ "lines": "2407",
89
+ "note": "applyLegacyCcModelMigration definition (function signature + once-per-process flag via _resetLegacyCcModelMigrationFlag)"
90
+ },
91
+ {
92
+ "file": "engine/cli.js",
93
+ "lines": "477",
94
+ "note": "Boot call site inside start(); wrapped in try/catch so a migration failure cannot block startup"
95
+ },
96
+ {
97
+ "file": "CLAUDE.md",
98
+ "lines": "316",
99
+ "note": "Architectural documentation calling out the in-memory promotion contract"
100
+ },
101
+ {
102
+ "file": "docs/slim-ux/concepts.md",
103
+ "lines": "671",
104
+ "note": "Surface-level concepts doc cross-reference"
105
+ },
106
+ {
107
+ "file": "test/unit.test.js",
108
+ "lines": "19801",
109
+ "note": "Source-inspection test pinning the CLAUDE.md description against the function name"
110
+ },
111
+ {
112
+ "file": "test/unit/runtime-fleet-helpers.test.js",
113
+ "lines": "209-254",
114
+ "note": "Behavioural unit tests (promotion, no-op when defaultModel set, no-op when ccModel unset, empty-string handling, once-only logging, null-safety)"
115
+ },
116
+ {
117
+ "file": "test/unit/runtime-fleet-helpers.test.js",
118
+ "lines": "500-505",
119
+ "note": "Source-inspection test pinning the cli.js boot call site"
120
+ }
62
121
  ],
63
122
  "removalGate": "Telemetry: the once-per-boot deprecation log line emitted by applyLegacyCcModelMigration (via the injected logger at engine/shared.js:2407) must show zero promotion events across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must confirm no `engine.ccModel` field remains. Once both conditions hold, removal deletes the function + _resetLegacyCcModelMigrationFlag export at engine/shared.js:4977, the boot call at engine/cli.js:477, the CLAUDE.md:316 paragraph and docs/slim-ux/concepts.md:671 reference, and the tests at runtime-fleet-helpers.test.js:209-254 + :500-505 + unit.test.js:19801.",
64
123
  "targetRemovalDate": null,
@@ -68,14 +127,39 @@
68
127
  "id": "sql-state-json-mirrors",
69
128
  "description": "Phase X.5 follow-up to the SQL state migration (commits 62bd6a2c..1111cf54, phases 0–7). Every engine state file that previously used mutateJsonFileLocked now routes through a SQL store, but each store still writes a JSON dual-write mirror after every mutation because a handful of direct-readers (a few unit tests + a couple of inline safeJson calls) have not been migrated to the SQL read path. Once those readers are confirmed routed through the SQL store (or rewritten to use the store's read helper), the mirror writers can be deleted and the JSON files retired.",
70
129
  "code": [
71
- { "file": "engine/dispatch-store.js", "note": "_mirrorJsonFromSql + _readDispatchJsonFallback — used when SQL is empty AND JSON has content (test seeding + first-time hydrate)" },
72
- { "file": "engine/work-items-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope) — same fallback contract as dispatch-store" },
73
- { "file": "engine/pull-requests-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope)" },
74
- { "file": "engine/logs-store.js", "note": "engine/log.json mirror written by shared._flushLogBuffer's byJsonPath loop — Phase 4.5 will retire" },
75
- { "file": "engine/metrics-store.js", "note": "_mirrorJsonFromSql + _readJsonObjectFallback" },
76
- { "file": "engine/watches-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback" },
77
- { "file": "engine/small-state-store.js", "note": "_mirrorScheduleRunsJson, _mirrorPipelineRunsJson, _mirrorManagedProcessesJson, _mirrorWorktreePoolJson + each store's _readJson fallback path" },
78
- { "file": "CLAUDE.md", "lines": "47-66, 240-265", "note": "State Files + Concurrency sections still describe JSON files as the source of truth; they describe a layered SQLite-then-mirror reality in places but the headline contract still reads as JSON-primary. Rewrite these sections to make SQL-as-source-of-truth the headline and the JSON mirrors a transitional compatibility detail." }
130
+ {
131
+ "file": "engine/dispatch-store.js",
132
+ "note": "_mirrorJsonFromSql + _readDispatchJsonFallback — used when SQL is empty AND JSON has content (test seeding + first-time hydrate)"
133
+ },
134
+ {
135
+ "file": "engine/work-items-store.js",
136
+ "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope) same fallback contract as dispatch-store"
137
+ },
138
+ {
139
+ "file": "engine/pull-requests-store.js",
140
+ "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope)"
141
+ },
142
+ {
143
+ "file": "engine/logs-store.js",
144
+ "note": "engine/log.json mirror written by shared._flushLogBuffer's byJsonPath loop — Phase 4.5 will retire"
145
+ },
146
+ {
147
+ "file": "engine/metrics-store.js",
148
+ "note": "_mirrorJsonFromSql + _readJsonObjectFallback"
149
+ },
150
+ {
151
+ "file": "engine/watches-store.js",
152
+ "note": "_mirrorJsonFromSql + _readJsonArrayFallback"
153
+ },
154
+ {
155
+ "file": "engine/small-state-store.js",
156
+ "note": "_mirrorScheduleRunsJson, _mirrorPipelineRunsJson, _mirrorManagedProcessesJson, _mirrorWorktreePoolJson + each store's _readJson fallback path"
157
+ },
158
+ {
159
+ "file": "CLAUDE.md",
160
+ "lines": "47-66, 240-265",
161
+ "note": "State Files + Concurrency sections still describe JSON files as the source of truth; they describe a layered SQLite-then-mirror reality in places but the headline contract still reads as JSON-primary. Rewrite these sections to make SQL-as-source-of-truth the headline and the JSON mirrors a transitional compatibility detail."
162
+ }
79
163
  ],
80
164
  "removalGate": "All direct-readers of the mirror JSON files must be confirmed routed through their respective SQL store's read helper. Specifically: (a) grep the codebase for `safeJson`, `safeJsonArr`, `safeJsonObj`, `readFileSync(...work-items.json|pull-requests.json|metrics.json|watches.json|schedule-runs.json|pipeline-runs.json|managed-processes.json|worktree-pool.json|log.json|dispatch.json...)` and confirm every hit is either (i) a test fixture that can move to the SQL helper, or (ii) intentionally documented as bypassing SQL. (b) Run the full test suite with each store's _mirrorJsonFromSql temporarily neutered (returning early before safeWrite) and confirm 0 failures — that proves no production code path depends on the mirror. Once both conditions hold, removal deletes each store's _mirrorJsonFromSql call site in shared.js (mutateWorkItems/mutatePullRequests/etc.), the corresponding _readJsonArrayFallback paths, and the JSON file gitignore entries. CLAUDE.md update can ship independently as soon as someone has bandwidth.",
81
165
  "targetRemovalDate": null,
@@ -85,15 +169,51 @@
85
169
  "id": "prune-default-claude-config",
86
170
  "description": "pruneDefaultClaudeConfig: active sanitizer that strips generated `config.claude.{binary,outputFormat,allowedTools,permissionMode}` defaults from persisted config.json so the `deprecated-config-claude` warning stops tripping on stale defaults left by older `minions init` versions. Sub-cluster of `config-claude-binary-override` — the prune deliberately preserves non-default user overrides (binary/allowedTools), which is what keeps the override branch in engine/runtimes/claude.js load-bearing.",
87
171
  "code": [
88
- { "file": "engine/shared.js", "lines": "3126", "note": "pruneDefaultClaudeConfig definition: preserves non-default binary/allowedTools, always strips permissionMode + outputFormat" },
89
- { "file": "engine/shared.js", "lines": "5673", "note": "Module export entry" },
90
- { "file": "dashboard.js", "lines": "202", "note": "Called when loading config for the dashboard UI" },
91
- { "file": "dashboard.js", "lines": "9116", "note": "Called during first config save handler" },
92
- { "file": "dashboard.js", "lines": "9331", "note": "Called during second config save path" },
93
- { "file": "dashboard.js", "lines": "9450", "note": "Called during third config save path" },
94
- { "file": "minions.js", "lines": "385", "note": "Called during CLI init/update flow" },
95
- { "file": "test/unit.test.js", "lines": "2260-2303", "note": "Behavioural unit tests (default strip, override preservation, outputFormat unconditional strip) + dashboard call-site source pin" },
96
- { "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "546", "note": "Source-inspection test pinning the dashboard handler call site" }
172
+ {
173
+ "file": "engine/shared.js",
174
+ "lines": "3126",
175
+ "note": "pruneDefaultClaudeConfig definition: preserves non-default binary/allowedTools, always strips permissionMode + outputFormat"
176
+ },
177
+ {
178
+ "file": "engine/shared.js",
179
+ "lines": "5673",
180
+ "note": "Module export entry"
181
+ },
182
+ {
183
+ "file": "dashboard.js",
184
+ "lines": "202",
185
+ "note": "Called when loading config for the dashboard UI"
186
+ },
187
+ {
188
+ "file": "dashboard.js",
189
+ "lines": "9116",
190
+ "note": "Called during first config save handler"
191
+ },
192
+ {
193
+ "file": "dashboard.js",
194
+ "lines": "9331",
195
+ "note": "Called during second config save path"
196
+ },
197
+ {
198
+ "file": "dashboard.js",
199
+ "lines": "9450",
200
+ "note": "Called during third config save path"
201
+ },
202
+ {
203
+ "file": "minions.js",
204
+ "lines": "385",
205
+ "note": "Called during CLI init/update flow"
206
+ },
207
+ {
208
+ "file": "test/unit.test.js",
209
+ "lines": "2260-2303",
210
+ "note": "Behavioural unit tests (default strip, override preservation, outputFormat unconditional strip) + dashboard call-site source pin"
211
+ },
212
+ {
213
+ "file": "test/unit/runtime-fleet-helpers.test.js",
214
+ "lines": "546",
215
+ "note": "Source-inspection test pinning the dashboard handler call site"
216
+ }
97
217
  ],
98
218
  "removalGate": "Telemetry: pruneDefaultClaudeConfig must return false (no mutation) for every call across all known engines for >=30 consecutive days (add an `_engine.pruneDefaultClaudeConfigStrips` counter if needed to observe this), AND the parent `config-claude-binary-override` entry must have already cleared its own gate. The dependency is strict: removing the prune while users still rely on the override branch would surface the `deprecated-config-claude` warning on every stale generated default. Once both conditions hold, removal is the function definition (engine/shared.js:3126), the export at :5673, all 5 call sites (dashboard.js:202, :9116, :9331, :9450; minions.js:385), and the tests at unit.test.js:2260-2303 + runtime-fleet-helpers.test.js:546.",
99
219
  "targetRemovalDate": null,
@@ -104,7 +224,10 @@
104
224
  "description": "Arg-less form of isAdoThrottled() in engine/ado.js. Introduced by W-mq03l6zh0006f0a1-b as a back-compat shim during the per-org ADO throttle isolation rollout: pre-rollout, isAdoThrottled() collapsed the single process-global tracker to one boolean; post-rollout, the canonical form is isAdoThrottled(orgBase) against the per-org Map. The arg-less call site is preserved transiently so engine code (and any in-process callers) that haven't yet been threaded through with a per-org `orgBase` keep returning the safe global-OR (true if ANY org is currently throttled) — preventing a regression where new poll work bypasses a still-warm throttle backoff on an unrelated noisy org.",
105
225
  "deprecated": "2026-06-04",
106
226
  "code": [
107
- { "file": "engine/ado.js", "note": "isAdoThrottled() arg-less branch and the global-OR fold over the per-org Map. Single call site to migrate: shared.getAdoOrgBase(project) is already in scope at every consumer." }
227
+ {
228
+ "file": "engine/ado.js",
229
+ "note": "isAdoThrottled() arg-less branch and the global-OR fold over the per-org Map. Single call site to migrate: shared.getAdoOrgBase(project) is already in scope at every consumer."
230
+ }
108
231
  ],
109
232
  "removalGate": "Two conditions must hold simultaneously: (a) grep `engine/ado.js` for `isAdoThrottled\\s*\\(\\s*\\)` and confirm zero arg-less call sites remain across the engine — every caller passes a concrete `orgBase` resolved via `shared.getAdoOrgBase(project)`; (b) `GET /api/diagnostics/ado-throttle` on a live engine has been observed for >=2 consecutive weeks reporting per-org keys (proves the per-org Map is populated under load and the global-OR isn't masking a regression). Once both hold, removal deletes the arg-less branch in isAdoThrottled and the global-OR fold; callers that still pass no argument become an immediate, surfaced bug rather than a silent over-throttle.",
110
233
  "targetRemovalDate": "2026-08-03",
@@ -114,7 +237,10 @@
114
237
  "id": "pr-link-autoObserve-body-param",
115
238
  "description": "Legacy `autoObserve` body parameter on `POST /api/pull-requests/link`. Replaced by canonical `contextOnly` body param (inverse boolean: `autoObserve: false` ⇔ `contextOnly: true`). This is a READ-BRIDGE on the input side only — the handler reads `body.contextOnly` first and falls back to `!body.autoObserve` for callers not yet migrated. No underscore alias is written onto the record (the record-field aliases already shipped out — see the three paired entries); the only thing kept alive is the input fallback.",
116
239
  "code": [
117
- { "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)." }
240
+ {
241
+ "file": "dashboard.js",
242
+ "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)."
243
+ }
118
244
  ],
119
245
  "deprecated": "2026-06-08",
120
246
  "targetRemovalDate": "2026-06-25",
@@ -124,9 +250,18 @@
124
250
  "id": "worktreemode-field-rename",
125
251
  "description": "Legacy `project.worktreeMode` config field (enum 'isolated'|'live'). Consolidated by W-mqiaw974 (issue #241) into a single `project.checkoutMode` field (enum 'worktree'|'live'): the old 'isolated' value became the implicit default 'worktree', and the overlapping/never-shipped 'shared' value was removed. The write side is migrated — buildProjectEntry, dashboard settings POST, and projects.addProject all write `checkoutMode`, and the dashboard settings/merge paths delete any stale `worktreeMode` key on save. What survives is a READ-BRIDGE ONLY: `shared.resolveCheckoutMode(project)` (and the `isLiveCheckoutProject` predicate built on it) reads canonical `checkoutMode` first, then falls back to the legacy `worktreeMode` field ('isolated'→'worktree', 'live'→'live') so an un-migrated config.json keeps dispatching live-checkout projects correctly. `validateCheckoutMode` also silently coerces a submitted legacy 'isolated' value to 'worktree'.",
126
252
  "code": [
127
- { "file": "engine/shared.js", "note": "resolveCheckoutMode reads project.worktreeMode as the fallback when checkoutMode is absent; validateCheckoutMode coerces 'isolated'→'worktree'. The only surviving reads of the legacy field are these two back-compat bridges." },
128
- { "file": "engine/projects.js", "note": "addProject threads options.checkoutMode ?? options.worktreeMode into buildProjectEntry (accepts the legacy options key)." },
129
- { "file": "dashboard.js", "note": "handleProjectsAdd reads body.checkoutMode ?? body.worktreeMode; handleSettingsUpdate + mergeSettingsConfigUpdate accept the legacy update.worktreeMode key and delete proj.worktreeMode on every save (active migration)." }
253
+ {
254
+ "file": "engine/shared.js",
255
+ "note": "resolveCheckoutMode reads project.worktreeMode as the fallback when checkoutMode is absent; validateCheckoutMode coerces 'isolated'→'worktree'. The only surviving reads of the legacy field are these two back-compat bridges."
256
+ },
257
+ {
258
+ "file": "engine/projects.js",
259
+ "note": "addProject threads options.checkoutMode ?? options.worktreeMode into buildProjectEntry (accepts the legacy options key)."
260
+ },
261
+ {
262
+ "file": "dashboard.js",
263
+ "note": "handleProjectsAdd reads body.checkoutMode ?? body.worktreeMode; handleSettingsUpdate + mergeSettingsConfigUpdate accept the legacy update.worktreeMode key and delete proj.worktreeMode on every save (active migration)."
264
+ }
130
265
  ],
131
266
  "deprecated": "2026-06-17",
132
267
  "targetRemovalDate": "2026-09-17",
@@ -136,20 +271,13 @@
136
271
  "id": "pr-observe-observe-body-param",
137
272
  "description": "Legacy `observe` body parameter on `POST /api/pull-requests/observe`. The W-mq5s5ttx000j7ab8 endpoint sub-WI introduces canonical `contextOnly` as the inverse (`observe: false` ⇔ `contextOnly: true`) and keeps `observe` accepted for backward compat. Registering the deprecation here so the alias has a documented removal path; the WI explicitly notes this entry is the implementer's call (it is kept for backward compat and may live longer than the underscore-prefixed record fields).",
138
273
  "code": [
139
- { "file": "dashboard.js", "note": "POST /api/pull-requests/observe handler reads `body.contextOnly` first, then falls back to `!body.observe` for backwards compat." }
274
+ {
275
+ "file": "dashboard.js",
276
+ "note": "POST /api/pull-requests/observe handler reads `body.contextOnly` first, then falls back to `!body.observe` for backwards compat."
277
+ }
140
278
  ],
141
279
  "deprecated": "2026-06-08",
142
280
  "targetRemovalDate": null,
143
281
  "notes": "targetRemovalDate intentionally null — unlike the record-field aliases (`_contextOnly`, `_autoObserve`, `_manual`) which carry a 7-day clock, the `observe` body param is documented as a longer-lived back-compat alias. Set targetRemovalDate to a concrete future date once the dashboard UI + any client scripts are confirmed to POST `contextOnly` exclusively. Removal scope when the date is set: drop the `body.observe` fallback in dashboard.js, drop `observe` from the route registry params, and update any client still POSTing `observe`."
144
- },
145
- {
146
- "id": "discover-review-skills-shim",
147
- "description": "engine/discover-review-skills.js is a 25-line re-export shim for engine/discover-project-skills.js. Zero production callers; only test files (discover-review-skills.test.js, discover-project-skills.test.js:706) reference it.",
148
- "addedDate": "2026-06-24",
149
- "targetRemovalDate": "2026-09-01",
150
- "removalGate": "No external callers import discover-review-skills.js; test files updated to import from discover-project-skills.js directly.",
151
- "removalScope": "Delete engine/discover-review-skills.js, delete test/unit/discover-review-skills.test.js, update test/unit/discover-project-skills.test.js:706 to import from discover-project-skills.js.",
152
- "autoRemoveSafe": false,
153
- "notes": "gate: zero test references to discover-review-skills; must update tests to point to discover-project-skills.js before file can be deleted"
154
282
  }
155
- ]
283
+ ]
@@ -7,8 +7,8 @@
7
7
  * plan / etc.) can reliably steer agents toward the right purpose-built
8
8
  * tooling instead of reinventing flows from first principles.
9
9
  *
10
- * This module is the generalized successor to engine/discover-review-skills.js
11
- * (PR #82, W-mq16xtdx001a347e). PR 82 hard-scoped discovery to review-flavored
10
+ * This module is the generalized successor to the PR #82 review-only helper
11
+ * (W-mq16xtdx001a347e). PR 82 hard-scoped discovery to review-flavored
12
12
  * skills via a single KEYWORD_RE; this module discovers EVERY project skill /
13
13
  * command / documented slash-command, classifies each one into a small closed
14
14
  * intent vocabulary, and lets callers filter to the intents they care about.
@@ -440,9 +440,8 @@ function renderProjectSkillsBlock(entries) {
440
440
  return lines.join('\n');
441
441
  }
442
442
 
443
- // ── Backward-compatibility shims for PR #82 callers (W-mq16xtdx001a347e) ──
444
- // These keep the discover-review-skills.js import surface working without
445
- // requiring every consumer to migrate in lockstep.
443
+ // ── Backward-compatibility aliases for PR #82 callers (W-mq16xtdx001a347e) ──
444
+ // These provide the original PR-82 function names as direct exports.
446
445
 
447
446
  function discoverReviewSkills(args) {
448
447
  // Old API: returns review-flavored entries only, without the new `intents`
@@ -479,7 +478,7 @@ module.exports = {
479
478
  renderProjectSkillsBlock,
480
479
  classifyIntents,
481
480
  INTENT_VOCABULARY,
482
- // Backward-compat (PR #82) — see discover-review-skills.js shim.
481
+ // Backward-compat aliases (PR #82, W-mq16xtdx001a347e).
483
482
  discoverReviewSkills,
484
483
  renderReviewSkillsBlock,
485
484
  // exported for tests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2268",
3
+ "version": "0.1.2270",
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"
@@ -1,25 +0,0 @@
1
- /**
2
- * engine/discover-review-skills.js — backward-compat shim for PR #82
3
- * (W-mq16xtdx001a347e).
4
- *
5
- * PR #82 introduced `discover-review-skills.js` as a review-only helper. The
6
- * follow-on W-mq1cczi90006b21f generalized it into
7
- * `engine/discover-project-skills.js`, which discovers every project skill
8
- * and classifies them into an intent vocabulary.
9
- *
10
- * This file re-exports the two public PR-82 names so existing imports keep
11
- * resolving without forcing every consumer to migrate in lockstep. The shim
12
- * filters to the `review` intent and renders the original "## Project review
13
- * skills" header copy. New code should import directly from
14
- * engine/discover-project-skills.js and call `filterByIntents` itself.
15
- */
16
-
17
- const {
18
- discoverReviewSkills,
19
- renderReviewSkillsBlock,
20
- } = require('./discover-project-skills');
21
-
22
- module.exports = {
23
- discoverReviewSkills,
24
- renderReviewSkillsBlock,
25
- };