@yemi33/minions 0.1.525 → 0.1.526

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,8 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.525 (2026-04-07)
3
+ ## 0.1.526 (2026-04-07)
4
4
 
5
5
  ### Features
6
+ - Replace new Date().toISOString() with ts() from shared.js (#411)
7
+ - Add Concurrency & Lock Ordering section to CLAUDE.md (#409)
6
8
  - 9 test isolation verification tests — run last to detect pollution
7
9
 
8
10
  ### Fixes
package/engine/ado.js CHANGED
@@ -173,7 +173,7 @@ async function pollPrStatus(config) {
173
173
  const headCommit = prData.lastMergeSourceCommit?.commitId || prData.sourceRefName || '';
174
174
  if (headCommit && pr._adoHeadCommit !== headCommit) {
175
175
  if (pr._adoHeadCommit) { // skip first detection — only track changes
176
- pr.lastPushedAt = new Date().toISOString();
176
+ pr.lastPushedAt = ts();
177
177
  }
178
178
  pr._adoHeadCommit = headCommit;
179
179
  updated = true;
package/engine/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const shared = require('./shared');
9
- const { safeRead, safeJson, safeWrite, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
9
+ const { safeRead, safeJson, safeWrite, ts, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
10
10
  const queries = require('./queries');
11
11
  const { getConfig, getControl, getDispatch, getAgentStatus,
12
12
  MINIONS_DIR, ENGINE_DIR, AGENTS_DIR, PLANS_DIR, PRD_DIR, CONTROL_PATH, DISPATCH_PATH } = queries;
@@ -174,7 +174,7 @@ const commands = {
174
174
  if (wi && wi.status !== WI_STATUS.DISPATCHED) {
175
175
  wi.status = WI_STATUS.DISPATCHED;
176
176
  wi.dispatched_to = wi.dispatched_to || agentId;
177
- wi.dispatched_at = wi.dispatched_at || new Date().toISOString();
177
+ wi.dispatched_at = wi.dispatched_at || ts();
178
178
  safeWrite(wiPath, wiItems);
179
179
  }
180
180
  } catch (err) { console.log(` Warning: failed to sync work item status: ${err.message}`); }
@@ -273,8 +273,8 @@ const commands = {
273
273
  const wi = items.find(w => w.id === item.meta.item.id);
274
274
  if (wi) {
275
275
  wi.status = status;
276
- if (isSuccess) { wi.completedAt = new Date().toISOString(); delete wi.failReason; }
277
- else { wi.failedAt = new Date().toISOString(); wi.failReason = 'Completed while engine was down'; }
276
+ if (isSuccess) { wi.completedAt = ts(); delete wi.failReason; }
277
+ else { wi.failedAt = ts(); wi.failReason = 'Completed while engine was down'; }
278
278
  safeWrite(wiPath, items);
279
279
  }
280
280
  }
@@ -9,7 +9,7 @@ const path = require('path');
9
9
  const crypto = require('crypto');
10
10
  const shared = require('./shared');
11
11
  const { safeRead, safeWrite, safeUnlink, runFile, cleanChildEnv,
12
- parseStreamJsonOutput, classifyInboxItem, KB_CATEGORIES, log, dateStamp } = shared;
12
+ parseStreamJsonOutput, classifyInboxItem, KB_CATEGORIES, log, ts, dateStamp } = shared;
13
13
  const { trackEngineUsage } = require('./llm');
14
14
  const queries = require('./queries');
15
15
  const { getInboxFiles, getNotes, INBOX_DIR, ENGINE_DIR, MINIONS_DIR,
@@ -440,7 +440,7 @@ function classifyToKnowledgeBase(items) {
440
440
  const dir = path.join(KNOWLEDGE_DIR, cat);
441
441
  if (fs.existsSync(dir)) count += fs.readdirSync(dir).length;
442
442
  }
443
- safeWrite(path.join(ENGINE_DIR, 'kb-checkpoint.json'), JSON.stringify({ count, updatedAt: new Date().toISOString() }));
443
+ safeWrite(path.join(ENGINE_DIR, 'kb-checkpoint.json'), JSON.stringify({ count, updatedAt: ts() }));
444
444
  } catch (err) { log('warn', `KB checkpoint: ${err.message}`); }
445
445
  }
446
446
 
package/engine/github.js CHANGED
@@ -217,7 +217,7 @@ async function pollPrStatus(config) {
217
217
  // Track head SHA changes to detect new pushes (used for review re-dispatch gating)
218
218
  if (prData.head?.sha && pr.headSha !== prData.head.sha) {
219
219
  pr.headSha = prData.head.sha;
220
- pr.lastPushedAt = new Date().toISOString();
220
+ pr.lastPushedAt = ts();
221
221
  updated = true;
222
222
  }
223
223
 
@@ -121,7 +121,7 @@ function checkPlanCompletion(meta, config) {
121
121
  ``,
122
122
  `**Project:** ${plan.project || 'Unknown'}`,
123
123
  `**Strategy:** ${plan.branch_strategy || 'parallel'}`,
124
- `**Completed:** ${new Date().toISOString().slice(0, 16).replace('T', ' ')}`,
124
+ `**Completed:** ${ts().slice(0, 16).replace('T', ' ')}`,
125
125
  `**Runtime:** ${runtimeMin >= 60 ? Math.floor(runtimeMin / 60) + 'h ' + (runtimeMin % 60) + 'm' : runtimeMin + 'm'}`,
126
126
  ``,
127
127
  `## Results`,
@@ -1136,7 +1136,7 @@ function handleDecompositionResult(stdout, meta, config) {
1136
1136
  sourcePlan: p.sourcePlan,
1137
1137
  branchStrategy: p.branchStrategy,
1138
1138
  featureBranch: p.featureBranch,
1139
- created: new Date().toISOString(),
1139
+ created: ts(),
1140
1140
  createdBy: 'decomposition',
1141
1141
  });
1142
1142
  }
@@ -1162,7 +1162,7 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1162
1162
  if (isSuccess && sessionId && agentId && !agentId.startsWith('temp-')) {
1163
1163
  try {
1164
1164
  shared.safeWrite(path.join(AGENTS_DIR, agentId, 'session.json'), {
1165
- sessionId, dispatchId: dispatchItem.id, savedAt: new Date().toISOString(),
1165
+ sessionId, dispatchId: dispatchItem.id, savedAt: ts(),
1166
1166
  branch: dispatchItem.meta?.branch || null,
1167
1167
  });
1168
1168
  } catch (err) { log('warn', `Session save: ${err.message}`); }
package/engine/llm.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  const path = require('path');
7
7
  const shared = require('./shared');
8
- const { safeWrite, safeUnlink, uid, runFile, cleanChildEnv, parseStreamJsonOutput, mutateJsonFileLocked } = shared;
8
+ const { safeWrite, safeUnlink, uid, ts, runFile, cleanChildEnv, parseStreamJsonOutput, mutateJsonFileLocked } = shared;
9
9
 
10
10
  const MINIONS_DIR = shared.MINIONS_DIR;
11
11
  const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
@@ -28,7 +28,7 @@ function trackEngineUsage(category, usage) {
28
28
  cat.cacheRead += usage.cacheRead || 0;
29
29
  cat.cacheCreation = (cat.cacheCreation || 0) + (usage.cacheCreation || 0);
30
30
 
31
- const today = new Date().toISOString().slice(0, 10);
31
+ const today = ts().slice(0, 10);
32
32
  if (!metrics._daily) metrics._daily = {};
33
33
  if (!metrics._daily[today]) metrics._daily[today] = { costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, tasks: 0 };
34
34
  const daily = metrics._daily[today];
package/engine/meeting.js CHANGED
@@ -6,7 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const shared = require('./shared');
9
- const { safeJson, safeWrite, safeRead, uid, log, ENGINE_DEFAULTS, WORK_TYPE, DISPATCH_RESULT } = shared;
9
+ const { safeJson, safeWrite, safeRead, uid, log, ts, ENGINE_DEFAULTS, WORK_TYPE, DISPATCH_RESULT } = shared;
10
10
  const queries = require('./queries');
11
11
  const { getDispatch, getConfig } = queries;
12
12
  const { renderPlaybook } = require('./playbook');
@@ -51,8 +51,8 @@ function createMeeting({ title, agenda, participants }) {
51
51
  round: 1,
52
52
  participants: participants || [],
53
53
  createdBy: 'human',
54
- createdAt: new Date().toISOString(),
55
- roundStartedAt: new Date().toISOString(),
54
+ createdAt: ts(),
55
+ roundStartedAt: ts(),
56
56
  findings: {},
57
57
  debate: {},
58
58
  conclusion: null,
@@ -209,16 +209,16 @@ function collectMeetingFindings(meetingId, agentId, roundName, output) {
209
209
  const content = rawContent;
210
210
 
211
211
  if (roundName === 'investigate') {
212
- meeting.findings[agentId] = { content, submittedAt: new Date().toISOString() };
213
- meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'finding', content, at: new Date().toISOString() });
212
+ meeting.findings[agentId] = { content, submittedAt: ts() };
213
+ meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'finding', content, at: ts() });
214
214
  } else if (roundName === 'debate') {
215
- meeting.debate[agentId] = { content, submittedAt: new Date().toISOString() };
216
- meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'debate', content, at: new Date().toISOString() });
215
+ meeting.debate[agentId] = { content, submittedAt: ts() };
216
+ meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'debate', content, at: ts() });
217
217
  } else if (roundName === 'conclude') {
218
- meeting.conclusion = { content, agent: agentId, submittedAt: new Date().toISOString() };
219
- meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'conclusion', content, at: new Date().toISOString() });
218
+ meeting.conclusion = { content, agent: agentId, submittedAt: ts() };
219
+ meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'conclusion', content, at: ts() });
220
220
  meeting.status = 'completed';
