@yemi33/minions 0.1.2155 → 0.1.2156

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/cli.js CHANGED
@@ -580,6 +580,21 @@ const commands = {
580
580
  }
581
581
  } catch (err) { e.log('warn', `cc worker pool force-on migration failed: ${err.message}`); }
582
582
 
583
+ // Repair work-item note links that broke before the rewriter covered
584
+ // completion-report artifact links (W-mq1j85cj00055a8f). Notes the engine
585
+ // auto-archived back then left the WI pill pointing at the gone
586
+ // notes/inbox/<name> path; this repoints them to the archived/KB location.
587
+ // Idempotent (only touches provably-broken + resolvable links) and routed
588
+ // through mutateWorkItems so SQL + JSON mirror stay consistent — a no-op
589
+ // once healed, so it's safe to run on every boot.
590
+ try {
591
+ const res = require('./note-link-backfill').runNoteLinkBackfill({ config });
592
+ if (res.fixedLinks > 0) {
593
+ e.log('info', `Repointed ${res.fixedLinks} broken note link(s) across ${res.fixedItems} work item(s)`);
594
+ console.log(` Repaired ${res.fixedLinks} broken note link(s) in ${res.fixedItems} work item(s).`);
595
+ }
596
+ } catch (err) { e.log('warn', `note-link backfill failed: ${err.message}`); }
597
+
583
598
  // Auto-heal projects missing workSources (cloned-repo / hand-rolled-config
584
599
  // footgun): without this block, discoverFromWorkItems / discoverFromPrs
585
600
  // bail silently and the engine looks healthy but never dispatches. The
@@ -1,31 +1,15 @@
1
1
  // engine/db/migrations/013-backfill-broken-note-links.js
2
2
  //
3
- // One-time data repair (not a schema change). Before the work-item rewriter
4
- // covered completion-report artifact links (W-mq1j85cj00055a8f, extended to
5
- // _artifacts.notes[] / artifacts[]), any inbox note the engine auto-archived
6
- // left the work item's note link pointing at the gone `notes/inbox/<name>` path
7
- // a dangling pill. This migration runs the backfill exactly once on the next
8
- // engine start: it scans existing work_items, finds links whose inbox file is
9
- // missing, discovers where the note went (notes/archive/ or knowledge/<cat>/),
10
- // and repoints them. Best-effort and self-isolating — wrapped so a failure can
11
- // never wedge boot or roll back the migration batch.
3
+ // No-op marker. The one-time repair of broken inbox note links was moved to an
4
+ // idempotent engine-boot sweep (engine/cli.js note-link-backfill.runNoteLink
5
+ // Backfill) because it must go through shared.mutateWorkItems to keep the SQL
6
+ // store and its JSON mirror consistent a raw in-migration UPDATE left the
7
+ // mirror diverged and could be reverted by the store's resync. This migration
8
+ // stays so schema_version progression is identical across installs that already
9
+ // recorded v13.
12
10
 
13
11
  module.exports = {
14
12
  version: 13,
15
- description: 'one-time backfill: repoint broken inbox note links in existing work items',
16
- up(db) {
17
- try {
18
- const { backfillBrokenNoteLinks } = require('../../note-link-backfill');
19
- const res = backfillBrokenNoteLinks(db);
20
- if (res && res.fixedLinks > 0) {
21
- // eslint-disable-next-line no-console -- migrate.js logs progress to stdout
22
- console.log(`[db-migrate] v13: repointed ${res.fixedLinks} broken note link(s) across ${res.fixedItems} work item(s) (${res.scanned} scanned)`);
23
- }
24
- } catch (e) {
25
- // One-time cosmetic repair — never block boot. Record the version anyway
26
- // so it doesn't retry forever against the same un-repairable state.
27
- // eslint-disable-next-line no-console -- surface the skip without throwing
28
- console.warn(`[db-migrate] v13: note-link backfill skipped: ${e && e.message ? e.message : e}`);
29
- }
30
- },
13
+ description: 'reserved (note-link backfill moved to an idempotent engine-boot sweep)',
14
+ up() { /* intentionally empty — see header */ },
31
15
  };
@@ -18,11 +18,10 @@
18
18
  // notes.md) are left untouched. Idempotent: a second pass finds nothing because
19
19
  // the repaired links no longer reference `notes/inbox/`.
20
20
  //
