commit f5702a88955a5400240178d3696867d2063b1867 Author: dbl8005 Date: Sun Aug 23 00:24:35 2026 +0300 feat: initial release of sitrep-panel v1.0.0 A live local HTML report skill for Claude Code — status board, running 'what's happening now' narrative, screenshot gallery, newest-first log, served on localhost with 2s-poll live-reload (no build step, no external requests). Optional tracker-synced board (Jira/GitHub Issues/Linear), falls back to a manual checklist when nothing's configured. Invoked via /sitrep-panel [start|open|stop|archive]. diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..c15572f --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json", + "name": "sitrep-panel", + "owner": { + "name": "Dave" + }, + "description": "Install sitrep-panel as a Claude Code plugin.", + "plugins": [ + { + "name": "sitrep-panel", + "source": "./", + "description": "A live local HTML report Claude keeps updated as it works — status board, running narrative, screenshots, newest-first log — so you can watch progress without reading the transcript.", + "license": "MIT", + "keywords": ["progress", "report", "dashboard", "status", "live-reload", "session"] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..4423fe5 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "sitrep-panel", + "description": "A live local HTML report Claude keeps updated as it works — status board, running narrative, screenshots, newest-first log — so you can watch progress without reading the transcript.", + "version": "1.0.0", + "author": { + "name": "Dave" + }, + "homepage": "https://github.com/dbl8005/sitrep-panel", + "repository": "https://github.com/dbl8005/sitrep-panel", + "license": "MIT", + "keywords": ["progress", "report", "dashboard", "status", "live-reload", "session"], + "skills": ["./"] +} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..3ad4de5 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,34 @@ +name: Check package + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Check package files + run: python3 scripts/validate-package.py + - name: Check server script starts and serves + run: | + set -e + mkdir -p /tmp/pd-check/report + cp assets/template.html /tmp/pd-check/report/index.html + echo '{"updated_at": "2026-01-01T00:00:00Z"}' > /tmp/pd-check/report/meta.json + python3 scripts/serve.py /tmp/pd-check/report 8934 > /tmp/pd-serve.log 2>&1 & + server_pid=$! + sleep 1 + url=$(grep -o 'http://localhost:[0-9]*/' /tmp/pd-serve.log | head -1) + test -n "$url" + status=$(curl -s -o /dev/null -w '%{http_code}' "${url}meta.json") + test "$status" = "200" + kill "$server_pid" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2348c91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +__pycache__/ +*.pyc diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2086888 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,120 @@ +# sitrep-panel + +A live local HTML report you keep updated as you work. It lives at +`.sitrep-panel/report/` inside whatever project you're invoked from, served +on localhost, and it live-reloads in the browser every time you update it — +the user never has to hit refresh. + +This is not a hosted artifact. It never leaves the user's machine, needs no +publish step, and works offline. Use it whenever the user wants to *watch* +a task happen rather than read about it after the fact — they'll typically +ask by name ("start a sitrep-panel", "/sitrep-panel") or describe wanting a +"progress report", "status page", or "live doc" for a task. + +## Invocation + +`sitrep-panel [start | open | stop | archive]` — default (no argument) is +`start`. + +- **`start`** (default) — scaffold a new report if none is active, or resume + the active one. Always ends by printing the localhost URL prominently. +- **`open`** — just print the URL of the active report (start the server if + it isn't running); never touches content. +- **`archive`** — move the current active report to + `.sitrep-panel/archive/-/` and scaffold a fresh one. Use + this when starting genuinely new, unrelated work rather than continuing + the current task. +- **`stop`** — kill the background server. Content on disk is untouched; + `start` or `open` later brings it back. + +## First run — scaffold + +If `.sitrep-panel/report/` doesn't exist yet in the current project: + +1. Create `.sitrep-panel/report/screenshots/`. +2. Copy `assets/template.html` (from this package's own directory) to + `.sitrep-panel/report/index.html`. Replace `{{TITLE}}` with a short name + for the task, and `{{SUBTITLE}}` with one line of context (what this + report is tracking, and when it started). +3. Write `.sitrep-panel/report/meta.json`: + ```json + {"updated_at": ""} + ``` +4. If the project is a git repo and `.sitrep-panel/` is not already covered + by `.gitignore`, append it — this is local scratch, not something to + commit. Mention you did this; don't ask first, it's trivially reversible. + +If `.sitrep-panel/report/` already exists, skip scaffolding — resume it. Use +`archive` first for unrelated new work if you want a clean slate. + +## Serving it + +Start the bundled server in the background, from this package's own +directory (so the relative path to `scripts/serve.py` resolves regardless of +the caller's cwd): + +```bash +python3 /scripts/serve.py /.sitrep-panel/report +``` + +Run this via your background-execution mechanism — it blocks forever. It +prints exactly one line, `SERVING http://localhost:/`, before +blocking; capture that for the URL. It auto-picks a free port (tries 8934 +first, falls back to any free port on conflict), so several reports can run +for different projects at once. Stdlib-only Python, no dependencies. + +Record the port for reuse: write `.sitrep-panel/report/.server.json` +(`{"port": , "started_at": ""}`), gitignored along with the +rest of `.sitrep-panel/`. On a later `start`/`open`, read this file first — +if a `GET` to `http://localhost:/meta.json` succeeds, the server is +already up; reuse that URL instead of spawning a second one. + +**Always tell the user the URL after `start` or `open`, even if you just +reused an existing server.** + +## The update protocol — what makes this useful + +A sitrep-panel that only gets written once at the start is worthless. +Update it at every real step: + +1. **Update "What's happening now"** (`#current-work-body`) with prose — + what you're doing right now and *why*. Replace the block's content each + time; it always reflects the present, not history. +2. **Prepend a new log entry** to `#entries` (newest-first — insert right + after the `
` opening tag). One entry per real step: + what happened, what was decided and why, what was verified. Skip entries + for trivial reads; log entries for writes, decisions, and verification. +3. **Save screenshots** into `.sitrep-panel/report/screenshots/` and + reference them with a relative `` — + inline in a log entry, appended to `#shots-body`, or both. +4. **Update the board** (`#board`) — add/move/remove `.board-card` elements + as steps start, block, or finish. See the commented example markup in + the template for the exact class names (`status-todo` / + `status-progress` / `status-done` / `status-blocked`). +5. **Touch `meta.json` last, always.** Rewrite `updated_at` to the current + ISO-8601 UTC timestamp as the final write of every update — the page + polls this file every 2s and reloads on change. This is the one step + you must never skip. + +## Tracker-synced board (optional) + +If the project has a working issue-tracker connection available right now +and there are real tickets relevant to this task, render the board from +that real data — real key, real title, real link, real status. Refresh it +each time you update the doc. + +If no tracker is connected, or nothing relevant is filed there, fall back +to a plain manually-maintained checklist kept in sync by hand. Never +fabricate ticket data, and don't silently guess which tracker to use if +more than one is plausible — ask. + +## Notes + +- One active report per project at a time by design (`.sitrep-panel/report/` + is a fixed path) — the URL stays stable across a whole session. Use + `archive` to start clean. +- Genuinely static HTML plus polling JS — no build step, no external + requests, works fully offline once loaded. +- If the user wants something shareable with people not at their machine, + that's a hosted artifact, not this — this is specifically for + local/private, zero-publish visibility. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1325f70 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Dave + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a5f739 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# sitrep-panel + +**Watch your AI agent work, live, in a browser tab — instead of scrolling a transcript.** + +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Claude Code Skill](https://img.shields.io/badge/Claude%20Code-Skill-6c9bff)](https://github.com/dbl8005/sitrep-panel) + +