221
- meeting.completedAt = new Date().toISOString();
221
+ meeting.completedAt = ts();
222
222
 
223
223
  // Write transcript to inbox so agents learn from it (slug-based dedup)
224
224
  try {
@@ -246,12 +246,12 @@ function collectMeetingFindings(meetingId, agentId, roundName, output) {
246
246
  if (meeting.status === 'investigating') {
247
247
  meeting.status = 'debating';
248
248
  meeting.round = 2;
249
- meeting.roundStartedAt = new Date().toISOString();
249
+ meeting.roundStartedAt = ts();
250
250
  log('info', `Meeting ${meetingId}: all findings in — advancing to debate`);
251
251
  } else if (meeting.status === 'debating') {
252
252
  meeting.status = 'concluding';
253
253
  meeting.round = 3;
254
- meeting.roundStartedAt = new Date().toISOString();
254
+ meeting.roundStartedAt = ts();
255
255
  log('info', `Meeting ${meetingId}: all debate responses in — advancing to conclusion`);
256
256
  }
257
257
  }
@@ -263,7 +263,7 @@ function addMeetingNote(meetingId, note) {
263
263
  const meeting = getMeeting(meetingId);
264
264
  if (!meeting) return null;
265
265
  meeting.humanNotes.push(note);
266
- meeting.transcript.push({ round: meeting.round, agent: 'human', type: 'note', content: note, at: new Date().toISOString() });
266
+ meeting.transcript.push({ round: meeting.round, agent: 'human', type: 'note', content: note, at: ts() });
267
267
  saveMeeting(meeting);
268
268
  return meeting;
269
269
  }
@@ -279,7 +279,7 @@ function _killMeetingDispatches(meetingId) {
279
279
  dp.active = (dp.active || []).filter(d => d.meta?.meetingId !== meetingId);
280
280
  dp.completed = dp.completed || [];
281
281
  for (const d of toKill) {
282
- dp.completed.push({ ...d, result: DISPATCH_RESULT.ERROR, reason: 'Meeting ended/advanced by human', completed_at: new Date().toISOString() });
282
+ dp.completed.push({ ...d, result: DISPATCH_RESULT.ERROR, reason: 'Meeting ended/advanced by human', completed_at: ts() });
283
283
  }
284
284
  if (dp.completed.length > 100) dp.completed = dp.completed.slice(-100);
285
285
  return dp;
@@ -295,9 +295,9 @@ function advanceMeetingRound(meetingId) {
295
295
  _killMeetingDispatches(meetingId);
296
296
  if (meeting.status === 'investigating') { meeting.status = 'debating'; meeting.round = 2; }
297
297
  else if (meeting.status === 'debating') { meeting.status = 'concluding'; meeting.round = 3; }
298
- else if (meeting.status === 'concluding') { meeting.status = 'completed'; meeting.completedAt = new Date().toISOString(); }
298
+ else if (meeting.status === 'concluding') { meeting.status = 'completed'; meeting.completedAt = ts(); }
299
299
  else return meeting; // no change
300
- meeting.roundStartedAt = new Date().toISOString();
300
+ meeting.roundStartedAt = ts();
301
301
  saveMeeting(meeting);
302
302
  return meeting;
303
303
  }
@@ -307,7 +307,7 @@ function endMeeting(meetingId) {
307
307
  if (!meeting) return null;
308
308
  _killMeetingDispatches(meetingId);
309
309
  meeting.status = 'completed';
310
- meeting.completedAt = new Date().toISOString();
310
+ meeting.completedAt = ts();
311
311
  saveMeeting(meeting);
312
312
  return meeting;
313
313
  }
@@ -316,7 +316,7 @@ function archiveMeeting(id) {
316
316
  const meeting = getMeeting(id);
317
317
  if (!meeting) return null;
318
318
  meeting.status = 'archived';
319
- meeting.archivedAt = new Date().toISOString();
319
+ meeting.archivedAt = ts();
320
320
  saveMeeting(meeting);
321
321
  return meeting;
322
322
  }
@@ -364,17 +364,17 @@ function checkMeetingTimeouts(config) {
364
364
 
365
365
  if (meeting.status === 'investigating') {
366
366
  log('warn', `Meeting ${meeting.id}: round 1 timed out after ${Math.round(elapsed / 60000)}min — ${respondedCount}/${totalCount} responded, advancing to debate`);
367
- meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round 1 timed out — ${respondedCount}/${totalCount} findings received`, at: new Date().toISOString() });
367
+ meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round 1 timed out — ${respondedCount}/${totalCount} findings received`, at: ts() });
368
368
  meeting.status = 'debating';
369
369
  meeting.round = 2;
370
- meeting.roundStartedAt = new Date().toISOString();
370
+ meeting.roundStartedAt = ts();
371
371
  saveMeeting(meeting);
372
372
  } else if (meeting.status === 'debating') {
373
373
  log('warn', `Meeting ${meeting.id}: round 2 timed out after ${Math.round(elapsed / 60000)}min — ${respondedCount}/${totalCount} responded, advancing to conclusion`);
374
- meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round 2 timed out — ${respondedCount}/${totalCount} debate responses received`, at: new Date().toISOString() });
374
+ meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round 2 timed out — ${respondedCount}/${totalCount} debate responses received`, at: ts() });
375
375
  meeting.status = 'concluding';