21
- // Wired as a one-time SQL migration (db/migrations/013-*) so it runs exactly
22
- // once on the next engine start after an update. Updates the canonical SQL
23
- // `work_items` rows only; the JSON mirror self-heals on the next mutateWorkItems
24
- // write and SQL is what readers serve (work-items-store), so rewriting the
25
- // passive mirror here would only risk dropping rows for no benefit.
21
+ // Wired as an idempotent engine-boot sweep (engine/cli.js, alongside the other
22
+ // one-shot boot migrations). It runs through shared.mutateWorkItems the same
23
+ // write path the live rewriter uses so the canonical SQL store and its JSON
24
+ // mirror stay consistent, and it's a no-op once every link is healed.
26
25
 
27
26
  'use strict';
28
27
 
@@ -149,10 +148,13 @@ function repointItemNoteLinks(item, resolve) {
149
148
  return fixed;
150
149
  }
151
150
 
152
- // Scan the canonical SQL `work_items` table and repair broken note links in
153
- // place. db is a node:sqlite DatabaseSync (the migration's connection). Per-row
154
- // try/catch so one corrupt row can't abort the pass. Returns a summary.
155
- function backfillBrokenNoteLinks(db, opts = {}) {
151
+ // Repair broken note links across every work-items file (each project's plus
152
+ // the central one), routed through shared.mutateWorkItems so the canonical SQL
153
+ // store AND its JSON mirror stay consistent the same write path the live
154
+ // rewriter uses. Idempotent: only touches links that are provably broken AND
155
+ // resolvable, so once healed a re-run writes nothing (skipWriteIfUnchanged).
156
+ // Safe to call on every engine boot. Per-file try/catch isolates a bad scope.
157
+ function runNoteLinkBackfill(opts = {}) {
156
158
  const shared = require('./shared');
157
159
  const minionsDir = opts.minionsDir || shared.MINIONS_DIR;
158
160
  const resolve = buildNoteResolver({
@@ -162,33 +164,34 @@ function backfillBrokenNoteLinks(db, opts = {}) {
162
164
  kbCategories: shared.KB_CATEGORIES,
163
165
  });
164
166
 
165
- let scopes;
166
- try {
167
- scopes = db.prepare('SELECT DISTINCT scope FROM work_items').all().map(r => r.scope);
168
- } catch {
169
- return { scanned: 0, fixedItems: 0, fixedLinks: 0 };
167
+ let config = opts.config;
168
+ if (!config) {
169
+ try { config = JSON.parse(fs.readFileSync(path.join(minionsDir, 'config.json'), 'utf8')); }
170
+ catch { config = {}; }
170
171
  }
172
+ const projects = Array.isArray(config.projects) ? config.projects : [];
173
+ const candidates = [];
174
+ for (const p of projects) {
175
+ if (p && p.name) candidates.push(path.join(minionsDir, 'projects', p.name, 'work-items.json'));
176
+ }
177
+ candidates.push(path.join(minionsDir, 'work-items.json'));
171
178
 
172
- const now = opts.now || Date.now();
173
- const upd = db.prepare('UPDATE work_items SET data = ?, updated_at = ? WHERE scope = ? AND id = ?');
174
179
  let scanned = 0, fixedItems = 0, fixedLinks = 0;
175
-
176
- for (const scope of scopes) {
177
- let rows;
178
- try { rows = db.prepare('SELECT id, data FROM work_items WHERE scope = ?').all(scope); } catch { continue; }
179
- for (const row of rows) {
180
- let item;
181
- try { item = JSON.parse(row.data); } catch { continue; }
182
- scanned++;
183
- try {
184
- const n = repointItemNoteLinks(item, resolve);
185
- if (n > 0) {
186
- upd.run(JSON.stringify(item), now, scope, item.id);
187
- fixedItems++;
188
- fixedLinks += n;
180
+ for (const wiPath of candidates) {
181
+ try {
182
+ shared.mutateWorkItems(wiPath, (items) => {
183
+ if (!Array.isArray(items)) return items;
184
+ for (const item of items) {
185
+ if (!item) continue;
186
+ scanned++;
187
+ try {
188
+ const n = repointItemNoteLinks(item, resolve);
189
+ if (n > 0) { fixedItems++; fixedLinks += n; }
190
+ } catch { /* skip unfixable item */ }
189
191
  }
190
- } catch { /* skip unfixable row */ }
191
- }
192
+ return items;
193
+ });
194
+ } catch { /* per-file isolation — one bad scope can't block the others */ }
192
195
  }
193
196
 
194
197
  return { scanned, fixedItems, fixedLinks };
@@ -198,5 +201,5 @@ module.exports = {
198
201
  inboxNameFromRef,
199
202
  buildNoteResolver,
200
203
  repointItemNoteLinks,
201
- backfillBrokenNoteLinks,
204
+ runNoteLinkBackfill,
202
205
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2155",
3
+ "version": "0.1.2156",
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"