@zigrivers/scaffold 3.18.1 → 3.20.0

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.
Files changed (99) hide show
  1. package/dist/cli/commands/adopt.d.ts.map +1 -1
  2. package/dist/cli/commands/adopt.js +6 -2
  3. package/dist/cli/commands/adopt.js.map +1 -1
  4. package/dist/cli/commands/complete.d.ts.map +1 -1
  5. package/dist/cli/commands/complete.js +1 -1
  6. package/dist/cli/commands/complete.js.map +1 -1
  7. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  8. package/dist/cli/commands/dashboard.js +104 -26
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/cli/commands/dashboard.test.js +210 -0
  11. package/dist/cli/commands/dashboard.test.js.map +1 -1
  12. package/dist/cli/commands/info.d.ts.map +1 -1
  13. package/dist/cli/commands/info.js +2 -2
  14. package/dist/cli/commands/info.js.map +1 -1
  15. package/dist/cli/commands/next.d.ts.map +1 -1
  16. package/dist/cli/commands/next.js +7 -2
  17. package/dist/cli/commands/next.js.map +1 -1
  18. package/dist/cli/commands/next.test.js +95 -0
  19. package/dist/cli/commands/next.test.js.map +1 -1
  20. package/dist/cli/commands/reset.d.ts.map +1 -1
  21. package/dist/cli/commands/reset.js +1 -1
  22. package/dist/cli/commands/reset.js.map +1 -1
  23. package/dist/cli/commands/rework.d.ts.map +1 -1
  24. package/dist/cli/commands/rework.js +6 -1
  25. package/dist/cli/commands/rework.js.map +1 -1
  26. package/dist/cli/commands/run.d.ts.map +1 -1
  27. package/dist/cli/commands/run.js +1 -1
  28. package/dist/cli/commands/run.js.map +1 -1
  29. package/dist/cli/commands/skip.d.ts.map +1 -1
  30. package/dist/cli/commands/skip.js +1 -1
  31. package/dist/cli/commands/skip.js.map +1 -1
  32. package/dist/cli/commands/status.d.ts.map +1 -1
  33. package/dist/cli/commands/status.js +14 -5
  34. package/dist/cli/commands/status.js.map +1 -1
  35. package/dist/cli/commands/status.test.js +60 -0
  36. package/dist/cli/commands/status.test.js.map +1 -1
  37. package/dist/core/pipeline/graph-hash.d.ts +18 -0
  38. package/dist/core/pipeline/graph-hash.d.ts.map +1 -0
  39. package/dist/core/pipeline/graph-hash.js +35 -0
  40. package/dist/core/pipeline/graph-hash.js.map +1 -0
  41. package/dist/core/pipeline/graph-hash.test.d.ts +2 -0
  42. package/dist/core/pipeline/graph-hash.test.d.ts.map +1 -0
  43. package/dist/core/pipeline/graph-hash.test.js +107 -0
  44. package/dist/core/pipeline/graph-hash.test.js.map +1 -0
  45. package/dist/core/pipeline/read-eligible.d.ts +20 -0
  46. package/dist/core/pipeline/read-eligible.d.ts.map +1 -0
  47. package/dist/core/pipeline/read-eligible.js +28 -0
  48. package/dist/core/pipeline/read-eligible.js.map +1 -0
  49. package/dist/core/pipeline/read-eligible.test.d.ts +2 -0
  50. package/dist/core/pipeline/read-eligible.test.d.ts.map +1 -0
  51. package/dist/core/pipeline/read-eligible.test.js +99 -0
  52. package/dist/core/pipeline/read-eligible.test.js.map +1 -0
  53. package/dist/core/pipeline/resolver.d.ts.map +1 -1
  54. package/dist/core/pipeline/resolver.js +24 -1
  55. package/dist/core/pipeline/resolver.js.map +1 -1
  56. package/dist/core/pipeline/resolver.test.js +86 -0
  57. package/dist/core/pipeline/resolver.test.js.map +1 -1
  58. package/dist/core/pipeline/types.d.ts +5 -0
  59. package/dist/core/pipeline/types.d.ts.map +1 -1
  60. package/dist/dashboard/generator.d.ts +57 -0
  61. package/dist/dashboard/generator.d.ts.map +1 -1
  62. package/dist/dashboard/generator.js +125 -1
  63. package/dist/dashboard/generator.js.map +1 -1
  64. package/dist/dashboard/multi-service.test.d.ts +2 -0
  65. package/dist/dashboard/multi-service.test.d.ts.map +1 -0
  66. package/dist/dashboard/multi-service.test.js +535 -0
  67. package/dist/dashboard/multi-service.test.js.map +1 -0
  68. package/dist/dashboard/template.d.ts +2 -1
  69. package/dist/dashboard/template.d.ts.map +1 -1
  70. package/dist/dashboard/template.js +400 -0
  71. package/dist/dashboard/template.js.map +1 -1
  72. package/dist/e2e/eligible-cache.test.d.ts +2 -0
  73. package/dist/e2e/eligible-cache.test.d.ts.map +1 -0
  74. package/dist/e2e/eligible-cache.test.js +104 -0
  75. package/dist/e2e/eligible-cache.test.js.map +1 -0
  76. package/dist/state/root-counter-reader.d.ts +11 -0
  77. package/dist/state/root-counter-reader.d.ts.map +1 -0
  78. package/dist/state/root-counter-reader.js +25 -0
  79. package/dist/state/root-counter-reader.js.map +1 -0
  80. package/dist/state/root-counter-reader.test.d.ts +2 -0
  81. package/dist/state/root-counter-reader.test.d.ts.map +1 -0
  82. package/dist/state/root-counter-reader.test.js +43 -0
  83. package/dist/state/root-counter-reader.test.js.map +1 -0
  84. package/dist/state/state-manager.d.ts +23 -2
  85. package/dist/state/state-manager.d.ts.map +1 -1
  86. package/dist/state/state-manager.js +57 -5
  87. package/dist/state/state-manager.js.map +1 -1
  88. package/dist/state/state-manager.test.js +246 -1
  89. package/dist/state/state-manager.test.js.map +1 -1
  90. package/dist/types/state.d.ts +19 -0
  91. package/dist/types/state.d.ts.map +1 -1
  92. package/dist/types/state.test.d.ts +2 -0
  93. package/dist/types/state.test.d.ts.map +1 -0
  94. package/dist/types/state.test.js +40 -0
  95. package/dist/types/state.test.js.map +1 -0
  96. package/dist/wizard/wizard.d.ts.map +1 -1
  97. package/dist/wizard/wizard.js +3 -1
  98. package/dist/wizard/wizard.js.map +1 -1
  99. package/package.json +1 -1
