@codyswann/lisa 2.235.0 → 2.237.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/cli/check-learnings-budget-cmd.d.ts +20 -0
  2. package/dist/cli/check-learnings-budget-cmd.d.ts.map +1 -0
  3. package/dist/cli/check-learnings-budget-cmd.js +59 -0
  4. package/dist/cli/check-learnings-budget-cmd.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +29 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/ui-automations-adapters.d.ts +50 -0
  10. package/dist/cli/ui-automations-adapters.d.ts.map +1 -0
  11. package/dist/cli/ui-automations-adapters.js +162 -0
  12. package/dist/cli/ui-automations-adapters.js.map +1 -0
  13. package/dist/cli/ui-automations.d.ts +89 -0
  14. package/dist/cli/ui-automations.d.ts.map +1 -0
  15. package/dist/cli/ui-automations.js +148 -0
  16. package/dist/cli/ui-automations.js.map +1 -0
  17. package/dist/cli/ui-cmd.d.ts +5 -0
  18. package/dist/cli/ui-cmd.d.ts.map +1 -1
  19. package/dist/cli/ui-cmd.js +17 -0
  20. package/dist/cli/ui-cmd.js.map +1 -1
  21. package/dist/cli/ui-deploy-pipeline-model.d.ts +70 -0
  22. package/dist/cli/ui-deploy-pipeline-model.d.ts.map +1 -0
  23. package/dist/cli/ui-deploy-pipeline-model.js +127 -0
  24. package/dist/cli/ui-deploy-pipeline-model.js.map +1 -0
  25. package/dist/cli/ui-deploy-pipeline.d.ts +25 -0
  26. package/dist/cli/ui-deploy-pipeline.d.ts.map +1 -0
  27. package/dist/cli/ui-deploy-pipeline.js +192 -0
  28. package/dist/cli/ui-deploy-pipeline.js.map +1 -0
  29. package/dist/cli/ui-detected-stacks.d.ts +20 -0
  30. package/dist/cli/ui-detected-stacks.d.ts.map +1 -0
  31. package/dist/cli/ui-detected-stacks.js +28 -0
  32. package/dist/cli/ui-detected-stacks.js.map +1 -0
  33. package/dist/cli/ui-enabled-plugins.d.ts +52 -0
  34. package/dist/cli/ui-enabled-plugins.d.ts.map +1 -0
  35. package/dist/cli/ui-enabled-plugins.js +253 -0
  36. package/dist/cli/ui-enabled-plugins.js.map +1 -0
  37. package/dist/cli/ui-github-repo-expected.d.ts +36 -0
  38. package/dist/cli/ui-github-repo-expected.d.ts.map +1 -0
  39. package/dist/cli/ui-github-repo-expected.js +118 -0
  40. package/dist/cli/ui-github-repo-expected.js.map +1 -0
  41. package/dist/cli/ui-github-repo-gh.d.ts +36 -0
  42. package/dist/cli/ui-github-repo-gh.d.ts.map +1 -0
  43. package/dist/cli/ui-github-repo-gh.js +112 -0
  44. package/dist/cli/ui-github-repo-gh.js.map +1 -0
  45. package/dist/cli/ui-github-repo-map.d.ts +25 -0
  46. package/dist/cli/ui-github-repo-map.d.ts.map +1 -0
  47. package/dist/cli/ui-github-repo-map.js +150 -0
  48. package/dist/cli/ui-github-repo-map.js.map +1 -0
  49. package/dist/cli/ui-github-repo.d.ts +51 -0
  50. package/dist/cli/ui-github-repo.d.ts.map +1 -0
  51. package/dist/cli/ui-github-repo.js +117 -0
  52. package/dist/cli/ui-github-repo.js.map +1 -0
  53. package/dist/cli/ui-observability-checks.d.ts +23 -0
  54. package/dist/cli/ui-observability-checks.d.ts.map +1 -0
  55. package/dist/cli/ui-observability-checks.js +156 -0
  56. package/dist/cli/ui-observability-checks.js.map +1 -0
  57. package/dist/cli/ui-observability-providers.d.ts +69 -0
  58. package/dist/cli/ui-observability-providers.d.ts.map +1 -0
  59. package/dist/cli/ui-observability-providers.js +99 -0
  60. package/dist/cli/ui-observability-providers.js.map +1 -0
  61. package/dist/core/learnings-budget-check.d.ts +42 -0
  62. package/dist/core/learnings-budget-check.d.ts.map +1 -0
  63. package/dist/core/learnings-budget-check.js +210 -0
  64. package/dist/core/learnings-budget-check.js.map +1 -0
  65. package/dist/core/learnings-document.js +6 -1
  66. package/dist/core/learnings-document.js.map +1 -1
  67. package/package.json +1 -1
  68. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-agy/plugin.json +1 -1
  71. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  74. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-expo-agy/plugin.json +1 -1
  81. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  86. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  91. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  96. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  101. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-rails-agy/plugin.json +1 -1
  106. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  111. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  116. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  118. package/scripts/check-learnings-budget.ts +29 -163
  119. package/ui/README.md +5 -3
  120. package/ui/index.html +1097 -432
package/ui/index.html CHANGED
@@ -326,6 +326,29 @@
326
326
  font-family: var(--mono);
327
327
  font-size: 11px;
328
328
  }
329
+ .plugins-state {
330
+ padding: 18px 20px;
331
+ border: 1px dashed var(--line);
332
+ border-radius: 10px;
333
+ background: var(--surface2);
334
+ color: var(--muted);
335
+ font-size: 13px;
336
+ line-height: 1.45;
337
+ }
338
+ .plugins-state.unknown {
339
+ border-style: solid;
340
+ border-color: var(--warn, #c9872a);
341
+ }
342
+ .plugins-state.unknown b {
343
+ display: block;
344
+ color: var(--text);
345
+ margin-bottom: 4px;
346
+ }
347
+ .plugins-live-block .plugin-status-badge.available {
348
+ background: transparent;
349
+ border: 1px solid var(--line);
350
+ color: var(--muted);
351
+ }
329
352
  .live-status-item .status-why {
330
353
  display: inline;
331
354
  margin: 0;
@@ -854,6 +877,23 @@
854
877
  grid-template-columns: 1fr;
855
878
  }
856
879
  }