376
376
  meeting.round = 3;
377
- meeting.roundStartedAt = new Date().toISOString();
377
+ meeting.roundStartedAt = ts();
378
378
  saveMeeting(meeting);
379
379
  } else if (meeting.status === 'concluding') {
380
380
  log('warn', `Meeting ${meeting.id}: conclusion round timed out after ${Math.round(elapsed / 60000)}min — auto-summarizing`);
@@ -386,10 +386,10 @@ function checkMeetingTimeouts(config) {
386
386
  `**${(config.agents || {})[agent]?.name || agent}**: ${(d.content || '').slice(0, 200)}`
387
387
  ).join('\n');
388
388
  const autoConclusion = `*Auto-generated — conclusion round timed out.*\n\n## Key Findings\n${findingsSummary || '(none)'}\n\n## Debate Summary\n${debateSummary || '(none)'}`;
389
- meeting.conclusion = { content: autoConclusion, agent: 'system', submittedAt: new Date().toISOString() };
390
- meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'conclusion', content: autoConclusion, at: new Date().toISOString() });
389
+ meeting.conclusion = { content: autoConclusion, agent: 'system', submittedAt: ts() };
390
+ meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'conclusion', content: autoConclusion, at: ts() });
391
391
  meeting.status = 'completed';
392
- meeting.completedAt = new Date().toISOString();
392
+ meeting.completedAt = ts();
393
393
 
