From bb56fb8572977024fe8f7b2ea867b0ea54f6eca3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:40:16 +0000 Subject: [PATCH] intake: widen the dashboard into the Mind's task page, and make it checkable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dashboard.md` was a backlog inventory: ten five-column tables, 133 rows, no in-flight work and no issue links. It answered "how many prompts are there?" for someone at a terminal. The page a human actually opens on GitHub — often on a phone — has to answer "what should I pick up now?", so the renderer now leads with the picks (high priority, then quick wins), lists what is in flight with its issue link, and puts parked/planned/the whole backlog behind `
`. Backlog rows are bullets, not table rows, because a wide table scrolls sideways on a phone and a bullet wraps. The issue links come from the registry rows (`active.md`, `parked.md`, `planned.md`) that name a prompt, never from a URL in the prompt body — a body URL is as often a cross-reference as the task's own issue. Same reason in-flight rows show the registry status and not the prompt's `Status:` header, which says "formalised" from conception until the day it ships. `dashboard --check` exits 1 when the committed page has drifted, ignoring the generation stamp so a re-render on an unchanged Mind is not drift. That is what PyAutoMind's new `dashboard_refresh.yml` runs. Rendering safety is pinned by tests because the failures are silent: a prompt titled with a leading `", "").strip() + return value.replace("[", r"\[").replace("]", r"\]") or "Untitled" + + +# Pick order. The dashboard exists to be *chosen from*, so every list is sorted +# most-pickable first: urgent before routine, small before enormous. Unknown +# (`-`, the headerless prompts) sorts last rather than being hidden. +PRIORITY_RANK = {"high": 0, "medium": 1, "normal": 2, "low": 3} +DIFFICULTY_RANK = {"small": 0, "medium": 1, "large": 2, "too-large": 3} +PICK_LIST_MAX = 12 +# Live GitHub views, for the half of this page a static file cannot hold: the +# issues themselves. Org-wide searches, so a new repo needs no edit here. +GH_SEARCH = "https://github.com/search?q=org%3APyAutoLabs+is%3A{kind}+is%3Aopen&type={kind}s" + + +def _pick_key(r: dict) -> tuple: + return (PRIORITY_RANK.get(r["priority"], 9), + DIFFICULTY_RANK.get(r["difficulty"], 9), + r["target"], r["path"]) + + +def _bullet(r: dict) -> str: + """One backlog prompt as a bullet — the phone-readable unit of this page. + + A bullet wraps; a five-column table does not. GitHub's mobile view scrolls + wide tables sideways, which makes a 133-row backlog unusable on a phone, + so the metadata rides after an em dash instead of in columns. + """ + facets = " · ".join(x for x in (r["target"], r["difficulty"], + r["autonomy"], r["priority"]) if x != "-") + return f"- [{_label(r['title'])}]({r['path']})" + (f" — {facets}" if facets else "") + + +def render_dashboard(c: dict) -> str: + """Render the census as the Mind's task page (`dashboard.md`). + + Tasks only, by design: no readiness verdicts, no test state — that is the + Heart's dashboard (`/health`). Two rules shape the layout: it must be + *pickable* (the top of the page answers "what should I do now?", not "how + many prompts are there?"), and it must read on a phone (bullets over wide + tables, long sections behind `
`). Links are repo-root-relative so + they resolve in GitHub's web and mobile markdown views alike. + """ + records = sorted(c["records"], key=_pick_key) L = [ - "# PyAutoMind backlog dashboard", + "# PyAutoMind task dashboard", "", f"", "", - f"**{c['total']}** filed prompts in the backlog · **{c['issued_count']}** " - "already dispatched to issues (`active/`). Backlog view only — organism " - "health lives with the Heart (`/health`), not here.", + "Every task the Mind is holding, on one page: what is in flight, what " + "is parked, and the whole backlog to pick from. Pick a line, then run " + "`/start_dev ` to start it.", + "", + "Tasks only — the organism's health lives with the Heart (`/health`), " + "not here.", + "", + "| Where | Count |", + "|-------|------:|", + f"| [In flight](#in-flight) (`active/`) | {c['issued_count']} |", + f"| [Parked](#parked) (`parked.md`) | {len(c['parked'])} |", + f"| [Planned](#planned) (`planned.md`) | {len(c['planned'])} |", + f"| [Backlog](#backlog) (`draft/`) | {c['total']} |", + "", + f"Live on GitHub: [open issues]({GH_SEARCH.format(kind='issue')}) · " + f"[open pull requests]({GH_SEARCH.format(kind='pr')})", + "", + "## Start here", "", - "| Work-type | Prompts |", - "|-----------|--------:|", ] - L += [f"| {wt} | {n} |" for wt, n in c["by_work_type"].items()] - for wt in c["by_work_type"]: - rows = [r for r in c["records"] if r["work_type"] == wt] - rows.sort(key=lambda r: (r["target"], r["path"])) - L += ["", f"## {wt} ({len(rows)})", "", - "| Prompt | Target | Difficulty | Autonomy | Priority |", - "|--------|--------|------------|----------|----------|"] - L += [f"| [{_cell(r['title'])}]({r['path']}) | {r['target']} " - f"| {r['difficulty']} | {r['autonomy']} | {r['priority']} |" - for r in rows] + + high = [r for r in records if r["priority"] == "high"] + quick = [r for r in records + if r["difficulty"] == "small" and r["autonomy"] == "safe"] + for title, note, rows in ( + ("Highest priority", "filed as `high`", high), + ("Quick wins", "small enough, and safe enough to run unattended", quick), + ): + shown = rows[:PICK_LIST_MAX] + more = f" — showing {len(shown)} of {len(rows)}" if len(rows) > len(shown) else "" + L += [f"**{title}** ({note}){more}", ""] + L += [_bullet(r) for r in shown] or ["- _(none right now)_"] + L += [""] + + L += ["## In flight", "", + "Issued — each has an open GitHub issue and usually a branch. The " + "full record for each is in [`active.md`](active.md).", ""] + for r in c["in_flight"]: + issue = f" — [issue #{r['issue_no']}]({r['issue']})" if r["issue_no"] else "" + status = f" — {_clip(r['status'])}" if r["status"] else "" + L.append(f"- [{_label(r['title'])}]({r['path']}){issue}{status}") + L += ([] if c["in_flight"] else ["- _(nothing in flight)_"]) + [""] + + for key, heading, blurb in ( + ("parked", "Parked", "Started or scoped, not currently in flight — " + "resume by moving the row back to `active.md`. " + "Full detail in [`parked.md`](parked.md)."), + ("planned", "Planned", "Scoped but not started; some are not yet prompt " + "files. Full detail in [`planned.md`](planned.md)."), + ): + rows = c[key] + L += [f"## {heading}", "", blurb, "", + "
", f"{len(rows)} task(s)", ""] + for e in rows: + issue = f" — [issue #{e['issue_no']}]({e['issue']})" if e["issue_no"] else "" + status = f" — {_clip(e['status'])}" if e["status"] else "" + L.append(f"- **{_label(e['slug'])}**{issue}{status}") + L += ([] if rows else ["- _(none)_"]) + ["", "
", ""] + + L += [f"## Backlog", "", + f"**{c['total']}** filed prompts, not started. Each section is sorted " + "most-pickable first (priority, then size).", ""] + for wt, n in c["by_work_type"].items(): + rows = [r for r in records if r["work_type"] == wt] + L += ["
", f"{wt} — {n}", ""] + L += [_bullet(r) for r in rows] + L += ["", "
", ""] + if c["hygiene"]: - L += ["", "## Hygiene", "", + L += ["## Hygiene", "", f"{len(c['hygiene'])} prompt(s) without a metadata header — they " - "show `-` above. Re-home or re-run intake on them when touched.", "", + "show no facets above. Re-home or re-run intake on them when " + "touched.", "", "
", "Headerless prompts", ""] L += [f"- `{h.split(' — ')[0]}`" for h in c["hygiene"]] L += ["", "
"] - return "\n".join(L) + "\n" + return "\n".join(L).rstrip("\n") + "\n" + + +def _dashboard_body(page: str) -> str: + """The page minus its generation stamp — what `--check` compares. + + The stamp changes every day the generator runs; comparing it would make + every re-render look like drift and the self-heal push a daily commit. + """ + return "\n".join(l for l in page.splitlines() + if not l.startswith("