880
+ .stacks-state {
881
+ border: 1px dashed var(--line);
882
+ border-radius: 10px;
883
+ background: var(--surface);
884
+ padding: 16px 18px;
885
+ color: var(--muted);
886
+ display: flex;
887
+ flex-direction: column;
888
+ gap: 4px;
889
+ }
890
+ .stacks-state.unknown {
891
+ border-color: var(--warn, #c99a2e);
892
+ border-style: solid;
893
+ }
894
+ .stacks-state.unknown b {
895
+ color: var(--ink);
896
+ }
857
897
  .stack-card {
858
898
  border: 1px solid var(--line);
859
899
  border-radius: 10px;
@@ -3134,77 +3174,22 @@
3134
3174
  },
3135
3175
  {
3136
3176
  type: "table",
3177
+ id: "deploy-pipeline-stages",
3137
3178
  card: "Deploy pipeline stages",
3138
- note: "Runs when an environment branch (dev / staging / main) receives a merge",
3179
+ note: "Live from deploy.yml + GitHub environment protection rules never fabricated",
3139
3180
  cols: ["Stage", "What it does", "Active"],
3140
3181
  rows: [
3141
3182
  [
3142
- { t: "mono", v: "🏗️ Build & package" },
3143
- {
3144
- t: "text",
3145
- v: "Builds the artifacts for the environment mapped to the pushed branch",
3146
- },
3147
- { t: "status", v: true },
3148
- ],
3149
- [
3150
- { t: "mono", v: "🚦 Release approval — production" },
3151
- {
3152
- t: "text",
3153
- v: "Pauses at the release_approval job until a configured reviewer approves the GitHub environment",
3154
- },
3155
- { t: "status", v: true },
3156
- ],
3157
- [
3158
- { t: "mono", v: "🚦 Release approval — staging" },
3183
+ { t: "mono", v: "" },
3159
3184
  {
3160
3185
  t: "text",
3161
- v: "Approval hold before staging deploys",
3186
+ v: "Loading live deploy pipeline stages…",
3162
3187
  },
3163
3188
  {
3164
3189
  t: "status",
3165
- v: false,
3166
- reason: "require_approval is off for staging",
3167
- },
3168
- ],
3169
- [
3170
- { t: "mono", v: "🚀 Deploy" },
3171
- {
3172
- t: "text",
3173
- v: "Runs the stack’s deploy (EAS / Serverless / CDK / Fabric) against the target environment",
3174
- },
3175
- { t: "status", v: true },
3176
- ],
3177
- [
3178
- { t: "mono", v: "📝 Version bump & changelog" },
3179
- {
3180
- t: "text",
3181
- v: "standard-version tags the release and pushes the chore(release) commit through the deploy key’s ruleset bypass",
3182
- },
3183
- { t: "status", v: true },
3184
- ],
3185
- [
3186
- { t: "mono", v: "🔄 Sync-down branches" },
3187
- {
3188
- t: "text",
3189
- v: "After a promotion merges, back-merges higher environment branches into lower ones",
3190
- },
3191
- { t: "status", v: true },
3192
- ],
3193
- [
3194
- { t: "mono", v: "🧯 Deploy auto-fix" },
3195
- {
3196
- t: "text",
3197
- v: "On a failed deploy, a Claude workflow investigates and opens a fix PR",
3198
- },
3199
- { t: "status", v: true },
3200
- ],
3201
- [
3202
- { t: "mono", v: "🎫 Issue on failure" },
3203
- {
3204
- t: "text",
3205
- v: "Files a tracker issue when a deploy run fails",
3190
+ v: "unknown",
3191
+ reason: "waiting for deploy-pipeline-stages probe",
3206
3192
  },
3207
- { t: "status", v: true },
3208
3193
  ],
3209
3194
  ],
3210
3195
  },
@@ -3392,183 +3377,14 @@
3392
3377
  {
3393
3378
  type: "table",
3394
3379
  card: "Scheduled jobs",
3395
- note: "Status and next run are mocked \u2014 live wiring is LCW-1 (#1544). Runs on = which scheduler fires the job; the nightly agents are pipeline files, inventoried under Pipelines.",
3396
- cols: [
3397
- "Automation",
3398
- "What it does",
3399
- "Runs on",
3400
- "Cadence",
3401
- "Last run",
3402
- "Next run",
3403
- "Enabled",
3404
- ],
3405
- rows: [
3406
- [
3407
- { t: "mono", v: "intake-tickets" },
3408
- {
3409
- t: "text",
3410
- v: "Claims the next build-ready ticket and runs the full build flow on it \u2014 one item per cycle",
3411
- },
3412
- { t: "badge", v: "harness", cls: "default" },
3413
- {
3414
- t: "select",
3415
- v: "every 10 min",
3416
- options: ["every 10 min", "every 30 min", "every 60 min"],
3417
- },
3418
- {
3419
- t: "run",
3420
- ok: true,
3421
- v: "12:50",
3422
- reason: "ok \u2014 queue empty, exited clean",
3423
- },
3424
- { t: "mono", v: "13:00" },
3425
- { t: "toggle", v: true },
3426
- ],
3427
- [
3428
- { t: "mono", v: "intake-prd" },
3429
- {
3430
- t: "text",
3431
- v: "Validates the next ready PRD at the gate and either creates its work units or blocks it with clarifying questions",
3432
- },
3433
- { t: "badge", v: "harness", cls: "default" },
3434
- {
3435
- t: "select",
3436
- v: "every 60 min",
3437
- options: ["every 10 min", "every 30 min", "every 60 min"],
3438
- },
3439
- {
3440
- t: "run",
3441
- ok: true,
3442
- v: "12:04",
3443
- reason: "ok \u2014 1 PRD ticketed",
3444
- },
3445
- { t: "mono", v: "13:04" },
3446
- { t: "toggle", v: true },
3447
- ],
3448
- [
3449
- { t: "mono", v: "intake-repair" },
3450
- {
3451
- t: "text",
3452
- v: "Finds stuck work \u2014 stalled builds, half-closed items, unreconciled rollups \u2014 and repairs each one",
3453
- },
3454
- { t: "badge", v: "harness", cls: "default" },
3455
- {
3456
- t: "select",
3457
- v: "every 60 min",
3458
- options: ["every 30 min", "every 60 min", "every 3 h"],
3459
- },
3460
- {
3461
- t: "run",
3462
- ok: true,
3463
- v: "12:04",
3464
- reason: "ok \u2014 0 stuck items",
3465
- },
3466
- { t: "mono", v: "13:04" },
3467
- { t: "toggle", v: true },
3468
- ],
3469
- [
3470
- { t: "mono", v: "exploratory-bugs" },
3471
- {
3472
- t: "text",
3473
- v: "QA loop: explores the product like a first-time user (respecting the mutation policy) and files bugs build-ready",
3474
- },
3475
- { t: "badge", v: "harness", cls: "default" },
3476
- { t: "select", v: "daily", options: ["daily", "weekly"] },
3477
- {
3478
- t: "run",
3479
- ok: true,
3480
- v: "04:00",
3481
- reason: "ok \u2014 2 bugs filed",
3482
- },
3483
- { t: "mono", v: "tomorrow 04:00" },
3484
- { t: "toggle", v: true },
3485
- ],
3486
- [
3487
- { t: "mono", v: "exploratory-prds" },
3488
- {
3489
- t: "text",
3490
- v: "Product Planning loop: ideates improvements and writes PRDs prd-ready",
3491
- },
3492
- { t: "badge", v: "harness", cls: "default" },
3493
- { t: "select", v: "daily", options: ["daily", "weekly"] },
3494
- {
3495
- t: "run",
3496
- ok: "warn",
3497
- v: "04:15",
3498
- reason: "skipped \u2014 queue pressure (1 prd-ready waiting)",
3499
- },
3500
- { t: "mono", v: "tomorrow 04:15" },
3501
- { t: "toggle", v: true },
3502
- ],
3503
- [
3504
- { t: "mono", v: "monitor" },
3505
- {
3506
- t: "text",
3507
- v: "Monitoring loop: audits the connected observability providers against the alert thresholds and files regression tickets",
3508
- },
3509
- { t: "badge", v: "harness", cls: "default" },
3510
- { t: "select", v: "daily", options: ["daily", "weekly"] },
3511
- {
3512
- t: "run",
3513
- ok: true,
3514
- v: "05:00",
3515
- reason: "ok \u2014 1 regression filed",
3516
- },
3517
- { t: "mono", v: "tomorrow 05:00" },
3518
- { t: "toggle", v: true },
3519
- ],
3520
- [
3521
- { t: "mono", v: "nightly-test-coverage" },
3522
- {
3523
- t: "text",
3524
- v: "Raises test coverage toward the thresholds and opens a PR \u2014 one module per run",
3525
- },
3526
- { t: "badge", v: "pipeline host", cls: "default" },
3527
- { t: "mono", v: "03:00 Mon\u2013Fri" },
3528
- {
3529
- t: "run",
3530
- ok: true,
3531
- v: "03:00",
3532
- reason: "ok \u2014 PR opened",
3533
- },
3534
- { t: "mono", v: "tomorrow 03:00" },
3535
- { t: "toggle", v: true },
3536
- ],
3537
- [
3538
- { t: "mono", v: "nightly-test-improvement" },
3539
- {
3540
- t: "text",
3541
- v: "Strengthens weak assertions in existing tests and opens a PR",
3542
- },
3543
- { t: "badge", v: "pipeline host", cls: "default" },
3544
- { t: "mono", v: "04:00 Mon\u2013Fri" },
3545
- {
3546
- t: "run",
3547
- ok: true,
3548
- v: "04:00",
3549
- reason: "ok \u2014 PR opened",
3550
- },
3551
- { t: "mono", v: "tomorrow 04:00" },
3552
- { t: "toggle", v: true },
3553
- ],
3554
- [
3555
- { t: "mono", v: "nightly-code-complexity" },
3556
- {
3557
- t: "text",
3558
- v: "Refactors a complexity hotspot without changing behavior and opens a PR",
3559
- },
3560
- { t: "badge", v: "pipeline host", cls: "default" },
3561
- { t: "mono", v: "05:00 Mon\u2013Fri" },
3562
- {
3563
- t: "run",
3564
- ok: true,
3565
- v: "05:00",
3566
- reason: "ok \u2014 no hotspot over budget",
3567
- },
3568
- { t: "mono", v: "tomorrow 05:00" },
3569
- { t: "toggle", v: true },
3570
- ],
3571
- ],
3380
+ id: "automations-scheduled-jobs",
3381
+ note: "Live from the harness scheduler (Codex automations or Claude /schedule). Only lisa-auto-<project>-* jobs are listed. Cadence is read-only in this phase \u2014 change jobs with /lisa:setup-automations. Nightly agents are pipeline files, inventoried under Pipelines.",
3382
+ cols: ["Automation", "Runs on", "Cadence", "Last run", "Status"],
3383
+ rows: [],
3384
+ empty: {
3385
+ state: "loading",
3386
+ message: "Loading harness scheduler\u2026",
3387
+ },
3572
3388
  },
3573
3389
  {
3574
3390
  card: "Fleet controls",
@@ -3672,7 +3488,7 @@
3672
3488
  t: "text",
3673
3489
  v: "Error events, issue volume, release health",
3674
3490
  },
3675
- { t: "status", v: true },
3491
+ { t: "status", v: true, provider: "sentry" },
3676
3492
  ],
3677
3493
  [
3678
3494
  { t: "mono", v: "AWS CloudWatch (Alarms)" },
@@ -3680,7 +3496,11 @@
3680
3496
  t: "text",
3681
3497
  v: "Alarm states, error-rate and latency metrics",
3682
3498
  },
3683
- { t: "status", v: true },
3499
+ {
3500
+ t: "status",
3501
+ v: true,
3502
+ provider: "cloudwatch-alarms",
3503
+ },
3684
3504
  ],
3685
3505
  [
3686
3506
  { t: "mono", v: "AWS X-Ray" },
@@ -3688,6 +3508,7 @@
3688
3508
  {
3689
3509
  t: "status",
3690
3510
  v: false,
3511
+ provider: "x-ray",
3691
3512
  reason: "no traces found — X-Ray is not instrumented",
3692
3513
  },
3693
3514
  ],
