@yemi33/minions 0.1.2332 → 0.1.2334

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.
@@ -125,7 +125,10 @@ function _requeueProjectlessCentralWorkItems(itemIds) {
125
125
  * @param {object} [options]
126
126
  * @param {'archive'|'keep'|'purge'} [options.dataMode='archive']
127
127
  * archive: move projects/<name>/ to projects/.archived/<name>-YYYYMMDD/
128
- * keep: leave projects/<name>/ in place
128
+ * keep: leave projects/<name>/ in place, but tombstone work-items.json /
129
+ * pull-requests.json into projects/<name>/.dropped/ so dropScope()
130
+ * (step 9) isn't immediately undone by the *-store.js resync-on-
131
+ * read path re-hydrating the still-present JSON (W-mr96oira000g979a)
129
132
  * purge: rm -rf projects/<name>/
130
133
  * @returns {object} summary { ok, project, cancelledItems, killedAgents,
131
134
  * drainedDispatches, cleanedWorktrees, disabledSchedules, archivedTo,
@@ -145,6 +148,7 @@ function removeProject(target, options = {}) {
145
148
  pipelineRefs: [],
146
149
  archivedTo: null,
147
150
  purgedDataDir: false,
151
+ tombstonedDataFiles: false,
148
152
  // P-bfa2d-remove-project-cleanup — soft-delete tag counts for entries in
149
153
  // the three central state files that survived the hard-cancel passes
150
154
  // (steps 1, 2). See step 7.5 for the orphan-tag policy rationale.
@@ -427,6 +431,35 @@ function removeProject(target, options = {}) {
427
431
  summary.archiveError = e.code ? `${e.code}: ${e.message}` : e.message;
428
432
  summary.warnings.push('archive data dir: ' + e.message);
429
433
  }
434
+ } else if (dataMode === 'keep') {
435
+ // W-mr96oira000g979a — 'keep' leaves projects/<name>/ (and therefore
436
+ // work-items.json / pull-requests.json) at its normal, live-resolving
437
+ // path. dropScope() below DELETEs the SQL rows for this scope, but the
438
+ // *-store.js resync-on-read path (_resyncScopeIfJsonDiverged) treats a
439
+ // scope with an empty SQL table + a still-present JSON file as a
440
+ // fresh-install first-touch and re-hydrates straight from that JSON —
441
+ // silently undoing the drop the very next time anything reads this
442
+ // scope. Tombstone the two live-state files into a `.dropped/`
443
+ // subdirectory so `_filePathForScope(scope)` (and the read-side
444
+ // resync/fallback logic keyed off it) no longer resolves to them,
445
+ // while keeping the historical data on disk for manual inspection —
446
+ // exactly what 'keep' promises, minus the auto-resurrection.
447
+ try {
448
+ const droppedDir = path.join(dataDir, '.dropped');
449
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
450
+ for (const fname of ['work-items.json', 'pull-requests.json']) {
451
+ const src = path.join(dataDir, fname);
452
+ if (!fs.existsSync(src)) continue;
453
+ fs.mkdirSync(droppedDir, { recursive: true });
454
+ let dest = path.join(droppedDir, `${fname}.${stamp}`);
455
+ let n = 1;
456
+ while (fs.existsSync(dest)) dest = path.join(droppedDir, `${fname}.${stamp}-${++n}`);
457
+ _renameWithRetry(src, dest);
458
+ summary.tombstonedDataFiles = true;
459
+ }
460
+ } catch (e) {
461
+ summary.warnings.push('tombstone kept data files: ' + e.message);
462
+ }
430
463
  }
431
464
  }
432
465
 
@@ -313,7 +313,18 @@ function dropScope(scope) {
313
313
  try {
314
314
  const { getDb } = require('./db');
315
315
  const result = getDb().prepare('DELETE FROM pull_requests WHERE scope = ?').run(scope);
316
- _lastMirrorHashByScope.delete(scope);
316
+ // Mirror the work-items-store.js fix (W-mr96m3hm000fd5d7): don't blindly
317
+ // forget the mirror-hash. _resyncScopeIfJsonDiverged treats "no recorded
318
+ // hash" + "zero SQL rows" as a first-install hydrate and rebuilds SQL from
319
+ // whatever JSON is on disk. removeProject with dataMode:'keep' leaves this
320
+ // scope's JSON file untouched, so forgetting the hash here would let the
321
+ // very next read/write resurrect the just-dropped project's stale PR rows.
322
+ // Adopt the file's current hash instead (or forget it only if the file is
323
+ // actually gone, e.g. dataMode: 'purge'/'archive').
324
+ const jsonPath = _filePathForScope(scope);
325
+ const currentHash = _fileContentHash(jsonPath);
326
+ if (currentHash == null) _lastMirrorHashByScope.delete(scope);
327
+ else _lastMirrorHashByScope.set(scope, currentHash);
317
328
  if (result && result.changes > 0) {
318
329
  try { require('./db-events').emitStateEvent('pull_requests'); } catch { /* optional */ }
319
330
  }
@@ -390,7 +390,21 @@ function dropScope(scope) {
390
390
  try {
391
391
  const { getDb } = require('./db');
392
392
  const result = getDb().prepare('DELETE FROM work_items WHERE scope = ?').run(scope);
393
- _lastMirrorHashByScope.delete(scope);
393
+ // Do NOT simply forget the mirror-hash here. _resyncScopeIfJsonDiverged
394
+ // treats "no recorded hash" + "zero SQL rows for the scope" as a genuine
395
+ // first-install hydrate and rebuilds SQL from whatever JSON is on disk.
396
+ // When the caller is removeProject with dataMode:'keep', the JSON file for
397
+ // this scope is deliberately left untouched — so the very next read/write
398
+ // against this scope would silently resurrect the just-dropped project's
399
+ // stale rows straight back into SQL. Instead, adopt the JSON file's
400
+ // *current* hash (if it still exists) so the next resync correctly reads
401
+ // that as "unchanged" and skips the rehydrate. If the file is genuinely
402
+ // gone (dataMode: 'purge'/'archive'), forgetting the hash is safe since
403
+ // _resyncScopeIfJsonDiverged no-ops when the file can't be hashed.
404
+ const jsonPath = _filePathForScope(scope);
405
+ const currentHash = _fileContentHash(jsonPath);
406
+ if (currentHash == null) _lastMirrorHashByScope.delete(scope);
407
+ else _lastMirrorHashByScope.set(scope, currentHash);
394
408
  if (result && result.changes > 0) {
395
409
  try { require('./db-events').emitStateEvent('work_items'); } catch { /* optional */ }
396
410
  try { require('./queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2332",
3
+ "version": "0.1.2334",
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"