+ + + A sitrep-panel report showing a status board, a running narrative, a screenshot, and a newest-first log for a fictional 'add rate limiting' task + +

+ +

Demo content — no real project, code, or data. Generated for this README.

+ +## Why + +Long agent sessions are hard to watch. The transcript scrolls faster than you +can read it, and there's no single place that answers "what is it doing +right now, and how did it get here?" without interrupting to ask. + +sitrep-panel is that place — a local HTML report the agent writes to itself +as it works, that live-reloads the instant it's updated. Not a chat log: +a running account of the actual state of the work. + +It is **not** a hosted artifact or a cloud dashboard. It never leaves your +machine, needs no publish step, no account, and no network access — +everything is one static HTML page plus a stdlib Python server. + +## What it shows + +- **Status board** — cards for what's to do, in progress, done, or blocked. + Synced from a connected issue tracker (Jira, GitHub Issues, Linear — + whatever the agent already has access to) when one's available, otherwise + a plain checklist the agent maintains by hand. Never fabricated. +- **What's happening now** — a standing paragraph of *why*, not a status + word. Replaced each update, always reflecting the present. +- **Screenshots** — dropped in as they're taken, inline in the log or in + their own gallery. +- **Log** — one entry per real step, newest first: what happened, what was + decided, what was verified. + +## How it live-reloads + +No WebSocket, no build step, no bundler. The page polls a small `meta.json` +file every 2 seconds and reloads the instant its timestamp changes — the +agent just touches that one file after every content update. Everything +else is plain static HTML served by Python's stdlib `http.server`. + +## Install + +```bash +claude plugin marketplace add dbl8005/sitrep-panel +claude plugin install sitrep-panel@sitrep-panel +``` + +Or clone and point Claude Code at it locally: + +```bash +git clone https://github.com/dbl8005/sitrep-panel.git +claude plugin marketplace add ./sitrep-panel +claude plugin install sitrep-panel@sitrep-panel +``` + +## Usage + +``` +/sitrep-panel +``` + +Starts (or resumes) a report for the current project and prints its +`http://localhost:/` URL. + +``` +/sitrep-panel open # just print the URL, don't touch content +/sitrep-panel archive # move the current report aside, start a fresh one +/sitrep-panel stop # stop the local server (content stays on disk) +``` + +Everything lives at `.sitrep-panel/` in the project you invoke it from — +gitignored automatically, since it's scratch, not history you commit. +Reports are single-project and single-active-report by design: the URL +stays stable for a whole session instead of minting a new page per task. + +## Design principles + +- **Local-first, always.** No account, no upload, no third-party server in + the loop. The report never exists anywhere but your machine. +- **Never fabricate.** The status board reflects real tracker data or a + manually-kept checklist — never invented tickets or guessed status. +- **Update, don't rewrite.** The protocol is additive (prepend log entries, + replace the current-work block, touch one timestamp file) so a crashed or + interrupted agent leaves a readable partial report, not a blank page. +- **Zero dependencies.** The server is stdlib Python. The page is HTML, CSS, + and about 20 lines of vanilla JS. Nothing to install, nothing to break. + +## Contributing + +Issues and PRs welcome. `python3 scripts/validate-package.py` checks the +package files; CI runs it plus a real server-start smoke test on every PR. + +## Versions + +- **1.0.0** — initial release: status board, live "what's happening now" + narrative, screenshot gallery, newest-first log, live-reload, optional + tracker-synced board. + +## License + +MIT diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..6a77d23 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,143 @@ +--- +name: sitrep-panel +description: | + Keep a live local HTML report updated as you work on a task — a status + board, a running "what's happening now" narrative, screenshots, and a + newest-first log — served on localhost so the user can watch progress in + a browser tab instead of reading the transcript. Use when the user invokes + /sitrep-panel, asks for a "progress report", "status page", "live doc", + or wants visibility into a multi-step task as it happens. +license: MIT +metadata: + version: "1.0.0" +--- + +# sitrep-panel + +A live local HTML report you keep updated as you work. It lives at +`.sitrep-panel/report/` inside whatever project you invoke it from, served +on localhost, and it live-reloads in the browser every time you update it — +the user never has to hit refresh. + +This is explicitly **not** a Claude Artifact. It never leaves the user's +machine, needs no publish step, and works offline. Use it whenever the user +wants to *watch* a task happen rather than read about it after the fact. + +## Invocation + +`/sitrep-panel [start | open | stop | archive]` — default (no argument) is +`start`. + +- **`start`** (default) — scaffold a new report if none is active, or resume + the active one. Always ends by printing the localhost URL prominently. +- **`open`** — just print the URL of the active report (start the server if + it isn't running); never touches content. +- **`archive`** — move the current active report to + `.sitrep-panel/archive/-/` and scaffold a fresh one. Use + this when starting genuinely new, unrelated work rather than continuing + the current task. +- **`stop`** — kill the background server. Content on disk is untouched; + `start` or `open` later brings it back. + +## First run — scaffold + +If `.sitrep-panel/report/` doesn't exist yet in the current project: + +1. Create `.sitrep-panel/report/screenshots/`. +2. Copy `assets/template.html` (from this skill's own directory) to + `.sitrep-panel/report/index.html`. Replace `{{TITLE}}` with a short name + for the task, and `{{SUBTITLE}}` with one line of context (what this + report is tracking, and when it started). +3. Write `.sitrep-panel/report/meta.json`: + ```json + {"updated_at": ""} + ``` +4. If the project is a git repo and `.sitrep-panel/` is not already covered + by `.gitignore`, append it (`echo '.sitrep-panel/' >> .gitignore`) — this + is local scratch, not something to commit. Mention you did this; don't + ask first, it's trivially reversible. If the user tells you they want + report history committed instead, skip this step and say so. + +If `.sitrep-panel/report/` **already exists**, skip scaffolding — resume it. +Use `archive` first if this is unrelated new work and you want a clean slate. + +## Serving it + +Start the bundled server in the background, from this skill's own directory +(so the relative path to `scripts/serve.py` resolves regardless of the +user's cwd): + +```bash +python3 /scripts/serve.py /.sitrep-panel/report +``` + +Run this with your background-execution mechanism (e.g. the Bash tool's +`run_in_background: true`) — it blocks forever. It prints exactly one line, +`SERVING http://localhost:/`, before blocking; capture that line for +the URL. It auto-picks a free port (tries 8934 first, falls back to any free +port on conflict), so it's safe to have several reports running for +different projects at once. + +Record the port for reuse: write `.sitrep-panel/report/.server.json` +(`{"port": , "started_at": ""}`) right after start, gitignored +along with the rest of `.sitrep-panel/`. On a later `start`/`open` in the +same or a resumed session, read this file first — if a `GET` to +`http://localhost:/meta.json` succeeds, the server is already up, +reuse that URL instead of spawning a second one. + +**Always tell the user the URL after `start` or `open`, even if you just +reused an existing server.** This is the whole point of the skill — don't +bury it in a paragraph. + +## The update protocol — what makes this useful + +This is the part that matters. A sitrep-panel that only gets written once at +the start is worthless. Update it at every real step, not just at the end: + +1. **Update "What's happening now"** (`#current-work-body`) with prose — what + you're doing right now and *why*, not just a status word. Replace the + whole block's content each time; this section always reflects the + present, not history. +2. **Prepend a new log entry** to `#entries` (newest-first — insert right + after the `
` opening tag, before whatever was already + there). One entry per real step: what happened, what you decided and why, + what you verified. Skip entries for trivial reads; log entries for + writes, decisions, and verification results. +3. **Save screenshots** into `.sitrep-panel/report/screenshots/` and + reference them with a relative `` — + either inline in a log entry, or appended to `#shots-body` (or both, when + a screenshot is the standout evidence for a step). +4. **Update the board** (`#board`) — add/move/remove `.board-card` elements + as steps start, block, or finish. See the commented example markup + already in the template for the exact class names + (`status-todo` / `status-progress` / `status-done` / `status-blocked`). +5. **Touch `meta.json` last, always.** Rewrite `updated_at` to the current + ISO-8601 UTC timestamp as the final write of every update — the page + polls this file every 2s and reloads on change, so writing it before the + content write would show a stale reload. This is the one step you must + never skip. + +## Tracker-synced board (optional) + +If the project has a working issue tracker connection available to you right +now (an already-connected Atlassian/Jira MCP, `gh issue list` for a GitHub +repo, a connected Linear MCP, etc.) **and** there are real tickets relevant +to this task, render the board from that real data — real ticket key, real +title, real link, real status mapped to the four status classes. Refresh it +each time you update the doc, the same as everything else. + +If no tracker is connected, or nothing relevant is filed there, fall back to +a plain manually-maintained checklist — steps you define yourself, kept in +sync by hand. **Never fabricate ticket data to fill the board**, and don't +silently guess which tracker to use — if more than one is plausible, ask. + +## Notes + +- One active report per project at a time by design (`.sitrep-panel/report/` + is a fixed path) — this keeps the URL stable across a whole session + instead of minting a new one per task. Use `archive` to start clean. +- The report is genuinely static HTML plus polling JS — no build step, no + external requests, works with the browser fully offline once loaded. +- If the user asks for a report shareable with people who aren't at their + machine, that's a Claude Artifact, not this — tell them so; this skill is + specifically for local/private, zero-publish visibility. diff --git a/assets/template.html b/assets/template.html new file mode 100644 index 0000000..4e45010 --- /dev/null +++ b/assets/template.html @@ -0,0 +1,240 @@ + + + + + + +{{TITLE}} — sitrep-panel + + + +
+
+

