@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.
- package/dist/cli/commands/adopt.d.ts.map +1 -1
- package/dist/cli/commands/adopt.js +6 -2
- package/dist/cli/commands/adopt.js.map +1 -1
- package/dist/cli/commands/complete.d.ts.map +1 -1
- package/dist/cli/commands/complete.js +1 -1
- package/dist/cli/commands/complete.js.map +1 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +104 -26
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/dashboard.test.js +210 -0
- package/dist/cli/commands/dashboard.test.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +2 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +7 -2
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/next.test.js +95 -0
- package/dist/cli/commands/next.test.js.map +1 -1
- package/dist/cli/commands/reset.d.ts.map +1 -1
- package/dist/cli/commands/reset.js +1 -1
- package/dist/cli/commands/reset.js.map +1 -1
- package/dist/cli/commands/rework.d.ts.map +1 -1
- package/dist/cli/commands/rework.js +6 -1
- package/dist/cli/commands/rework.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/skip.d.ts.map +1 -1
- package/dist/cli/commands/skip.js +1 -1
- package/dist/cli/commands/skip.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +14 -5
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/status.test.js +60 -0
- package/dist/cli/commands/status.test.js.map +1 -1
- package/dist/core/pipeline/graph-hash.d.ts +18 -0
- package/dist/core/pipeline/graph-hash.d.ts.map +1 -0
- package/dist/core/pipeline/graph-hash.js +35 -0
- package/dist/core/pipeline/graph-hash.js.map +1 -0
- package/dist/core/pipeline/graph-hash.test.d.ts +2 -0
- package/dist/core/pipeline/graph-hash.test.d.ts.map +1 -0
- package/dist/core/pipeline/graph-hash.test.js +107 -0
- package/dist/core/pipeline/graph-hash.test.js.map +1 -0
- package/dist/core/pipeline/read-eligible.d.ts +20 -0
- package/dist/core/pipeline/read-eligible.d.ts.map +1 -0
- package/dist/core/pipeline/read-eligible.js +28 -0
- package/dist/core/pipeline/read-eligible.js.map +1 -0
- package/dist/core/pipeline/read-eligible.test.d.ts +2 -0
- package/dist/core/pipeline/read-eligible.test.d.ts.map +1 -0
- package/dist/core/pipeline/read-eligible.test.js +99 -0
- package/dist/core/pipeline/read-eligible.test.js.map +1 -0
- package/dist/core/pipeline/resolver.d.ts.map +1 -1
- package/dist/core/pipeline/resolver.js +24 -1
- package/dist/core/pipeline/resolver.js.map +1 -1
- package/dist/core/pipeline/resolver.test.js +86 -0
- package/dist/core/pipeline/resolver.test.js.map +1 -1
- package/dist/core/pipeline/types.d.ts +5 -0
- package/dist/core/pipeline/types.d.ts.map +1 -1
- package/dist/dashboard/generator.d.ts +57 -0
- package/dist/dashboard/generator.d.ts.map +1 -1
- package/dist/dashboard/generator.js +125 -1
- package/dist/dashboard/generator.js.map +1 -1
- package/dist/dashboard/multi-service.test.d.ts +2 -0
- package/dist/dashboard/multi-service.test.d.ts.map +1 -0
- package/dist/dashboard/multi-service.test.js +535 -0
- package/dist/dashboard/multi-service.test.js.map +1 -0
- package/dist/dashboard/template.d.ts +2 -1
- package/dist/dashboard/template.d.ts.map +1 -1
- package/dist/dashboard/template.js +400 -0
- package/dist/dashboard/template.js.map +1 -1
- package/dist/e2e/eligible-cache.test.d.ts +2 -0
- package/dist/e2e/eligible-cache.test.d.ts.map +1 -0
- package/dist/e2e/eligible-cache.test.js +104 -0
- package/dist/e2e/eligible-cache.test.js.map +1 -0
- package/dist/state/root-counter-reader.d.ts +11 -0
- package/dist/state/root-counter-reader.d.ts.map +1 -0
- package/dist/state/root-counter-reader.js +25 -0
- package/dist/state/root-counter-reader.js.map +1 -0
- package/dist/state/root-counter-reader.test.d.ts +2 -0
- package/dist/state/root-counter-reader.test.d.ts.map +1 -0
- package/dist/state/root-counter-reader.test.js +43 -0
- package/dist/state/root-counter-reader.test.js.map +1 -0
- package/dist/state/state-manager.d.ts +23 -2
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +57 -5
- package/dist/state/state-manager.js.map +1 -1
- package/dist/state/state-manager.test.js +246 -1
- package/dist/state/state-manager.test.js.map +1 -1
- package/dist/types/state.d.ts +19 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/state.test.d.ts +2 -0
- package/dist/types/state.test.d.ts.map +1 -0
- package/dist/types/state.test.js +40 -0
- package/dist/types/state.test.js.map +1 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +3 -1
- package/dist/wizard/wizard.js.map +1 -1
- 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">⚠ 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 @@
|
|
|
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 @@
|
|
|
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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"}
|