@codyswann/lisa 2.275.0 → 2.277.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/ui-cmd.d.ts +3 -0
- package/dist/cli/ui-cmd.d.ts.map +1 -1
- package/dist/cli/ui-cmd.js +11 -2
- package/dist/cli/ui-cmd.js.map +1 -1
- package/dist/cli/ui-health.d.ts +18 -0
- package/dist/cli/ui-health.d.ts.map +1 -0
- package/dist/cli/ui-health.js +140 -0
- package/dist/cli/ui-health.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +15 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +30 -0
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +30 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +30 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +30 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +30 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +30 -0
- package/ui/README.md +18 -10
- package/ui/index.html +316 -96
|
@@ -49,6 +49,36 @@ without a human between the loops and the pipeline. Pass `false` explicitly to o
|
|
|
49
49
|
human triage. The two auto-start flags affect **only** the two exploratory automations; the intake
|
|
50
50
|
gates' adversarial validation remains the quality control either way.
|
|
51
51
|
|
|
52
|
+
## Repository-readiness advisory
|
|
53
|
+
|
|
54
|
+
Before creating or updating registrations, locate the standing repository-readiness report through
|
|
55
|
+
RRR-3's canonical `resolveReadinessReportPath` contract (currently `.lisa/readiness.json` under the
|
|
56
|
+
resolved project root). This consumes the shared report; it never creates a second readiness
|
|
57
|
+
assessment. Read the report exactly once. Do not re-run `lisa doctor --readiness`, because doing so
|
|
58
|
+
would execute journeys and replace the standing evidence the operator is about to act on.
|
|
59
|
+
|
|
60
|
+
A report is usable for this advisory only when all of these are true:
|
|
61
|
+
|
|
62
|
+
- the file parses to a JSON object whose `schema_version` is `1` and whose `verdict` is `NOT_READY`;
|
|
63
|
+
- `blocker_count` is a positive integer and the `blockers` array length exactly equals
|
|
64
|
+
`blocker_count`;
|
|
65
|
+
- each blocker has a unique `id` in the closed set B1 through B7 (duplicate blocker ids make the
|
|
66
|
+
report internally inconsistent, and uniqueness caps the count at seven) and a non-empty
|
|
67
|
+
operator-facing `label`; and
|
|
68
|
+
- `narrowed_claim` is a non-empty string.
|
|
69
|
+
|
|
70
|
+
For one usable report, emit exactly one warning naming the blocker count, each blocker's `id` and
|
|
71
|
+
`label`, and the narrowed claim. For example: "Repository readiness warning — 3 standing ship
|
|
72
|
+
blockers: B2 Release path bypasses validation; B4 Failing loop has no recovery; B7 Operability
|
|
73
|
+
cannot be proved. Narrowed claim: ready for supervised changes only." This is a warning only:
|
|
74
|
+
setup continues, and no registration or runbook step is skipped because blockers stand.
|
|
75
|
+
|
|
76
|
+
Treat a missing or unreadable file, invalid JSON, unsupported schema, or internally inconsistent
|
|
77
|
+
report as unavailable: emit no readiness warning and no error, then continue setup normally. Do not
|
|
78
|
+
invent an artifact freshness, age, or TTL rule; the persisted contract defines no such field. This
|
|
79
|
+
advisory is never a precondition and follows the same never-block-always-degrade posture as runbook
|
|
80
|
+
scaffolding below.
|
|
81
|
+
|
|
52
82
|
## The automations to create
|
|
53
83
|
|
|
54
84
|
Each automation runs **one cycle** of a Lisa command and respects that command's confirmation policy
|
package/ui/README.md
CHANGED
|
@@ -134,8 +134,13 @@ path, harness-neutral optional agentic composition API at
|
|
|
134
134
|
`@codyswann/lisa/health`, `lisa health` CLI, and `/lisa:health` skill across all
|
|
135
135
|
six supported harnesses. Completed results are validated and atomically stored
|
|
136
136
|
at the gitignored `.lisa/health/latest.json`; `health.schedule` is the only v1
|
|
137
|
-
configuration key and accepts `off`, `daily`, or `weekly`. The console
|
|
138
|
-
|
|
137
|
+
configuration key and accepts `off`, `daily`, or `weekly`. The console now
|
|
138
|
+
reads that stored result on boot and its **Run deterministic health check** button invokes the
|
|
139
|
+
shared consumer directly against the directory served by `lisa ui`. Browser
|
|
140
|
+
runs are intentionally deterministic: the local server starts no child process
|
|
141
|
+
or nested coding agent. Agentic composition remains the responsibility of the
|
|
142
|
+
`/lisa:health` skill in the active coding harness. The scheduler remains
|
|
143
|
+
downstream work.
|
|
139
144
|
|
|
140
145
|
**Is Lisa on the latest version?** — always-on status. Lisa's CLI already
|
|
141
146
|
checks npm on every invocation; the console surfaces the result permanently
|
|
@@ -144,13 +149,14 @@ Health section's version card. When behind, `lisa update` prints (or runs
|
|
|
144
149
|
with `--yes`) the package-manager update command.
|
|
145
150
|
|
|
146
151
|
**Is the project completely in band?** — on-demand scan behind the
|
|
147
|
-
"Run health check" button (plus an optional scheduled cadence via
|
|
152
|
+
"Run deterministic health check" button (plus an optional scheduled cadence via
|
|
148
153
|
`health.schedule` that files a ticket when drift is found). "In band" means
|
|
149
154
|
every Lisa-managed surface matches what the installed Lisa version would
|
|
150
|
-
emit. The
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
emit. The console and `lisa health` CLI invoke the same deterministic persisted
|
|
156
|
+
consumer. `/lisa:health` is the current-harness surface that may compose the
|
|
157
|
+
optional agentic layer before persisting a final result; the console can read
|
|
158
|
+
and render that stored full result, but a browser-initiated run remains
|
|
159
|
+
deterministic. The scheduled consumer remains downstream work:
|
|
154
160
|
|
|
155
161
|
- **Deterministic layer** (fast, exact — reuses what exists today):
|
|
156
162
|
`lisa doctor`, template diffing for copy-overwrite/managed-block files,
|
|
@@ -168,9 +174,11 @@ console and cron integrations can reuse it:
|
|
|
168
174
|
finalization revalidates the evidence digest before persisting one final
|
|
169
175
|
result.
|
|
170
176
|
|
|
171
|
-
The
|
|
172
|
-
|
|
173
|
-
|
|
177
|
+
The console renders every canonical finding field (`check`, `layer`, `status`,
|
|
178
|
+
and `reason`) in its existing per-check table, keeps the stored completion stamp
|
|
179
|
+
and verdict visible, and gives the health verdict priority in the top-bar chip
|
|
180
|
+
while retaining Lisa version context. A failed run clears prior findings and
|
|
181
|
+
the prior green verdict before surfacing a generic failure; it does not retry.
|
|
174
182
|
|
|
175
183
|
### Remote-environment requirements
|
|
176
184
|
|
package/ui/index.html
CHANGED
|
@@ -1837,12 +1837,11 @@
|
|
|
1837
1837
|
DATA.sections.health = {
|
|
1838
1838
|
title: "Health",
|
|
1839
1839
|
desc: "Whether this project is on the latest Lisa and completely in band — every Lisa-managed surface matching what the installed version would emit. Version status is always visible (top bar); the in-band scan runs on demand.",
|
|
1840
|
-
src: "lisa
|
|
1840
|
+
src: "lisa health · /lisa:health skill · .lisa/health/latest.json",
|
|
1841
1841
|
blocks: [
|
|
1842
1842
|
{
|
|
1843
1843
|
type: "callout",
|
|
1844
|
-
|
|
1845
|
-
text: "<b>Planned feature.</b> The version status is real today (lisa's npm update check). The in-band scan below documents the contract for a health-check skill that mixes deterministic checks with agentic review — not wired up yet.",
|
|
1844
|
+
text: '<b>Live health.</b> The console runs the shared deterministic Health v1 consumer against this local project. It reads and writes the same canonical result as <span class="mono">lisa health</span>; optional agentic evaluation remains a coding-harness concern.',
|
|
1846
1845
|
},
|
|
1847
1846
|
{
|
|
1848
1847
|
card: "Lisa version",
|
|
@@ -1874,17 +1873,18 @@
|
|
|
1874
1873
|
},
|
|
1875
1874
|
{
|
|
1876
1875
|
card: "In-band scan",
|
|
1877
|
-
note: "Deterministic
|
|
1876
|
+
note: "Deterministic Health v1 checks",
|
|
1878
1877
|
rows: [
|
|
1879
1878
|
{
|
|
1880
1879
|
key: "",
|
|
1881
|
-
label: "Run health check",
|
|
1882
|
-
desc: "Scans every Lisa-managed surface and reports drift: managed files vs templates, package.json governance, config population, hooks, plugins, CI workflows, rulesets
|
|
1880
|
+
label: "Run deterministic health check",
|
|
1881
|
+
desc: "Scans every Lisa-managed surface deterministically and reports drift: managed files vs templates, package.json governance, config population, hooks, plugins, CI workflows, and rulesets.",
|
|
1883
1882
|
control: {
|
|
1884
1883
|
type: "button",
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1884
|
+
id: "runHealthCheckButton",
|
|
1885
|
+
action: "run-health",
|
|
1886
|
+
label: "Run deterministic health check",
|
|
1887
|
+
statusId: "healthRunStatus",
|
|
1888
1888
|
},
|
|
1889
1889
|
},
|
|
1890
1890
|
{
|
|
@@ -1895,86 +1895,30 @@
|
|
|
1895
1895
|
},
|
|
1896
1896
|
{
|
|
1897
1897
|
key: "",
|
|
1898
|
-
label: "
|
|
1898
|
+
label: "Latest deterministic check",
|
|
1899
1899
|
desc: "",
|
|
1900
|
-
control:
|
|
1900
|
+
control: {
|
|
1901
|
+
...stat("Never run", "mono"),
|
|
1902
|
+
id: "healthLastRun",
|
|
1903
|
+
},
|
|
1901
1904
|
},
|
|
1902
1905
|
],
|
|
1903
1906
|
},
|
|
1904
1907
|
{
|
|
1905
1908
|
type: "table",
|
|
1909
|
+
id: "healthResults",
|
|
1906
1910
|
card: "Latest results",
|
|
1907
|
-
note: "
|
|
1908
|
-
cols: ["Check", "Layer", "Status"],
|
|
1909
|
-
rows: [
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
},
|
|
1915
|
-
{ t: "mono", v: "deterministic" },
|
|
1916
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1917
|
-
],
|
|
1918
|
-
[
|
|
1919
|
-
{
|
|
1920
|
-
t: "text",
|
|
1921
|
-
v: "package.json governance (force/defaults/merge)",
|
|
1922
|
-
},
|
|
1923
|
-
{ t: "mono", v: "deterministic" },
|
|
1924
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1925
|
-
],
|
|
1926
|
-
[
|
|
1927
|
-
{
|
|
1928
|
-
t: "text",
|
|
1929
|
-
v: ".lisa.config.json fully populated (sync clean)",
|
|
1930
|
-
},
|
|
1931
|
-
{ t: "mono", v: "deterministic" },
|
|
1932
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1933
|
-
],
|
|
1934
|
-
[
|
|
1935
|
-
{ t: "text", v: "Git hooks installed and unmodified" },
|
|
1936
|
-
{ t: "mono", v: "deterministic" },
|
|
1937
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1938
|
-
],
|
|
1939
|
-
[
|
|
1940
|
-
{ t: "text", v: "Plugins enabled and version-current" },
|
|
1941
|
-
{ t: "mono", v: "deterministic" },
|
|
1942
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1943
|
-
],
|
|
1944
|
-
[
|
|
1945
|
-
{ t: "text", v: "CI workflows drifted from stack template" },
|
|
1946
|
-
{ t: "mono", v: "deterministic" },
|
|
1947
|
-
{ t: "badge", v: "warn", cls: "warn" },
|
|
1948
|
-
],
|
|
1949
|
-
[
|
|
1950
|
-
{
|
|
1951
|
-
t: "text",
|
|
1952
|
-
v: "Local overrides still justified (eslint.config.local.ts)",
|
|
1953
|
-
},
|
|
1954
|
-
{ t: "mono", v: "agentic" },
|
|
1955
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1956
|
-
],
|
|
1957
|
-
[
|
|
1958
|
-
{
|
|
1959
|
-
t: "text",
|
|
1960
|
-
v: "Skipped CI jobs have a recorded reason",
|
|
1961
|
-
},
|
|
1962
|
-
{ t: "mono", v: "agentic" },
|
|
1963
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1964
|
-
],
|
|
1965
|
-
[
|
|
1966
|
-
{
|
|
1967
|
-
t: "text",
|
|
1968
|
-
v: "Grandfathered lint globs still needed",
|
|
1969
|
-
},
|
|
1970
|
-
{ t: "mono", v: "agentic" },
|
|
1971
|
-
{ t: "badge", v: "pass", cls: "pass" },
|
|
1972
|
-
],
|
|
1973
|
-
],
|
|
1911
|
+
note: "No completed health check yet",
|
|
1912
|
+
cols: ["Check", "Layer", "Status", "Reason"],
|
|
1913
|
+
rows: [],
|
|
1914
|
+
empty: {
|
|
1915
|
+
state: "never-run",
|
|
1916
|
+
message: "Run deterministic health check to measure this project",
|
|
1917
|
+
},
|
|
1974
1918
|
},
|
|
1975
1919
|
{
|
|
1976
1920
|
type: "callout",
|
|
1977
|
-
text:
|
|
1921
|
+
text: '<b>How it works.</b> Browser runs are deliberately deterministic: the local server invokes the shared Health v1 consumer directly, without a child process or nested coding agent. The optional agentic layer is composed by <span class="mono">/lisa:health</span> in a supported coding harness.',
|
|
1978
1922
|
},
|
|
1979
1923
|
],
|
|
1980
1924
|
};
|
|
@@ -5131,6 +5075,15 @@
|
|
|
5131
5075
|
"detecting" so the Stacks section never presents the metadata catalog
|
|
5132
5076
|
as if it were the detection result. */
|
|
5133
5077
|
let stackDetection = { status: "detecting", ids: [] };
|
|
5078
|
+
/* Version context and Health v1 verdict are painted together. Health
|
|
5079
|
+
takes priority once measured without erasing the update context. */
|
|
5080
|
+
let lisaVersionChipState = {
|
|
5081
|
+
dotClass: "",
|
|
5082
|
+
label: "@codyswann/lisa · checking…",
|
|
5083
|
+
};
|
|
5084
|
+
let healthChipState = { state: "idle" };
|
|
5085
|
+
/* Durable Health view model projected by every whole-page render. */
|
|
5086
|
+
let healthViewState = { state: "idle" };
|
|
5134
5087
|
|
|
5135
5088
|
function el(tag, cls, html) {
|
|
5136
5089
|
const node = document.createElement(tag);
|
|
@@ -5275,6 +5228,7 @@
|
|
|
5275
5228
|
} else if (control.type === "button") {
|
|
5276
5229
|
const b = el("button", "action-btn", esc(control.label));
|
|
5277
5230
|
b.type = "button";
|
|
5231
|
+
if (control.id) b.id = control.id;
|
|
5278
5232
|
if (control.disabled) {
|
|
5279
5233
|
b.disabled = true;
|
|
5280
5234
|
if (control.reason) {
|
|
@@ -5282,11 +5236,35 @@
|
|
|
5282
5236
|
b.setAttribute("aria-describedby", id + "-reason");
|
|
5283
5237
|
}
|
|
5284
5238
|
} else {
|
|
5285
|
-
b.addEventListener("click", () =>
|
|
5286
|
-
|
|
5287
|
-
|
|
5239
|
+
b.addEventListener("click", () => {
|
|
5240
|
+
if (control.action === "run-health") {
|
|
5241
|
+
void runHealthCheck();
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
showToast(control.toast || "Prototype action");
|
|
5245
|
+
});
|
|
5288
5246
|
}
|
|
5289
5247
|
wrap.appendChild(b);
|
|
5248
|
+
if (control.statusId) {
|
|
5249
|
+
const status = el(
|
|
5250
|
+
"span",
|
|
5251
|
+
"status-why health-run-status",
|
|
5252
|
+
esc(
|
|
5253
|
+
control.statusMessage ||
|
|
5254
|
+
"Ready to run deterministic health check"
|
|
5255
|
+
)
|
|
5256
|
+
);
|
|
5257
|
+
status.id = control.statusId;
|
|
5258
|
+
status.setAttribute(
|
|
5259
|
+
"role",
|
|
5260
|
+
control.statusRole === "alert" ? "alert" : "status"
|
|
5261
|
+
);
|
|
5262
|
+
status.setAttribute(
|
|
5263
|
+
"aria-live",
|
|
5264
|
+
control.statusRole === "alert" ? "assertive" : "polite"
|
|
5265
|
+
);
|
|
5266
|
+
wrap.appendChild(status);
|
|
5267
|
+
}
|
|
5290
5268
|
if (control.disabled && control.reason) {
|
|
5291
5269
|
const reason = el(
|
|
5292
5270
|
"span",
|
|
@@ -5297,9 +5275,13 @@
|
|
|
5297
5275
|
wrap.appendChild(reason);
|
|
5298
5276
|
}
|
|
5299
5277
|
} else if (control.type === "static") {
|
|
5300
|
-
|
|
5301
|
-
|
|
5278
|
+
const value = el(
|
|
5279
|
+
"span",
|
|
5280
|
+
control.cls === "mono" ? "mono" : "",
|
|
5281
|
+
esc(control.value)
|
|
5302
5282
|
);
|
|
5283
|
+
if (control.id) value.id = control.id;
|
|
5284
|
+
wrap.appendChild(value);
|
|
5303
5285
|
}
|
|
5304
5286
|
return wrap;
|
|
5305
5287
|
}
|
|
@@ -5930,6 +5912,7 @@
|
|
|
5930
5912
|
}
|
|
5931
5913
|
|
|
5932
5914
|
function renderAll() {
|
|
5915
|
+
syncHealthViewModel();
|
|
5933
5916
|
$main.innerHTML = "";
|
|
5934
5917
|
$nav.innerHTML = "";
|
|
5935
5918
|
ctlSeq = 0;
|
|
@@ -6422,8 +6405,6 @@
|
|
|
6422
6405
|
* @param probes - Live-status probe map from /api/status
|
|
6423
6406
|
*/
|
|
6424
6407
|
function applyLisaVersion(probes) {
|
|
6425
|
-
const chip = document.getElementById("healthChip");
|
|
6426
|
-
if (!chip) return;
|
|
6427
6408
|
const raw =
|
|
6428
6409
|
probes && Object.hasOwn(probes, "lisa-version")
|
|
6429
6410
|
? probes["lisa-version"]
|
|
@@ -6436,12 +6417,9 @@
|
|
|
6436
6417
|
message: "The lisa-version probe was not reported",
|
|
6437
6418
|
}
|
|
6438
6419
|
: normalizeLiveStatusResult(raw);
|
|
6439
|
-
const
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
dot.setAttribute("aria-hidden", "true");
|
|
6443
|
-
chip.appendChild(dot);
|
|
6444
|
-
chip.appendChild(document.createTextNode(" " + label));
|
|
6420
|
+
const setVersion = (dotClass, label) => {
|
|
6421
|
+
lisaVersionChipState = { dotClass, label };
|
|
6422
|
+
paintHealthChip();
|
|
6445
6423
|
};
|
|
6446
6424
|
if (
|
|
6447
6425
|
result.state === "value" &&
|
|
@@ -6458,30 +6436,271 @@
|
|
|
6458
6436
|
latest.length > 0
|
|
6459
6437
|
) {
|
|
6460
6438
|
if (outdated) {
|
|
6461
|
-
|
|
6439
|
+
setVersion(
|
|
6462
6440
|
"warn",
|
|
6463
6441
|
"@codyswann/lisa " + current + " · latest " + latest
|
|
6464
6442
|
);
|
|
6465
6443
|
return;
|
|
6466
6444
|
}
|
|
6467
|
-
|
|
6445
|
+
setVersion("ok", "@codyswann/lisa " + current + " · up to date");
|
|
6468
6446
|
return;
|
|
6469
6447
|
}
|
|
6470
6448
|
}
|
|
6471
6449
|
if (result.state === "unknown") {
|
|
6472
6450
|
const reason = result.reason || "unknown";
|
|
6473
6451
|
const message = result.message || "Version status unavailable";
|
|
6474
|
-
|
|
6452
|
+
setVersion(
|
|
6475
6453
|
"unknown",
|
|
6476
6454
|
"@codyswann/lisa · unknown (" + reason + ": " + message + ")"
|
|
6477
6455
|
);
|
|
6478
6456
|
return;
|
|
6479
6457
|
}
|
|
6480
|
-
|
|
6458
|
+
setVersion(
|
|
6481
6459
|
"unknown",
|
|
6482
6460
|
"@codyswann/lisa · unknown (invalid-value: lisa-version probe returned an unusable value)"
|
|
6483
6461
|
);
|
|
6484
6462
|
}
|
|
6463
|
+
|
|
6464
|
+
/** Paint one chip from the latest version context and health state. */
|
|
6465
|
+
function paintHealthChip() {
|
|
6466
|
+
const chip = document.getElementById("healthChip");
|
|
6467
|
+
if (!chip) return;
|
|
6468
|
+
let dotClass = lisaVersionChipState.dotClass;
|
|
6469
|
+
let label = lisaVersionChipState.label;
|
|
6470
|
+
if (healthChipState.state === "result") {
|
|
6471
|
+
dotClass = healthChipState.verdict === "in band" ? "ok" : "warn";
|
|
6472
|
+
label = healthChipState.verdict + " · " + lisaVersionChipState.label;
|
|
6473
|
+
} else if (healthChipState.state === "running") {
|
|
6474
|
+
dotClass = "unknown";
|
|
6475
|
+
label = "health check running · " + lisaVersionChipState.label;
|
|
6476
|
+
} else if (healthChipState.state === "error") {
|
|
6477
|
+
dotClass = "unknown";
|
|
6478
|
+
label = "health unavailable · " + lisaVersionChipState.label;
|
|
6479
|
+
}
|
|
6480
|
+
chip.innerHTML = "";
|
|
6481
|
+
const dot = el("span", "dot" + (dotClass ? " " + dotClass : ""));
|
|
6482
|
+
dot.setAttribute("aria-hidden", "true");
|
|
6483
|
+
chip.appendChild(dot);
|
|
6484
|
+
chip.appendChild(document.createTextNode(" " + label));
|
|
6485
|
+
}
|
|
6486
|
+
|
|
6487
|
+
/** Return the existing Health results-table descriptor. */
|
|
6488
|
+
function healthResultsBlock() {
|
|
6489
|
+
return DATA.sections.health.blocks.find(
|
|
6490
|
+
block => block.id === "healthResults"
|
|
6491
|
+
);
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6494
|
+
/** Return the existing deterministic-run row descriptor. */
|
|
6495
|
+
function healthRunRow() {
|
|
6496
|
+
const scan = DATA.sections.health.blocks.find(
|
|
6497
|
+
block => block.card === "In-band scan"
|
|
6498
|
+
);
|
|
6499
|
+
return scan && scan.rows
|
|
6500
|
+
? scan.rows.find(
|
|
6501
|
+
row => row.control && row.control.action === "run-health"
|
|
6502
|
+
)
|
|
6503
|
+
: undefined;
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
/** Return the existing latest deterministic-check row descriptor. */
|
|
6507
|
+
function healthStampRow() {
|
|
6508
|
+
const scan = DATA.sections.health.blocks.find(
|
|
6509
|
+
block => block.card === "In-band scan"
|
|
6510
|
+
);
|
|
6511
|
+
return scan && scan.rows
|
|
6512
|
+
? scan.rows.find(
|
|
6513
|
+
row => row.control && row.control.id === "healthLastRun"
|
|
6514
|
+
)
|
|
6515
|
+
: undefined;
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
/** Project durable Health state into the existing section descriptors. */
|
|
6519
|
+
function syncHealthViewModel() {
|
|
6520
|
+
const runRow = healthRunRow();
|
|
6521
|
+
const stampRow = healthStampRow();
|
|
6522
|
+
const block = healthResultsBlock();
|
|
6523
|
+
if (!runRow || !stampRow || !block) return;
|
|
6524
|
+
const running = healthViewState.state === "running";
|
|
6525
|
+
runRow.control.label = running
|
|
6526
|
+
? "Running…"
|
|
6527
|
+
: "Run deterministic health check";
|
|
6528
|
+
runRow.control.disabled = running;
|
|
6529
|
+
runRow.control.statusRole =
|
|
6530
|
+
healthViewState.state === "error" ? "alert" : "status";
|
|
6531
|
+
runRow.control.statusMessage =
|
|
6532
|
+
healthViewState.state === "running"
|
|
6533
|
+
? "Deterministic health check running"
|
|
6534
|
+
: healthViewState.state === "error"
|
|
6535
|
+
? healthViewState.message
|
|
6536
|
+
: healthViewState.state === "result"
|
|
6537
|
+
? "Deterministic health check completed: " +
|
|
6538
|
+
healthViewState.result.summary.verdict
|
|
6539
|
+
: "Ready to run deterministic health check";
|
|
6540
|
+
|
|
6541
|
+
const visibleResult = healthViewState.result;
|
|
6542
|
+
if (visibleResult) {
|
|
6543
|
+
block.note =
|
|
6544
|
+
visibleResult.mode +
|
|
6545
|
+
" · " +
|
|
6546
|
+
visibleResult.summary.verdict +
|
|
6547
|
+
" · run " +
|
|
6548
|
+
visibleResult.runId;
|
|
6549
|
+
block.rows = visibleResult.findings.map(finding => [
|
|
6550
|
+
{ t: "mono", v: finding.check },
|
|
6551
|
+
{ t: "mono", v: finding.layer },
|
|
6552
|
+
{ t: "badge", v: finding.status, cls: finding.status },
|
|
6553
|
+
{ t: "text", v: finding.reason },
|
|
6554
|
+
]);
|
|
6555
|
+
delete block.empty;
|
|
6556
|
+
stampRow.control.value =
|
|
6557
|
+
visibleResult.completedAt + " · " + visibleResult.summary.verdict;
|
|
6558
|
+
} else if (healthViewState.state === "error") {
|
|
6559
|
+
block.note = "Deterministic health check failed";
|
|
6560
|
+
block.rows = [];
|
|
6561
|
+
block.empty = { state: "error", message: healthViewState.message };
|
|
6562
|
+
stampRow.control.value = "Run failed · no current verdict";
|
|
6563
|
+
} else {
|
|
6564
|
+
block.note = "No completed deterministic health check yet";
|
|
6565
|
+
block.rows = [];
|
|
6566
|
+
block.empty = {
|
|
6567
|
+
state: "never-run",
|
|
6568
|
+
message: "Run deterministic health check to measure this project",
|
|
6569
|
+
};
|
|
6570
|
+
stampRow.control.value = "Never run";
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
healthChipState =
|
|
6574
|
+
healthViewState.state === "running"
|
|
6575
|
+
? { state: "running" }
|
|
6576
|
+
: healthViewState.state === "error"
|
|
6577
|
+
? { state: "error" }
|
|
6578
|
+
: visibleResult
|
|
6579
|
+
? {
|
|
6580
|
+
state: "result",
|
|
6581
|
+
verdict: visibleResult.summary.verdict,
|
|
6582
|
+
}
|
|
6583
|
+
: { state: "idle" };
|
|
6584
|
+
}
|
|
6585
|
+
|
|
6586
|
+
/** Replace only the existing Health results card after state changes. */
|
|
6587
|
+
function repaintHealthResults() {
|
|
6588
|
+
syncHealthViewModel();
|
|
6589
|
+
const block = healthResultsBlock();
|
|
6590
|
+
const current = document.getElementById("healthResults");
|
|
6591
|
+
if (block && current) current.replaceWith(buildTable(block));
|
|
6592
|
+
const runRow = healthRunRow();
|
|
6593
|
+
const currentButton = document.getElementById("runHealthCheckButton");
|
|
6594
|
+
const currentControl =
|
|
6595
|
+
currentButton && currentButton.closest(".control");
|
|
6596
|
+
if (runRow && currentControl) {
|
|
6597
|
+
currentControl.replaceWith(buildControl(runRow.control, runRow.key));
|
|
6598
|
+
}
|
|
6599
|
+
const stampRow = healthStampRow();
|
|
6600
|
+
const stamp = document.getElementById("healthLastRun");
|
|
6601
|
+
if (stampRow && stamp) stamp.textContent = stampRow.control.value;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
/** Validate the browser-facing closed Health v1 shape before rendering. */
|
|
6605
|
+
function requireHealthResult(candidate) {
|
|
6606
|
+
const isObject = value =>
|
|
6607
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6608
|
+
const hasExactKeys = (value, keys) =>
|
|
6609
|
+
isObject(value) &&
|
|
6610
|
+
Object.keys(value).sort().join(",") === [...keys].sort().join(",");
|
|
6611
|
+
if (
|
|
6612
|
+
!hasExactKeys(candidate, [
|
|
6613
|
+
"schemaVersion",
|
|
6614
|
+
"runId",
|
|
6615
|
+
"mode",
|
|
6616
|
+
"startedAt",
|
|
6617
|
+
"completedAt",
|
|
6618
|
+
"findings",
|
|
6619
|
+
"summary",
|
|
6620
|
+
]) ||
|
|
6621
|
+
candidate.schemaVersion !== 1 ||
|
|
6622
|
+
typeof candidate.runId !== "string" ||
|
|
6623
|
+
!["deterministic", "full"].includes(candidate.mode) ||
|
|
6624
|
+
typeof candidate.startedAt !== "string" ||
|
|
6625
|
+
typeof candidate.completedAt !== "string" ||
|
|
6626
|
+
!Array.isArray(candidate.findings) ||
|
|
6627
|
+
candidate.findings.length === 0 ||
|
|
6628
|
+
!hasExactKeys(candidate.summary, ["verdict", "counts"]) ||
|
|
6629
|
+
!["in band", "drift detected"].includes(candidate.summary.verdict) ||
|
|
6630
|
+
!hasExactKeys(candidate.summary.counts, ["pass", "warn", "fail"])
|
|
6631
|
+
) {
|
|
6632
|
+
throw new TypeError("Health response is not valid Health v1 JSON");
|
|
6633
|
+
}
|
|
6634
|
+
candidate.findings.forEach(finding => {
|
|
6635
|
+
if (
|
|
6636
|
+
!hasExactKeys(finding, ["check", "layer", "status", "reason"]) ||
|
|
6637
|
+
typeof finding.check !== "string" ||
|
|
6638
|
+
!["deterministic", "agentic"].includes(finding.layer) ||
|
|
6639
|
+
!["pass", "warn", "fail"].includes(finding.status) ||
|
|
6640
|
+
typeof finding.reason !== "string"
|
|
6641
|
+
) {
|
|
6642
|
+
throw new TypeError("Health response contains an invalid finding");
|
|
6643
|
+
}
|
|
6644
|
+
});
|
|
6645
|
+
return candidate;
|
|
6646
|
+
}
|
|
6647
|
+
|
|
6648
|
+
/** Render the exact canonical finding fields into the existing table. */
|
|
6649
|
+
function applyHealthResult(candidate) {
|
|
6650
|
+
const result = requireHealthResult(candidate);
|
|
6651
|
+
healthViewState = { state: "result", result };
|
|
6652
|
+
repaintHealthResults();
|
|
6653
|
+
paintHealthChip();
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
/** Clear prior findings and surface one current failure without retrying. */
|
|
6657
|
+
function applyHealthError(message) {
|
|
6658
|
+
healthViewState = { state: "error", message };
|
|
6659
|
+
repaintHealthResults();
|
|
6660
|
+
paintHealthChip();
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6663
|
+
/** Read stored Health v1 state once when the console boots. */
|
|
6664
|
+
async function hydrateHealth() {
|
|
6665
|
+
try {
|
|
6666
|
+
const response = await fetch("/api/health", { cache: "no-store" });
|
|
6667
|
+
if (response.status === 404) return;
|
|
6668
|
+
if (!response.ok) throw new Error("Unable to read Lisa health");
|
|
6669
|
+
applyHealthResult(await response.json());
|
|
6670
|
+
} catch (error) {
|
|
6671
|
+
applyHealthError(
|
|
6672
|
+
error instanceof Error
|
|
6673
|
+
? error.message
|
|
6674
|
+
: "Unable to read Lisa health"
|
|
6675
|
+
);
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
|
|
6679
|
+
/** Run exactly one deterministic Health v1 check through the local server. */
|
|
6680
|
+
async function runHealthCheck() {
|
|
6681
|
+
const button = document.getElementById("runHealthCheckButton");
|
|
6682
|
+
if (!button || button.disabled || healthViewState.state === "running")
|
|
6683
|
+
return;
|
|
6684
|
+
const previousResult = healthViewState.result;
|
|
6685
|
+
healthViewState = {
|
|
6686
|
+
state: "running",
|
|
6687
|
+
...(previousResult ? { result: previousResult } : {}),
|
|
6688
|
+
};
|
|
6689
|
+
repaintHealthResults();
|
|
6690
|
+
paintHealthChip();
|
|
6691
|
+
try {
|
|
6692
|
+
const response = await fetch("/api/health", {
|
|
6693
|
+
method: "POST",
|
|
6694
|
+
cache: "no-store",
|
|
6695
|
+
});
|
|
6696
|
+
if (!response.ok) throw new Error("Unable to run Lisa health");
|
|
6697
|
+
applyHealthResult(await response.json());
|
|
6698
|
+
} catch (error) {
|
|
6699
|
+
applyHealthError(
|
|
6700
|
+
error instanceof Error ? error.message : "Unable to run Lisa health"
|
|
6701
|
+
);
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6485
6704
|
/**
|
|
6486
6705
|
* Paint the CI Quality jobs Active column from the ci-quality-jobs probe.
|
|
6487
6706
|
* Never leaves a fabricated active/off value when the probe is unknown.
|
|
@@ -7005,6 +7224,7 @@
|
|
|
7005
7224
|
hydrateFromLiveConfig();
|
|
7006
7225
|
renderAll();
|
|
7007
7226
|
void hydrateLiveStatuses();
|
|
7227
|
+
void hydrateHealth();
|
|
7008
7228
|
showSection(location.hash ? location.hash.slice(1) : "overview");
|
|
7009
7229
|
if (!document.getElementById("section-" + activeSection))
|
|
7010
7230
|
showSection("overview");
|