@rsaglobaltech/specgate 0.22.3 → 0.23.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/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,19 @@ The release process is in [`docs/release-process.md`](docs/release-process.md).
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.23.0] — 2026-10-25
|
|
13
|
+
|
|
14
|
+
The live view, read like a CI run: watch the harness work through a run requirement by requirement, step by step.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **The live view reads like a CI run** (REQ-179): `/live` listed every
|
|
19
|
+
requirement the same way, by id alone. It now has a sidebar of the run's
|
|
20
|
+
requirements by module, a summary of the run, its requirements as a graph of
|
|
21
|
+
jobs, and the selected one's steps (Agent, each gate stage, the stages still
|
|
22
|
+
to come) with the agent's stream as the log of the step in progress. Each
|
|
23
|
+
requirement shows its title from the specification. Light and dark.
|
|
24
|
+
|
|
12
25
|
## [0.22.3] — 2026-10-25
|
|
13
26
|
|
|
14
27
|
What a first project from a brief tripped over, from statuses to the harness's first attempt: rehearsed on a Spanish brief with eight modules and a Next.js monorepo.
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.foldRequirement = foldRequirement;
|
|
37
|
+
exports.requirementTitles = requirementTitles;
|
|
37
38
|
exports.readLive = readLive;
|
|
38
39
|
exports.liveSignature = liveSignature;
|
|
39
40
|
const fs = __importStar(require("node:fs"));
|
|
@@ -86,6 +87,8 @@ function foldRequirement(req, events) {
|
|
|
86
87
|
.find((e) => (e.kind === "test" || e.type === "gate") && (e.pass !== undefined || e.fail !== undefined));
|
|
87
88
|
return {
|
|
88
89
|
req,
|
|
90
|
+
title: "",
|
|
91
|
+
group: "",
|
|
89
92
|
attempt,
|
|
90
93
|
phase,
|
|
91
94
|
state: end ? (end.result === "pass" ? "pass" : "fail") : "running",
|
|
@@ -110,9 +113,58 @@ function eventFiles(projectDir) {
|
|
|
110
113
|
return [];
|
|
111
114
|
}
|
|
112
115
|
}
|
|
116
|
+
const TITLE = /^(?:##|###\s+Requirement:)\s+(REQ-\d+)\s+[—–-]\s+(.+?)\s*$/gm;
|
|
117
|
+
/**
|
|
118
|
+
* Requirement titles from spec.md and every markdown file under docs/specs —
|
|
119
|
+
* capability specs and open changes' deltas alike — so the live view can say
|
|
120
|
+
* what REQ-022 is (REQ-179). The first title found for an id wins.
|
|
121
|
+
*/
|
|
122
|
+
function requirementTitles(projectDir) {
|
|
123
|
+
const titles = new Map();
|
|
124
|
+
const read = (file) => {
|
|
125
|
+
const parts = path.relative(projectDir, file).split(path.sep);
|
|
126
|
+
const at = parts.findIndex((p) => p === "changes" || p === "capabilities");
|
|
127
|
+
const group = at >= 0 && parts[at + 1] && parts[at + 1] !== "archive"
|
|
128
|
+
? parts[at + 1].replace(/^draft-/, "")
|
|
129
|
+
: "";
|
|
130
|
+
let text = "";
|
|
131
|
+
try {
|
|
132
|
+
text = fs.readFileSync(file, "utf8");
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
for (const m of text.matchAll(TITLE)) {
|
|
138
|
+
// A template's "<a short name>" is not a title.
|
|
139
|
+
if (!titles.has(m[1]) && !/[<>]/.test(m[2]))
|
|
140
|
+
titles.set(m[1], { title: m[2], group });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
read(path.join(projectDir, "spec.md"));
|
|
144
|
+
const walk = (dir) => {
|
|
145
|
+
let entries = [];
|
|
146
|
+
try {
|
|
147
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const e of entries) {
|
|
153
|
+
const full = path.join(dir, e.name);
|
|
154
|
+
if (e.isDirectory())
|
|
155
|
+
walk(full);
|
|
156
|
+
// DRAFTING.md shows the format with an example requirement; it names none.
|
|
157
|
+
else if (e.name.endsWith(".md") && e.name !== "DRAFTING.md")
|
|
158
|
+
read(full);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
walk(path.join(projectDir, "docs", "specs"));
|
|
162
|
+
return titles;
|
|
163
|
+
}
|
|
113
164
|
/** Every requirement with events, the most recently active first. */
|
|
114
165
|
function readLive(projectDir) {
|
|
115
166
|
const requirements = [];
|
|
167
|
+
const titles = requirementTitles(projectDir);
|
|
116
168
|
for (const file of eventFiles(projectDir)) {
|
|
117
169
|
let text = "";
|
|
118
170
|
try {
|
|
@@ -122,8 +174,14 @@ function readLive(projectDir) {
|
|
|
122
174
|
continue;
|
|
123
175
|
}
|
|
124
176
|
const folded = foldRequirement(path.basename(file, ".jsonl"), parse(text.split("\n")));
|
|
125
|
-
if (folded)
|
|
126
|
-
|
|
177
|
+
if (folded) {
|
|
178
|
+
const known = titles.get(folded.req);
|
|
179
|
+
requirements.push({
|
|
180
|
+
...folded,
|
|
181
|
+
title: known ? known.title : "",
|
|
182
|
+
group: known ? known.group : "",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
127
185
|
}
|
|
128
186
|
requirements.sort((a, b) => b.updated - a.updated);
|
|
129
187
|
return { requirements };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-model.js","sourceRoot":"","sources":["../../../scripts/lib/live-model.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"live-model.js","sourceRoot":"","sources":["../../../scripts/lib/live-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,84 +5,274 @@ exports.livePage = livePage;
|
|
|
5
5
|
* The live view's page (ADR-0037): one self-contained document, no external
|
|
6
6
|
* resources, filled from `/live.json` and kept current by `/live/stream`.
|
|
7
7
|
* Every value from an event is set with textContent, never as HTML.
|
|
8
|
+
*
|
|
9
|
+
* REQ-179: laid out the way CI runs are read. A sidebar lists the run's
|
|
10
|
+
* requirements by module with their status and duration; the main pane sums
|
|
11
|
+
* the run up, draws its requirements as a graph of jobs, and opens the
|
|
12
|
+
* selected one (the requirement at work, unless another is clicked) into its
|
|
13
|
+
* steps — Agent, then each stage of the gate, up to the merge — with the step
|
|
14
|
+
* in progress showing the agent's own stream as its log.
|
|
15
|
+
*
|
|
16
|
+
* The script lives inside this template literal: it uses no backticks, no
|
|
17
|
+
* dollar-brace, and no backslash escapes.
|
|
8
18
|
*/
|
|
9
19
|
function livePage(projectName) {
|
|
10
|
-
const title = String(projectName).replace(/[&<>"]/g, "");
|
|
20
|
+
const title = String(projectName).replace(/[&<>"']/g, "");
|
|
11
21
|
return `<!doctype html>
|
|
12
22
|
<html lang="en"><head><meta charset="utf-8"/>
|
|
13
23
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
14
24
|
<title>Harness live — ${title}</title>
|
|
15
25
|
<style>
|
|
16
|
-
:root{--bg:#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
.
|
|
26
|
+
:root{--bg:#f6f8fa;--panel:#ffffff;--line:#d1d9e0;--soft:#eff2f5;--ink:#1f2328;--muted:#59636e;--faint:#8c959f;
|
|
27
|
+
--pass:#1a7f37;--fail:#cf222e;--run:#9a6700;--run-bg:#fff8c5;--accent:#0969da;--sel:#ddf4ff;
|
|
28
|
+
--log:#1f2328;--log-ink:#d1d9e0;--log-muted:#8c959f;
|
|
29
|
+
--mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
|
|
30
|
+
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif;color-scheme:light}
|
|
31
|
+
@media (prefers-color-scheme: dark){:root{--bg:#0d1117;--panel:#151b23;--line:#3d444d;--soft:#212830;--ink:#f0f6fc;
|
|
32
|
+
--muted:#9198a1;--faint:#656c76;--pass:#3fb950;--fail:#f85149;--run:#d29922;--run-bg:#2c2513;--accent:#4493f8;
|
|
33
|
+
--sel:#16263d;--log:#010409;--log-ink:#d1d7e0;--log-muted:#656c76;color-scheme:dark}}
|
|
34
|
+
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 var(--sans)}
|
|
35
|
+
button{font:inherit;color:inherit}
|
|
36
|
+
.top{height:56px;display:flex;align-items:center;gap:10px;padding:0 24px;background:var(--panel);border-bottom:1px solid var(--line)}
|
|
37
|
+
.top svg{width:22px;height:22px} .crumb{color:var(--accent)} .sep{color:var(--faint)}
|
|
38
|
+
.pill{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
|
|
39
|
+
.pill i{width:8px;height:8px;border-radius:50%;background:var(--faint)} .pill.on i{background:var(--pass)}
|
|
40
|
+
.layout{display:flex;min-height:calc(100vh - 56px)}
|
|
41
|
+
aside{width:300px;flex:none;background:var(--panel);border-right:1px solid var(--line);padding:16px 12px;overflow:auto}
|
|
42
|
+
aside h3{margin:16px 10px 6px;font-size:12px;font-weight:600;color:var(--muted)}
|
|
43
|
+
.job{display:flex;width:100%;align-items:center;gap:8px;padding:6px 10px;border:0;border-radius:6px;background:none;text-align:left;cursor:pointer}
|
|
44
|
+
.job:hover{background:var(--soft)} .job.sel{background:var(--sel);font-weight:600}
|
|
45
|
+
.job .name{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
46
|
+
.job .d{font-size:12px;color:var(--muted);font-weight:400;font-variant-numeric:tabular-nums}
|
|
47
|
+
.summary-btn{margin-bottom:4px}
|
|
48
|
+
main{flex:1;min-width:0;padding:24px 28px;display:flex;flex-direction:column;gap:16px}
|
|
49
|
+
.head{display:flex;align-items:center;gap:12px;flex-wrap:wrap} .head h1{margin:0;font-size:20px;font-weight:600}
|
|
50
|
+
.badge{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
|
|
51
|
+
.badge.running{background:var(--run-bg);color:var(--run)} .badge.pass{background:rgba(31,136,61,.12);color:var(--pass)}
|
|
52
|
+
.badge.fail{background:rgba(207,34,46,.12);color:var(--fail)} .badge i{width:8px;height:8px;border-radius:50%;background:currentColor}
|
|
53
|
+
.box{background:var(--panel);border:1px solid var(--line);border-radius:6px}
|
|
54
|
+
#progress{display:flex;flex-wrap:wrap;gap:12px 40px;padding:14px 18px}
|
|
55
|
+
#progress div span{display:block;font-size:12px;color:var(--muted)} #progress div b{font-weight:400;font-variant-numeric:tabular-nums}
|
|
56
|
+
.graph{padding:8px 4px} #chain{display:block;width:100%}
|
|
57
|
+
#focus{display:flex;flex-direction:column;overflow:hidden}
|
|
58
|
+
#focus .fh{padding:12px 16px;border-bottom:1px solid var(--line);display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
|
|
59
|
+
#focus .fh b{font-weight:600} #focus .fh span{font-size:12px;color:var(--muted)}
|
|
60
|
+
#pipeline{list-style:none;margin:0;padding:0}
|
|
61
|
+
#pipeline>li{display:flex;align-items:center;gap:10px;padding:8px 16px;border-bottom:1px solid var(--soft);font-size:13px}
|
|
62
|
+
#pipeline>li:last-child{border-bottom:0} #pipeline .t{flex:1;min-width:0;overflow-wrap:anywhere}
|
|
63
|
+
#pipeline .mono{font-family:var(--mono);font-size:12.5px} #pipeline .d{color:var(--muted);font-variant-numeric:tabular-nums}
|
|
64
|
+
#pipeline li.running{background:var(--run-bg)} #pipeline li.running .d{color:var(--run)}
|
|
65
|
+
#pipeline li.idle{color:var(--faint)} #pipeline li.logrow{display:block;padding:0;background:var(--log)}
|
|
66
|
+
#feed{list-style:none;margin:0;padding:10px 16px 10px 42px;max-height:300px;overflow:auto;color:var(--log-ink);font:12px/1.65 var(--mono)}
|
|
67
|
+
#feed li{display:grid;grid-template-columns:64px 56px minmax(0,1fr);gap:10px;animation:in .3s ease both}
|
|
68
|
+
#feed .ts{color:var(--log-muted)} #feed .k{color:var(--log-muted)} #feed .ok{color:#3fb950} #feed .bad{color:#f85149}
|
|
69
|
+
#feed .say{font-family:var(--sans);font-size:12.5px} #feed .x{overflow-wrap:anywhere}
|
|
70
|
+
.ic{flex:none;width:16px;height:16px;display:inline-block}
|
|
71
|
+
.spin{animation:spin 1s linear infinite;transform-origin:50% 50%}
|
|
72
|
+
@keyframes spin{to{transform:rotate(360deg)}} @keyframes in{from{opacity:0}to{opacity:1}}
|
|
73
|
+
.empty{padding:48px 16px;text-align:center;color:var(--muted)}
|
|
74
|
+
@media (max-width:900px){.layout{flex-direction:column} aside{width:auto;border-right:0;border-bottom:1px solid var(--line);max-height:280px}}
|
|
75
|
+
@media (prefers-reduced-motion: reduce){*{animation:none!important}}
|
|
42
76
|
</style></head>
|
|
43
77
|
<body>
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
78
|
+
<div class="top">
|
|
79
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/><path d="M9 12l2 2 4-4"/></svg>
|
|
80
|
+
<span class="crumb">${title}</span><span class="sep">/</span><span class="crumb">Harness</span><span class="sep">/</span><b>Live</b>
|
|
81
|
+
<span id="conn" class="pill"><i></i><span id="connText">connecting</span></span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="layout">
|
|
84
|
+
<aside aria-label="Requirements of the run"><div id="jobs"></div></aside>
|
|
85
|
+
<main>
|
|
86
|
+
<div class="head"><span id="runBadge" class="badge running"><i></i><span id="runState">Waiting</span></span><h1 id="runTitle">Harness run</h1></div>
|
|
87
|
+
<div id="progress" class="box" role="status"></div>
|
|
88
|
+
<div class="box graph"><svg id="chain" role="img" aria-label="The requirements of the run as a graph of jobs"></svg></div>
|
|
89
|
+
<section id="focus" class="box" aria-live="polite">
|
|
90
|
+
<div class="fh"><b id="fName">—</b><span id="fMeta"></span></div>
|
|
91
|
+
<ol id="pipeline" aria-label="Steps: Agent, Specification, Architecture, Tests, Merge"></ol>
|
|
92
|
+
</section>
|
|
93
|
+
</main>
|
|
94
|
+
</div>
|
|
48
95
|
<script>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
function
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
(function(){
|
|
97
|
+
var NS = "http:" + "//www.w3.org/2000/svg";
|
|
98
|
+
var model = {requirements:[]}, pinned = null, logKey = "";
|
|
99
|
+
function $(id){ return document.getElementById(id); }
|
|
100
|
+
function el(tag, cls, text){ var e = document.createElement(tag); if (cls) e.className = cls; if (text !== undefined) e.textContent = text; return e; }
|
|
101
|
+
function s(tag, attrs, parent){ var e = document.createElementNS(NS, tag); for (var k in attrs) e.setAttribute(k, attrs[k]); if (parent) parent.appendChild(e); return e; }
|
|
102
|
+
function now(){ return Date.now() / 1000; }
|
|
103
|
+
function dur(sec){ sec = Math.max(0, Math.round(sec)); var h = Math.floor(sec / 3600), m = Math.floor(sec % 3600 / 60), x = sec % 60;
|
|
104
|
+
return h ? h + "h " + m + "m" : m ? m + "m " + x + "s" : x + "s"; }
|
|
105
|
+
function two(n){ return (n < 10 ? "0" : "") + n; }
|
|
106
|
+
function stamp(t){ if (!t) return ""; var d = new Date(t * 1000); return two(d.getHours()) + ":" + two(d.getMinutes()) + ":" + two(d.getSeconds()); }
|
|
107
|
+
function elapsed(r){ return r.state === "running" ? Math.max(r.elapsed, now() - r.started) : r.elapsed; }
|
|
108
|
+
function clip(t, n){ t = String(t || ""); return t.length > n ? t.slice(0, n - 1) + "…" : t; }
|
|
109
|
+
|
|
110
|
+
/** A status icon: a check, a cross, a turning ring, or an empty circle. */
|
|
111
|
+
function icon(state, size){
|
|
112
|
+
var v = s("svg", {viewBox:"0 0 16 16", width:size || 16, height:size || 16, "class":"ic", "aria-hidden":"true"});
|
|
113
|
+
if (state === "pass") { s("circle", {cx:8, cy:8, r:7, fill:"var(--pass)"}, v); s("path", {d:"M4.5 8.2l2.3 2.3 4.7-4.9", fill:"none", stroke:"#fff", "stroke-width":"1.8", "stroke-linecap":"round", "stroke-linejoin":"round"}, v); }
|
|
114
|
+
else if (state === "fail") { s("circle", {cx:8, cy:8, r:7, fill:"var(--fail)"}, v); s("path", {d:"M5.5 5.5l5 5M10.5 5.5l-5 5", stroke:"#fff", "stroke-width":"1.8", "stroke-linecap":"round"}, v); }
|
|
115
|
+
else if (state === "running") { s("circle", {cx:8, cy:8, r:6, fill:"none", stroke:"var(--run)", "stroke-width":"2", opacity:".25"}, v);
|
|
116
|
+
s("path", {d:"M8 2a6 6 0 0 1 6 6", fill:"none", stroke:"var(--run)", "stroke-width":"2", "stroke-linecap":"round", "class":"spin"}, v); }
|
|
117
|
+
else s("circle", {cx:8, cy:8, r:6.25, fill:"none", stroke:"var(--faint)", "stroke-width":"1.5"}, v);
|
|
118
|
+
return v;
|
|
119
|
+
}
|
|
120
|
+
function selected(reqs){
|
|
121
|
+
if (pinned) { var p = reqs.filter(function(r){ return r.req === pinned; })[0]; if (p) return p; }
|
|
122
|
+
return reqs.filter(function(r){ return r.state === "running"; })[0] || reqs[0] || null;
|
|
123
|
+
}
|
|
124
|
+
function ordered(reqs){ return reqs.slice().sort(function(a, b){ return a.started - b.started; }); }
|
|
125
|
+
function groups(list){
|
|
126
|
+
var out = [], at = {};
|
|
127
|
+
list.forEach(function(r){ var g = r.group || ""; if (!(g in at)) { at[g] = out.length; out.push({name:g, reqs:[]}); } out[at[g]].reqs.push(r); });
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function pick(id){ pinned = pinned === id ? null : id; logKey = ""; draw(); }
|
|
131
|
+
|
|
132
|
+
function sidebar(list, sel){
|
|
133
|
+
var box = $("jobs"); box.replaceChildren();
|
|
134
|
+
var sum = el("button", "job summary-btn" + (pinned ? "" : " sel"), "Summary"); sum.type = "button";
|
|
135
|
+
sum.addEventListener("click", function(){ pinned = null; logKey = ""; draw(); }); box.appendChild(sum);
|
|
136
|
+
groups(list).forEach(function(g){
|
|
137
|
+
box.appendChild(el("h3", "", g.name ? g.name.charAt(0).toUpperCase() + g.name.slice(1) : "Requirements"));
|
|
138
|
+
g.reqs.forEach(function(r){
|
|
139
|
+
var b = el("button", "job" + (sel && sel.req === r.req ? " sel" : "")); b.type = "button";
|
|
140
|
+
b.setAttribute("aria-current", sel && sel.req === r.req ? "true" : "false");
|
|
141
|
+
b.append(icon(r.state), el("span", "name", r.req + " " + (r.title || "")), el("span", "d", dur(elapsed(r))));
|
|
142
|
+
b.addEventListener("click", function(){ pick(r.req); });
|
|
143
|
+
box.appendChild(b);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function summary(list){
|
|
149
|
+
var p = 0, f = 0, run = 0, first = 0, last = 0;
|
|
150
|
+
list.forEach(function(r){ if (r.state === "pass") p++; else if (r.state === "fail") f++; else run++;
|
|
151
|
+
if (!first || r.started < first) first = r.started; last = Math.max(last, r.state === "running" ? now() : r.updated); });
|
|
152
|
+
var state = run ? "running" : f ? "fail" : list.length ? "pass" : "running";
|
|
153
|
+
$("runBadge").className = "badge " + state;
|
|
154
|
+
$("runState").textContent = run ? "In progress" : f ? "Failed" : list.length ? "Completed" : "Waiting";
|
|
155
|
+
var mods = groups(list).map(function(g){ return g.name; }).filter(Boolean);
|
|
156
|
+
$("runTitle").textContent = mods.length ? mods.map(function(m){ return m.charAt(0).toUpperCase() + m.slice(1); }).join(", ") : "Harness run";
|
|
157
|
+
var box = $("progress"); box.replaceChildren();
|
|
158
|
+
[["Started", first ? stamp(first) : "—"], ["Duration", first ? dur(last - first) : "—"],
|
|
159
|
+
["Requirements", p + " passed · " + run + " running · " + f + " failed"],
|
|
160
|
+
["Agent time", dur(list.reduce(function(a, r){ return a + elapsed(r); }, 0))]].forEach(function(x){
|
|
161
|
+
var d = el("div"); d.append(el("span", "", x[0]), el("b", "", x[1])); box.appendChild(d);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The graph of jobs: one row per module, boxes joined in the order they ran, a bend between rows. */
|
|
166
|
+
function graph(list, sel){
|
|
167
|
+
var svg = $("chain"), W = Math.max(svg.clientWidth || 900, 360), bw = 168, bh = 40, gx = 24, gy = 34, padX = 18, padY = 30;
|
|
168
|
+
var perRow = Math.max(1, Math.floor((W - padX * 2 + gx) / (bw + gx)));
|
|
169
|
+
var rows = [];
|
|
170
|
+
groups(list).forEach(function(g){ for (var i = 0; i < g.reqs.length; i += perRow) rows.push({name: i ? "" : g.name, reqs: g.reqs.slice(i, i + perRow)}); });
|
|
171
|
+
var H = Math.max(80, padY + rows.length * (bh + gy) + 8);
|
|
172
|
+
svg.setAttribute("viewBox", "0 0 " + W + " " + H); svg.setAttribute("height", String(H)); svg.replaceChildren();
|
|
173
|
+
if (!list.length) { var t = s("text", {x:W / 2, y:H / 2, "text-anchor":"middle", fill:"var(--muted)", "font-size":"13"}, svg); t.textContent = "No harness events yet. Run specgate harness run: each requirement appears here as it starts."; return; }
|
|
174
|
+
var pos = {}, prev = null;
|
|
175
|
+
rows.forEach(function(row, ri){
|
|
176
|
+
var y = padY + ri * (bh + gy);
|
|
177
|
+
if (row.name) { var l = s("text", {x:padX, y:y - 10, "font-size":"12", "font-weight":"600", fill:"var(--muted)"}, svg); l.textContent = row.name.charAt(0).toUpperCase() + row.name.slice(1); }
|
|
178
|
+
row.reqs.forEach(function(r, i){ pos[r.req] = {x:padX + i * (bw + gx), y:y}; });
|
|
179
|
+
});
|
|
180
|
+
list.forEach(function(r){
|
|
181
|
+
var a = prev && pos[prev.req], b = pos[r.req];
|
|
182
|
+
if (a) {
|
|
183
|
+
var c = r.state === "running" ? "var(--run)" : prev.state === "pass" ? "var(--faint)" : "var(--line)";
|
|
184
|
+
if (a.y === b.y) s("line", {x1:a.x + bw, y1:a.y + bh / 2, x2:b.x, y2:b.y + bh / 2, stroke:c, "stroke-width":"1.5"}, svg);
|
|
185
|
+
else s("path", {d:"M" + (a.x + bw) + " " + (a.y + bh / 2) + " h" + (gx / 2) + " V" + (a.y + bh + gy / 2) + " H" + (b.x - gx / 2) + " V" + (b.y + bh / 2) + " H" + b.x,
|
|
186
|
+
fill:"none", stroke:c, "stroke-width":"1.5"}, svg);
|
|
187
|
+
}
|
|
188
|
+
prev = r;
|
|
189
|
+
});
|
|
190
|
+
list.forEach(function(r){
|
|
191
|
+
var p = pos[r.req], on = sel && sel.req === r.req;
|
|
192
|
+
var g = s("g", {transform:"translate(" + p.x + "," + p.y + ")", tabindex:"0", role:"button", style:"cursor:pointer",
|
|
193
|
+
"aria-label": r.req + " " + (r.title || "") + ", " + r.state}, svg);
|
|
194
|
+
s("rect", {width:bw, height:bh, rx:6, fill: on ? "var(--sel)" : "var(--panel)", stroke: on ? "var(--accent)" : "var(--line)", "stroke-width": on ? 2 : 1}, g);
|
|
195
|
+
var ic = icon(r.state, 16); ic.setAttribute("x", "12"); ic.setAttribute("y", String(bh / 2 - 8)); g.appendChild(ic);
|
|
196
|
+
var n = s("text", {x:36, y:bh / 2 + 4.5, "font-size":"13", "font-weight": on ? "600" : "400", fill:"var(--ink)"}, g); n.textContent = r.req;
|
|
197
|
+
var d = s("text", {x:bw - 12, y:bh / 2 + 4.5, "text-anchor":"end", "font-size":"12", fill:"var(--muted)"}, g); d.textContent = dur(elapsed(r));
|
|
198
|
+
var tip = s("title", {}, g); tip.textContent = r.req + " — " + (r.title || "");
|
|
199
|
+
g.addEventListener("click", function(){ pick(r.req); });
|
|
200
|
+
g.addEventListener("keydown", function(ev){ if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); pick(r.req); } });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The selected requirement's steps: the agent, then each stage of the gate as it ran, the rest still to come. */
|
|
205
|
+
function steps(r){
|
|
206
|
+
var ol = $("pipeline");
|
|
207
|
+
if (!r) { $("fName").textContent = "—"; $("fMeta").textContent = ""; ol.replaceChildren(el("li", "empty", "Each requirement opens here as the harness works on it.")); return; }
|
|
208
|
+
$("fName").textContent = r.req + " · " + (r.title || "");
|
|
209
|
+
$("fMeta").textContent = "attempt " + r.attempt + (r.group ? " · " + r.group : "") + " · harness/" + r.req;
|
|
210
|
+
var events = r.steps.slice().reverse(), firstGate = events.filter(function(e){ return e.type === "gate"; })[0];
|
|
211
|
+
var rows = [], seen = {Specification:false, Architecture:false, Tests:false, Merge:false};
|
|
212
|
+
var agentEnd = firstGate ? Number(firstGate.t) : (r.state === "running" ? now() : r.updated);
|
|
213
|
+
rows.push({name:"Agent", mono:false, state: firstGate ? "pass" : r.state === "running" ? "running" : r.state, d: dur(agentEnd - r.started), log: !firstGate});
|
|
214
|
+
var prevT = agentEnd;
|
|
215
|
+
events.forEach(function(e){
|
|
216
|
+
if (e.type !== "gate" && e.type !== "merge") return;
|
|
217
|
+
var stage = e.type === "merge" ? "merge into " + (e.into || "HEAD") : String(e.stage || "");
|
|
218
|
+
var kind = e.type === "merge" || stage.indexOf("post-merge") === 0 ? "Merge" : stage.indexOf("validate") >= 0 ? "Specification" : stage.indexOf("architecture") >= 0 ? "Architecture" : "Tests";
|
|
219
|
+
seen[kind] = true;
|
|
220
|
+
rows.push({name:stage, mono:true, state: e.type === "merge" || e.result === "pass" ? "pass" : "fail", d: dur(Number(e.t) - prevT)});
|
|
221
|
+
prevT = Number(e.t);
|
|
222
|
+
});
|
|
223
|
+
var lastGate = events.filter(function(e){ return e.type === "gate"; }).slice(-1)[0];
|
|
224
|
+
if (r.state === "running" && firstGate && !(lastGate && lastGate.result !== "pass")) {
|
|
225
|
+
var next = ["Specification","Architecture","Tests","Merge"].filter(function(k){ return !seen[k]; })[0];
|
|
226
|
+
if (next) { rows.push({name: next, mono:false, state:"running", d: dur(now() - prevT), log:true}); seen[next] = true; }
|
|
76
227
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
228
|
+
["Specification","Architecture","Tests","Merge"].forEach(function(k){ if (!seen[k] && r.state === "running") rows.push({name:k, mono:false, state:"idle", d:""}); });
|
|
229
|
+
ol.replaceChildren();
|
|
230
|
+
rows.forEach(function(x){
|
|
231
|
+
var li = el("li", x.state === "running" ? "running" : x.state === "idle" ? "idle" : "");
|
|
232
|
+
li.append(icon(x.state), el("span", "t" + (x.mono ? " mono" : ""), x.name), el("span", "d", x.d));
|
|
233
|
+
ol.appendChild(li);
|
|
234
|
+
if (x.log) { var lr = el("li", "logrow"); lr.appendChild(log(r)); ol.appendChild(lr); }
|
|
235
|
+
});
|
|
236
|
+
if (!rows.some(function(x){ return x.log; }) && r.steps.length) { var lr2 = el("li", "logrow"); lr2.appendChild(log(r)); ol.appendChild(lr2); }
|
|
237
|
+
}
|
|
238
|
+
function log(r){
|
|
239
|
+
var ul = el("ol"); ul.id = "feed";
|
|
240
|
+
r.steps.slice(0, 40).slice().reverse().forEach(function(e){
|
|
241
|
+
var li = el("li"), t = Number(e.ended || e.t) || 0, kind = e.type === "step" ? String(e.kind || "") : String(e.type || "");
|
|
242
|
+
var what = e.type === "message" ? e.text : e.type === "gate" ? (e.result === "pass" ? "passed " : "failed ") + e.stage
|
|
243
|
+
: e.type === "merge" ? "merged into " + (e.into || "HEAD") : (e.target || e.tool || "");
|
|
244
|
+
if (e.added !== undefined || e.removed !== undefined) what += " +" + (Number(e.added) || 0) + " −" + (Number(e.removed) || 0);
|
|
245
|
+
var x = el("span", "x" + (e.type === "message" ? " say" : e.type === "gate" ? (e.result === "pass" ? " ok" : " bad") : ""), clip(what, 400));
|
|
246
|
+
li.append(el("span", "ts", stamp(t)), el("span", "k", e.type === "message" ? "agent" : kind), x);
|
|
247
|
+
ul.appendChild(li);
|
|
248
|
+
});
|
|
249
|
+
if (!ul.childNodes.length) ul.appendChild(el("li", "", "The agent's steps appear here as it works."));
|
|
250
|
+
requestAnimationFrame(function(){ ul.scrollTop = ul.scrollHeight; });
|
|
251
|
+
return ul;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function draw(){
|
|
255
|
+
var list = ordered(model.requirements || []), sel = selected(list);
|
|
256
|
+
sidebar(list, sel); summary(list); graph(list, sel);
|
|
257
|
+
var key = sel ? sel.req + ":" + sel.attempt + ":" + sel.steps.length + ":" + sel.state : "";
|
|
258
|
+
if (key !== logKey) { logKey = key; steps(sel); }
|
|
259
|
+
}
|
|
260
|
+
function tick(){
|
|
261
|
+
var list = ordered(model.requirements || []), sel = selected(list);
|
|
262
|
+
summary(list); sidebar(list, sel);
|
|
263
|
+
var run = document.querySelector("#pipeline li.running .d");
|
|
264
|
+
if (run && sel && sel.state === "running") draw();
|
|
265
|
+
}
|
|
266
|
+
function accept(m){ model = m && m.requirements ? m : {requirements:[]}; draw(); }
|
|
267
|
+
|
|
268
|
+
fetch("live.json").then(function(x){ return x.json(); }).then(accept).catch(function(){});
|
|
269
|
+
var es = new EventSource("live/stream");
|
|
270
|
+
es.addEventListener("live", function(e){ try { accept(JSON.parse(e.data)); } catch (err) {} });
|
|
271
|
+
es.onopen = function(){ $("conn").classList.add("on"); $("connText").textContent = "Live"; };
|
|
272
|
+
es.onerror = function(){ $("conn").classList.remove("on"); $("connText").textContent = "Reconnecting"; };
|
|
273
|
+
setInterval(tick, 1000);
|
|
274
|
+
var t; window.addEventListener("resize", function(){ clearTimeout(t); t = setTimeout(function(){ logKey = ""; draw(); }, 120); });
|
|
275
|
+
})();
|
|
86
276
|
</script>
|
|
87
277
|
</body></html>`;
|
|
88
278
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-page.js","sourceRoot":"","sources":["../../../scripts/lib/live-page.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"live-page.js","sourceRoot":"","sources":["../../../scripts/lib/live-page.ts"],"names":[],"mappings":";;AAeA,4BAmQC;AAlRD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,QAAQ,CAAC,WAAmB;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1D,OAAO;;;wBAGe,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAwDL,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAqMd,CAAC;AAChB,CAAC"}
|