@yemi33/minions 0.1.1032 → 0.1.1034

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,5 +1,82 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1034 (2026-04-16)
4
+
5
+ ### Features
6
+ - Doc-chat performance — debounced persistence, session TTL, smart disk reads (#1164)
7
+ - split getStatus() into fast/slow state tiers with separate TTLs (#1170)
8
+ - Cache _countWorktrees() with 30s TTL (#1166)
9
+ - Pre-cache gzipped status buffer alongside JSON cache (#1171)
10
+ - parallelize ADO and GitHub PR polling with Promise.allSettled (#1172)
11
+ - route implement items to dedicated implement playbook (#1115)
12
+
13
+ ### Fixes
14
+ - use locked write for failed-with-PR reconciliation in cleanup.js (#1169)
15
+ - prevent test from corrupting live meeting state
16
+ - scheduler enabled field falsy check treats undefined as disabled (#1160)
17
+ - update branch-dedup tests to use canonical PR IDs after merge with master (#1146)
18
+ - auto-review not firing for manually-linked PRs with autoObserve=true
19
+ - mark PR abandoned on 404 instead of silently retrying each tick
20
+ - replace undefined PROJECTS with config.projects in checkWatches (#1108)
21
+ - write permission for publish workflow
22
+ - run tests inline and post check runs for publish PRs
23
+ - add maxBuffer to all GitHub CLI spawns + paginate PR list (#1130)
24
+ - add required CI checks for PRs + update publish for auto-merge
25
+ - revert to PR merge now that stale status check is removed
26
+ - guard live review check against undefined vote/state values (#1132)
27
+ - push version bump directly to master instead of via PR
28
+ - add push-triggered CI for chore/publish branches
29
+ - publish workflow chore PRs failing to merge
30
+ - harden KB ordering
31
+ - harden audited state transitions
32
+
33
+ ### Other
34
+ - [E2E] Dashboard & engine perf: gzip cache, tiered status, lock backoff, polling parallelization (#1174)
35
+ - Fix doc chat session isolation
36
+ - test(pipeline): add unit tests for CRUD, stage execution, run lifecycle (#1162)
37
+ - chore: test publish after removing stale status check
38
+ - chore: trigger publish test
39
+ - chore: test publish workflow fix
40
+ - chore: trigger publish workflow test
41
+
42
+ ## 0.1.1033 (2026-04-16)
43
+
44
+ ### Features
45
+ - split getStatus() into fast/slow state tiers with separate TTLs (#1170)
46
+ - Cache _countWorktrees() with 30s TTL (#1166)
47
+ - Pre-cache gzipped status buffer alongside JSON cache (#1171)
48
+ - parallelize ADO and GitHub PR polling with Promise.allSettled (#1172)
49
+ - route implement items to dedicated implement playbook (#1115)
50
+
51
+ ### Fixes
52
+ - use locked write for failed-with-PR reconciliation in cleanup.js (#1169)
53
+ - prevent test from corrupting live meeting state
54
+ - scheduler enabled field falsy check treats undefined as disabled (#1160)
55
+ - update branch-dedup tests to use canonical PR IDs after merge with master (#1146)
56
+ - auto-review not firing for manually-linked PRs with autoObserve=true
57
+ - mark PR abandoned on 404 instead of silently retrying each tick
58
+ - replace undefined PROJECTS with config.projects in checkWatches (#1108)
59
+ - write permission for publish workflow
60
+ - run tests inline and post check runs for publish PRs
61
+ - add maxBuffer to all GitHub CLI spawns + paginate PR list (#1130)
62
+ - add required CI checks for PRs + update publish for auto-merge
63
+ - revert to PR merge now that stale status check is removed
64
+ - guard live review check against undefined vote/state values (#1132)
65
+ - push version bump directly to master instead of via PR
66
+ - add push-triggered CI for chore/publish branches
67
+ - publish workflow chore PRs failing to merge
68
+ - harden KB ordering
69
+ - harden audited state transitions
70
+
71
+ ### Other
72
+ - [E2E] Dashboard & engine perf: gzip cache, tiered status, lock backoff, polling parallelization (#1174)
73
+ - Fix doc chat session isolation
74
+ - test(pipeline): add unit tests for CRUD, stage execution, run lifecycle (#1162)
75
+ - chore: test publish after removing stale status check
76
+ - chore: trigger publish test
77
+ - chore: test publish workflow fix
78
+ - chore: trigger publish workflow test
79
+
3
80
  ## 0.1.1032 (2026-04-16)
4
81
 
5
82
  ### Features
@@ -428,6 +428,7 @@ async function _processQaMessage(message, selection, opts) {
428
428
  selection: selection,
429
429
  filePath: capturedFilePath || null,
430
430
  model: window._lastStatus?.autoMode?.ccModel || undefined,
431
+ contentHash: capturedDocContext.content ? capturedDocContext.content.length + ':' + capturedDocContext.content.charCodeAt(0) + ':' + capturedDocContext.content.charCodeAt(capturedDocContext.content.length - 1) : undefined,
431
432
  }),
432
433
  });
433
434
  const data = await res.json();
package/dashboard.js CHANGED
@@ -852,16 +852,18 @@ async function executeCCActions(actions) {
852
852
  // Session store for doc modals — keyed by filePath or title, persisted to disk
853
853
  const CC_SESSIONS_PATH = path.join(ENGINE_DIR, 'cc-sessions.json');
854
854
  const DOC_SESSIONS_PATH = path.join(ENGINE_DIR, 'doc-sessions.json');
855
+ const DOC_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 604800000 — 7 days
855
856
  const docSessions = new Map(); // key → { sessionId, lastActiveAt, turnCount }
856
857
 
857
858
  // Load persisted doc sessions on startup
858
859
  try {
859
860
  const saved = safeJson(DOC_SESSIONS_PATH);
860
861
  if (saved && typeof saved === 'object') {
862
+ const now = Date.now();
861
863
  for (const [key, s] of Object.entries(saved)) {
862
- if (s.turnCount < CC_SESSION_MAX_TURNS) {
863
- docSessions.set(key, s);
864
- }
864
+ if (s.turnCount >= CC_SESSION_MAX_TURNS) continue;
865
+ if (s.lastActiveAt && now - new Date(s.lastActiveAt).getTime() > DOC_SESSION_TTL_MS) continue;
866
+ docSessions.set(key, s);
865
867
  }
866
868
  }
867
869
  } catch { /* optional */ }
@@ -872,6 +874,25 @@ function persistDocSessions() {
872
874
  safeWrite(DOC_SESSIONS_PATH, obj);
873
875
  }
874
876
 
877
+ // Debounced variant — coalesces rapid writes (e.g. back-to-back doc-chat turns)
878
+ let _persistDocSessionsTimer = null;
879
+ function schedulePersistDocSessions() {
880
+ if (_persistDocSessionsTimer) clearTimeout(_persistDocSessionsTimer);
881
+ _persistDocSessionsTimer = setTimeout(() => {
882
+ _persistDocSessionsTimer = null;
883
+ persistDocSessions();
884
+ }, 5000); // 5s debounce — rapid turns produce one write per burst
885
+ }
886
+
887
+ /** Flush any pending debounced write immediately (call on shutdown). */
888
+ function flushPendingDocSessions() {
889
+ if (_persistDocSessionsTimer) {
890
+ clearTimeout(_persistDocSessionsTimer);
891
+ _persistDocSessionsTimer = null;
892
+ persistDocSessions();
893
+ }
894
+ }
895
+
875
896
  // Resolve session from any store (CC global or doc-specific)
876
897
  function resolveSession(store, key) {
877
898
  if (store === 'cc') {
@@ -885,6 +906,11 @@ function resolveSession(store, key) {
885
906
  persistDocSessions();
886
907
  return null;
887
908
  }
909
+ if (s.lastActiveAt && Date.now() - new Date(s.lastActiveAt).getTime() > DOC_SESSION_TTL_MS) {
910
+ docSessions.delete(key);
911
+ persistDocSessions();
912
+ return null;
913
+ }
888
914
  return s;
889
915
  }
890
916
 
@@ -909,7 +935,7 @@ function updateSession(store, key, sessionId, existing) {
909
935
  turnCount: (existing && prev ? prev.turnCount : 0) + 1,
910
936
  _docHash: prev?._docHash || null,
911
937
  });
912
- persistDocSessions();
938
+ schedulePersistDocSessions();
913
939
  }
914
940
  }
915
941
 
@@ -972,7 +998,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
972
998
  safeWrite(path.join(ENGINE_DIR, 'cc-session.json'), ccSession);
973
999
  } else if (sessionKey) {
974
1000
  docSessions.delete(sessionKey);
975
- persistDocSessions();
1001
+ schedulePersistDocSessions();
976
1002
  }
977
1003
  }
978
1004
 
@@ -1007,6 +1033,12 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
1007
1033
  return result;
1008
1034
  }
1009
1035
 
1036
+ // Lightweight content fingerprint — same algorithm used browser-side (no crypto needed)
1037
+ function contentFingerprint(str) {
1038
+ if (!str) return '';
1039
+ return str.length + ':' + str.charCodeAt(0) + ':' + str.charCodeAt(str.length - 1);
1040
+ }
1041
+
1010
1042
  // Doc-specific wrapper — adds document context, parses ---DOCUMENT---
1011
1043
  async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady }) {
1012
1044
  const sessionKey = filePath || title;
@@ -1047,7 +1079,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
1047
1079
  // One-shot call — discard the session ccCall just stored so it cannot
1048
1080
  // bleed into future interactions under the same key.
1049
1081
  docSessions.delete(sessionKey);
1050
- persistDocSessions();
1082
+ schedulePersistDocSessions();
1051
1083
  } else if (result.code === 0 && result.sessionId) {
1052
1084
  // Store doc hash for next call's unchanged check
1053
1085
  const session = resolveSession('doc', sessionKey);
@@ -3221,7 +3253,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3221
3253
  try { shared.sanitizePath(body.filePath, MINIONS_DIR); } catch { return jsonReply(res, 400, { error: 'path must be under minions directory' }); }
3222
3254
  fullPath = path.resolve(MINIONS_DIR, body.filePath);
3223
3255
  const diskContent = safeRead(fullPath);
3224
- if (diskContent !== null) currentContent = diskContent;
3256
+ if (diskContent !== null) {
3257
+ // If client sent a contentHash and it matches disk, skip replacement — client copy is fresh
3258
+ if (body.contentHash && contentFingerprint(diskContent) === body.contentHash) {
3259
+ // body.document is already current — no override needed
3260
+ } else {
3261
+ currentContent = diskContent;
3262
+ }
3263
+ }
3225
3264
  }
3226
3265
 
3227
3266
  const { answer, content, actions } = await ccDocCall({
@@ -4741,7 +4780,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4741
4780
  { method: 'GET', path: /^\/api\/knowledge\/([^/]+)\/([^?]+)/, desc: 'Read a specific knowledge base entry', handler: handleKnowledgeRead },
4742
4781
 
4743
4782
  // Doc chat
4744
- { method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?', handler: handleDocChat },
4783
+ { method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?, contentHash?', handler: handleDocChat },
4745
4784
 
4746
4785
  // Inbox
4747
4786
  { method: 'POST', path: '/api/inbox/persist', desc: 'Promote an inbox item to team notes', params: 'name', handler: handleInboxPersist },
@@ -5102,3 +5141,8 @@ server.on('error', e => {
5102
5141
  }
5103
5142
  process.exit(1);
5104
5143
  });
5144
+
5145
+ // ── Graceful shutdown: flush debounced writes ──────────────────────────────
5146
+ server.on('close', () => flushPendingDocSessions());
5147
+ process.on('SIGTERM', () => { flushPendingDocSessions(); process.exit(0); });
5148
+ process.on('SIGINT', () => { flushPendingDocSessions(); process.exit(0); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1032",
3
+ "version": "0.1.1034",
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"