@yemi33/minions 0.1.291 → 0.1.292

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/CHANGELOG.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.291 (2026-04-03)
3
+ ## 0.1.292 (2026-04-03)
4
4
 
5
5
  ### Features
6
+ - harden shared.js — backup verification, lock TOCTOU, docs
6
7
  - all doc-chats use Sonnet with full tools (agent change)
7
8
 
8
9
  ### Fixes
10
+ - add behavioral tests for CRITICAL propagation and stale lock ENOENT
11
+ - CRITICAL errors in safeJson now propagate to callers
9
12
  - defer plan archiving until verify completes, add 20 verify tests
10
13
  - enforce worktree isolation — 4 code paths fixed
11
14
  - ' not 'Evaluate:'
package/engine/shared.js CHANGED
@@ -52,14 +52,37 @@ function safeJson(p) {
52
52
  const backupData = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
53
53
  // Backup is valid — restore it to the primary file (atomic via safeWrite)
54
54
  console.log(`[safeJson] restored ${path.basename(p)} from .backup sidecar`);
55
- try { safeWrite(p, backupData); } catch { /* best-effort restore */ }
55
+ try {
56
+ safeWrite(p, backupData);
57
+ // Verify the restored file matches expected content
58
+ const verifyData = JSON.parse(fs.readFileSync(p, 'utf8'));
59
+ if (JSON.stringify(verifyData) !== JSON.stringify(backupData)) {
60
+ const errMsg = `[safeJson] CRITICAL: backup restore verification failed for ${p} — written data does not match backup`;
61
+ console.error(errMsg);
62
+ throw new Error(errMsg);
63
+ }
64
+ } catch (restoreErr) {
65
+ // Re-throw CRITICAL errors so they propagate to callers
66
+ if (restoreErr.message && restoreErr.message.includes('CRITICAL')) throw restoreErr;
67
+ const errMsg = `[safeJson] CRITICAL: backup restore failed for ${p}: ${restoreErr.message}`;
68
+ console.error(errMsg);
69
+ throw new Error(errMsg);
70
+ }
56
71
  return backupData;
57
- } catch {
72
+ } catch (outerErr) {
73
+ // Let CRITICAL errors propagate — callers must know about data integrity failures
74
+ if (outerErr.message && outerErr.message.includes('CRITICAL')) throw outerErr;
58
75
  return null;
59
76
  }
60
77
  }
61
78
  }
62
79
 
80
+ /**
81
+ * Monotonic counter for generating unique temp file names within this process.
82
+ * Assumes single-thread execution (no worker_threads). If worker_threads are
83
+ * introduced, this must be replaced with an atomic or thread-safe counter to
84
+ * avoid temp file name collisions.
85
+ */
63
86
  let _tmpCounter = 0;
64
87
 
65
88
  function safeWrite(p, data) {
@@ -129,10 +152,19 @@ function withFileLock(lockPath, fn, {
129
152
  try {
130
153
  const stat = fs.statSync(lockPath);
131
154
  if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
132
- try { fs.unlinkSync(lockPath); } catch { /* race: another process removed it */ }
133
- continue; // retry immediately after removing stale lock
155
+ try {
156
+ fs.unlinkSync(lockPath);
157
+ } catch (unlinkErr) {
158
+ // ENOENT: another process deleted the lock between stat and unlink — safe to retry
159
+ if (unlinkErr.code !== 'ENOENT') throw unlinkErr;
160
+ }
161
+ sleepMs(retryDelayMs); // avoid busy-loop on contention
162
+ continue;
134
163
  }
135
- } catch { /* lock file disappeared between EEXIST and stat — retry will succeed */ }
164
+ } catch (staleErr) {
165
+ // ENOENT from statSync: lock file disappeared between EEXIST and stat — retry will succeed
166
+ if (staleErr.code !== 'ENOENT') throw staleErr;
167
+ }
136
168
  sleepMs(retryDelayMs);
137
169
  }
138
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.291",
3
+ "version": "0.1.292",
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"