@yemi33/minions 0.1.1962 → 0.1.1964

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.
@@ -570,6 +570,7 @@
570
570
  .dispatch-type.meeting { background: rgba(88,166,255,0.2); color: var(--blue); }
571
571
  .dispatch-type.decompose { background: rgba(188,140,255,0.2); color: var(--purple); }
572
572
  .dispatch-type.manual { background: rgba(139,148,158,0.15); color: var(--muted); }
573
+ .dispatch-type.docs { background: rgba(56,189,248,0.15); color: #38bdf8; }
573
574
  .dispatch-agent { font-weight: 600; color: var(--text); }
574
575
  .dispatch-task { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
575
576
  .dispatch-time { font-size: var(--text-sm); color: var(--border); }
package/docs/README.md CHANGED
@@ -14,12 +14,18 @@ Hands-on stories and distribution guides for people running or evaluating Minion
14
14
  Architecture, design proposals, and lifecycle references for people working on the engine, dashboard, or playbooks.
15
15
 
16
16
  - [command-center.md](command-center.md) — Command Center (CC) chat panel: persistent Sonnet sessions, `--resume` semantics, system-prompt invalidation, and per-tab session storage.
17
+ - [completion-reports.md](completion-reports.md) — Canonical schema for the per-dispatch completion JSON every agent writes (status, verdict, retryability, nonce trust boundary, artifacts).
17
18
  - [copilot-cli-schema.md](copilot-cli-schema.md) — Behavior and schema reference for the GitHub Copilot CLI adapter (capability flags, stdin vs `-p`, model discovery, effort levels).
18
19
  - [design-state-storage.md](design-state-storage.md) — Design proposal evaluating five database options for replacing Minions' file-based JSON state; recommends `node:sqlite` as the medium-term target.
20
+ - [kb-sweep.md](kb-sweep.md) — Knowledge-base consolidation sweep (hash dedup → LLM batch dedup/reclassify → per-entry compress) and the detached runner that keeps it alive across `minions restart`.
19
21
  - [plan-lifecycle.md](plan-lifecycle.md) — Full plan pipeline from `/plan` through PRD materialization, dispatch with dependency gating, verify task, and human archive.
20
22
  - [pr-review-fix-loop.md](pr-review-fix-loop.md) — How the engine moves a PR from creation through review, fix dispatch, and re-review, including stale-status guards.
21
23
  - [rfc-completion-json.md](rfc-completion-json.md) — RFC for replacing stdout regex-scraping with a structured `completion.json` control-plane protocol.
24
+ - [runtime-adapters.md](runtime-adapters.md) — Adapter contract (`engine/runtimes/<name>.js`), capability flags, runtime/model resolution helpers, and the "no `runtime.name === …` branching" rule.
22
25
  - [self-improvement.md](self-improvement.md) — The six self-improvement mechanisms (learnings inbox, per-agent history, review feedback, quality metrics, etc.) that form Minions' continuous feedback loop.
26
+ - [skills.md](skills.md) — `.claude/skills/<name>/SKILL.md` format, `scope: minions` vs `scope: project`, auto-extraction from agent output, and per-runtime skill roots.
27
+ - [team-memory.md](team-memory.md) — How `pinned.md`, `notes.md`, `knowledge/`, and per-agent memory files in `knowledge/agents/` flow into every agent prompt.
28
+ - [watches.md](watches.md) — Persistent watch jobs: registry-driven target types, condition catalog, follow-up action registry, and the user-extensible `watches.d/` plugin folder.
23
29
 
24
30
  ## Operations
25
31
 
@@ -28,6 +34,7 @@ Operational runbooks for engine operators and fleet maintainers.
28
34
  - [auto-discovery.md](auto-discovery.md) — Auto-discovery and execution pipeline: the per-tick orchestration loop and the four work-discovery sources.
29
35
  - [engine-restart.md](engine-restart.md) — How agents survive an engine restart: state persistence, the 20-minute startup grace period, and orphan reattachment via PID files and `live-output.log`.
30
36
  - [human-vs-automated.md](human-vs-automated.md) — Quick reference table of which features humans start, run, decide, and recover, and the two human approval gates.
37
+ - [onboarding.md](onboarding.md) — First-run setup: installing the CLI, linking a project, the doctor preflight, and the first dispatch.
31
38
 
32
39
  ---
33
40
 
@@ -1,6 +1,6 @@
1
1
  # Auto-Discovery & Execution Pipeline
2
2
 
3
- > Last verified: 2026-05-12 against `engine.js` `tickInner()` (lines 4439-4750).
3
+ > Last verified: 2026-05-16 against `engine.js` `tickInner()` (lines 5381-5717).
4
4
 
5
5
  How the minions engine finds work and dispatches agents automatically.
6
6
 
@@ -16,14 +16,16 @@ tick()
16
16
  1c. meetingTimeouts() Advance round-based meetings whose timer fired
17
17
  2. consolidateInbox() Merge learnings into notes.md (Haiku-powered)
18
18
  2.5 runCleanup() Periodic cleanup (every 10 ticks ≈ 10min)
19
+ 2.52 sweepKeepProcesses() keep_processes TTL/dead-PID sweep (every 30 ticks)
19
20
  2.55 checkWatches() Persistent watch jobs (every 3 tick-equivalents)
20
21
  2.6 pollPrStatus() Poll ADO + GitHub for build, review, merge status (wall-clock cadence from prPollStatusEvery × tickInterval, default ≈ 12min)
21
22
  processPendingRebases() Run any rebase work queued from the previous tick
22
23
  syncPrdFromPrs() Backfill PRD item status from active PRs
23
24
  checkPlanCompletion() Mark plans completed when all features done/in-pr
24
- 2.7 pollPrHumanComments() Poll PR threads for human @minions comments (wall-clock cadence from prPollCommentsEvery × tickInterval, default ≈ 12min)
25
+ 2.7 pollPrHumanComments() Poll PR threads for human comments (wall-clock cadence from prPollCommentsEvery × tickInterval, default ≈ 12min)
25
26
  reconcilePrs() (ADO+GH) Reconciliation sweep (runs regardless of poll flags)
26
27
  2.9 stallRecovery() Auto-retry failed items blocking pending deps (every 20 ticks)
28
+ 3a. pruneStalePrDispatches() Clear pending PR dispatches whose underlying PRs no longer warrant action
27
29
  3. discoverWork() Scan ALL linked projects for new tasks
28
30
  4. updateSnapshot() Write identity/now.md
29
31
  5. dispatch Spawn agents for pending items (up to maxConcurrent)
@@ -195,6 +197,7 @@ routing.md table (see the file for the authoritative list):
195
197
  verify → dallas (fallback: ralph)
196
198
  decompose → ripley (fallback: rebecca)
197
199
  meeting → ripley (fallback: lambert)
200
+ docs → lambert (fallback: _any_)
198
201
  ```
199
202
 
200
203
  Resolution order:
@@ -1,6 +1,6 @@
1
1
  # Engine Restart & Agent Survival
2
2
 
3
- > Last verified: 2026-05-12 against `engine.js` (`engineRestartGraceUntil`, line 161) and `engine/shared.js` `ENGINE_DEFAULTS` (`restartGracePeriod: 1200000`, `heartbeatTimeout: 300000`, `agentTimeout: 18000000`).
3
+ > Last verified: 2026-05-16 against `engine.js` (`engineRestartGraceUntil`, line 168) and `engine/shared.js` `ENGINE_DEFAULTS` (`restartGracePeriod: 1200000`, `heartbeatTimeout: 300000`, `agentTimeout: 18000000`).
4
4
 
5
5
  ## The Problem
6
6
 
package/docs/watches.md CHANGED
@@ -19,40 +19,43 @@ A watch is a small JSON record persisted to `engine/watches.json`. It binds:
19
19
  | `action` | Optional follow-up action (see "Follow-Up Actions" below) |
20
20
  | `status` | `WATCH_STATUS.ACTIVE` \| `PAUSED` \| `TRIGGERED` \| `EXPIRED` |
21
21
 
22
- `createWatch()` allocates a `watch-<uid>` id, defaults the fields above, and persists atomically via `mutateJsonFileLocked` *(source: `engine/watches.js:103-145`)*.
22
+ `createWatch()` allocates a `watch-<uid>` id, defaults the fields above, and persists atomically via `mutateJsonFileLocked` *(source: `engine/watches.js:184`)*.
23
23
 
24
24
  ## Lifecycle (`WATCH_STATUS`)
25
25
 
26
- Defined in `engine/shared.js:1557`:
26
+ Defined in `engine/shared.js:1875`:
27
27
 
28
28
  | Status | Meaning |
29
29
  |-------------|-------------------------------------------------------------------------|
30
30
  | `active` | Eligible for evaluation each tick |
31
31
  | `paused` | Skipped by `checkWatches`; persists indefinitely until resumed/deleted |
32
32
  | `triggered` | Reserved status (set on demand by callers; not auto-applied) |
33
- | `expired` | Auto-set when `stopAfter` is reached, or on first trigger for absolute conditions when `stopAfter === 0`. The watch is left on disk for audit but no longer evaluated *(source: `engine/watches.js:305-310`)* |
33
+ | `expired` | Auto-set when `stopAfter` is reached, or on first trigger for absolute conditions when `stopAfter === 0`. The watch is left on disk for audit but no longer evaluated *(source: `engine/watches.js:507-508`)* |
34
34
 
35
- Pause/resume flips the `status` field via `POST /api/watches/update` *(source: `engine/watches.js:153-178`, `dashboard.js:6400-6412`)*.
35
+ Pause/resume flips the `status` field via `POST /api/watches/update` *(source: `engine/watches.js:153-178`, `dashboard.js` `handleWatchesUpdate`)*.
36
36
 
37
37
  ## Conditions (`WATCH_CONDITION`)
38
38
 
39
- Defined in `engine/shared.js:1573-1592`. Conditions split into two families:
39
+ Defined in `engine/shared.js:1891-1929`. Conditions split into two families:
40
40
 
41
41
  ### Absolute conditions (`WATCH_ABSOLUTE_CONDITIONS`)
42
- *(source: `engine/shared.js:1595-1599`)*
42
+ *(source: `engine/shared.js:1938-1954`)*
43
43
 
44
- `merged`, `build-fail`, `build-pass`, `completed`, `failed`, `concluded`, `approved`, `rejected`.
44
+ `merged`, `build-fail`, `build-pass`, `completed`, `failed`, `concluded`, `approved`, `rejected`, `ready-for-merge`, `retry-limit-reached`, `all-items-done`, `item-failed-n-times`.
45
45
 
46
- When `stopAfter === 0`, these are **fire-once** — the engine flips the watch to `expired` after the first trigger so a permanently-merged PR doesn't keep notifying *(source: `engine/watches.js:305-310`)*.
46
+ When `stopAfter === 0`, these are **fire-once** — the engine flips the watch to `expired` after the first trigger so a permanently-merged PR (or a permanently-true compound state assertion like `ready-for-merge`) doesn't keep notifying *(source: `engine/watches.js:507-508`)*.
47
47
 
48
48
  ### Change-based conditions
49
- `status-change`, `any`, `new-comments`, `vote-change`, `stage-complete`, `ran`, `enabled`, `disabled`, `activity-change`.
49
+ `status-change`, `any`, `new-comments`, `vote-change`, `stage-complete`, `ran`, `enabled`, `disabled`, `activity-change`, `head-commit-change`, `mergeable-flipped`, `behind-master`, `draft-flipped`, `dependency-met`, `stage-advanced`.
50
50
 
51
- These compare the live entity against the watch's `_lastState` snapshot and run forever when `stopAfter === 0`. Baseline `_lastState` is captured on the first check so the very next change triggers the watch *(source: `engine/watches.js:262-266`)*.
51
+ ### Tick-counted conditions
52
+ `stalled`, `stuck-in-stage` — require N consecutive unchanged captures (default `WATCH_STALLED_DEFAULT_TICKS = 12`, `WATCH_STUCK_STAGE_DEFAULT_TICKS = 12`, both in `engine/shared.js:1934-1935`). Counters (`_unchangedTicks`, `_stuckStageTicks`) are recomputed inside `_captureState` by comparing the fresh snapshot against `prevState`.
53
+
54
+ Change-based and tick-counted conditions compare the live entity against the watch's `_lastState` snapshot and run forever when `stopAfter === 0`. Baseline `_lastState` is captured on the first check so the very next change triggers the watch *(source: `engine/watches.js:434, 520`)*.
52
55
 
53
56
  ## Target Types — `TARGET_TYPES` Registry
54
57
 
55
- Target-type behavior in `engine/watches.js` is **data-driven via a registry** *(source: `engine/watches.js:50-72`)*. Each spec must provide:
58
+ Target-type behavior in `engine/watches.js` is **data-driven via a registry** *(source: `engine/watches.js:101, 124-160`)*. Each spec must provide:
56
59
 
57
60
  - `label` — human name shown in dashboard pickers
58
61
  - `description` — short help text
@@ -61,28 +64,34 @@ Target-type behavior in `engine/watches.js` is **data-driven via a registry** *(
61
64
  - `captureState(entity)` — snapshot used for change-detection diffs
62
65
  - `evaluate(condition, entity, prevState, target)` — returns `{ triggered, message }`
63
66
 
64
- The registry IS the allowlist for `createWatch` and `/api/watches/target-types`; the old hardcoded "pr or work-item" check is gone. Add a new target type at runtime with `registerTargetType(type, spec)` and look one up with `getTargetType(type)`. `listTargetTypes()` returns the serializable form used by the dashboard *(source: `engine/watches.js:62-88`)*.
67
+ The registry IS the allowlist for `createWatch` and `/api/watches/target-types`; the old hardcoded "pr or work-item" check is gone. Add a new target type at runtime with `registerTargetType(type, spec)` and look one up with `getTargetType(type)`. `listTargetTypes()` returns the serializable form used by the dashboard *(source: `engine/watches.js:124-184`)*.
68
+
69
+ ### User-extensible via `watches.d/` (W-mp7hg58e000b5212)
70
+
71
+ At engine boot, every `*.js` file in `<MINIONS_DIR>/watches.d/` is auto-loaded **after** the built-in registrations *(source: `engine/watches.js:1314-1340`)*, so plugins can both add new target types and override built-ins. A plugin file exports either `{ name, spec }` or an array of such objects. Failures are logged-and-skipped — one bad plugin must not break boot or block other plugins. Reloads require an engine restart.
72
+
73
+ Canonical example: `watches.d/http.js` (W-mp7i22mu00191b07) — a generic HTTP poller covering the full plugin contract including `extractState` (custom snapshot fields not on the entity itself) and `extendTemplateVars` (custom action-template vars like `{{httpStatus}}`, `{{prevExtracted}}`).
65
74
 
66
75
  ### Built-in target types
67
76
 
68
- The eight built-ins are registered at module load *(source: `engine/watches.js:399-748`)*. Constants live at `engine/shared.js:1563-1572` (`WATCH_TARGET_TYPE`).
77
+ The eight built-ins are registered at module load *(source: `engine/watches.js:672-1271`)*. Constants live at `engine/shared.js:1881-1890` (`WATCH_TARGET_TYPE`).
69
78
 
70
79
  | `targetType` | Target value | Conditions | Notes |
71
80
  |---------------|--------------------------------------|----------------------------------------------------------------------------|-------|
72
- | `pr` | PR number, canonical id, or display id | `merged`, `build-fail`, `build-pass`, `status-change`, `any`, `new-comments`, `vote-change` | Reads from `pull-requests.json` for any project; `new-comments` watches `humanFeedback.lastProcessedCommentDate` |
73
- | `work-item` | Work item id | `completed`, `failed`, `status-change`, `any` | `completed` matches `DONE_STATUSES`; `failed` matches `WI_STATUS.FAILED` |
81
+ | `pr` | PR number, canonical id, or display id | `merged`, `build-fail`, `build-pass`, `status-change`, `any`, `new-comments`, `vote-change`, `head-commit-change`, `mergeable-flipped`, `ready-for-merge`, `behind-master`, `draft-flipped` | Reads from `pull-requests.json` for any project; `new-comments` watches `humanFeedback.lastProcessedCommentDate`; `behind-master` requires `engine.watchesIncludeBehindBy: true` so the GH poller populates `pr.behindBy` |
82
+ | `work-item` | Work item id | `completed`, `failed`, `status-change`, `any`, `stalled`, `retry-limit-reached`, `dependency-met` | `completed` matches `DONE_STATUSES`; `failed` matches `WI_STATUS.FAILED`; `stalled` fires after N unchanged captures (default 12 ≈ 60 min); `retry-limit-reached` fires when `_retryCount >= ENGINE_DEFAULTS.maxRetries` |
74
83
  | `meeting` | Meeting id | `concluded`, `status-change`, `any` | `concluded` fires on terminal status (`completed`, `archived`) |
75
- | `plan` | PRD JSON filename or plan id | `approved`, `rejected`, `completed`, `status-change`, `any` | Looked up by `_source` (PRD file), `_sourcePlan` (.md), or `id`; uses `PLAN_STATUS` |
84
+ | `plan` | PRD JSON filename or plan id | `approved`, `rejected`, `completed`, `status-change`, `any`, `all-items-done`, `item-failed-n-times` | Looked up by `_source` (PRD file), `_sourcePlan` (.md), or `id`; uses `PLAN_STATUS` |
76
85
  | `schedule` | Schedule id | `ran`, `enabled`, `disabled`, `status-change`, `any` | `ran` fires when `lastRun` advances; `enabled`/`disabled` fire on the flip |
77
- | `pipeline` | Pipeline id (latest run is tracked) | `completed`, `failed`, `stage-complete`, `status-change`, `any` | `failed` covers `PIPELINE_STATUS.FAILED` and `STOPPED`; `stage-complete` only counts within the same `runId` |
86
+ | `pipeline` | Pipeline id (latest run is tracked) | `completed`, `failed`, `stage-complete`, `status-change`, `any`, `stage-advanced`, `stuck-in-stage` | `failed` covers `PIPELINE_STATUS.FAILED` and `STOPPED`; `stage-complete`/`stage-advanced` only count within the same `runId`; `stuck-in-stage` fires after N unchanged captures (default 12) |
78
87
  | `dispatch` | Dispatch entry id | `completed`, `failed`, `status-change`, `any` | Looks across `pending` / `active` / `completed` lists |
79
88
  | `agent` | Agent id | `activity-change`, `status-change`, `any` | `activity-change` fires only on transitions in/out of `'working'` |
80
89
 
81
- `evaluateWatch` dispatches to `tt.evaluate(...)`; unknown target types return `"Unknown target type: ..."` and unknown conditions return `"Unknown condition: ..."` — both are non-triggering *(source: `engine/watches.js:208-224`)*.
90
+ `evaluateWatch` dispatches to `tt.evaluate(...)`; unknown target types return `"Unknown target type: ..."` and unknown conditions return `"Unknown condition: ..."` — both are non-triggering *(source: `engine/watches.js:318+`)*.
82
91
 
83
92
  ## Tick Integration
84
93
 
85
- `engine.js` calls `checkWatches(config, state)` every 3 ticks (~3 min at the default 60s tick) inside its own `safe('checkWatches', ...)` block *(source: `engine.js:4480-4538`)*. The engine builds the state object from cached project files + module reads:
94
+ `engine.js` calls `checkWatches(config, state)` every 3 ticks (~3 min at the default 60s tick) inside its own `safe('checkWatches', ...)` block *(source: `engine.js:5449-5507`)*. The engine builds the state object from cached project files + module reads:
86
95
 
87
96
  ```
88
97
  {
@@ -93,7 +102,7 @@ The eight built-ins are registered at module load *(source: `engine/watches.js:3
93
102
  }
94
103
  ```
95
104
 
96
- `checkWatches` walks every active watch and, inside a single `mutateJsonFileLocked` callback *(source: `engine/watches.js:241-345`)*:
105
+ `checkWatches` walks every active watch and, inside a single `mutateJsonFileLocked` callback *(source: `engine/watches.js:410+`)*:
97
106
 
98
107
  1. Skips paused/expired watches and any watch checked within its `interval`.
99
108
  2. Captures a baseline `_lastState` on first check (so change conditions have something to diff).
@@ -103,11 +112,11 @@ The eight built-ins are registered at module load *(source: `engine/watches.js:3
103
112
  6. On non-trigger: writes a per-poll inbox note when `onNotMet === 'notify'`.
104
113
  7. Refreshes `_lastState` for the next check.
105
114
 
106
- I/O happens **outside the lock**: notifications via `writeToInbox`, follow-up actions via `_runActionTask` (`Promise` per action, failures isolated). Each action's result is persisted back onto the watch as `_lastActionResult` in a follow-up locked write *(source: `engine/watches.js:330-377`)*.
115
+ I/O happens **outside the lock**: notifications via `writeToInbox`, follow-up actions via `_runActionTask` (`Promise` per action, failures isolated). Each action's result is persisted back onto the watch as `_lastActionResult` in a follow-up locked write *(source: `engine/watches.js:410+`)*.
107
116
 
108
117
  ## Follow-Up Actions on Trigger
109
118
 
110
- `watch.action` is an optional structured action that runs after the inbox notification fires. Action types live in a sibling registry in `engine/watch-actions.js` and are validated at create/update time *(source: `engine/watches.js:112-115`, `engine/watch-actions.js:50-73`)*. `GET /api/watches/action-types` returns the live list for dashboard pickers.
119
+ `watch.action` is an optional structured action that runs after the inbox notification fires. Action types live in a sibling registry in `engine/watch-actions.js` and are validated at create/update time *(source: `engine/watches.js:112-115`, `engine/watch-actions.js:223-330`)*. `GET /api/watches/action-types` returns the live list for dashboard pickers.
111
120
 
112
121
  ### Built-in actions
113
122
 
@@ -117,16 +126,17 @@ I/O happens **outside the lock**: notifications via `writeToInbox`, follow-up ac
117
126
  | `dispatch-work-item` | Append a new WI to the project (or central) `work-items.json` with `createdBy: "watch:<id>"` |
118
127
  | `run-skill` | Wrapper around `dispatch-work-item` that asks the agent to run a specific `.claude` skill |
119
128
  | `webhook` | `http`/`https` request to an arbitrary URL (10s safety timeout, JSON or string body) |
129
+ | `minions-api` | Loopback call to the in-process dashboard at `http://127.0.0.1:${MINIONS_PORT \|\| 7331}` — `endpoint` must start with `/api/`; sets `X-Minions-Internal: 1`; returns `{ok, status, summary, response}` for chain-step templating |
120
130
  | `cancel-work-item` | Flip a WI to `WI_STATUS.CANCELLED` across all known work-items files |
121
131
  | `trigger-pipeline` | Start a new pipeline run (skipped if the pipeline already has an active run) |
122
132
  | `archive-plan` | Set PRD `status="archived"` + `archivedAt` |
123
133
  | `resume-plan` | Set PRD `status=PLAN_STATUS.ACTIVE` and clear `planStale` |
124
134
 
125
- Constants live in `WATCH_ACTION_TYPE` (`engine/shared.js:1605-1616`); handlers in `engine/watch-actions.js:185-464`.
135
+ Constants live in `WATCH_ACTION_TYPE` (`engine/shared.js:1960+`); handlers in `engine/watch-actions.js:332-700`.
126
136
 
127
137
  ### Templating
128
138
 
129
- Action params support `{{var}}` substitution from the trigger context *(source: `engine/watch-actions.js:83-99`)*. Built-in vars from `buildTriggerContext` *(source: `engine/watch-actions.js:107-154`)*:
139
+ Action params support `{{var}}` substitution from the trigger context *(source: `engine/watch-actions.js:145-220`)*. Built-in vars from `buildTriggerContext`:
130
140
 
131
141
  - Always: `target`, `targetType`, `condition`, `watchId`, `triggerCount`, `message`
132
142
  - All scalar fields from `newState` (e.g. `status`, `buildStatus`, `reviewStatus`, `lastRun`)
@@ -136,7 +146,7 @@ Unknown vars are left as `{{var}}` so callers can detect them downstream.
136
146
 
137
147
  ### Failure isolation
138
148
 
139
- Action handlers must return `{ ok: false, summary }` rather than throw — `runWatchAction` wraps them in try/catch as a backstop *(source: `engine/watch-actions.js:160-178`)*. A bad action never blocks other watches in the same tick, and the failed result is persisted onto the watch as `_lastActionResult` for debugging.
149
+ Action handlers must return `{ ok: false, summary }` rather than throw — `runWatchAction` wraps them in try/catch as a backstop *(source: `engine/watch-actions.js:223-270`)*. A bad action never blocks other watches in the same tick, and the failed result is persisted onto the watch as `_lastActionResult` for debugging. Multi-step action chains run via `runWatchActionChain` *(source: `engine/watch-actions.js:271+`)* — each step's `{ok, summary, result}` is templated into the next step's params as `{{steps.N.result.<field>}}`.
140
150
 
141
151
  ## Dashboard Surface
142
152
 
@@ -150,11 +160,11 @@ Action handlers must return `{ ok: false, summary }` rather than throw — `runW
150
160
  | `/api/watches/update` | POST | `handleWatchesUpdate` | Pause/resume/modify (body: `id, status?, interval?, description?, notify?, stopAfter?, onNotMet?, condition?, action?`) |
151
161
  | `/api/watches/delete` | POST | `handleWatchesDelete` | Delete (body: `id`) |
152
162
 
153
- *(source: route table in `dashboard.js:7395-7400`; handlers in `dashboard.js:6370-6422`)*
163
+ *(source: route table in `dashboard.js`; handlers `handleWatchesList` / `handleWatchesTargetTypes` / `handleWatchesActionTypes` / `handleWatchesCreate` / `handleWatchesUpdate` / `handleWatchesDelete`)*
154
164
 
155
- The watches page is rendered by `dashboard/js/render-watches.js`; the **+ New** button calls `openCreateWatchModal()` which fetches `/api/watches/target-types` and `/api/watches/action-types` to populate the picker dynamically *(source: `dashboard/pages/watches.html:3`, `dashboard/js/render-watches.js:354-414`)*.
165
+ The watches page is rendered by `dashboard/js/render-watches.js`; the **+ New** button calls `openCreateWatchModal()` which fetches `/api/watches/target-types` and `/api/watches/action-types` to populate the picker dynamically *(source: `dashboard/pages/watches.html`, `dashboard/js/render-watches.js`)*.
156
166
 
157
- The CC state preamble injects a `Watches: <count>` line via `getWatches()` so the chat brain knows how many watches exist *(source: `dashboard.js:1342`)*.
167
+ The CC state preamble injects a `Watches: <count>` line via `getWatches()` so the chat brain knows how many watches exist *(source: `dashboard.js` `buildCCStatePreamble`)*.
158
168
 
159
169
  ## Command Center Integration
160
170
 
@@ -182,20 +192,20 @@ Absolute conditions firing under `stopAfter === 0` flip `status` to `expired`; `
182
192
  | Watch never fires | Check `status === 'active'`; check `last_checked` advancing each cycle; confirm engine tick is running and `interval` isn't longer than your test window |
183
193
  | `evaluateWatch` returns `"<label> <target> not found"` | `fetchEntity` got nothing back — wrong `target` (e.g. PR display id vs canonical id), the target type isn't loaded, or the underlying file (PR cache, plan PRD) doesn't exist |
184
194
  | `"Unknown target type"` / `"Unknown condition"` | The registry doesn't recognise the value. Check `GET /api/watches/target-types` to see what's registered server-side; condition must be in that target type's `conditions[]` |
185
- | Change condition fires immediately on first tick | Won't happen — baseline `_lastState` is captured on the first check before `evaluate` runs *(source: `engine/watches.js:262-266`)*. If you see this, suspect manual edits to `watches.json` |
195
+ | Change condition fires immediately on first tick | Won't happen — baseline `_lastState` is captured on the first check before `evaluate` runs *(source: `engine/watches.js:434, 520`)*. If you see this, suspect manual edits to `watches.json` |
186
196
  | Absolute watch fires forever instead of once | `stopAfter` is set to a non-zero value; only `stopAfter === 0` triggers fire-once expiration |
187
197
  | Action runs but inbox notification doesn't | `notify` field isn't `'inbox'`, or `owner` is empty. `notify` and `action` are independent — both can fire, or only one |
188
198
  | `_lastActionResult.ok === false` with `"unknown action type"` | The `action.type` isn't registered. List with `listActionTypes()` / `GET /api/watches/action-types` |
189
- | Webhook action returns `"only http/https allowed"` | URLs must use `http://` or `https://` schemes; other protocols are rejected by design *(source: `engine/watch-actions.js:297-299`)* |
199
+ | Webhook action returns `"only http/https allowed"` | URLs must use `http://` or `https://` schemes; other protocols are rejected by design *(source: `engine/watch-actions.js` `WEBHOOK` handler)* |
190
200
  | Trigger fires but follow-up `dispatch-work-item` is missing | Check the engine log for `Watch <id> action <type>: <summary>`. Common reasons: missing `title`, the project's `work-items.json` couldn't be written, or the WI landed in central `work-items.json` because no project was specified |
191
- | Watch `_lastActionResult` shows `"timeout"` for webhook | Webhooks have a 10s safety timeout to keep the watches tick fast *(source: `engine/watch-actions.js:334-337`)* |
192
- | `checkWatches` block crashes silently | Wrapped in `safe('checkWatches', ...)` so one failure doesn't abort the tick *(source: `engine.js:4484`)*. Inspect `engine/log.json` for `Watch check error (<id>)` lines. Regression #1088: the block must use `getProjects(config)`, never the long-removed `PROJECTS` constant |
201
+ | Watch `_lastActionResult` shows `"timeout"` for webhook | Webhooks have a 10s safety timeout to keep the watches tick fast *(source: `engine/watch-actions.js:482`)* |
202
+ | `checkWatches` block crashes silently | Wrapped in `safe('checkWatches', ...)` so one failure doesn't abort the tick *(source: `engine.js:5453`)*. Inspect `engine/log.json` for `Watch check error (<id>)` lines. Regression #1088: the block must use `getProjects(config)`, never the long-removed `PROJECTS` constant |
193
203
 
194
204
  ## See Also
195
205
 
196
- - `engine/shared.js:1557-1616` — `WATCH_STATUS`, `WATCH_TARGET_TYPE`, `WATCH_CONDITION`, `WATCH_ABSOLUTE_CONDITIONS`, `WATCH_ACTION_TYPE` constants
197
- - `engine/watches.js` — registry, lifecycle, tick integration
198
- - `engine/watch-actions.js` — action registry and built-in handlers
206
+ - `engine/shared.js:1875-1960` — `WATCH_STATUS`, `WATCH_TARGET_TYPE`, `WATCH_CONDITION`, `WATCH_ABSOLUTE_CONDITIONS`, `WATCH_ACTION_TYPE` constants
207
+ - `engine/watches.js` — registry, lifecycle, tick integration, `watches.d/` plugin loader
208
+ - `engine/watch-actions.js` — action registry and built-in handlers (including `minions-api`)
199
209
  - `dashboard/pages/watches.html`, `dashboard/js/render-watches.js` — dashboard UI
200
- - `test/unit/watches-module.test.js`, `test/unit/watch-actions.test.js` — module-level tests
210
+ - `test/unit/watches-module.test.js`, `test/unit/watch-actions.test.js`, `test/unit/watches-plugin-loader.test.js` — module-level tests
201
211
  - [`auto-discovery.md`](auto-discovery.md) — overall tick cycle context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1962",
3
+ "version": "0.1.1964",
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"