394
394
  // Write transcript to inbox (same as normal conclusion path)
395
395
  try {
@@ -9,7 +9,7 @@ const path = require('path');
9
9
  const shared = require('./shared');
10
10
  const queries = require('./queries');
11
11
 
12
- const { safeJson, safeRead, getProjects, log, dateStamp, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT } = shared;
12
+ const { safeJson, safeRead, getProjects, log, ts, dateStamp, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT } = shared;
13
13
  const { getConfig, getDispatch, getNotes, getAgentCharter, getPrs, AGENTS_DIR } = queries;
14
14
 
15
15
  const MINIONS_DIR = path.resolve(__dirname, '..');
@@ -241,7 +241,7 @@ function renderPlaybook(type, vars) {
241
241
  content += `**Never delete, move, or overwrite files in \`knowledge/\`.** The sweep (consolidation engine) is the only process that writes to \`knowledge/\`. If you think a KB file is wrong, note it in your learnings file — do not touch \`knowledge/\` directly.\n`;
242
242
 
243
243
  // Inject learnings requirement
244
- const timeStamp = new Date().toISOString().slice(11, 16).replace(':', '');
244
+ const timeStamp = ts().slice(11, 16).replace(':', '');
245
245
  const inboxSlug = [vars.agent_id || 'agent', vars.task_id || '', dateStamp(), timeStamp].filter(Boolean).join('-');
246
246
  content += `\n\n---\n\n## REQUIRED: Write Learnings\n\n`;
247
247
  content += `After completing your task, write **one** findings file to:\n`;
@@ -24,7 +24,7 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./shared');
27
- const { safeJson, safeWrite, mutateJsonFileLocked, WI_STATUS } = shared;
27
+ const { safeJson, safeWrite, mutateJsonFileLocked, ts, WI_STATUS } = shared;
28
28
 
29
29
  const SCHEDULE_RUNS_PATH = path.join(__dirname, 'schedule-runs.json');
30
30
 
@@ -126,7 +126,7 @@ function discoverScheduledWork(config) {
126
126
  priority: sched.priority || 'medium',
127
127
  description: sched.description || sched.title,
128
128
  status: WI_STATUS.PENDING,
129
- created: new Date().toISOString(),
129
+ created: ts(),
130
130
  createdBy: 'scheduler',
131
131
  agent: sched.agent || null,
132
132
  project: sched.project || null,
@@ -134,7 +134,7 @@ function discoverScheduledWork(config) {
134
134
  });
135
135
 
136
136
  // Record run time inside the lock
137
- runs[sched.id] = new Date().toISOString();
137
+ runs[sched.id] = ts();
138
138
  }
139
139
  }, { defaultValue: {} });