@@ -5113,175 +4934,83 @@
5113
4934
  };
5114
4935
 
5115
4936
  /* ---------------------------------- Plugins ---------------------------------- */
4937
+ /* Catalog copy only — enablement comes from the enabled-plugins probe.
4938
+ Demo toggles are never rendered as a fallback. */
5116
4939
  DATA.sections.plugins = {
5117
4940
  title: "Plugins & MCP",
5118
4941
  desc: "Lisa’s own plugin set (base + one per detected stack), curated third-party plugins, and the MCP servers each one carries. Non-Claude agents receive generated equivalents of everything here.",
5119
4942
  src: ".claude/settings.json enabledPlugins · plugins/src/",
5120
4943
  blocks: [
5121
4944
  {
5122
- type: "table",
5123
- card: "Lisa plugins",
5124
- cols: ["Plugin", "Bundles", "Enabled"],
5125
- rows: [
5126
- [
5127
- { t: "mono", v: "lisa@lisa" },
5128
- {
5129
- t: "text",
5130
- v: "Base governance: ~145 skills, 26 agents, 12 hooks, /lisa:* commands, rules, Sentry MCP",
5131
- },
5132
- { t: "toggle", v: true },
5133
- ],
5134
- [
5135
- { t: "mono", v: "lisa-typescript@lisa" },
5136
- {
5137
- t: "text",
5138
- v: "format-on-edit, lint-on-edit, ast-grep-on-edit, suppress-directive blocking",
5139
- },
5140
- { t: "toggle", v: true },
5141
- ],
5142
- [
5143
- { t: "mono", v: "lisa-expo@lisa" },
5144
- {
5145
- t: "text",
5146
- v: "~40 skills (EAS, expo-router, gluestack), ops-specialist agent, Expo MCP",
5147
- },
5148
- { t: "toggle", v: true },
5149
- ],
5150
- [
5151
- { t: "mono", v: "lisa-nestjs@lisa" },
5152
- {
5153
- t: "text",
5154
- v: "GraphQL/TypeORM skills, migration-edit blocking",
5155
- },
5156
- { t: "toggle", v: false },
5157
- ],
5158
- [
5159
- { t: "mono", v: "lisa-phaser@lisa" },
5160
- { t: "text", v: "8 skills, 20 game-dev agents, Phaser rules" },
5161
- { t: "toggle", v: false },
5162
- ],
5163
- [
5164
- { t: "mono", v: "lisa-rails@lisa" },
5165
- {
5166
- t: "text",
5167
- v: "20 skills, rubocop-on-edit, Rails conventions",
5168
- },
5169
- { t: "toggle", v: false },
5170
- ],
5171
- [
5172
- { t: "mono", v: "lisa-harper-fabric@lisa" },
5173
- { t: "text", v: "12 skills, generated-artifact protection" },
5174
- { t: "toggle", v: false },
5175
- ],
5176
- [
5177
- { t: "mono", v: "lisa-wiki@lisa" },
5178
- {
5179
- t: "text",
5180
- v: "In-repo LLM wiki: 22 skills, ingestion connectors (default-on for phaser only)",
5181
- },
5182
- { t: "toggle", v: false },
5183
- ],
5184
- [
5185
- { t: "mono", v: "lisa-openclaw@lisa" },
5186
- {
5187
- t: "text",
5188
- v: "Telegram/Slack routing via OpenClaw (opt-in)",
5189
- },
5190
- { t: "toggle", v: false },
5191
- ],
5192
- ],
5193
- },
5194
- {
5195
- type: "table",
5196
- card: "Curated third-party plugins",
5197
- note: "Flip an entry to enable or disable; Lisa never touches third-party keys on re-apply",
5198
- cols: ["Plugin", "Purpose", "Enabled"],
5199
- rows: [
5200
- [
5201
- { t: "mono", v: "code-review@claude-plugins-official" },
5202
- { t: "text", v: "Pre-ship diff defect hunt" },
5203
- { t: "toggle", v: true },
5204
- ],
5205
- [
5206
- { t: "mono", v: "code-simplifier@claude-plugins-official" },
5207
- { t: "text", v: "Behavior-preserving cleanup" },
5208
- { t: "toggle", v: true },
5209
- ],
5210
- [
5211
- { t: "mono", v: "coderabbit@claude-plugins-official" },
5212
- { t: "text", v: "AI PR review (also a required check)" },
5213
- { t: "toggle", v: true },
5214
- ],
5215
- [
5216
- { t: "mono", v: "sentry@claude-plugins-official" },
5217
- { t: "text", v: "Issue debugging, Seer, instrumentation" },
5218
- { t: "toggle", v: true },
5219
- ],
5220
- [
5221
- { t: "mono", v: "safety-net@cc-marketplace" },
5222
- { t: "text", v: "Guardrail rulebooks" },
5223
- { t: "toggle", v: false },
5224
- ],
5225
- [
5226
- { t: "mono", v: "typescript-lsp@claude-plugins-official" },
5227
- { t: "text", v: "TypeScript language server" },
5228
- { t: "toggle", v: true },
5229
- ],
5230
- [
5231
- { t: "mono", v: "playwright@claude-plugins-official" },
5232
- { t: "text", v: "Browser automation MCP" },
5233
- { t: "toggle", v: true },
5234
- ],
5235
- [
5236
- { t: "mono", v: "posthog@claude-plugins-official" },
5237
- { t: "text", v: "Product analytics" },
5238
- { t: "toggle", v: true },
5239
- ],
5240
- [
5241
- { t: "mono", v: "impeccable@impeccable" },
5242
- { t: "text", v: "UI design polish" },
5243
- { t: "toggle", v: true },
5244
- ],
5245
- [
5246
- { t: "mono", v: "skill-creator@claude-plugins-official" },
5247
- { t: "text", v: "Author and optimize skills" },
5248
- { t: "toggle", v: false },
5249
- ],
5250
- [
5251
- { t: "mono", v: "ruby-lsp@claude-plugins-official" },
5252
- { t: "text", v: "Ruby language server (rails stacks)" },
5253
- { t: "toggle", v: false },
5254
- ],
5255
- ],
4945
+ type: "plugins-live",
4946
+ lisaCatalog: {
4947
+ "lisa@lisa":
4948
+ "Base governance: ~145 skills, 26 agents, 12 hooks, /lisa:* commands, rules, Sentry MCP",
4949
+ "lisa-typescript@lisa":
4950
+ "format-on-edit, lint-on-edit, ast-grep-on-edit, suppress-directive blocking",
4951
+ "lisa-expo@lisa":
4952
+ "~40 skills (EAS, expo-router, gluestack), ops-specialist agent, Expo MCP",
4953
+ "lisa-nestjs@lisa":
4954
+ "GraphQL/TypeORM skills, migration-edit blocking",
4955
+ "lisa-phaser@lisa": "8 skills, 20 game-dev agents, Phaser rules",
4956
+ "lisa-rails@lisa":
4957
+ "20 skills, rubocop-on-edit, Rails conventions",
4958
+ "lisa-harper-fabric@lisa":
4959
+ "12 skills, generated-artifact protection",
4960
+ "lisa-wiki@lisa":
4961
+ "In-repo LLM wiki: 22 skills, ingestion connectors (default-on for phaser only)",
4962
+ "lisa-openclaw@lisa":
4963
+ "Telegram/Slack routing via OpenClaw (opt-in)",
4964
+ "lisa-cdk@lisa": "AWS CDK plugin",
4965
+ },
4966
+ thirdPartyCatalog: {
4967
+ "code-review@claude-plugins-official":
4968
+ "Pre-ship diff defect hunt",
4969
+ "code-simplifier@claude-plugins-official":
4970
+ "Behavior-preserving cleanup",
4971
+ "coderabbit@claude-plugins-official":
4972
+ "AI PR review (also a required check)",
4973
+ "sentry@claude-plugins-official":
4974
+ "Issue debugging, Seer, instrumentation",
4975
+ "safety-net@cc-marketplace": "Guardrail rulebooks",
4976
+ "typescript-lsp@claude-plugins-official":
4977
+ "TypeScript language server",
4978
+ "playwright@claude-plugins-official": "Browser automation MCP",
4979
+ "posthog@claude-plugins-official": "Product analytics",
4980
+ "impeccable@impeccable": "UI design polish",
4981
+ "skill-creator@claude-plugins-official":
4982
+ "Author and optimize skills",
4983
+ "ruby-lsp@claude-plugins-official":
4984
+ "Ruby language server (rails stacks)",
4985
+ "atlassian@claude-plugins-official": "Jira / Confluence access",
4986
+ "hookify@claude-plugins-official": "Custom hook authoring",
4987
+ },
5256
4988
  },
5257
4989
  {
5258
4990
  type: "table",
5259
4991
  card: "MCP servers",
5260
- cols: ["Server", "Transport", "Enablement", "Enabled"],
4992
+ note: "Catalog of MCP servers commonly carried by plugins — enablement follows the hosting plugin (read-only in this phase).",
4993
+ cols: ["Server", "Transport", "Enablement"],
5261
4994
  rows: [
5262
4995
  [
5263
4996
  { t: "mono", v: "sentry" },
5264
4997
  { t: "mono", v: "http" },
5265
4998
  { t: "text", v: "always-on via base plugin" },
5266
- { t: "toggle", v: true },
5267
4999
  ],
5268
5000
  [
5269
5001
  { t: "mono", v: "expo" },
5270
5002
  { t: "mono", v: "http" },
5271
5003
  { t: "text", v: "on when the expo stack is detected" },
5272
- { t: "toggle", v: true },
5273
5004
  ],
5274
5005
  [
5275
5006
  { t: "mono", v: "linear-server" },
5276
5007
  { t: "mono", v: "http" },
5277
5008
  { t: "text", v: "project-level .mcp.json" },
5278
- { t: "toggle", v: false },
5279
5009
  ],
5280
5010
  [
5281
5011
  { t: "mono", v: "phaser-editor" },
5282
5012
  { t: "mono", v: "stdio" },
5283
5013
  { t: "text", v: "shipped disabled (disabledMcpjsonServers)" },
5284
- { t: "toggle", v: false },
5285
5014
  ],
5286
5015
  ],
5287
5016
  },
@@ -5391,6 +5120,18 @@
5391
5120
  const $toast = document.getElementById("toast");
5392
5121
  const dirty = new Set();
5393
5122
 
5123
+ /* Live enabled-plugins state, hydrated from GET /api/status. Starts as
5124
+ "loading" so the Plugins section never presents demo toggles as truth. */
5125
+ let pluginsLive = {
5126
+ status: "loading",
5127
+ settingsPresent: false,
5128
+ plugins: [],
5129
+ };
5130
+ /* Live detected-stacks state, hydrated from GET /api/status. Starts as
5131
+ "detecting" so the Stacks section never presents the metadata catalog
5132
+ as if it were the detection result. */
5133
+ let stackDetection = { status: "detecting", ids: [] };
5134
+
5394
5135
  function el(tag, cls, html) {
5395
5136
  const node = document.createElement(tag);
5396
5137
  if (cls) node.className = cls;
@@ -5599,35 +5340,116 @@
5599
5340
  return card;
5600
5341
  }
5601
5342
 
5602
- function buildTable(block) {
5603
- const card = el("div", "card");
5604
- if (block.card) {
5605
- const head = el("div", "card-head");
5606
- head.appendChild(el("h2", "", esc(block.card)));
5607
- if (block.note) head.appendChild(el("span", "note", esc(block.note)));
5608
- card.appendChild(head);
5343
+ function paintProviderStatus(td, cell) {
5344
+ td.innerHTML = "";
5345
+ if (cell.v === true || cell.v === "connected") {
5346
+ td.appendChild(el("span", "badge pass", "✓ connected"));
5347
+ return;
5609
5348
  }
5610
- const scroll = el("div", "table-scroll");
5611
- const table = el("table", "grid");
5612
- const thead = el("thead");
5613
- const trh = el("tr");
5614
- block.cols.forEach(c => {
5615
- // Cols accept either a plain string or {v, tip} — tip renders as a
5616
- // hover tooltip on the header for jargon that needs a definition.
5617
- if (typeof c === "string") {
5618
- trh.appendChild(el("th", "", esc(c)));
5619
- return;
5349
+ if (cell.v === "connected-but-empty") {
5350
+ td.appendChild(el("span", "badge warn", "◐ connected-but-empty"));
5351
+ if (cell.reason) {
5352
+ td.appendChild(el("span", "status-why", esc(cell.reason)));
5620
5353
  }
5621
- const th = el("th", c.tip ? "th-help" : "", esc(c.v));
5622
- if (c.tip) th.title = c.tip;
5623
- trh.appendChild(th);
5354
+ return;
5355
+ }
5356
+ if (cell.v === "pending") {
5357
+ td.appendChild(el("span", "badge default", "checking…"));
5358
+ return;
5359
+ }
5360
+ if (cell.v === "unknown") {
5361
+ td.appendChild(el("span", "badge warn", "unknown"));
5362
+ if (cell.reason) {
5363
+ td.appendChild(el("span", "status-why", esc(cell.reason)));
5364
+ }
5365
+ return;
5366
+ }
5367
+ if (cell.v) {
5368
+ td.appendChild(el("span", "badge pass", "✓ active"));
5369
+ return;
5370
+ }
5371
+ td.appendChild(el("span", "badge fail", "✗ off"));
5372
+ if (cell.reason) {
5373
+ td.appendChild(el("span", "status-why", esc(cell.reason)));
5374
+ }
5375
+ }
5376
+ function buildStatusCell(cell) {
5377
+ const td = el("td");
5378
+ if (cell.provider) td.dataset.provider = cell.provider;
5379
+ if (cell.jobId) td.dataset.jobId = cell.jobId;
5380
+ if (cell.provider) {
5381
+ paintProviderStatus(td, cell);
5382
+ return td;
5383
+ }
5384
+ if (cell.v === "unknown" || cell.v === null || cell.v === undefined) {
5385
+ td.appendChild(el("span", "badge warn", "unknown"));
5386
+ if (cell.reason) {
5387
+ td.appendChild(el("span", "status-why", esc(cell.reason)));
5388
+ }
5389
+ return td;
5390
+ }
5391
+ if (cell.v) {
5392
+ td.appendChild(el("span", "badge pass", "✓ active"));
5393
+ return td;
5394
+ }
5395
+ td.appendChild(el("span", "badge fail", "✗ off"));
5396
+ if (cell.reason) {
5397
+ td.appendChild(el("span", "status-why", esc(cell.reason)));
5398
+ }
5399
+ return td;
5400
+ }
5401
+
5402
+ function buildTable(block) {
5403
+ const card = el("div", "card");
5404
+ if (block.id) {
5405
+ card.id = block.id;
5406
+ card.dataset.testid = block.id;
5407
+ }
5408
+ if (block.card) {
5409
+ const head = el("div", "card-head");
5410
+ head.appendChild(el("h2", "", esc(block.card)));
5411
+ if (block.note) head.appendChild(el("span", "note", esc(block.note)));
5412
+ card.appendChild(head);
5413
+ }
5414
+ if (block.empty && (!block.rows || block.rows.length === 0)) {
5415
+ const empty = el(
5416
+ "div",
5417
+ "status-why automations-empty",
5418
+ esc(block.empty.message || "No rows")
5419
+ );
5420
+ empty.dataset.testid = (block.id || "table") + "-empty";
5421
+ if (block.empty.state) empty.dataset.state = block.empty.state;
5422
+ card.appendChild(empty);
5423
+ return card;
5424
+ }
5425
+ const scroll = el("div", "table-scroll");
5426
+ const table = el("table", "grid");
5427
+ const thead = el("thead");
5428
+ const trh = el("tr");
5429
+ block.cols.forEach(c => {
5430
+ // Cols accept either a plain string or {v, tip} — tip renders as a
5431
+ // hover tooltip on the header for jargon that needs a definition.
5432
+ if (typeof c === "string") {
5433
+ trh.appendChild(el("th", "", esc(c)));
5434
+ return;
5435
+ }
5436
+ const th = el("th", c.tip ? "th-help" : "", esc(c.v));
5437
+ if (c.tip) th.title = c.tip;
5438
+ trh.appendChild(th);
5624
5439
  });
5625
5440
  thead.appendChild(trh);
5626
5441
  table.appendChild(thead);
5627
5442
  const tbody = el("tbody");
5628
5443
  block.rows.forEach(cells => {
5629
5444
  const tr = el("tr");
5445
+ if (cells && cells._automationId) {
5446
+ tr.dataset.automationId = cells._automationId;
5447
+ }
5630
5448
  cells.forEach(cell => {
5449
+ if (cell.t === "status") {
5450
+ tr.appendChild(buildStatusCell(cell));
5451
+ return;
5452
+ }
5631
5453
  const td = el("td");
5632
5454
  if (cell.t === "mono") td.className = "mono";
5633
5455
  if (cell.t === "text" || cell.t === "mono") {
@@ -5718,46 +5540,118 @@
5718
5540
  return card;
5719
5541
  }
5720
5542
 
5721
- function buildStacks(block) {
5722
- const grid = el("div", "stack-grid");
5543
+ /* The 8-entry stack metadata, keyed by id. This is a catalog of what each
5544
+ stack installs NOT the detection result; only ids the probe reports
5545
+ are rendered from it. */
5546
+ function stackCatalog(block) {
5547
+ const catalog = new Map();
5723
5548
  block.items.forEach(s => {
5724
- const card = el("div", "stack-card" + (s.on ? " on" : ""));
5725
- const head = el("div", "head");
5726
- head.appendChild(el("span", "glyph", esc(s.glyph)));
5727
- const t = el("div", "t");
5728
- t.appendChild(el("b", "", esc(s.name)));
5729
- t.appendChild(el("i", "", esc(s.tagline)));
5730
- head.appendChild(t);
5731
- const toggleWrap = buildControl(
5732
- { type: "toggle", value: s.on },
5733
- "stack." + s.id
5549
+ catalog.set(s.id, s);
5550
+ });
5551
+ return catalog;
5552
+ }
5553
+
5554
+ /* A detected id with no catalog entry still gets a card (id as name) so
5555
+ detection truth is never dropped just because copy is missing. */
5556
+ function minimalStackMeta(id) {
5557
+ return {
5558
+ id,
5559
+ glyph: id.slice(0, 2).toUpperCase(),
5560
+ name: id,
5561
+ tagline: "",
5562
+ detect: "",
5563
+ badges: [],
5564
+ more: [],
5565
+ };
5566
+ }
5567
+
5568
+ function stackCard(s) {
5569
+ const card = el("div", "stack-card on");
5570
+ const head = el("div", "head");
5571
+ head.appendChild(el("span", "glyph", esc(s.glyph)));
5572
+ const t = el("div", "t");
5573
+ t.appendChild(el("b", "", esc(s.name)));
5574
+ t.appendChild(el("i", "", esc(s.tagline)));
5575
+ head.appendChild(t);
5576
+ const toggleWrap = buildControl(
5577
+ { type: "toggle", value: true },
5578
+ "stack." + s.id
5579
+ );
5580
+ toggleWrap.querySelector("input").addEventListener("change", e => {
5581
+ card.classList.toggle("on", e.target.checked);
5582
+ markDirty("stack." + s.id, null);
5583
+ });
5584
+ head.appendChild(toggleWrap);
5585
+ card.appendChild(head);
5586
+ const meta = el("div", "meta");
5587
+ (s.badges || []).forEach(b =>
5588
+ meta.appendChild(el("span", "tag", esc(b)))
5589
+ );
5590
+ card.appendChild(meta);
5591
+ card.appendChild(el("div", "detect", s.detect || ""));
5592
+ if (s.more && s.more.length) {
5593
+ const det = el("details", "stack-more");
5594
+ det.appendChild(el("summary", "", "What this stack installs"));
5595
+ const ul = el("ul");
5596
+ s.more.forEach(m => ul.appendChild(el("li", "", esc(m))));
5597
+ det.appendChild(ul);
5598
+ card.appendChild(det);
5599
+ }
5600
+ return card;
5601
+ }
5602
+
5603
+ function renderStacksInto(wrap, block) {
5604
+ wrap.innerHTML = "";
5605
+ const d = stackDetection;
5606
+ if (d.status === "detecting") {
5607
+ wrap.appendChild(
5608
+ el("div", "stacks-state detecting", "Detecting project stacks…")
5734
5609
  );
5735
- toggleWrap.querySelector("input").addEventListener("change", e => {
5736
- card.classList.toggle("on", e.target.checked);
5737
- markDirty("stack." + s.id, null);
5738
- });
5739
- head.appendChild(toggleWrap);
5740
- card.appendChild(head);
5741
- const meta = el("div", "meta");
5742
- s.badges.forEach(b => meta.appendChild(el("span", "tag", esc(b))));
5743
- card.appendChild(meta);
5744
- card.appendChild(el("div", "detect", s.detect));
5745
- if (s.more && s.more.length) {
5746
- const det = el("details", "stack-more");
5747
- det.appendChild(el("summary", "", "What this stack installs"));
5748
- const ul = el("ul");
5749
- s.more.forEach(m => ul.appendChild(el("li", "", esc(m))));
5750
- det.appendChild(ul);
5751
- card.appendChild(det);
5752
- }
5753
- grid.appendChild(card);
5610
+ return;
5611
+ }
5612
+ if (d.status === "unknown") {
5613
+ const box = el("div", "stacks-state unknown");
5614
+ box.appendChild(el("b", "", "Stack detection unavailable"));
5615
+ box.appendChild(
5616
+ el("span", "status-why", esc(d.reason + ": " + d.message))
5617
+ );
5618
+ wrap.appendChild(box);
5619
+ return;
5620
+ }
5621
+ if (!d.ids || d.ids.length === 0) {
5622
+ wrap.appendChild(
5623
+ el(
5624
+ "div",
5625
+ "stacks-state empty",
5626
+ "No Lisa stack detected in this project."
5627
+ )
5628
+ );
5629
+ return;
5630
+ }
5631
+ const catalog = stackCatalog(block);
5632
+ const grid = el("div", "stack-grid");
5633
+ d.ids.forEach(id => {
5634
+ grid.appendChild(stackCard(catalog.get(id) || minimalStackMeta(id)));
5754
5635
  });
5755
- const wrap = el("div");
5756
5636
  wrap.appendChild(grid);
5757
5637
  wrap.appendChild(el("div", "", "&nbsp;"));
5638
+ }
5639
+
5640
+ function buildStacks(block) {
5641
+ const wrap = el("div", "stacks-block");
5642
+ renderStacksInto(wrap, block);
5758
5643
  return wrap;
5759
5644
  }
5760
5645
 
5646
+ /* Re-render the Stacks section in place after detection hydrates. */
5647
+ function refreshStacks() {
5648
+ const block = DATA.sections.stacks.blocks.find(
5649
+ b => b.type === "stacks"
5650
+ );
5651
+ const wrap = document.querySelector(".stacks-block");
5652
+ if (block && wrap) renderStacksInto(wrap, block);
5653
+ }
5654
+
5761
5655
  function buildHooks(block) {
5762
5656
  const grid = el("div", "hooks-grid");
5763
5657
  block.items.forEach(h => {
@@ -5897,6 +5791,131 @@
5897
5791
  return card;
5898
5792
  }
5899
5793
 
5794
+ function pluginDescription(id, probeDescription, catalog) {
5795
+ if (Object.hasOwn(catalog, id) && typeof catalog[id] === "string") {
5796
+ return catalog[id];
5797
+ }
5798
+ return typeof probeDescription === "string" ? probeDescription : "";
5799
+ }
5800
+
5801
+ function pluginTableRows(entries, catalog) {
5802
+ return entries.map(plugin => {
5803
+ const enabled = plugin.status === "enabled";
5804
+ return [
5805
+ { t: "mono", v: plugin.id },
5806
+ {
5807
+ t: "text",
5808
+ v: pluginDescription(plugin.id, plugin.description, catalog),
5809
+ },
5810
+ {
5811
+ t: "badge",
5812
+ v: enabled ? "enabled" : "available, not enabled",
5813
+ cls: enabled ? "on" : "plugin-status-badge available",
5814
+ },
5815
+ ];
5816
+ });
5817
+ }
5818
+
5819
+ function renderPluginsInto(wrap, block) {
5820
+ wrap.innerHTML = "";
5821
+ const state = pluginsLive;
5822
+ if (state.status === "loading") {
5823
+ wrap.appendChild(
5824
+ el("div", "plugins-state loading", "Loading plugin enablement…")
5825
+ );
5826
+ return;
5827
+ }
5828
+ if (state.status === "unknown") {
5829
+ const box = el("div", "plugins-state unknown");
5830
+ box.appendChild(el("b", "", "Plugin enablement unavailable"));
5831
+ box.appendChild(
5832
+ el(
5833
+ "span",
5834
+ "status-why",
5835
+ esc((state.reason || "unknown") + ": " + (state.message || ""))
5836
+ )
5837
+ );
5838
+ wrap.appendChild(box);
5839
+ return;
5840
+ }
5841
+ if (!state.settingsPresent) {
5842
+ wrap.appendChild(
5843
+ el(
5844
+ "div",
5845
+ "plugins-state empty",
5846
+ "No .claude/settings.json in this project — plugin enablement is not configured."
5847
+ )
5848
+ );
5849
+ return;
5850
+ }
5851
+ const plugins = Array.isArray(state.plugins) ? state.plugins : [];
5852
+ const lisaCatalog =
5853
+ block.lisaCatalog && typeof block.lisaCatalog === "object"
5854
+ ? block.lisaCatalog
5855
+ : {};
5856
+ const thirdPartyCatalog =
5857
+ block.thirdPartyCatalog && typeof block.thirdPartyCatalog === "object"
5858
+ ? block.thirdPartyCatalog
5859
+ : {};
5860
+ const lisa = [];
5861
+ const thirdParty = [];
5862
+ plugins.forEach(plugin => {
5863
+ if (!plugin || typeof plugin !== "object") return;
5864
+ const id = plugin.id;
5865
+ if (typeof id !== "string" || id.trim().length === 0) return;
5866
+ if (id.endsWith("@lisa") || Object.hasOwn(lisaCatalog, id)) {
5867
+ lisa.push(plugin);
5868
+ } else {
5869
+ thirdParty.push(plugin);
5870
+ }
5871
+ });
5872
+ if (lisa.length === 0 && thirdParty.length === 0) {
5873
+ wrap.appendChild(
5874
+ el(
5875
+ "div",
5876
+ "plugins-state empty",
5877
+ "No plugins enabled or available in the marketplace catalog."
5878
+ )
5879
+ );
5880
+ return;
5881
+ }
5882
+ if (lisa.length > 0) {
5883
+ wrap.appendChild(
5884
+ buildTable({
5885
+ type: "table",
5886
+ card: "Lisa plugins",
5887
+ cols: ["Plugin", "Bundles", "Status"],
5888
+ rows: pluginTableRows(lisa, lisaCatalog),
5889
+ })
5890
+ );
5891
+ }
5892
+ if (thirdParty.length > 0) {
5893
+ wrap.appendChild(
5894
+ buildTable({
5895
+ type: "table",
5896
+ card: "Third-party plugins",
5897
+ note: "Read-only: enablement comes from .claude/settings.json enabledPlugins",
5898
+ cols: ["Plugin", "Purpose", "Status"],
5899
+ rows: pluginTableRows(thirdParty, thirdPartyCatalog),
5900
+ })
5901
+ );
5902
+ }
5903
+ }
5904
+
5905
+ function buildPluginsLive(block) {
5906
+ const wrap = el("div", "plugins-live-block");
5907
+ renderPluginsInto(wrap, block);
5908
+ return wrap;
5909
+ }
5910
+
5911
+ function refreshPlugins() {
5912
+ const block = DATA.sections.plugins.blocks.find(
5913
+ b => b.type === "plugins-live"
5914
+ );
5915
+ const wrap = document.querySelector(".plugins-live-block");
5916
+ if (block && wrap) renderPluginsInto(wrap, block);
5917
+ }
5918
+
5900
5919
  function buildBlock(block) {
5901
5920
  if (block.type === "tiles") return buildTiles(block);
5902
5921
  if (block.type === "flow") return buildFlow(block);
@@ -5906,6 +5925,7 @@
5906
5925
  if (block.type === "table") return buildTable(block);
5907
5926
  if (block.type === "tabs") return buildTabs(block);
5908
5927
  if (block.type === "checklist") return buildChecklist(block);
5928
+ if (block.type === "plugins-live") return buildPluginsLive(block);
5909
5929
  return buildCard(block);
5910
5930
  }
5911
5931
 
@@ -6097,6 +6117,7 @@
6097
6117
  if (value !== undefined) hydrateControl(row.control, value);
6098
6118
  });
6099
6119
  });
6120
+ markObservabilityProvidersPending();
6100
6121
  }
6101
6122
  configureRemoteEnvironment(cfg);
6102
6123
  const repoEl = document.querySelector(".project-switch .repo");
@@ -6156,6 +6177,115 @@
6156
6177
  }
6157
6178
  return snapshot.probes;
6158
6179
  }
6180
+ function connectedProvidersBlock() {
6181
+ const blocks = DATA.sections.monitor && DATA.sections.monitor.blocks;
6182
+ if (!Array.isArray(blocks)) return null;
6183
+ return (
6184
+ blocks.find(
6185
+ block =>
6186
+ block &&
6187
+ block.type === "table" &&
6188
+ block.card === "Connected providers"
6189
+ ) || null
6190
+ );
6191
+ }
6192
+ function setProviderStatusCell(provider, cell) {
6193
+ const block = connectedProvidersBlock();
6194
+ if (!block || !Array.isArray(block.rows)) return;
6195
+ block.rows.forEach(row => {
6196
+ if (!Array.isArray(row)) return;
6197
+ row.forEach((entry, index) => {
6198
+ if (entry && entry.t === "status" && entry.provider === provider) {
6199
+ row[index] = cell;
6200
+ }
6201
+ });
6202
+ });
6203
+ const td = document.querySelector(
6204
+ 'td[data-provider="' + provider + '"]'
6205
+ );
6206
+ if (td) paintProviderStatus(td, cell);
6207
+ }
6208
+ function markObservabilityProvidersPending() {
6209
+ ["sentry", "cloudwatch-alarms", "x-ray"].forEach(provider => {
6210
+ setProviderStatusCell(provider, {
6211
+ t: "status",
6212
+ v: "pending",
6213
+ provider,
6214
+ });
6215
+ });
6216
+ }
6217
+ /**
6218
+ * Paint Connected providers from sentry / cloudwatch-alarms / x-ray probes.
6219
+ * unknown ≠ disconnected — never invent an unevidenced "off" claim.
6220
+ * @param probes - Live-status probe map from /api/status
6221
+ */
6222
+ function applyObservabilityProviders(probes) {
6223
+ const specs = [
6224
+ {
6225
+ id: "sentry",
6226
+ emptyReason: "reachable — no Sentry data observed",
6227
+ },
6228
+ {
6229
+ id: "cloudwatch-alarms",
6230
+ emptyReason: "connected — no alarms",
6231
+ },
6232
+ {
6233
+ id: "x-ray",
6234
+ emptyReason: "connected — no traces",
6235
+ },
6236
+ ];
6237
+ specs.forEach(spec => {
6238
+ const raw =
6239
+ probes && Object.hasOwn(probes, spec.id)
6240
+ ? probes[spec.id]
6241
+ : undefined;
6242
+ const result =
6243
+ raw === undefined
6244
+ ? {
6245
+ state: "unknown",
6246
+ reason: "missing-probe",
6247
+ message: "The " + spec.id + " probe was not reported",
6248
+ }
6249
+ : normalizeLiveStatusResult(raw);
6250
+ if (
6251
+ result.state === "value" &&
6252
+ result.value &&
6253
+ typeof result.value === "object" &&
6254
+ result.value.status === "connected"
6255
+ ) {
6256
+ setProviderStatusCell(spec.id, {
6257
+ t: "status",
6258
+ v: "connected",
6259
+ provider: spec.id,
6260
+ });
6261
+ return;
6262
+ }
6263
+ if (
6264
+ result.state === "value" &&
6265
+ result.value &&
6266
+ typeof result.value === "object" &&
6267
+ result.value.status === "connected-but-empty"
6268
+ ) {
6269
+ setProviderStatusCell(spec.id, {
6270
+ t: "status",
6271
+ v: "connected-but-empty",
6272
+ provider: spec.id,
6273
+ reason: spec.emptyReason,
6274
+ });
6275
+ return;
6276
+ }
6277
+ const reason =
6278
+ result.state === "unknown"
6279
+ ? result.reason + ": " + result.message
6280
+ : "invalid-value: provider probe returned an unusable value";
6281
+ setProviderStatusCell(spec.id, {
6282
+ t: "status",
6283
+ v: "unknown",
6284
+ provider: spec.id,
6285
+ reason,
6286
+ });
6287
+ });
6288
+ }
6159
6289
  function renderLiveStatuses(probes) {
6160
6290
  const list = document.getElementById("liveStatusList");
6161
6291
  list.innerHTML = "";
@@ -6190,6 +6320,101 @@
6190
6320
  list.appendChild(item);
6191
6321
  });
6192
6322
  }
6323
+ /**
6324
+ * Map the enabled-plugins probe into pluginsLive. A missing probe or an
6325
+ * unusable value becomes an explicit unknown state — never demo toggles.
6326
+ * @param probes - Live-status probe map from /api/status
6327
+ */
6328
+ function applyEnabledPlugins(probes) {
6329
+ const raw =
6330
+ probes && Object.hasOwn(probes, "enabled-plugins")
6331
+ ? probes["enabled-plugins"]
6332
+ : undefined;
6333
+ if (raw === undefined) {
6334
+ pluginsLive = {
6335
+ status: "unknown",
6336
+ settingsPresent: false,
6337
+ plugins: [],
6338
+ reason: "missing-probe",
6339
+ message: "The enabled-plugins probe was not reported",
6340
+ };
6341
+ refreshPlugins();
6342
+ return;
6343
+ }
6344
+ const result = normalizeLiveStatusResult(raw);
6345
+ if (
6346
+ result.state === "value" &&
6347
+ result.value &&
6348
+ typeof result.value === "object"
6349
+ ) {
6350
+ const settingsPresent = result.value.settingsPresent === true;
6351
+ const plugins = result.value.plugins;
6352
+ if (!Array.isArray(plugins)) {
6353
+ pluginsLive = {
6354
+ status: "unknown",
6355
+ settingsPresent: false,
6356
+ plugins: [],
6357
+ reason: "invalid-value",
6358
+ message: "enabled-plugins did not return a plugins array",
6359
+ };
6360
+ } else {
6361
+ const rows = [];
6362
+ let invalid = false;
6363
+ for (const entry of plugins) {
6364
+ if (
6365
+ !entry ||
6366
+ typeof entry !== "object" ||
6367
+ typeof entry.id !== "string" ||
6368
+ entry.id.trim().length === 0 ||
6369
+ (entry.status !== "enabled" && entry.status !== "available")
6370
+ ) {
6371
+ invalid = true;
6372
+ break;
6373
+ }
6374
+ rows.push({
6375
+ id: entry.id,
6376
+ status: entry.status,
6377
+ description:
6378
+ typeof entry.description === "string"
6379
+ ? entry.description
6380
+ : "",
6381
+ });
6382
+ }
6383
+ if (invalid) {
6384
+ pluginsLive = {
6385
+ status: "unknown",
6386
+ settingsPresent: false,
6387
+ plugins: [],
6388
+ reason: "invalid-value",
6389
+ message: "enabled-plugins returned an unusable plugin row",
6390
+ };
6391
+ } else {
6392
+ pluginsLive = {
6393
+ status: "value",
6394
+ settingsPresent,
6395
+ plugins: rows,
6396
+ };
6397
+ }
6398
+ }
6399
+ } else if (result.state === "not-applicable") {
6400
+ pluginsLive = {
6401
+ status: "unknown",
6402
+ settingsPresent: false,
6403
+ plugins: [],
6404
+ reason: "not-applicable",
6405
+ message: "enabled-plugins reported not applicable",
6406
+ };
6407
+ } else {
6408
+ pluginsLive = {
6409
+ status: "unknown",
6410
+ settingsPresent: false,
6411
+ plugins: [],
6412
+ reason: result.reason || "unknown",
6413
+ message: result.message || "Plugin enablement unavailable",
6414
+ };
6415
+ }
6416
+ refreshPlugins();
6417
+ }
6193
6418
  /**
6194
6419
  * Paint `#healthChip` from the lisa-version probe.
6195
6420
  * green=ok (up to date), amber=warn (behind), unknown≠ok.
@@ -6309,6 +6534,413 @@
6309
6534
  renderAll();
6310
6535
  showSection(current);
6311
6536
  }
6537
+ function githubRepoHostBlock() {
6538
+ return {
6539
+ card: "Host",
6540
+ rows: [
6541
+ {
6542
+ key: "",
6543
+ label: "Git host",
6544
+ desc: "Which host this repository lives on. GitHub is the only host Lisa provisions today.",
6545
+ control: sel("GitHub", ["GitHub", "GitLab", "Bitbucket"]),
6546
+ },
6547
+ ],
6548
+ };
6549
+ }
6550
+ function githubRepoSettingsRows(settings) {
6551
+ return [
6552
+ {
6553
+ label: "Allow merge commits",
6554
+ desc: "The fleet is merge-only — squash and rebase are disabled because squashing flattens release commits and breaks promotion.",
6555
+ control: tog(!!settings.allow_merge_commit),
6556
+ },
6557
+ {
6558
+ label: "Allow squash merge",
6559
+ desc: "",
6560
+ control: tog(!!settings.allow_squash_merge),
6561
+ },
6562
+ {
6563
+ label: "Allow rebase merge",
6564
+ desc: "",
6565
+ control: tog(!!settings.allow_rebase_merge),
6566
+ },
6567
+ {
6568
+ label: "Auto-merge",
6569
+ desc: "Lisa PRs enable auto-merge and drive themselves to green.",
6570
+ control: tog(!!settings.allow_auto_merge),
6571
+ },
6572
+ {
6573
+ label: "Suggest updating PR branches",
6574
+ desc: "",
6575
+ control: tog(!!settings.allow_update_branch),
6576
+ },
6577
+ {
6578
+ label: "Delete head branch on merge",
6579
+ desc: "Environment branches survive via the prevent-delete ruleset.",
6580
+ control: tog(!!settings.delete_branch_on_merge),
6581
+ },
6582
+ {
6583
+ label: "Merge commit title",
6584
+ desc: "",
6585
+ control: sel(String(settings.merge_commit_title || ""), [
6586
+ "MERGE_MESSAGE",
6587
+ "PR_TITLE",
6588
+ ]),
6589
+ },
6590
+ {
6591
+ label: "Issues tab",
6592
+ desc: "",
6593
+ control: tog(!!settings.has_issues),
6594
+ },
6595
+ {
6596
+ label: "Wiki tab",
6597
+ desc: "Off — Lisa uses the in-repo wiki/ directory instead.",
6598
+ control: tog(!!settings.has_wiki),
6599
+ },
6600
+ {
6601
+ label: "Secret scanning + push protection",
6602
+ desc: "Best-effort; skipped when the plan lacks GitHub Advanced Security.",
6603
+ control: tog(!!settings.secret_scanning),
6604
+ },
6605
+ {
6606
+ label: "Default branch",
6607
+ desc: "Live default branch from the repository.",
6608
+ control: txt(String(settings.default_branch || ""), {
6609
+ mono: true,
6610
+ }),
6611
+ },
6612
+ ];
6613
+ }
6614
+ function applyGithubRepoPanel(result) {
6615
+ const host = githubRepoHostBlock();
6616
+ if (result.state !== "value") {
6617
+ const reason =
6618
+ result.state === "unknown"
6619
+ ? result.reason + ": " + result.message
6620
+ : "not applicable";
6621
+ DATA.sections.repository.blocks = [
6622
+ host,
6623
+ {
6624
+ type: "callout",
6625
+ variant: "warn",
6626
+ text:
6627
+ "<b>unknown — not authenticated / unavailable</b> — " +
6628
+ esc(reason) +
6629
+ ". No setting, ruleset, label, or secret state is shown as a concrete value.",
6630
+ },
6631
+ ];
6632
+ return;
6633
+ }
6634
+ const value = result.value || {};
6635
+ const settings = value.settings || {};
6636
+ const rulesets = Array.isArray(value.rulesets) ? value.rulesets : [];
6637
+ const labels = Array.isArray(value.labels) ? value.labels : [];
6638
+ const secrets = Array.isArray(value.secrets) ? value.secrets : [];
6639
+ DATA.sections.repository.blocks = [
6640
+ host,
6641
+ {
6642
+ card: "Repository settings",
6643
+ note:
6644
+ "Live from gh api repos/" +
6645
+ String(value.owner || "") +
6646
+ "/" +
6647
+ String(value.repo || ""),
6648
+ rows: githubRepoSettingsRows(settings),
6649
+ },
6650
+ {
6651
+ type: "table",
6652
+ card: "Rulesets",
6653
+ note: "Live from gh api repos/{owner}/{repo}/rulesets",
6654
+ cols: ["Ruleset", "Applies to", "Enforces", "Active"],
6655
+ rows: rulesets.map(row => [
6656
+ { t: "mono", v: String(row.name || "") },
6657
+ { t: "text", v: String(row.appliesTo || "—") },
6658
+ { t: "text", v: String(row.enforces || "—") },
6659
+ {
6660
+ t: "badge",
6661
+ v: row.active ? "active" : "disabled",
6662
+ cls: row.active ? "on" : "off",
6663
+ },
6664
+ ]),
6665
+ },
6666
+ {
6667
+ type: "table",
6668
+ card: "Lifecycle labels",
6669
+ note: "Config-expected labels vs live gh label list",
6670
+ cols: ["Label", "Role", "Color", "Status"],
6671
+ rows: labels.map(row => [
6672
+ { t: "mono", v: String(row.name || "") },
6673
+ { t: "text", v: String(row.role || "") },
6674
+ row.present && row.color
6675
+ ? {
6676
+ t: "color",
6677
+ v: "#" + String(row.color).replace(/^#/u, ""),
6678
+ }
6679
+ : { t: "text", v: "—" },
6680
+ {
6681
+ t: "badge",
6682
+ v: row.present ? "present" : "missing",
6683
+ cls: row.present ? "on" : "off",
6684
+ },
6685
+ ]),
6686
+ },
6687
+ {
6688
+ type: "table",
6689
+ card: "Secrets",
6690
+ note: "Presence only from gh secret list — values are never read",
6691
+ cols: ["Secret", "Purpose", "Status"],
6692
+ rows: secrets.map(row => [
6693
+ { t: "mono", v: String(row.name || "") },
6694
+ { t: "text", v: String(row.purpose || "") },
6695
+ {
6696
+ t: "badge",
6697
+ v: row.set ? "set" : "missing",
6698
+ cls: row.set ? "on" : "off",
6699
+ },
6700
+ ]),
6701
+ },
6702
+ ];
6703
+ }
6704
+ function reRenderAfterGithubRepoHydration() {
6705
+ const current = activeSection;
6706
+ renderAll();
6707
+ showSection(current || "overview");
6708
+ }
6709
+ /**
6710
+ * Replace the Deploy pipeline stages table from the live probe.
6711
+ * Never leaves demo hold/no-hold claims in place.
6712
+ * @param probes - Live-status probe map from /api/status
6713
+ */
6714
+ function applyDeployPipelineStages(probes) {
6715
+ const section = DATA.sections.deploy;
6716
+ if (!section || !Array.isArray(section.blocks)) return;
6717
+ const block = section.blocks.find(
6718
+ candidate =>
6719
+ candidate &&
6720
+ candidate.type === "table" &&
6721
+ candidate.id === "deploy-pipeline-stages"
6722
+ );
6723
+ if (!block) return;
6724
+ const raw =
6725
+ probes && Object.hasOwn(probes, "deploy-pipeline-stages")
6726
+ ? probes["deploy-pipeline-stages"]
6727
+ : undefined;
6728
+ const result =
6729
+ raw === undefined
6730
+ ? {
6731
+ state: "unknown",
6732
+ reason: "missing-probe",
6733
+ message: "The deploy-pipeline-stages probe was not reported",
6734
+ }
6735
+ : normalizeLiveStatusResult(raw);
6736
+ if (result.state === "not-applicable") {
6737
+ block.rows = [
6738
+ [
6739
+ { t: "mono", v: "—" },
6740
+ {
6741
+ t: "text",
6742
+ v: "No deploy.yml or github.environments configured for this project",
6743
+ },
6744
+ { t: "status", v: "unknown", reason: "not-applicable" },
6745
+ ],
6746
+ ];
6747
+ } else if (
6748
+ result.state === "value" &&
6749
+ result.value &&
6750
+ typeof result.value === "object" &&
6751
+ Array.isArray(result.value.stages)
6752
+ ) {
6753
+ block.rows = result.value.stages.map(stage => [
6754
+ {
6755
+ t: "mono",
6756
+ v: String(stage.name || stage.id || "stage"),
6757
+ },
6758
+ {
6759
+ t: "text",
6760
+ v: String(stage.description || ""),
6761
+ },
6762
+ {
6763
+ t: "status",
6764
+ v: stage.active === "unknown" ? "unknown" : stage.active === true,
6765
+ reason:
6766
+ typeof stage.reason === "string" && stage.reason.length > 0
6767
+ ? stage.reason
6768
+ : undefined,
6769
+ },
6770
+ ]);
6771
+ if (block.rows.length === 0) {
6772
+ block.rows = [
6773
+ [
6774
+ { t: "mono", v: "—" },
6775
+ { t: "text", v: "Deploy pipeline reported zero stages" },
6776
+ {
6777
+ t: "status",
6778
+ v: "unknown",
6779
+ reason: "empty-stages",
6780
+ },
6781
+ ],
6782
+ ];
6783
+ }
6784
+ } else {
6785
+ const reason =
6786
+ result.state === "unknown"
6787
+ ? result.reason + ": " + result.message
6788
+ : "invalid-value: deploy-pipeline-stages probe returned an unusable value";
6789
+ block.rows = [
6790
+ [
6791
+ { t: "mono", v: "—" },
6792
+ {
6793
+ t: "text",
6794
+ v: "Deploy pipeline stages unavailable",
6795
+ },
6796
+ { t: "status", v: "unknown", reason: reason },
6797
+ ],
6798
+ ];
6799
+ }
6800
+ const existing = document.getElementById("deploy-pipeline-stages");
6801
+ if (existing) {
6802
+ existing.replaceWith(buildTable(block));
6803
+ }
6804
+ }
6805
+ /* Map the detected-stacks probe into stackDetection. A missing probe or a
6806
+ non-value/non-array result becomes an explicit unknown state — never a
6807
+ false empty and never the metadata catalog. */
6808
+ function applyDetectedStacks(probes) {
6809
+ const raw =
6810
+ probes && Object.hasOwn(probes, "detected-stacks")
6811
+ ? probes["detected-stacks"]
6812
+ : undefined;
6813
+ if (raw === undefined) {
6814
+ stackDetection = {
6815
+ status: "unknown",
6816
+ ids: [],
6817
+ reason: "missing-probe",
6818
+ message: "The detected-stacks probe was not reported",
6819
+ };
6820
+ refreshStacks();
6821
+ return;
6822
+ }
6823
+ const result = normalizeLiveStatusResult(raw);
6824
+ if (result.state === "value" && Array.isArray(result.value)) {
6825
+ const ids = result.value.filter(v => typeof v === "string");
6826
+ if (ids.length !== result.value.length) {
6827
+ // Any non-string element makes the whole array untrustworthy;
6828
+ // an empty state here would fabricate a "no stacks" certainty.
6829
+ stackDetection = {
6830
+ status: "unknown",
6831
+ ids: [],
6832
+ reason: "invalid-value",
6833
+ message: "detected-stacks did not return a string array",
6834
+ };
6835
+ } else {
6836
+ stackDetection =
6837
+ ids.length > 0
6838
+ ? { status: "value", ids }
6839
+ : { status: "empty", ids: [] };
6840
+ }
6841
+ } else if (result.state === "value") {
6842
+ stackDetection = {
6843
+ status: "unknown",
6844
+ ids: [],
6845
+ reason: "invalid-value",
6846
+ message: "detected-stacks did not return a string array",
6847
+ };
6848
+ } else if (result.state === "not-applicable") {
6849
+ stackDetection = {
6850
+ status: "unknown",
6851
+ ids: [],
6852
+ reason: "not-applicable",
6853
+ message: "detected-stacks reported not applicable",
6854
+ };
6855
+ } else {
6856
+ stackDetection = {
6857
+ status: "unknown",
6858
+ ids: [],
6859
+ reason: result.reason,
6860
+ message: result.message,
6861
+ };
6862
+ }
6863
+ refreshStacks();
6864
+ }
6865
+ function markStacksUnreachable(error) {
6866
+ stackDetection = {
6867
+ status: "unknown",
6868
+ ids: [],
6869
+ reason: "status-unavailable",
6870
+ message: error instanceof Error ? error.message : String(error),
6871
+ };
6872
+ refreshStacks();
6873
+ }
6874
+ function automationsScheduledJobsBlock() {
6875
+ const blocks =
6876
+ (DATA.sections.automations && DATA.sections.automations.blocks) || [];
6877
+ return blocks.find(block => block.id === "automations-scheduled-jobs");
6878
+ }
6879
+ function automationRowFromProbe(entry) {
6880
+ const cadence =
6881
+ typeof entry.cadence === "string" && entry.cadence.trim().length > 0
6882
+ ? entry.cadence
6883
+ : "unknown";
6884
+ const lastRun =
6885
+ typeof entry.lastRunAt === "string" &&
6886
+ entry.lastRunAt.trim().length > 0
6887
+ ? entry.lastRunAt
6888
+ : "—";
6889
+ const status =
6890
+ typeof entry.status === "string" && entry.status.trim().length > 0
6891
+ ? entry.status
6892
+ : "unknown";
6893
+ const runtime =
6894
+ entry.runtime === "claude" ? "claude /schedule" : "codex";
6895
+ const row = [
6896
+ { t: "mono", v: entry.id },
6897
+ { t: "badge", v: runtime, cls: "default" },
6898
+ { t: "mono", v: cadence },
6899
+ { t: "mono", v: lastRun },
6900
+ {
6901
+ t: "badge",
6902
+ v: status,
6903
+ cls: status.toUpperCase() === "ACTIVE" ? "pass" : "default",
6904
+ },
6905
+ ];
6906
+ row._automationId = entry.id;
6907
+ return row;
6908
+ }
6909
+ function hydrateAutomationsSection(probeResult) {
6910
+ const block = automationsScheduledJobsBlock();
6911
+ if (!block) return;
6912
+ const result = normalizeLiveStatusResult(probeResult);
6913
+ if (result.state === "value") {
6914
+ const automations = Array.isArray(result.value.automations)
6915
+ ? result.value.automations
6916
+ : [];
6917
+ block.rows = automations.map(automationRowFromProbe);
6918
+ if (block.rows.length === 0) {
6919
+ block.empty = {
6920
+ state: "empty",
6921
+ message:
6922
+ "No lisa-auto-<project>- automations found in the harness scheduler",
6923
+ };
6924
+ } else {
6925
+ delete block.empty;
6926
+ }
6927
+ } else if (result.state === "not-applicable") {
6928
+ block.rows = [];
6929
+ block.empty = {
6930
+ state: "not-applicable",
6931
+ message: "Automations are not applicable on this machine",
6932
+ };
6933
+ } else {
6934
+ block.rows = [];
6935
+ block.empty = {
6936
+ state: "unknown",
6937
+ message: result.reason + ": " + result.message,
6938
+ };
6939
+ }
6940
+ renderAll();
6941
+ showSection(activeSection);
6942
+ }
6943
+
6312
6944
  async function hydrateLiveStatuses() {
6313
6945
  try {
6314
6946
  const response = await fetch("/api/status", {
@@ -6318,9 +6950,29 @@
6318
6950
  const snapshot = await response.json();
6319
6951
  const probes = liveStatusProbeMap(snapshot);
6320
6952
  renderLiveStatuses(probes);
6321
- // Quality jobs re-renders the page; paint the version chip after.
6953
+ applyEnabledPlugins(probes);
6954
+ applyDetectedStacks(probes);
6955
+ // Quality jobs / automations re-render the page; paint the version chip after.
6322
6956
  applyCiQualityJobs(probes);
6957
+ hydrateAutomationsSection(
6958
+ probes.automations || {
6959
+ state: "unknown",
6960
+ reason: "probe-missing",
6961
+ message: "Automations probe was omitted from /api/status",
6962
+ }
6963
+ );
6323
6964
  applyLisaVersion(probes);
6965
+ const repoResult = normalizeLiveStatusResult(
6966
+ probes["github-repo"] || {
6967
+ state: "unknown",
6968
+ reason: "probe-missing",
6969
+ message: "GitHub repository probe did not run",
6970
+ }
6971
+ );
6972
+ applyGithubRepoPanel(repoResult);
6973
+ reRenderAfterGithubRepoHydration();
6974
+ applyDeployPipelineStages(probes);
6975
+ applyObservabilityProviders(probes);
6324
6976
  } catch (error) {
6325
6977
  const message =
6326
6978
  error instanceof Error ? error.message : String(error);
@@ -6330,8 +6982,21 @@
6330
6982
  message,
6331
6983
  };
6332
6984
  renderLiveStatuses({ transport: unavailable });
6985
+ applyEnabledPlugins({ "enabled-plugins": unavailable });
6333
6986
  applyCiQualityJobs({ "ci-quality-jobs": unavailable });
6987
+ hydrateAutomationsSection(unavailable);
6334
6988
  applyLisaVersion({ "lisa-version": unavailable });
6989
+ applyGithubRepoPanel(unavailable);
6990
+ reRenderAfterGithubRepoHydration();
6991
+ applyDeployPipelineStages({
6992
+ "deploy-pipeline-stages": unavailable,
6993
+ });
6994
+ applyObservabilityProviders({
6995
+ sentry: unavailable,
6996
+ "cloudwatch-alarms": unavailable,
6997
+ "x-ray": unavailable,
6998
+ });
6999
+ markStacksUnreachable(error);
6335
7000
  }
6336
7001
  }
6337
7002