@yemi33/minions 0.1.2047 → 0.1.2049

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.
@@ -310,6 +310,7 @@ function renderPipelines(pipelines) {
310
310
  const start = _pipelinePage * PIPELINE_PER_PAGE;
311
311
  const pageItems = pipelines.slice(start, start + PIPELINE_PER_PAGE);
312
312
 
313
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: pipeline id, title, trigger, resources, stages)
313
314
  el.innerHTML = pageItems.map(function(p) {
314
315
  const activeRun = _getPipelineActiveRun(p);
315
316
  const lastRun = (p.runs || []).slice(-1)[0];
@@ -351,6 +352,7 @@ function renderPipelines(pipelines) {
351
352
  }).join('');
352
353
 
353
354
  if (pipelines.length > PIPELINE_PER_PAGE) {
355
+ // eslint-disable-next-line no-unsanitized/method -- reason: composed from internal numeric page bounds and fixed pager actions (no user data flows in)
354
356
  el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
355
357
  '<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + PIPELINE_PER_PAGE, pipelines.length) + ' of ' + pipelines.length + '</span>' +
356
358
  '<div class="pr-pager-btns">' +
@@ -474,6 +476,7 @@ function openPipelineDetail(id) {
474
476
  html += '</div>';
475
477
 
476
478
  document.getElementById('modal-title').textContent = 'Pipeline: ' + displayTitle;
479
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes user-controlled stage output before assembling HTML; all other user data wrapped in escHtml() (see dashboard/js/utils.js)
477
480
  document.getElementById('modal-body').innerHTML = html;
478
481
  document.getElementById('modal').classList.add('open');
479
482
 
@@ -684,6 +687,7 @@ function openCreatePipelineModal() {
684
687
  }).join('');
685
688
 
686
689
  document.getElementById('modal-title').textContent = 'New Pipeline';
690
+ // eslint-disable-next-line no-unsanitized/property -- reason: composed from fixed scheduler option arrays and escaped timezone (no user data flows in)
687
691
  document.getElementById('modal-body').innerHTML =
688
692
  '<div style="display:flex;flex-direction:column;gap:10px">' +
689
693
  '<label style="color:var(--text);font-size:var(--text-md)">ID<input id="pl-id" style="' + inputStyle + '" placeholder="e.g. daily-audit-cycle"></label>' +
@@ -785,6 +789,7 @@ function openEditPipelineModal(id) {
785
789
  window._editPipelineId = id;
786
790
 
787
791
  document.getElementById('modal-title').textContent = 'Edit Pipeline: ' + displayTitle;
792
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: pipeline id, title, stages JSON, timezone)
788
793
  document.getElementById('modal-body').innerHTML =
789
794
  '<div style="display:flex;flex-direction:column;gap:10px">' +
790
795
  '<div style="color:var(--muted);font-size:11px">ID: <strong style="color:var(--text)">' + escHtml(id) + '</strong></div>' +
@@ -13,6 +13,7 @@ function openCreatePlanModal() {
13
13
  const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
14
14
 
15
15
  document.getElementById('modal-title').textContent = 'Create Plan';
16
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: project names)
16
17
  document.getElementById('modal-body').innerHTML =
17
18
  '<div style="display:flex;flex-direction:column;gap:10px">' +
18
19
  '<label style="color:var(--text);font-size:var(--text-md)">Title <input id="plan-new-title" style="' + inputStyle + '" placeholder="e.g. Add user authentication with JWT"></label>' +
@@ -383,6 +384,7 @@ function renderPlans(plans) {
383
384
  '</div>';
384
385
  }
385
386
 
387
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan file, summary, project, generatedBy, revision feedback, action target files)
386
388
  el.innerHTML = html;
387
389
  restoreNotifBadges();
388
390
  }
@@ -413,6 +415,7 @@ function openArchivedPlansModal() {
413
415
  }).join('');
414
416
 
415
417
  document.getElementById('modal-title').textContent = 'Archived Plans';
418
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: archived plan file, summary, project, generatedBy)
416
419
  document.getElementById('modal-body').innerHTML = html;