140
140
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
- const { exec, runFile, cleanChildEnv, killGracefully, killImmediate } = require('./shared');
11
+ const { exec, runFile, cleanChildEnv, killGracefully, killImmediate, ts } = require('./shared');
12
12
 
13
13
  const [,, promptFile, sysPromptFile, ...extraArgs] = process.argv;
14
14
 
@@ -83,7 +83,7 @@ if (!claudeBin) {
83
83
  const tmpDir = path.join(__dirname, 'tmp');
84
84
  if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
85
85
  const debugPath = path.join(tmpDir, 'spawn-debug.log');
86
- fs.writeFileSync(debugPath, `spawn-agent.js at ${new Date().toISOString()}\nclaudeBin=${claudeBin || 'not found'}\nnative=${claudeIsNative}\nprompt=${promptFile}\nsysPrompt=${sysPromptFile}\nextraArgs=${extraArgs.join(' ')}\n`);
86
+ fs.writeFileSync(debugPath, `spawn-agent.js at ${ts()}\nclaudeBin=${claudeBin || 'not found'}\nnative=${claudeIsNative}\nprompt=${promptFile}\nsysPrompt=${sysPromptFile}\nextraArgs=${extraArgs.join(' ')}\n`);
87
87
 
88
88
  // When resuming a session, skip system prompt (it's baked into the session)
89
89
  const isResume = extraArgs.includes('--resume');
@@ -119,7 +119,7 @@ if (_sysPromptFileSupported === null) {
119
119
  ? spawnSync(claudeBin, ['--help'], { encoding: 'utf8', timeout: 10000, windowsHide: true })
120
120
  : spawnSync(process.execPath, [claudeBin, '--help'], { encoding: 'utf8', timeout: 10000, windowsHide: true });
121
121
  _sysPromptFileSupported = (testResult.stdout || '').includes('system-prompt-file');
122
- try { fs.writeFileSync(capsCachePath, JSON.stringify({ claudeBin, sysPromptFile: _sysPromptFileSupported, checkedAt: new Date().toISOString() })); } catch { /* optional */ }
122
+ try { fs.writeFileSync(capsCachePath, JSON.stringify({ claudeBin, sysPromptFile: _sysPromptFileSupported, checkedAt: ts() })); } catch { /* optional */ }
123
123
  } catch { _sysPromptFileSupported = true; /* assume supported */ }
124
124
  }
