@yemi33/minions 0.1.1154 → 0.1.1156

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1154 (2026-04-18)
3
+ ## 0.1.1156 (2026-04-18)
4
4
 
5
5
  ### Features
6
6
  - redact ADO tokens and JWTs from engine/log.json writes (#1297)
@@ -9,6 +9,8 @@
9
9
  - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
10
10
 
11
11
  ### Fixes
12
+ - scheduler substitutes {{date}} in title and description (#1275)
13
+ - prevent archived plans from re-triggering via orphaned .backup restore
12
14
  - pinned context optimistic save (closes #1295) (#1316)
13
15
  - re-check throttle state per-PR iteration to avoid stale fixThrottled
14
16
  - preserve buildErrorLog through transient states, persist poll time (#1273)
@@ -27,8 +29,6 @@
27
29
  - cap temp-agent creation at maxConcurrent per tick (#1219)
28
30
  - reassign pending items from unspawned temp agents to idle named agents (#1204) (#1212)
29
31
  - guard undefined agent in pending dispatch loop (closes #1206) (#1210)
30
- - improve fallback meeting conclusion
31
- - remove command center chevron
32
32
 
33
33
  ### Other
34
34
  - refactor: hoist fixThrottled before PR loop and drop underscore prefix
@@ -355,8 +355,12 @@ function archivePlan(planFile, plan, projects, config) {
355
355
  // Remove .backup sidecar — if left behind, safeJson() would restore the pre-completion
356
356
  // snapshot (status: approved, no _completionNotified) on engine restart, re-triggering
357
357
  // plan completion and spawning duplicate verify tasks for already-archived plans.
358
+ // On Windows, the unlink can fail due to file locking; overwrite with archived status
359
+ // as a fallback so a restored backup is inert even if deletion fails.
358
360
  const backupPath = planPath + '.backup';
359
- if (fs.existsSync(backupPath)) fs.unlinkSync(backupPath);
361
+ try { fs.unlinkSync(backupPath); } catch {
362
+ try { fs.writeFileSync(backupPath, JSON.stringify({ status: 'archived' })); } catch { }
363
+ }
360
364
  } catch (err) {
361
365
  log('warn', `Failed to archive PRD ${planFile}: ${err.message}`);
362
366
  }
@@ -24,10 +24,29 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./shared');
27
- const { safeJson, safeWrite, mutateJsonFileLocked, ts, WI_STATUS } = shared;
27
+ const { safeJson, safeWrite, mutateJsonFileLocked, ts, dateStamp, WI_STATUS } = shared;
28
28
 
29
29
  const SCHEDULE_RUNS_PATH = path.join(__dirname, 'schedule-runs.json');
30
30
 
31
+ /**
32
+ * Substitute schedule-time template variables in a string.
33
+ * Currently supports:
34
+ * {{date}} — today's date as YYYY-MM-DD (UTC, via dateStamp())
35
+ *
36
+ * Downstream playbook rendering (engine/playbook.js) is a single-pass replace,
37
+ * so any {{date}} embedded in a schedule's title/description would survive
38
+ * substitution of {{task_description}} and surface as an "unresolved template
39
+ * variables: date" warning plus a literal "{{date}}" in agent filenames.
40
+ * Resolve these fields at schedule time so the work item carries a concrete
41
+ * date string from the moment it's created.
42
+ *
43
+ * Safe on undefined/null/empty/non-string inputs — returns the input unchanged.
44
+ */
45
+ function resolveScheduleTemplateVars(str) {
46
+ if (typeof str !== 'string' || str.length === 0) return str;
47
+ return str.replace(/\{\{date\}\}/g, dateStamp());
48
+ }
49
+
31
50
  // Parse a single cron field into a matcher function.
32
51
  // field: e.g., "*", "5", "1,3,5", "*/15"
33
52
  // min/max: valid range (0-59 for minute, 0-23 for hour, 0-6 for dow)
@@ -141,13 +160,16 @@ function discoverScheduledWork(config) {
141
160
  const lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || null);
142
161
  if (!shouldRunNow(sched, lastRun)) continue;
143
162
 
163
+ // Substitute schedule-time template vars (e.g. {{date}}) before the work
164
+ // item is written — single-pass playbook rendering can't reach placeholders
165
+ // embedded inside task_description, so they must be resolved up front.
144
166
  const workItemId = `sched-${sched.id}-${Date.now()}`;
145
167
  work.push({
146
168
  id: workItemId,
147
- title: sched.title,
169
+ title: resolveScheduleTemplateVars(sched.title),
148
170
  type: sched.type || 'implement',
149
171
  priority: sched.priority || 'medium',
150
- description: sched.description || sched.title,
172
+ description: resolveScheduleTemplateVars(sched.description || sched.title),
151
173
  status: WI_STATUS.PENDING,
152
174
  created: ts(),
153
175
  createdBy: 'scheduler',
@@ -170,4 +192,4 @@ function discoverScheduledWork(config) {
170
192
  return work;
171
193
  }
172
194
 
173
- module.exports = { parseCronExpr, parseCronField, shouldRunNow, discoverScheduledWork, SCHEDULE_RUNS_PATH };
195
+ module.exports = { parseCronExpr, parseCronField, shouldRunNow, discoverScheduledWork, resolveScheduleTemplateVars, SCHEDULE_RUNS_PATH };
package/engine.js CHANGED
@@ -3130,6 +3130,13 @@ async function discoverWork(config) {
3130
3130
  if (fs.existsSync(prdDir)) {
3131
3131
  for (const f of fs.readdirSync(prdDir).filter(f => f.endsWith('.json'))) {
3132
3132
  if (completedPlanCache.has(f)) continue;
3133
+ if (fs.existsSync(path.join(prdDir, 'archive', f))) {
3134
+ // Orphaned backup restore — plan is already archived. Purge the ghost copy.
3135
+ try { fs.unlinkSync(path.join(prdDir, f)); } catch { }
3136
+ try { fs.unlinkSync(path.join(prdDir, f + '.backup')); } catch { }
3137
+ completedPlanCache.add(f);
3138
+ continue;
3139
+ }
3133
3140
  const plan = safeJson(path.join(prdDir, f));
3134
3141
  if (!plan?.missing_features || plan.status === 'completed') {
3135
3142
  if (plan?.status === 'completed') completedPlanCache.add(f);
@@ -3309,6 +3316,13 @@ async function tickInner() {
3309
3316
  const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
3310
3317
  for (const file of prdFiles) {
3311
3318
  if (completedPlanCache.has(file)) continue;
3319
+ if (fs.existsSync(path.join(PRD_DIR, 'archive', file))) {
3320
+ // Orphaned backup restore — plan is already archived. Purge the ghost copy.
3321
+ try { fs.unlinkSync(path.join(PRD_DIR, file)); } catch { }
3322
+ try { fs.unlinkSync(path.join(PRD_DIR, file + '.backup')); } catch { }
3323
+ completedPlanCache.add(file);
3324
+ continue;
3325
+ }
3312
3326
  const plan = safeJson(path.join(PRD_DIR, file));
3313
3327
  if (plan && plan.missing_features && plan.status !== 'completed') {
3314
3328
  const completed = checkPlanCompletion({ item: { sourcePlan: file } }, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1154",
3
+ "version": "0.1.1156",
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"