417
420
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
418
421
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -456,6 +459,7 @@ async function planExecute(file, project, btn) {
456
459
  function planReexecuteModal(file, project) {
457
460
  const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
458
461
  document.getElementById('modal-title').textContent = 'Re-execute Plan';
462
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan file, project)
459
463
  document.getElementById('modal-body').innerHTML =
460
464
  '<div style="display:flex;flex-direction:column;gap:10px">' +
461
465
  '<div style="font-size:12px;color:var(--muted)">' + escapeHtml(file) + '</div>' +
@@ -545,6 +549,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
545
549
  if (normalizedFile.endsWith('.json')) {
546
550
  let plan;
547
551
  try { plan = JSON.parse(raw); } catch (e) {
552
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: parse error message, raw plan excerpt)
548
553
  document.getElementById('modal-body').innerHTML = '<p style="color:var(--red)">Failed to parse plan JSON: ' + escapeHtml(e.message) + '</p><pre style="font-size:10px;max-height:200px;overflow:auto">' + escapeHtml((raw || '').slice(0, 500)) + '</pre>';
549
554
  return;
550
555
  }
@@ -632,6 +637,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
632
637
  const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + formatLocalDateTime(lastMod) + '</div>' : '';
633
638
  const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' + modalActions + '</div>';
634
639
 
640
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan title, version label, action target files)
635
641
  document.getElementById('modal-title').innerHTML = escapeHtml(title) + (versionLabel ? ' <span style="font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue)">' + escapeHtml(versionLabel) + '</span>' : '') + lastModLabel + actionBtns;
636
642
  const modalBody = document.getElementById('modal-body');
637
643
  const scrollTop = modalBody.scrollTop;
@@ -640,6 +646,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
640
646
  modalBody.style.fontFamily = 'Consolas, monospace';
641
647
  modalBody.style.whiteSpace = 'pre-wrap';
642
648
  } else {
649
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
643
650
  modalBody.innerHTML = renderMd(text);
644
651
  }
645
652
  modalBody.scrollTop = scrollTop;
@@ -882,6 +889,7 @@ async function planOpenInDocChat(file) {
882
889
  document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
883
890
  document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
884
891
  } else {
892
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
885
893
  document.getElementById('modal-body').innerHTML = renderMd(text);
886
894
  }
887
895
  _modalDocContext = { title: title, content: text, selection: '' };
@@ -932,6 +940,7 @@ async function openVerifyGuide(file) {
932
940
  const content = await fetch('/api/plans/' + encodeURIComponent(normalizedFile)).then(r => r.text());
933
941
  document.getElementById('modal-title').innerHTML = 'Manual Testing Guide' +
934
942
  ' <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px;vertical-align:middle" onclick="openArchivedPrdModal()">Back</button>';
943
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
935
944
  document.getElementById('modal-body').innerHTML = renderMd(content);
936
945
  _modalDocContext = { title: 'Manual Testing Guide', content, selection: '' };
937
946
  _modalFilePath = 'prd/' + normalizedFile; showModalQa();
@@ -44,6 +44,7 @@ async function _savePrdDesc(source, itemId) {
44
44
  });
45
45
  if (res.ok) {
46
46
  _prdDescRawCache = newDesc;
47
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
47
48
  view.innerHTML = newDesc ? renderMd(newDesc) : '<span style="color:var(--muted);font-size:11px">No description</span>';
48
49
  view.style.display = '';
49
50
  editor.style.display = 'none';
@@ -116,6 +117,7 @@ function renderPrd(prd, prog) {
116
117
  ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;margin-left:4px" onclick="planArchive(\'' + escHtml(prdFile) + '\',this)">Archive</button>';
117
118
  }
118
119
  }
120
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: PRD file; status label/color and age text are derived UI state)
119
121
  badge.innerHTML = '<span style="font-weight:600;font-size:11px;color:' + (statusColors[effectiveStatus] || 'var(--muted)') + '">' + (statusLabels[effectiveStatus] || effectiveStatus) + '</span>' +
120
122
  ' <span style="color:var(--muted);font-size:10px">' + (prd.age || '') + '</span>' + actions;
121
123
  } else {
@@ -134,6 +136,7 @@ function renderPrd(prd, prog) {
134
136
  const parts = Object.entries(counts).filter(([, n]) => n > 0).map(([s, n]) =>
135
137
  '<span style="font-size:10px;color:' + (statusColors[s] || 'var(--muted)') + '">' + n + ' ' + (statusLabels[s] || s).toLowerCase() + '</span>'
136
138
  );
139
+ // eslint-disable-next-line no-unsanitized/property -- reason: composed from compile-time constants (no user data flows in)
137
140
  badge.innerHTML = '<span style="font-weight:600;font-size:11px;color:var(--text)">' + existing.length + ' PRDs</span> ' + parts.join(' · ');
138
141
  }
139
142
  section.innerHTML = '';
@@ -644,6 +647,7 @@ function renderPrdProgress(prog) {
644
647
  '</div>';
645
648
  }
646
649
 
650
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() by PRD renderers (fields: item ids/names/descriptions, source, projects, PR links, branches, agent labels)
647
651
  el.innerHTML = html;
648
652
  restoreNotifBadges();
649
653
  }