125
125
  if (!isResume) try {
package/engine.js CHANGED
@@ -24,7 +24,7 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./engine/shared');
27
- const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS,
27
+ const { exec, execSilent, runFile, ts, ENGINE_DEFAULTS: DEFAULTS,
28
28
  WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
29
29
  const queries = require('./engine/queries');
30
30
 
@@ -542,7 +542,7 @@ function spawnAgent(dispatchItem, config) {
542
542
  if (obj.session_id) {
543
543
  procInfo.sessionId = obj.session_id;
544
544
  safeWrite(path.join(AGENTS_DIR, agentId, 'session.json'), {
545
- sessionId: obj.session_id, dispatchId: id, savedAt: new Date().toISOString(), branch: branchName
545
+ sessionId: obj.session_id, dispatchId: id, savedAt: ts(), branch: branchName
546
546
  });
547
547
  break;
548
548
  }
@@ -1098,7 +1098,7 @@ function materializePlansAsWorkItems(config) {
1098
1098
  log('info', `Source plan ${plan.source_plan} updated — re-syncing PRD ${file}`);
1099
1099
  autoCleanPrdWorkItems(file, config);
1100
1100
  plan.sourcePlanModifiedAt = new Date(sourceMtime).toISOString();
1101
- plan.lastSyncedFromPlan = new Date().toISOString();
1101
+ plan.lastSyncedFromPlan = ts();
1102
1102
 
1103
1103
  // Handle PRD based on current status
1104
1104
  const prdStatus = plan.status || (plan.requires_approval ? 'awaiting-approval' : null);
@@ -1170,7 +1170,7 @@ function materializePlansAsWorkItems(config) {
1170
1170
  if (planStatus === 'awaiting-approval') {
1171
1171
  if (config.engine?.autoApprovePlans) {
1172
1172
  plan.status = 'approved';
1173
- plan.approvedAt = new Date().toISOString();
1173
+ plan.approvedAt = ts();
1174
1174
  plan.approvedBy = 'auto-mode';
1175
1175
  safeWrite(path.join(PRD_DIR, file), plan);
1176
1176
  log('info', `Auto-approved plan: ${file}`);
@@ -1452,7 +1452,7 @@ function discoverFromPrs(config, project) {
1452
1452
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) {
1453
1453
  // Coalesce: save feedback for next dispatch
1454
1454
  if (pr.humanFeedback?.feedbackContent) {
1455
- setCooldownWithContext(key, { feedbackContent: pr.humanFeedback.feedbackContent, timestamp: new Date().toISOString() });
1455
+ setCooldownWithContext(key, { feedbackContent: pr.humanFeedback.feedbackContent, timestamp: ts() });
1456
1456
  }
1457
1457
  continue;
1458
1458
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.525",
3
+ "version": "0.1.526",
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"