@@ -828,4 +828,404 @@ body.modal-open { overflow: hidden; }
828
828
  </html>`;
829
829
  }
830
830
  /* eslint-enable max-len */
831
+ /* eslint-disable max-len */
832
+ export function buildMultiServiceTemplate(dataJson, data) {
833
+ const generatedAt = new Date(data.generatedAt);
834
+ const ageMs = Date.now() - generatedAt.getTime();
835
+ const staleNotice = ageMs > 3600000
836
+ ? '<div id="stale-notice" class="stale-notice">&#9888; Data may be stale (generated more than 1 hour ago)</div>'
837
+ : '';
838
+ const methodology = escapeHtml(data.methodology);
839
+ const avgPct = data.aggregate.averagePercentage;
840
+ const serviceCards = data.services.map(svc => {
841
+ const name = escapeHtml(svc.name);
842
+ const projectType = escapeHtml(svc.projectType);
843
+ const pct = svc.percentage;
844
+ // "Complete" badge requires pipeline steps to have existed AND all to be done.
845
+ // total=0 (skeleton for missing state.json) renders "Not started" instead.
846
+ const isNotStarted = svc.total === 0;
847
+ const isComplete = !isNotStarted && svc.currentPhaseNumber === null;
848
+ const phaseLine = isComplete
849
+ ? '<span class="service-complete-badge">Complete</span>'
850
+ : isNotStarted
851
+ ? '<span class="service-not-started-badge">Not started</span>'
852
+ : `<span class="service-phase">Phase ${svc.currentPhaseNumber ?? ''}: ${escapeHtml(svc.currentPhaseName ?? '')}</span>`;
853
+ const nextLine = svc.nextEligibleSlug
854
+ ? `<div class="service-next"><span class="service-next-label">Next:</span> <span class="service-next-slug">${escapeHtml(svc.nextEligibleSlug)}</span>${svc.nextEligibleSummary ? ` <span class="service-next-summary">${escapeHtml(svc.nextEligibleSummary)}</span>` : ''}</div>`
855
+ : '';
856
+ // Command copy uses data-copy attribute + JS event listener (see script below).
857
+ // Avoids inline onclick XSS vectors where attacker-controlled svc.name could
858
+ // escape the single-quoted JS string (Codex MMR P1 — v3.20.0 PR #292).
859
+ const cmdRaw = `scaffold dashboard --service ${svc.name}`;
860
+ const cmdAttr = escapeHtml(cmdRaw);
861
+ return [
862
+ `<div class="service-card" data-service="${name}" data-copy="${cmdAttr}">`,
863
+ ' <div class="service-card-head">',
864
+ ` <div class="service-name">${name}</div>`,
865
+ ` <span class="service-type-pill">${projectType}</span>`,
866
+ ' </div>',
867
+ ' <div class="service-progress-row">',
868
+ ` <div class="service-progress-bar"><div class="service-progress-fill" style="width:${pct}%"></div></div>`,
869
+ ` <span class="service-pct">${pct}%</span>`,
870
+ ' </div>',
871
+ ' <div class="service-counts">',
872
+ ` <span class="count-pill count-completed">${svc.completed} done</span>`,
873
+ svc.skipped > 0 ? ` <span class="count-pill count-skipped">${svc.skipped} skipped</span>` : '',
874
+ svc.inProgress > 0 ? ` <span class="count-pill count-in-progress">${svc.inProgress} active</span>` : '',
875
+ svc.pending > 0 ? ` <span class="count-pill count-pending">${svc.pending} pending</span>` : '',
876
+ ' </div>',
877
+ ` <div class="service-phase-line">${phaseLine}</div>`,
878
+ nextLine,
879
+ ` <div class="service-cmd-hint"><code>${escapeHtml(cmdRaw)}</code><span class="service-cmd-copy">Click to copy</span></div>`,
880
+ '</div>',
881
+ ].filter(Boolean).join('\n');
882
+ }).join('\n');
883
+ const servicesByPhase = data.aggregate.servicesByPhase
884
+ .filter(p => p.reachedCount > 0)
885
+ .map(p => [
886
+ '<div class="phase-indicator">',
887
+ ` <div class="phase-indicator-num">P${p.phaseNumber}</div>`,
888
+ ` <div class="phase-indicator-name">${escapeHtml(p.phaseName)}</div>`,
889
+ ` <div class="phase-indicator-count">${p.reachedCount} / ${data.aggregate.totalServices}</div>`,
890
+ '</div>',
891
+ ].join('\n')).join('\n');
892
+ return `<!DOCTYPE html>
893
+ <html lang="en">
894
+ <head>
895
+ <meta charset="UTF-8">
896
+ <meta name="viewport" content="width=device-width, initial-scale=1">
897
+ <title>Scaffold Multi-Service Dashboard</title>
898
+ <script id="scaffold-data" type="application/json">
899
+ ${dataJson}
900
+ </script>
901
+ <style>
902
+ [data-theme="dark"] {
903
+ --bg: #0f172a;
904
+ --card-bg: #1e293b;
905
+ --border: #334155;
906
+ --text: #e2e8f0;
907
+ --muted: #94a3b8;
908
+ --faint: #64748b;
909
+ --status-completed: #4ade80;
910
+ --status-skipped: #818cf8;
911
+ --status-in-progress: #fbbf24;
912
+ --status-pending: #64748b;
913
+ --accent: #6366f1;
914
+ --accent-light: #818cf8;
915
+ --stale-bg: #451a03;
916
+ --stale-text: #fef3c7;
917
+ }
918
+ [data-theme="light"] {
919
+ --bg: #f8fafc;
920
+ --card-bg: #ffffff;
921
+ --border: #e2e8f0;
922
+ --text: #1e293b;
923
+ --muted: #64748b;
924
+ --faint: #94a3b8;
925
+ --status-completed: #4ade80;
926
+ --status-skipped: #818cf8;
927
+ --status-in-progress: #fbbf24;
928
+ --status-pending: #64748b;
929
+ --accent: #6366f1;
930
+ --accent-light: #818cf8;
931
+ --stale-bg: #fef3c7;
932
+ --stale-text: #92400e;
933
+ }
934
+ *, *::before, *::after { box-sizing: border-box; }
935
+ body {
936
+ font-family: system-ui, -apple-system, sans-serif;
937
+ background: var(--bg);
938
+ color: var(--text);
939
+ margin: 0;
940
+ padding: 24px;
941
+ line-height: 1.5;
942
+ }
943
+ .container { max-width: 1120px; margin: 0 auto; }
944
+ .header {
945
+ display: flex;
946
+ justify-content: space-between;
947
+ align-items: flex-start;
948
+ margin-bottom: 24px;
949
+ flex-wrap: wrap;
950
+ gap: 12px;
951
+ }
952
+ .header h1 { margin: 0 0 4px; font-size: 1.5rem; }
953
+ .header-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
954
+ .methodology-badge {
955
+ background: var(--accent);
956
+ color: #fff;
957
+ padding: 2px 10px;
958
+ border-radius: 12px;
959
+ font-size: 0.8rem;
960
+ font-weight: 600;
961
+ text-transform: uppercase;
962
+ }
963
+ .pct-label { color: var(--muted); font-size: 0.9rem; }
964
+ .theme-toggle {
965
+ cursor: pointer;
966
+ background: var(--card-bg);
967
+ border: 1px solid var(--border);
968
+ padding: 6px 14px;
969
+ border-radius: 6px;
970
+ color: var(--text);
971
+ font-size: 0.85rem;
972
+ }
973
+ .theme-toggle:hover { border-color: var(--accent); }
974
+ .stale-notice {
975
+ background: var(--stale-bg);
976
+ color: var(--stale-text);
977
+ padding: 10px 16px;
978
+ border-radius: 6px;
979
+ margin-bottom: 16px;
980
+ font-size: 0.9rem;
981
+ }
982
+ .aggregate-block {
983
+ background: var(--card-bg);
984
+ border: 1px solid var(--border);
985
+ border-radius: 10px;
986
+ padding: 20px 24px;
987
+ margin-bottom: 28px;
988
+ }
989
+ .aggregate-row {
990
+ display: flex;
991
+ align-items: center;
992
+ gap: 16px;
993
+ margin-bottom: 14px;
994
+ flex-wrap: wrap;
995
+ }
996
+ .aggregate-progress-bar {
997
+ flex: 1;
998
+ min-width: 200px;
999
+ height: 10px;
1000
+ background: var(--border);
1001
+ border-radius: 5px;
1002
+ overflow: hidden;
1003
+ }
1004
+ .aggregate-progress-fill {
1005
+ height: 100%;
1006
+ background: var(--status-completed);
1007
+ }
1008
+ .aggregate-pct { font-size: 1.1rem; font-weight: 700; min-width: 48px; text-align: right; }
1009
+ .aggregate-stat { color: var(--muted); font-size: 0.9rem; }
1010
+ .aggregate-stat strong { color: var(--text); font-weight: 700; }
1011
+ .phase-indicators {
1012
+ display: flex;
1013
+ gap: 10px;
1014
+ flex-wrap: wrap;
1015
+ padding-top: 8px;
1016
+ border-top: 1px solid var(--border);
1017
+ margin-top: 8px;
1018
+ }
1019
+ .phase-indicator {
1020
+ background: var(--bg);
1021
+ border: 1px solid var(--border);
1022
+ border-radius: 6px;
1023
+ padding: 6px 10px;
1024
+ min-width: 90px;
1025
+ }
1026
+ .phase-indicator-num { font-size: 0.7rem; color: var(--faint); font-weight: 700; }
1027
+ .phase-indicator-name { font-size: 0.8rem; font-weight: 600; }
1028
+ .phase-indicator-count { font-size: 0.75rem; color: var(--muted); }
1029
+ .services-grid {
1030
+ display: grid;
1031
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
1032
+ gap: 16px;
1033
+ }
1034
+ .service-card {
1035
+ background: var(--card-bg);
1036
+ border: 1px solid var(--border);
1037
+ border-radius: 10px;
1038
+ padding: 16px 18px;
1039
+ cursor: pointer;
1040
+ transition: border-color 0.15s, transform 0.1s;
1041
+ }
1042
+ .service-card:hover { border-color: var(--accent); transform: translateY(-1px); }
1043
+ .service-card-head {
1044
+ display: flex;
1045
+ justify-content: space-between;
1046
+ align-items: center;
1047
+ gap: 8px;
1048
+ margin-bottom: 10px;
1049
+ }
1050
+ .service-name { font-size: 1.1rem; font-weight: 700; }
1051
+ .service-type-pill {
1052
+ background: var(--border);
1053
+ color: var(--muted);
1054
+ padding: 2px 8px;
1055
+ border-radius: 10px;
1056
+ font-size: 0.7rem;
1057
+ font-weight: 600;
1058
+ text-transform: lowercase;
1059
+ }
1060
+ .service-progress-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
1061
+ .service-progress-bar { flex: 1; height: 8px; background: var(--border); border-radius: 4px; overflow: hidden; }
1062
+ .service-progress-fill { height: 100%; background: var(--status-completed); }
1063
+ .service-pct { font-size: 0.85rem; font-weight: 600; min-width: 42px; text-align: right; }
1064
+ .service-counts { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
1065
+ .count-pill {
1066
+ font-size: 0.7rem;
1067
+ padding: 2px 8px;
1068
+ border-radius: 10px;
1069
+ font-weight: 600;
1070
+ }
1071
+ .count-completed { background: var(--status-completed); color: #064e3b; }
1072
+ .count-skipped { background: var(--status-skipped); color: #1e1b4b; }
1073
+ .count-in-progress { background: var(--status-in-progress); color: #451a03; }
1074
+ .count-pending { background: var(--border); color: var(--muted); }
1075
+ .service-phase-line { font-size: 0.85rem; color: var(--muted); margin-bottom: 6px; }
1076
+ .service-phase { color: var(--text); font-weight: 500; }
1077
+ .service-complete-badge {
1078
+ background: var(--status-completed);
1079
+ color: #064e3b;
1080
+ padding: 2px 10px;
1081
+ border-radius: 10px;
1082
+ font-size: 0.75rem;
1083
+ font-weight: 700;
1084
+ text-transform: uppercase;
1085
+ }
1086
+ .service-not-started-badge {
1087
+ background: var(--border);
1088
+ color: var(--muted);
1089
+ padding: 2px 10px;
1090
+ border-radius: 10px;
1091
+ font-size: 0.75rem;
1092
+ font-weight: 700;
1093
+ text-transform: uppercase;
1094
+ }
1095
+ .service-next { font-size: 0.8rem; color: var(--muted); margin-bottom: 10px; }
1096
+ .service-next-label { color: var(--faint); text-transform: uppercase; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; }
1097
+ .service-next-slug { color: var(--accent-light); font-weight: 600; }
1098
+ .service-next-summary { color: var(--muted); }
1099
+ .service-cmd-hint {
1100
+ display: flex;
1101
+ justify-content: space-between;
1102
+ align-items: center;
1103
+ gap: 8px;
1104
+ padding: 8px 10px;
1105
+ background: var(--bg);
1106
+ border: 1px solid var(--border);
1107
+ border-radius: 6px;
1108
+ font-size: 0.75rem;
1109
+ }
1110
+ .service-cmd-hint code { color: var(--text); }
1111
+ .service-cmd-copy { color: var(--faint); }
1112
+ .service-card.copied { border-color: var(--status-completed); }
1113
+ .service-card.copied .service-cmd-copy { color: var(--status-completed); font-weight: 600; }
1114
+ .footer {
1115
+ margin-top: 40px;
1116
+ padding-top: 16px;
1117
+ border-top: 1px solid var(--border);
1118
+ color: var(--faint);
1119
+ font-size: 0.75rem;
1120
+ display: flex;
1121
+ justify-content: space-between;
1122
+ flex-wrap: wrap;
1123
+ gap: 8px;
1124
+ }
1125
+ @media (max-width: 480px) {
1126
+ body { padding: 16px; }
1127
+ .header { flex-direction: column; }
1128
+ .services-grid { grid-template-columns: 1fr; }
1129
+ }
1130
+ </style>
1131
+ </head>
1132
+ <body>
1133
+ <script>
1134
+ var stored = localStorage.getItem('scaffold-theme');
1135
+ var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1136
+ var theme = stored || (prefersDark ? 'dark' : 'light');
1137
+ document.documentElement.setAttribute('data-theme', theme);
1138
+ </script>
1139
+ <div class="container">
1140
+ <div class="header">
1141
+ <div>
1142
+ <h1>Multi-Service Pipeline</h1>
1143
+ <div class="header-meta">
1144
+ <span class="methodology-badge">${methodology}</span>
1145
+ <span class="pct-label">${avgPct}% average</span>
1146
+ </div>
1147
+ </div>
1148
+ <button class="theme-toggle" onclick="toggleTheme()">Toggle theme</button>
1149
+ </div>
1150
+ ${staleNotice}
1151
+ <div class="aggregate-block">
1152
+ <div class="aggregate-row">
1153
+ <div class="aggregate-progress-bar"><div class="aggregate-progress-fill" style="width:${avgPct}%"></div></div>
1154
+ <span class="aggregate-pct">${avgPct}%</span>
1155
+ </div>
1156
+ <div class="aggregate-stat"><strong>${data.aggregate.servicesComplete}</strong> of <strong>${data.aggregate.totalServices}</strong> services complete</div>
1157
+ ${servicesByPhase ? `<div class="phase-indicators">${servicesByPhase}</div>` : ''}
1158
+ </div>
1159
+ <div class="services-grid">
1160
+ ${serviceCards}
1161
+ </div>
1162
+ <div class="footer">
1163
+ <span>Generated ${escapeHtml(data.generatedAt)}</span>
1164
+ <span>Scaffold v${escapeHtml(data.scaffoldVersion)}</span>
1165
+ </div>
1166
+ </div>
1167
+ <script>
1168
+ (function() {
1169
+ function toggleTheme() {
1170
+ var current = document.documentElement.getAttribute('data-theme');
1171
+ var next = current === 'dark' ? 'light' : 'dark';
1172
+ document.documentElement.setAttribute('data-theme', next);
1173
+ localStorage.setItem('scaffold-theme', next);
1174
+ }
1175
+ window.toggleTheme = toggleTheme;
1176
+
1177
+ function copyCommand(text, card) {
1178
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1179
+ navigator.clipboard.writeText(text).then(function() {
1180
+ if (card) {
1181
+ card.classList.add('copied');
1182
+ var label = card.querySelector('.service-cmd-copy');
1183
+ if (label) {
1184
+ var orig = label.textContent;
1185
+ label.textContent = 'Copied!';
1186
+ setTimeout(function() {
1187
+ label.textContent = orig;
1188
+ card.classList.remove('copied');
1189
+ }, 1500);
1190
+ }
1191
+ }
1192
+ });
1193
+ }
1194
+ }
1195
+ window.copyCommand = copyCommand;
1196
+
1197
+ // Wire up service-card clicks via data-copy (no inline onclick — XSS-safe).
1198
+ var cards = document.querySelectorAll('.service-card[data-copy]');
1199
+ for (var i = 0; i < cards.length; i++) {
1200
+ (function(card) {
1201
+ var cmd = card.getAttribute('data-copy') || '';
1202
+ card.addEventListener('click', function() { copyCommand(cmd, card); });
1203
+ })(cards[i]);
1204
+ }
1205
+
1206
+ (function() {
1207
+ var dataEl = document.getElementById('scaffold-data');
1208
+ if (!dataEl) return;
1209
+ var data = JSON.parse(dataEl.textContent || '{}');
1210
+ if (data.generatedAt) {
1211
+ var age = Date.now() - new Date(data.generatedAt).getTime();
1212
+ if (age > 3600000) {
1213
+ var existing = document.getElementById('stale-notice');
1214
+ if (!existing) {
1215
+ var notice = document.createElement('div');
1216
+ notice.id = 'stale-notice';
1217
+ notice.className = 'stale-notice';
1218
+ notice.textContent = '\u26A0 Data may be stale (generated more than 1 hour ago)';
1219
+ var container = document.querySelector('.container');
1220
+ if (container) container.insertBefore(notice, container.children[1]);
1221
+ }
1222
+ }
1223
+ }
1224
+ })();
1225
+ })();
1226
+ </script>
1227
+ </body>
1228
+ </html>`;
1229
+ }
1230
+ /* eslint-enable max-len */
831
1231
  //# sourceMappingURL=template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/dashboard/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/dashboard/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=eligible-cache.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligible-cache.test.d.ts","sourceRoot":"","sources":["../../src/e2e/eligible-cache.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { loadPipelineContext } from '../core/pipeline/context.js';
6
+ import { resolvePipeline } from '../core/pipeline/resolver.js';
7
+ import { StateManager } from '../state/state-manager.js';
8
+ import { StatePathResolver } from '../state/state-path-resolver.js';
9
+ import { readEligible } from '../core/pipeline/read-eligible.js';
10
+ import { readRootSaveCounter } from '../state/root-counter-reader.js';
11
+ import { createOutputContext } from '../cli/output/context.js';
12
+ describe('Eligible-Step Cache v2 — E2E', () => {
13
+ let tmpRoot;
14
+ beforeEach(() => {
15
+ tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ec2-e2e-'));
16
+ fs.mkdirSync(path.join(tmpRoot, '.scaffold', 'services', 'api'), { recursive: true });
17
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'config.yml'), `version: 2
18
+ methodology: deep
19
+ platforms: [claude-code]
20
+ project:
21
+ services:
22
+ - name: api
23
+ projectType: backend
24
+ backendConfig:
25
+ apiStyle: rest
26
+ `);
27
+ const baseState = {
28
+ 'schema-version': 3,
29
+ 'scaffold-version': '1.0.0',
30
+ init_methodology: 'deep',
31
+ config_methodology: 'deep',
32
+ 'init-mode': 'greenfield',
33
+ created: '2026-04-20T00:00:00.000Z',
34
+ in_progress: null,
35
+ steps: {},
36
+ next_eligible: [],
37
+ 'extra-steps': [],
38
+ };
39
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), JSON.stringify(baseState));
40
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'services', 'api', 'state.json'), JSON.stringify(baseState));
41
+ });
42
+ afterEach(() => {
43
+ fs.rmSync(tmpRoot, { recursive: true, force: true });
44
+ });
45
+ it('AC2: service cache is invalidated by root state mutation (cross-file)', () => {
46
+ const context = loadPipelineContext(tmpRoot);
47
+ const output = createOutputContext('auto');
48
+ const rootPipeline = resolvePipeline(context, { output });
49
+ const rootPathResolver = new StatePathResolver(tmpRoot);
50
+ const rootSm = new StateManager(tmpRoot, rootPipeline.computeEligible, () => context.config ?? undefined, rootPathResolver, rootPipeline.globalSteps, rootPipeline.getPipelineHash('global'));
51
+ // Seed root counter by doing an initial root save
52
+ rootSm.saveState(rootSm.loadState());
53
+ const svcPipeline = resolvePipeline(context, { output, serviceId: 'api' });
54
+ const pathResolver = new StatePathResolver(tmpRoot, 'api');
55
+ const sm = new StateManager(tmpRoot, svcPipeline.computeEligible, () => context.config ?? undefined, pathResolver, svcPipeline.globalSteps, svcPipeline.getPipelineHash('service'));
56
+ const state = sm.loadState();
57
+ state.steps['some-step'] = { status: 'pending', source: 'pipeline', produces: [] };
58
+ sm.saveState(state);
59
+ const svcDisk = JSON.parse(fs.readFileSync(path.join(tmpRoot, '.scaffold', 'services', 'api', 'state.json'), 'utf8'));
60
+ expect(svcDisk.next_eligible_root_counter).toBe(1);
61
+ expect(typeof svcDisk.next_eligible_hash).toBe('string');
62
+ // Mutate root — bumps save_counter from 1 to 2
63
+ rootSm.saveState(rootSm.loadState());
64
+ expect(readRootSaveCounter(tmpRoot)).toBe(2);
65
+ // readEligible must fall back because next_eligible_root_counter (1) !== current root counter (2)
66
+ const liveCalls = [];
67
+ const sentinelPipeline = {
68
+ ...svcPipeline,
69
+ computeEligible: ((steps, opts) => {
70
+ liveCalls.push('live-recompute-fired');
71
+ return svcPipeline.computeEligible(steps, opts);
72
+ }),
73
+ };
74
+ readEligible(sm.loadState(), sentinelPipeline, { scope: 'service', globalSteps: svcPipeline.globalSteps }, () => readRootSaveCounter(tmpRoot));
75
+ expect(liveCalls).toContain('live-recompute-fired');
76
+ });
77
+ it('AC3: pipeline-graph change (different hash on re-resolution) invalidates cache on read', () => {
78
+ const context = loadPipelineContext(tmpRoot);
79
+ const output = createOutputContext('auto');
80
+ const pipelineA = resolvePipeline(context, { output });
81
+ const pathResolver = new StatePathResolver(tmpRoot);
82
+ const sm = new StateManager(tmpRoot, pipelineA.computeEligible, () => context.config ?? undefined, pathResolver, pipelineA.globalSteps, pipelineA.getPipelineHash('global'));
83
+ sm.saveState(sm.loadState());
84
+ // Build a new context whose metaPrompts differs — delete first slug
85
+ const firstSlug = [...context.metaPrompts.keys()][0];
86
+ expect(firstSlug).toBeDefined();
87
+ const mutatedMetaPrompts = new Map(context.metaPrompts);
88
+ mutatedMetaPrompts.delete(firstSlug);
89
+ const mutatedContext = { ...context, metaPrompts: mutatedMetaPrompts };
90
+ const pipelineB = resolvePipeline(mutatedContext, { output });
91
+ expect(pipelineB.getPipelineHash('global')).not.toBe(pipelineA.getPipelineHash('global'));
92
+ const liveCalls = [];
93
+ const sentinelPipeline = {
94
+ ...pipelineB,
95
+ computeEligible: ((steps, opts) => {
96
+ liveCalls.push('live-fired');
97
+ return pipelineB.computeEligible(steps, opts);
98
+ }),
99
+ };
100
+ readEligible(sm.loadState(), sentinelPipeline, undefined, undefined);
101
+ expect(liveCalls).toContain('live-fired');
102
+ });
103
+ });
104
+ //# sourceMappingURL=eligible-cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligible-cache.test.js","sourceRoot":"","sources":["../../src/e2e/eligible-cache.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Reads `.scaffold/state.json` and returns the `save_counter` field.
3
+ * Returns null on any failure (missing file, invalid JSON, missing field).
4
+ *
5
+ * Used by service-scope cache readers (readEligible) to verify that the
6
+ * service's cached next_eligible was written against the current root state
7
+ * (spec §6). Not used at cache WRITE time — StateManager captures the counter
8
+ * internally during loadState to avoid TOCTOU.
9
+ */
10
+ export declare function readRootSaveCounter(projectRoot: string): number | null;
11
+ //# sourceMappingURL=root-counter-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-counter-reader.d.ts","sourceRoot":"","sources":["../../src/state/root-counter-reader.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUtE"}
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Reads `.scaffold/state.json` and returns the `save_counter` field.
5
+ * Returns null on any failure (missing file, invalid JSON, missing field).
6
+ *
7
+ * Used by service-scope cache readers (readEligible) to verify that the
8
+ * service's cached next_eligible was written against the current root state
9
+ * (spec §6). Not used at cache WRITE time — StateManager captures the counter
10
+ * internally during loadState to avoid TOCTOU.
11
+ */
12
+ export function readRootSaveCounter(projectRoot) {
13
+ const rootStatePath = path.join(projectRoot, '.scaffold', 'state.json');
14
+ try {
15
+ if (!fs.existsSync(rootStatePath))
16
+ return null;
17
+ const raw = JSON.parse(fs.readFileSync(rootStatePath, 'utf8'));
18
+ const counter = raw['save_counter'];
19
+ return typeof counter === 'number' ? counter : null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ //# sourceMappingURL=root-counter-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-counter-reader.js","sourceRoot":"","sources":["../../src/state/root-counter-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,CAAA;IACvE,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,OAAO,IAAI,CAAA;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAA4B,CAAA;QACzF,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,CAAA;QACnC,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=root-counter-reader.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-counter-reader.test.d.ts","sourceRoot":"","sources":["../../src/state/root-counter-reader.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { readRootSaveCounter } from './root-counter-reader.js';
6
+ describe('readRootSaveCounter', () => {
7
+ let tmpRoot;
8
+ beforeEach(() => {
9
+ tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'rcr-'));
10
+ fs.mkdirSync(path.join(tmpRoot, '.scaffold'), { recursive: true });
11
+ });
12
+ afterEach(() => {
13
+ fs.rmSync(tmpRoot, { recursive: true, force: true });
14
+ });
15
+ it('returns null when root state file is missing', () => {
16
+ expect(readRootSaveCounter(tmpRoot)).toBeNull();
17
+ });
18
+ it('returns the counter when state file has a valid save_counter', () => {
19
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), JSON.stringify({ save_counter: 42, 'schema-version': 3 }));
20
+ expect(readRootSaveCounter(tmpRoot)).toBe(42);
21
+ });
22
+ it('returns null when state file has invalid JSON', () => {
23
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), '{ not valid json');
24
+ expect(readRootSaveCounter(tmpRoot)).toBeNull();
25
+ });
26
+ it('returns null when state file lacks save_counter (legacy file)', () => {
27
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), JSON.stringify({ 'schema-version': 3 }));
28
+ expect(readRootSaveCounter(tmpRoot)).toBeNull();
29
+ });
30
+ it('returns null when save_counter is non-number (Codex + Gemini MMR coverage lock)', () => {
31
+ // Coverage lock: regression guard against a future change that forgets
32
+ // the typeof check and coerces with Number() or String().
33
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), JSON.stringify({ save_counter: '42', 'schema-version': 3 }));
34
+ expect(readRootSaveCounter(tmpRoot)).toBeNull();
35
+ });
36
+ it('returns 0 (not null) when save_counter is the number zero', () => {
37
+ // Coverage lock: 0 is a valid counter value (truthy-confusing). A future
38
+ // refactor from `typeof === "number"` to `if (counter)` would regress.
39
+ fs.writeFileSync(path.join(tmpRoot, '.scaffold', 'state.json'), JSON.stringify({ save_counter: 0, 'schema-version': 3 }));
40
+ expect(readRootSaveCounter(tmpRoot)).toBe(0);
41
+ });
42
+ });
43
+ //# sourceMappingURL=root-counter-reader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-counter-reader.test.js","sourceRoot":"","sources":["../../src/state/root-counter-reader.test.ts"],"names":[],"mappings":"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"}