@@ -686,6 +690,7 @@ function openArchivedPrdModal() {
686
690
  }
687
691
 
688
692
  document.getElementById('modal-title').textContent = 'Archived PRDs';
693
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: archived PRD file, summary, projects, completion date)
689
694
  document.getElementById('modal-body').innerHTML = html;
690
695
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
691
696
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -725,6 +730,7 @@ function showArchivedPrdDetail(idxOrFile) {
725
730
  '</div>';
726
731
 
727
732
  document.getElementById('modal-title').textContent = g.summary || g.file;
733
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() by archived PRD renderers (fields: PRD file, group header, items, stats, E2E links)
728
734
  document.getElementById('modal-body').innerHTML = toggleHtml + content;
729
735
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
730
736
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -816,6 +822,7 @@ async function prdItemEdit(source, itemId) {
816
822
  '</div>';
817
823
 
818
824
  document.getElementById('modal-title').textContent = item.id + ' — ' + (item.name || '').slice(0, 60);
825
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() (fields: PRD item name/description, source, item id, agent, completion summary, PR links)
819
826
  document.getElementById('modal-body').innerHTML = html;
820
827
  document.getElementById('modal-body').style.fontFamily = '';
821
828
  document.getElementById('modal-body').style.whiteSpace = '';
@@ -1003,6 +1010,7 @@ function openArchive(i) {
1003
1010
 
1004
1011
  html += '</div>';
1005
1012
 
1013
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: archive summary, feature ids/names/descriptions/statuses, rationale, affected areas, open questions)
1006
1014
  document.getElementById('modal-body').innerHTML = html;
1007
1015
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
1008
1016
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -170,6 +170,7 @@ function renderPrs(prs) {
170
170
 
171
171
  const tableWrap = el.querySelector('.pr-table-wrap');
172
172
  const savedScroll = tableWrap ? tableWrap.scrollLeft : 0;
173
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all PR user data wrapped in escapeHtml() by prRow() (fields: PR id, title, description, agent, branch, review/build/status labels)
173
174
  el.innerHTML = prTableHtml(rows) + pager;
174
175
  if (savedScroll) {
175
176
  const newWrap = el.querySelector('.pr-table-wrap');
@@ -184,6 +185,7 @@ function openAllPrs() {
184
185
  const modalEl = document.querySelector('#modal .modal');
185
186
  if (modalEl) modalEl.classList.add('modal-wide');
186
187
  document.getElementById('modal-title').textContent = 'All Pull Requests (' + allPrs.length + ')';
188
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all PR user data wrapped in escapeHtml() by prRow() (fields: PR id, title, description, agent, branch, review/build/status labels)
187
189
  document.getElementById('modal-body').innerHTML = prTableHtml(allPrs.map(prRow).join(''));
188
190
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
189
191
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -194,6 +196,7 @@ function openModal(i) {
194
196
  const item = inboxData[i];
195
197
  if (!item) return;
196
198
  document.getElementById('modal-title').textContent = item.name;
199
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes item.content before assembling HTML; item.name is wrapped in escapeHtml() (see dashboard/js/utils.js)
197
200
  document.getElementById('modal-body').innerHTML =
198
201
  '<div style="margin-bottom:12px"><button class="pr-pager-btn" style="font-size:10px;padding:3px 10px" onclick="promoteToKB(\'' + escapeHtml(item.name) + '\')">Add to Knowledge Base</button></div>' +
199
202
  '<div style="font-size:12px;line-height:1.7;color:var(--muted)">' + renderMd(item.content) + '</div>';
@@ -224,6 +227,7 @@ function openAddPrModal() {
224
227
  : '';
225
228
 
226
229
  document.getElementById('modal-title').textContent = 'Link Pull Request';
230
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: project names)
227
231
  document.getElementById('modal-body').innerHTML =
228
232
  '<div style="display:flex;flex-direction:column;gap:10px">' +
229
233
  noProjectsWarning +
@@ -369,6 +369,7 @@ function renderSchedules(schedules) {
369
369
  }
370
370
  }
371
371
 
372
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by schedule renderers (fields: schedule id/title/cron/type/project/agent/description)
372
373
  el.innerHTML = html;
373
374
  }
374
375
 
@@ -382,6 +383,7 @@ function openScheduleDetail(id) {
382
383
  const lastRun = s._lastRun ? formatLocalDateTime(s._lastRun) : 'never';
383
384
  const enabledLabel = s.enabled ? '<span class="pr-badge approved">enabled</span>' : '<span class="pr-badge rejected">disabled</span>';
384
385
 
386
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: schedule title, schedule id)
385
387
  document.getElementById('modal-title').innerHTML = escHtml(s.title || s.id) +
386
388
  ' <div style="display:flex;gap:4px;margin-top:4px">' +
387
389
  '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" onclick="runScheduleNow(\'' + escHtml(s.id) + '\',this)">Run now</button>' +
@@ -402,6 +404,7 @@ function openScheduleDetail(id) {
402
404
  (s.description ? '<div><strong style="color:var(--muted)">Description:</strong><div style="margin-top:4px;padding:8px;background:var(--surface2);border-radius:4px">' + renderMd(s.description) + '</div></div>' : '') +
403
405
  '</div>';
404
406
 
407
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() (fields: schedule id/title/cron/type/priority/project/agent/description/last run)
405
408
  document.getElementById('modal-body').innerHTML = body;
406
409
  document.getElementById('modal-body').style.whiteSpace = 'normal';
407
410
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
@@ -518,6 +521,7 @@ function openCreateScheduleModal() {
518
521
  document.getElementById('modal-title').textContent = 'New Scheduled Task';
519
522
  document.getElementById('modal-body').style.whiteSpace = 'normal';
520
523
  document.getElementById('modal-body').style.fontFamily = '';
524
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _scheduleFormHtml() (fields: project names, agent ids/names)
521
525
  document.getElementById('modal-body').innerHTML = _scheduleFormHtml({}, false);
522
526
  document.getElementById('modal').classList.add('open');
523
527
  _updateCronPreview();
@@ -530,6 +534,7 @@ function openEditScheduleModal(id) {
530
534
  document.getElementById('modal-title').textContent = 'Edit Schedule: ' + id;
531
535
  document.getElementById('modal-body').style.whiteSpace = 'normal';
532
536
  document.getElementById('modal-body').style.fontFamily = '';
537
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _scheduleFormHtml() (fields: schedule id/title/cron/description, project names, agent ids/names)
533
538
  document.getElementById('modal-body').innerHTML = _scheduleFormHtml(sched, true);
534
539
  window._editScheduleId = id;
535
540
  document.getElementById('modal').classList.add('open');
@@ -113,6 +113,7 @@ function renderSkills(skills) {
113
113
  html += '</div>';
114
114
  }
115
115
 
116
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or _jsArg() (fields: skill file/source/dir/name/description/tab labels)
116
117
  el.innerHTML = html;
117
118
  window._lastSkills = skills;
118
119
  }
@@ -131,6 +132,7 @@ function openSkill(file, source, dir) {
131
132
  .then(r => r.text())
132
133
  .then(content => {
133
134
  document.getElementById('modal-title').textContent = file;
135
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
134
136
  document.getElementById('modal-body').innerHTML = renderMd(content);
135
137
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
136
138
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -187,6 +187,7 @@ function renderWatches(watchesData) {
187
187
  '</div>';
188
188
  }
189
189
 
190
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: watch id/target/description/action type/owner/timestamps)
190
191
  el.innerHTML = html;
191
192
  }
192
193
 
@@ -278,6 +279,7 @@ function openWatchDetail(id) {
278
279
  var targetLabel = _targetTypeLabel(w.targetType);
279
280
  var condLabel = _conditionLabel(w.condition);
280
281
 
282
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: watch description/target/id)
281
283
  document.getElementById('modal-title').innerHTML = escHtml(w.description || w.target) +
282
284
  ' <div style="display:flex;gap:4px;margin-top:4px">' +
283
285
  (w.status === 'active' ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" onclick="toggleWatchPause(\'' + escHtml(w.id) + '\',true);closeModal()">Pause</button>' : '') +
@@ -318,6 +320,7 @@ function openWatchDetail(id) {
318
320
  '<div id="watch-history-' + escHtml(w.id) + '"><div style="color:var(--muted);font-style:italic;font-size:11px">Loading history…</div></div>' +
319
321
  '</div>';
320
322
 
323
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by watch detail renderers (fields: watch id/target/owner/description/action params/requires/history/result messages)
321
324
  document.getElementById('modal-body').innerHTML = body;
322
325
  document.getElementById('modal-body').style.whiteSpace = 'normal';
323
326
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
@@ -336,10 +339,12 @@ function openWatchDetail(id) {
336
339
  return res.json();
337
340
  }).then(function(data) {
338
341
  if (!container.isConnected) return; // user closed the modal
342
+ // eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
339
343
  container.innerHTML = _renderWatchHistoryDetail(data && data.history);
340
344
  }).catch(function() {
341
345
  if (!container.isConnected) return;
342
346
  // Fall back to the inline _history from /api/watches (best-effort).
347
+ // eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
343
348
  container.innerHTML = _renderWatchHistoryDetail(w._history || []);
344
349
  });
345
350
  })();
@@ -489,6 +494,7 @@ function _addWatchStepRow(initial) {
489
494
  row.className = 'watch-step-row';
490
495
  row.setAttribute('data-step-idx', String(idx));
491
496
  row.style.cssText = 'border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px;display:flex;flex-direction:column;gap:6px;background:var(--surface)';
497
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: action registry values/labels/descriptions)
492
498
  row.innerHTML =
493
499
  '<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
494
500
  '<select id="watch-step-' + idx + '-type" style="' + inputStyle + ';margin-top:0;flex:1" onchange="_updateWatchStepParamsHint(' + idx + ')">' + actionOpts + '</select>' +
@@ -551,6 +557,7 @@ function _addRequireRow(initial) {
551
557
  row.className = 'watch-require-row';
552
558
  row.setAttribute('data-require-idx', String(idx));
553
559
  row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:6px;align-items:end';
560
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: target type registry values/labels)
554
561
  row.innerHTML =
555
562
  '<label style="color:var(--text);font-size:11px;margin:0">Target<input id="watch-require-' + idx + '-target" placeholder="e.g. PR-123, pipeline-X" style="' + inputStyle + '"></label>' +
556
563
  '<label style="color:var(--text);font-size:11px;margin:0">Target Type<select id="watch-require-' + idx + '-target-type" style="' + inputStyle + '" onchange="_updateRequireConditionOptions(' + idx + ')">' + ttOpts + '</select></label>' +
@@ -576,6 +583,7 @@ function _updateRequireConditionOptions(idx) {
576
583
  if (!ttSel || !condSel || !_watchTargetTypesCache) return;
577
584
  var entry = _watchTargetTypesCache.find(function(t) { return t.value === ttSel.value; });
578
585
  var conditions = (entry && entry.conditions) || [];
586
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: condition registry values, condition labels)
579
587
  condSel.innerHTML = (conditions.length ? '' : '<option value="">— select target type first —</option>') +
580
588
  conditions.map(function(c) {
581
589
  return '<option value="' + escHtml(c) + '">' + escHtml(_conditionLabel(c)) + '</option>';
@@ -589,6 +597,7 @@ function _updateWatchConditionOptions() {
589
597
  if (!ttSel || !condSel || !_watchTargetTypesCache) return;
590
598
  var entry = _watchTargetTypesCache.find(function(t) { return t.value === ttSel.value; });
591
599
  var conditions = (entry && entry.conditions) || [];
600
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: condition registry values, condition labels)
592
601
  condSel.innerHTML = conditions.map(function(c) {
593
602
  return '<option value="' + escHtml(c) + '">' + escHtml(_conditionLabel(c)) + '</option>';
594
603
  }).join('');
@@ -597,6 +606,7 @@ function _updateWatchConditionOptions() {
597
606
  function _renderCreateWatchModal() {
598
607
  document.getElementById('modal-title').innerHTML = 'Create Watch' +
599
608
  ' <button class="pr-pager-btn" style="font-size:10px;padding:2px 12px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="submitWatch()">Create</button>';
609
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _watchFormHtml() (fields: target/action registries, project names, agent ids/names)
600
610
  document.getElementById('modal-body').innerHTML = _watchFormHtml();
601
611
  document.getElementById('modal-body').style.whiteSpace = 'normal';
602
612
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
@@ -149,6 +149,7 @@ function renderWorkItems(items) {
149
149
 
150
150
  const tableWrap = el.querySelector('.pr-table-wrap');
151
151
  const savedScroll = tableWrap ? tableWrap.scrollLeft : 0;
152
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() by wiRow() (fields: work item id/title/source/status/agent/PR links/dates/follow-up metadata)
152
153
  el.innerHTML = html;
153
154
  if (savedScroll) {
154
155
  const newWrap = el.querySelector('.pr-table-wrap');
@@ -200,6 +201,7 @@ async function editWorkItem(id, source) {
200
201
 
201
202
  document.getElementById('modal-title').textContent = 'Edit Work Item ' + id;
202
203
  document.getElementById('modal-body').style.whiteSpace = 'normal';
204
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: title, description, references, acceptance criteria, dependencies, agent ids/names, work item id/source)
203
205
  document.getElementById('modal-body').innerHTML =
204
206
  '<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
205
207
  '<label style="color:var(--text);font-size:var(--text-md)">Title' +
@@ -320,6 +322,7 @@ async function toggleWorkItemArchive() {
320
322
  try {
321
323
  const items = await fetch('/api/work-items/archive').then(r => r.json());
322
324
  if (!items.length) { el.innerHTML = '<p class="empty">No archived work items.</p>'; return; }
325
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: archived work item id/title/type/status/agent)
323
326
  el.innerHTML = '<div style="font-size:10px;color:var(--muted);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Archived (' + items.length + ')</div>' +
324
327
  '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Agent</th><th>Archived</th></tr></thead><tbody>' +
325
328
  items.map(function(i) {
@@ -371,6 +374,7 @@ let _feedbackRating = null;
371
374
  function feedbackWorkItem(id, source) {
372
375
  _feedbackRating = null;
373
376
  document.getElementById('modal-title').textContent = 'Feedback on ' + id;
377
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: work item id, source)
374
378
  document.getElementById('modal-body').innerHTML =
375
379
  '<div style="display:flex;flex-direction:column;gap:16px">' +
376
380
  '<div style="display:flex;gap:16px;justify-content:center">' +
@@ -431,6 +435,7 @@ function openCreateWorkItemModal() {
431
435
  const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
432
436
 
433
437
  document.getElementById('modal-title').textContent = 'Create Work Item';
438
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: agent ids/names, project names)
434
439
  document.getElementById('modal-body').innerHTML =
435
440
  '<div style="display:flex;flex-direction:column;gap:10px">' +
436
441
  '<label style="color:var(--text);font-size:var(--text-md)">Title <input id="wi-new-title" style="' + inputStyle + '" placeholder="What needs to be done?"></label>' +
@@ -646,6 +651,7 @@ function openWorkItemDetail(id) {
646
651
 
647
652
  const initial = needsHydration ? Object.assign({}, cached, { _descriptionLoading: true }) : cached;
648
653
  document.getElementById('modal-title').textContent = initial.title || initial.id;
654
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
649
655
  document.getElementById('modal-body').innerHTML = _wiRenderDetail(initial);
650
656
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
651
657
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -669,6 +675,7 @@ function openWorkItemDetail(id) {
669
675
  merged.description = full.description || cached.description || '';
670
676
  if (Array.isArray(full.acceptanceCriteria)) merged.acceptanceCriteria = full.acceptanceCriteria;
671
677
  if (Array.isArray(full.references)) merged.references = full.references;
678
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
672
679
  document.getElementById('modal-body').innerHTML = _wiRenderDetail(merged);
673
680
  })
674
681
  .catch(function() {
@@ -681,6 +688,7 @@ function openAllWorkItems() {
681
688
  document.getElementById('modal-title').textContent = 'All Work Items (' + allWorkItems.length + ')';
682
689
  const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>' +
683
690
  allWorkItems.map(wiRow).join('') + '</tbody></table></div>';
691
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() by wiRow() (fields: work item id/title/source/status/agent/PR links/dates/follow-up metadata)
684
692
  document.getElementById('modal-body').innerHTML = html;
685
693
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
686
694
  document.getElementById('modal-body').style.whiteSpace = 'normal';
@@ -696,6 +704,7 @@ function viewAgentOutput(logPath) {
696
704
  fetch('/api/agent-output?file=' + encodeURIComponent(logPath))
697
705
  .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
698
706
  .then(function(content) {
707
+ // eslint-disable-next-line no-unsanitized/property -- reason: renderAgentOutput() escapes all user-controlled fields before assembling HTML (see dashboard/js/render-utils.js)
699
708
  document.getElementById('modal-body').innerHTML = renderAgentOutput(content);
700
709
  })
701
710
  .catch(function() {
@@ -351,6 +351,7 @@ async function openSettings() {
351
351
  saveBtn.onclick = saveSettings;
352
352
  actions.insertBefore(saveBtn, actions.lastElementChild);
353
353
  }
354
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: engine, claude, agents, projects, routing, features)
354
355
  document.getElementById('modal-body').innerHTML = '<div id="settings-status" style="font-size:11px;min-height:16px;margin-bottom:4px"></div>' + html;
355
356
  document.getElementById('modal-body').style.fontFamily = '';
356
357
  document.getElementById('modal-body').style.whiteSpace = '';
@@ -411,9 +412,11 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
411
412
  const names = runtimes.length ? runtimes.map(rt => rt.name) : ['copilot'];
412
413
  const currentDefault = engineCfg.defaultCli || 'copilot';
413
414
  const currentCc = engineCfg.ccCli || '';
415
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: runtime names)
414
416
  cliSelect.innerHTML = names.map(n =>
415
417
  '<option value="' + escHtml(n) + '"' + (n === currentDefault ? ' selected' : '') + '>' + escHtml(n) + '</option>'
416
418
  ).join('');
419
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: runtime names)
417
420
  ccCliSelect.innerHTML =
418
421
  '<option value=""' + (!currentCc ? ' selected' : '') + '>Inherit Default CLI</option>' +
419
422
  names.map(n =>
@@ -427,6 +430,7 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
427
430
  const agentId = cell.getAttribute('data-runtime-cli');
428
431
  const agent = (agentsCfg || {})[agentId] || {};
429
432
  const current = agent.cli || '';
433
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, runtime names)
430
434
  cell.innerHTML =
431
435
  '<select data-agent="' + escHtml(agentId) + '" data-field="cli" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px">' +
432
436
  '<option value=""' + (!current ? ' selected' : '') + '>(fleet default)</option>' +
@@ -486,6 +490,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
486
490
  if (!wrap) return;
487
491
  const token = _nextModelLoadToken('runtime', inputId);
488
492
  if (!runtimeName) {
493
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
489
494
  wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="(no runtime selected)" disabled style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--muted);font-size:12px">';
490
495
  return;
491
496
  }
@@ -500,6 +505,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
500
505
  if (!models || models.length === 0) {
501
506
  // Free-text fallback — let the user type anything (custom Anthropic /
502
507
  // OpenAI model IDs, future models, etc.).
508
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
503
509
  wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="Default (CLI chooses)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">';
504
510
  return;
505
511
  }
@@ -516,6 +522,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
516
522
  if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
517
523
  opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom)</option>';
518
524
  }
525
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: model id, model label, currentValue; inputId is an internal fixed DOM id)
519
526
  wrap.innerHTML = '<select id="' + inputId + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' + opts + '</select>';
520
527
  }
521
528
 
@@ -533,6 +540,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
533
540
  const baseStyle = 'width:120px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px';
534
541
  const token = _nextModelLoadToken('agent', agentId);
535
542
  if (!runtimeName) {
543
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, currentValue)
536
544
  cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="(no runtime)" disabled style="' + baseStyle + ';color:var(--muted)">';
537
545
  return;
538
546
  }
@@ -545,6 +553,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
545
553
  if (!_isCurrentModelLoad('agent', agentId, token)) return;
546
554
  const models = Array.isArray(payload.models) ? payload.models : null;
547
555
  if (!models || models.length === 0) {
556
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, runtimeName, currentValue)
548
557
  cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="' + escHtml(runtimeName) + ' default" style="' + baseStyle + '">';
549
558
  return;
550
559
  }
@@ -559,6 +568,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
559
568
  if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
560
569
  opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom — invalid for ' + escHtml(runtimeName) + '?)</option>';
561
570
  }
571
+ // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, model id, model label, currentValue, runtimeName)
562
572
  cell.innerHTML = '<select ' + baseAttrs + ' style="' + baseStyle + '">' + opts + '</select>';
563
573
  }
564
574
 
@@ -919,10 +919,10 @@
919
919
  var flags = (data && data.features) || [];
920
920
 
921
921
  // Build the body as a document fragment so we avoid .innerHTML for
922
- // the dynamic parts (SEC-03 ratchet see DYNAMIC_INNERHTML_BASELINE
923
- // in test/unit.test.js). Static intro paragraph uses innerHTML
924
- // because it carries a <code> tag and is a pure string literal
925
- // (exempted by _isStaticInnerHtmlRhs).
922
+ // the dynamic parts. Static intro paragraph uses innerHTML because
923
+ // it carries a <code> tag and is a pure string literal (exempted
924
+ // by the no-unsanitized rule).
925
+ // (Comment retained for legibility; ratchet test is being retired.)
926
926
  var intro = document.createElement('p');
927
927
  intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
928
928
  var frag = document.createDocumentFragment();
@@ -1342,8 +1342,8 @@
1342
1342
  // Zero-project: a hint to add one. Selection persists per-browser in
1343
1343
  // localStorage and is included in every /api/command-center/stream call.
1344
1344
  // Build the "+ Add" button as a real DOM node (was an HTML string + innerHTML
1345
- // injection — SEC-03 ratchet prefers we avoid that pattern, see
1346
- // test/unit.test.js DYNAMIC_INNERHTML_BASELINE).
1345
+ // injection — the no-unsanitized/property rule prefers we avoid that pattern
1346
+ // entirely for dynamic data).
1347
1347
  function makeContextAddBtn() {
1348
1348
  var btn = document.createElement('button');
1349
1349
  btn.type = 'button';
@@ -1911,8 +1911,8 @@
1911
1911
  listEl.innerHTML = '<div class="history-empty">Nothing has happened yet — Command Center will fill this in.</div>';
1912
1912
  return;
1913
1913
  }
1914
- // Build the feed as DOM nodes so SEC-03's .innerHTML ratchet stays happy
1915
- // (test/unit.test.js DYNAMIC_INNERHTML_BASELINE). Each event becomes
1914
+ // Build the feed as DOM nodes to avoid dynamic .innerHTML
1915
+ // (no-unsanitized/property rule). Each event becomes
1916
1916
  // .history-item > .history-head + .history-title + .history-meta, with
1917
1917
  // the optional PR "open" link constructed as a real <a> node.
1918
1918
  var frag = document.createDocumentFragment();
@@ -19,10 +19,14 @@
19
19
  "id": "completion-fallback-parsers",
20
20
  "description": "parseStructuredCompletion and parseCompletionFieldSummary in engine/lifecycle.js",
21
21
  "file": "engine/lifecycle.js",
22
- "lines": "2747, 2848",
23
- "telemetryGate": "_engine.completionFallbacks must read 0 across sweep window",
22
+ "lines": "2856, 3054",
23
+ "telemetryGate": "_engine.completionFallbacks must read 0 (both fenced and summary counters) across sweepWindowDays starting from sweepStartDate",
24
+ "sweepWindowDays": 14,
25
+ "sweepStartDate": "2026-05-27",
26
+ "sweepRationale": "14 days matches the dead-code audit cadence (one full weekly audit cycle plus a buffer week). Policy decision pending confirmation in open-question #1 of the 2026-05-27 Bug Audit Review meeting conclusion — if the human teammate prefers a different cadence, repin this field and the matching enforcingSweepWindowTest fixture.",
24
27
  "enforcingTest": "test/unit/completion-fallback-telemetry.test.js:217-234",
25
- "notes": "Do NOT set removedAt until telemetry confirms zero usage"
28
+ "enforcingSweepWindowTest": "test/unit/completion-fallback-sweep-window.test.js",
29
+ "notes": "Do NOT set removedAt until telemetry confirms zero usage across the sweepWindowDays from sweepStartDate. The follow-up code-removal PR (dropping parseStructuredCompletion at engine/lifecycle.js:2856, parseCompletionFieldSummary at :3054, and the gated fallback at :3852-3877) is dispatched separately once the window is observed clean."
26
30
  }
27
31
  ]
28
32
 
package/engine/cleanup.js CHANGED
@@ -1075,7 +1075,30 @@ async function runCleanup(config, verbose = false) {
1075
1075
  mutateWorkItems(wiPath, items => {
1076
1076
  for (const item of items) if (_migrateLegacyItem(item)) migrated++;
1077
1077
  });
1078
- if (migrated > 0) log('info', `Migrated ${migrated} legacy status(es) in ${label}`);
1078
+ if (migrated > 0) {
1079
+ log('info', `Migrated ${migrated} legacy status(es) in ${label}`);
1080
+ // P-dcc27d-legacy-status-counter — make the docs/deprecated.json
1081
+ // gate ("legacy-done-aliases" removal after 30 consecutive days zero
1082
+ // migrations) measurable by bumping a rolling-daily counter under
1083
+ // `_engine.legacyStatusMigrations[YYYY-MM-DD]`. Parallel to
1084
+ // `_engine.completionFallbacks` at engine/lifecycle.js:3870-3871.
1085
+ // Inline mutateJsonFileLocked by design — promote to a shared
1086
+ // `mutateMetrics(key, date)` helper if more N-day-silence gates land.
1087
+ try {
1088
+ const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
1089
+ const dateKey = new Date().toISOString().slice(0, 10);
1090
+ mutateJsonFileLocked(metricsPath, (metrics) => {
1091
+ metrics = metrics || {};
1092
+ if (!metrics._engine) metrics._engine = {};
1093
+ if (!metrics._engine.legacyStatusMigrations) metrics._engine.legacyStatusMigrations = {};
1094
+ metrics._engine.legacyStatusMigrations[dateKey] =
1095
+ (metrics._engine.legacyStatusMigrations[dateKey] || 0) + migrated;
1096
+ return metrics;
1097
+ });
1098
+ } catch (err) {
1099
+ log('warn', `telemetry: legacy-status-migration metrics write failed: ${err.message}`);
1100
+ }
1101
+ }
1079
1102
  } catch (e) { log('warn', `migrate legacy statuses (${label}): ${e.message}`); }
1080
1103
  }
1081
1104
  for (const project of projects) _migrateLegacyItemsAt(projectWorkItemsPath(project), `${project.name} work items`);
@@ -4685,7 +4685,6 @@ module.exports = {
4685
4685
  checkForLearnings,
4686
4686
  extractSkillsFromOutput,
4687
4687
  updateAgentHistory,
4688
- reviewFeedbackSourceMatches,
4689
4688
  createReviewFeedbackForAuthor,
4690
4689
  updateMetrics,
4691
4690
  parseAgentOutput,