{{TITLE}}

+

{{SUBTITLE}}

+ just started +
+ +
+ +
+ +
+

What's happening now

+
+

Nothing yet — this fills in once work starts.

+
+
+ +
+

Screenshots

+
+
+ +
+

Log (newest first)

+
+ +
+
+
+ + + + diff --git a/docs/screenshots/report-dark.png b/docs/screenshots/report-dark.png new file mode 100644 index 0000000..5c0ec43 Binary files /dev/null and b/docs/screenshots/report-dark.png differ diff --git a/docs/screenshots/report-light.png b/docs/screenshots/report-light.png new file mode 100644 index 0000000..b7bb329 Binary files /dev/null and b/docs/screenshots/report-light.png differ diff --git a/scripts/serve.py b/scripts/serve.py new file mode 100644 index 0000000..c15c3d2 --- /dev/null +++ b/scripts/serve.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Serve a sitrep-panel report directory on a free localhost port. + +Usage: python3 serve.py [preferred_port] + +Prints exactly one line to stdout on success: `SERVING http://localhost:/` +then blocks, running the HTTP server — launch this with the caller's +background/detached mechanism (e.g. Claude Code's Bash tool with +run_in_background: true). Stdlib only, no dependencies. +""" + +from __future__ import annotations + +import functools +import http.server +import sys + + +class QuietHandler(http.server.SimpleHTTPRequestHandler): + """SimpleHTTPRequestHandler that suppresses per-request access logging, + so stdout stays to the one clean SERVING line callers parse.""" + + def log_message(self, format: str, *args: object) -> None: # noqa: A002 + pass + + +def main() -> None: + if len(sys.argv) < 2: + print("usage: serve.py [preferred_port]", file=sys.stderr) + sys.exit(1) + + directory = sys.argv[1] + preferred_port = int(sys.argv[2]) if len(sys.argv) > 2 else 8934 + handler = functools.partial(QuietHandler, directory=directory) + + try: + httpd = http.server.ThreadingHTTPServer(("127.0.0.1", preferred_port), handler) + except OSError: + # Preferred port is taken — let the OS assign a free one rather than + # guessing and racing another process for it. + httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler) + + port = httpd.server_address[1] + print(f"SERVING http://localhost:{port}/", flush=True) + httpd.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/scripts/validate-package.py b/scripts/validate-package.py new file mode 100644 index 0000000..0813ad4 --- /dev/null +++ b/scripts/validate-package.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Check sitrep-panel's package files without external dependencies.""" + +from __future__ import annotations + +import ast +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SKILL = (ROOT / "SKILL.md").read_text(encoding="utf-8") +README = (ROOT / "README.md").read_text(encoding="utf-8") +AGENTS = (ROOT / "AGENTS.md").read_text(encoding="utf-8") +TEMPLATE = (ROOT / "assets" / "template.html").read_text(encoding="utf-8") +PLUGIN = json.loads((ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) +MARKETPLACE = json.loads((ROOT / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8")) +SERVE_PY = (ROOT / "scripts" / "serve.py").read_text(encoding="utf-8") + + +def require_match(match: re.Match[str] | None, message: str) -> re.Match[str]: + if match is None: + raise SystemExit(message) + return match + + +def require(condition: bool, message: str) -> None: + if not condition: + raise SystemExit(message) + + +# --- SKILL.md frontmatter --------------------------------------------------- +yaml_metadata = require_match( + re.match(r"\A---\n(.*?)\n---\n", SKILL, re.DOTALL), + "SKILL.md must begin with YAML metadata", +).group(1) + +require( + re.search(r"(?m)^name:\s*sitrep-panel\s*$", yaml_metadata) is not None, + "SKILL.md metadata must set name: sitrep-panel", +) +require( + re.search(r"(?m)^description:", yaml_metadata) is not None, + "SKILL.md metadata must set a description", +) +require( + re.search(r"(?m)^license:\s*MIT\s*$", yaml_metadata) is not None, + "SKILL.md metadata must set license: MIT", +) + +skill_version = require_match( + re.search(r'(?m)^\s+version:\s*["\']([^"\']+)["\']\s*$', yaml_metadata), + "Add metadata.version to SKILL.md", +).group(1) + +# --- version consistency across README / plugin.json / marketplace.json --- +readme_version = require_match( + re.search(r"(?m)^- \*\*([0-9]+\.[0-9]+\.[0-9]+)\*\*", README), + "Add a version entry to README.md's Versions section", +).group(1) + +require( + skill_version == readme_version == PLUGIN["version"], + f"Version mismatch: SKILL.md={skill_version} README.md={readme_version} plugin.json={PLUGIN['version']}", +) +require(PLUGIN["name"] == "sitrep-panel", "plugin.json name must be sitrep-panel") +require(PLUGIN["license"] == "MIT", "plugin.json license must be MIT") +require( + any(p["name"] == "sitrep-panel" for p in MARKETPLACE["plugins"]), + "marketplace.json must list a sitrep-panel plugin entry", +) + +# --- AGENTS.md exists and isn't a stub -------------------------------------- +require(len(AGENTS.strip()) > 200, "AGENTS.md looks empty or too short") + +# --- template.html has the ids the SKILL.md protocol depends on ------------ +for element_id in ("board", "current-work-body", "shots-body", "entries", "updated-badge"): + require(f'id="{element_id}"' in TEMPLATE, f'template.html is missing id="{element_id}"') +for token in ("{{TITLE}}", "{{SUBTITLE}}"): + require(token in TEMPLATE, f"template.html is missing the {token} placeholder") +require("meta.json" in TEMPLATE, "template.html must poll meta.json for live-reload") + +# --- serve.py is at least syntactically valid Python ------------------------ +try: + ast.parse(SERVE_PY, filename="scripts/serve.py") +except SyntaxError as exc: # pragma: no cover - fails loud on purpose + raise SystemExit(f"scripts/serve.py has a syntax error: {exc}") from exc +require("SERVING http://localhost" in SERVE_PY, "serve.py must print the SERVING line callers parse") + +print("sitrep-panel package checks passed.")