Design: weekly portfolio review MVP
Design: weekly portfolio review MVP
TL;DR: One state file per review, reviews/YYYY-MM-DD.json, written by scripts/review_prepare.py (sweep + gaps + priority history) and rendered to reviews/YYYY-MM-DD.md by scripts/review_render.py. The AI fills the shortlist, drill-down and harvest sections by editing the JSON and re-rendering; Rufus steers at two gates. A new review mode in the planning skill holds the steps. Implements the brief for 2620.
1. The state file
Location. reviews/YYYY-MM-DD.json (Monday of the week), rendered page beside it as reviews/YYYY-MM-DD.md. A new top-level folder: weeks/ stays the agreed plan people read; reviews/ is the working file the AI and scripts own. Both scripts take --out <path.json> to write elsewhere; the trial for this build uses --out docs/plans/2026-10-26-weekly-review-trial.json (the brief says not to touch weeks/2026-10-26.md).
Rules. Scripts own meta, projects and gaps and overwrite them on every review_prepare run; the AI owns shortlist, drilldown, harvest, feedback and gaps[].fix/gaps[].accepted, which review_prepare preserves when the file already exists (merge by slug / gap id). Closed projects (done, superseded, dropped, archived, cancelled) are not listed, only counted.
{
"meta": {"week": "2026-10-26", "generated": "2026-10-26T15:02:00", "step": "shortlist", // sweep|gaps|shortlist|drilldown|harvest|done
"cache": "portfolio/projects-cache.json", "previous_review": "reviews/2026-10-19.json", // or null
"latest_week_plan": "weeks/2026-10-12.md", "closed_projects": 1},
"projects": [{
"slug": "2609-seeds-of-renaissance-website", "title": "Seeds of Renaissance Website — 2026",
"initiative": "seeds-of-renaissance", "initiative_title": "Seeds of Renaissance", "initiative_path": ["life-itself", "seeds-of-renaissance"],
"repo": "life-itself/planning", "file": "../projects/2609-seeds-of-renaissance-website.md",
"phase": "shipping", "status": "in-progress", "owner": "rufus", "owner_inherited": false,
"done_when": true, "latest_note": "2026-10-26 — Phase shaping → shipping …", "note_date": "2026-10-26",
"priority": {"value": 4, "source": "record"}, // or {"value": 3, "source": "review:2026-10-19"} or {"value": null, "source": null}
"group": "active", // active | ice
"epics": [{"id": "sorw-ol5", "repo": "sor-website", "title": "SoR website v1", "status": "in_progress", "open": 12, "ready": 4}]
}],
"gaps": [{"id": "g03", "kind": "epic-no-project", "subject": "ais-61b (ai-wise)", "evidence": "open epic, no project: label, not in any record's beads",
"fix": "Add ais-61b to beads: in research/projects/2622-ai-sensemaking.md and label it project:2622-ai-sensemaking", "accepted": null}],
"shortlist": [{"slug": "2607-seeds-of-renaissance-launch", "order": 1, "priority": 4,
"reason": "Launch is 1 Nov; everything else in SoR feeds it.", "questions": ["Is 1 Nov still the date?"], "set_by": "ai"}], // ai | rufus
"drilldown": {"2607-seeds-of-renaissance-launch": {
"readiness": "ready", // ready | needs-planning | needs-next-action
"planning": {"record": true, "done_when": true, "tree": true},
"route": {"epic": "lip-42k.3", "ready_tasks": 2},
"key_tasks": [{"id": "lip-42k.3.4", "title": "Launch package", "owner": "uriel"}],
"note": "Rufus: keep to two tasks; webinar moves to Wisdom paper."}},
"harvest": {
"permanent": [{"file": "projects/2609-seeds-of-renaissance-website.md", "change": "status_notes: 2026-10-26 — … (Rufus, weekly review)", "applied": false}],
"week": {"goal": "Site and comms ready for 1 Nov launch.",
"focus": [{"slug": "2607-seeds-of-renaissance-launch", "owner": "uriel", "outcome": "Launch package signed off", "ids": ["lip-42k.3.4"]}],
"hard_dates": [{"date": "2026-11-01", "what": "SoR launch", "ids": ["2607-seeds-of-renaissance-launch"]}],
"not_this_week": ["2627-retreat"]},
"beads": [{"action": "update", "repo": "planning", "id": "lip-42k.3.4", "change": "--assignee uriel", "applied": false}],
"people": {"uriel": "Launch package and brand rollout; Film stays iced."}
},
"feedback": [{"at": "2026-10-26T15:40", "gate": "shortlist", "said": "Drop 2612, move the Collective Action course to top", "applied": "shortlist reordered; 2612 → not_this_week"}]
}
Group rule (script): ice when status is paused, someday or backlog, or the previous review's final week.not_this_week names the slug and nothing in the record changed since (note date ≤ previous review date); otherwise active, with unknown status shown in the active table and marked. The AI may flip group at the shortlist gate; the script keeps that value on re-runs within the same week.
Last logged priority (script, simplest reliable rule): (1) the previous review file's final shortlist[].priority for that slug, source review:<date>; else (2) the record's own priority field, source record; else null. weeks/*.md is prose and is not parsed; the AI reads the latest one as context. From this review on, the review file is the priority log, which matches the data model (project priority set week to week).
2. Scripts
Two new scripts; project_sweep.py is reused, not extended (its cache already carries every field the table needs). The only change to it: none required. Both scripts reuse run, read_records, initiative_repos, tree_order from the existing modules.
scripts/review_prepare.py
.venv/bin/python scripts/review_prepare.py --week 2026-10-26 [--refresh] [--no-sweep] [--out reviews/2026-10-26.json] [--since-days 30] [--stale-days 21]
- Runs
project_sweep.py(subprocess,--refreshpassed through;--no-sweepskips it) and readsportfolio/projects-cache.jsonplusinitiatives/*.mdfor titles and tree order. - Builds
projects[]: one entry per non-closed, non-shadowed cache project;initiative_pathfromtree_order;note_date= leadingYYYY-MM-DDoflatest_note;done_whenas a boolean; priority and group by the rules above (previous review = latestreviews/*.jsondated before--week). - Reads open epics: for every sibling checkout with
.beadsamong initiative repos plusplanning,bd -C ../<repo> --readonly list --type=epic --status=open,in_progress --limit 0 --json; children counts via onebd list --status open,in_progress,blocked --limit 0 --jsonper repo (group byparent/ id prefix<epic>.), ready viabd ready --limit 0 --json. An epic belongs to a project if its id is in the record'sbeadslist or it carriesproject:<slug>. Pointer epics (title startsPointer:) are ignored. - Gaps (each with
kind,subject,evidence, scriptedfix,accepted: null):repo-no-record:gh api 'orgs/life-itself/repos?sort=pushed&per_page=100'pushed within--since-days, not inIGNORE_REPOS(knowledge bases and personal repos: archipelago, plumvillage, sutras, wilber-wiki, without-hot-air, library, climate, tao, changelog, rufus, asdra-planning, planning), and not covered. Covered = a project record lives in it, or an initiative/project names it ingithub/project_repos/tracker, or a record'sbeadsid prefix matches the repo's Beads prefix. Fix: "Add a record in<repo>/projects/under<initiative guess>or add the repo toIGNORE_REPOS."epic-no-project: open top-level epic (--no-parent) with noproject:label and not in any record'sbeads. Fix: "Labelproject:<slug>or list it in the record'sbeads; if no project fits, propose a record."record-missing: owner null, phase null,done_whenempty or statusunknown, for non-ice projects; one gap per record naming the fields. Fix: "Set<fields>in<file>."record-stale: statusin-progress/nextandnote_dateolder than--stale-days(or no dated note). Fix: "Add a dated status note or change status."initiative-no-repo,initiative-no-projects: copied fromproject_sweep's gap logic forin-progress/nextinitiatives.epic-no-owner: open epic linked to a project but with no assignee. Fix: "bd update <id> --assignee <record owner>."
- Writes the file. If it exists, replaces
meta,projects,gaps(keepingfix/acceptedfor a gap with the samekind+subject, andgroupset by Rufus) and keeps everything else. Setsmeta.steptogapson first write, otherwise leaves it. Then callsreview_render.py.
Takes about one bd call per repo; prints a one-line summary to stderr (31 projects, 9 gaps, 14 epics across 11 repos).
scripts/review_render.py
.venv/bin/python scripts/review_render.py reviews/2026-10-26.json # writes reviews/2026-10-26.md
Pure function of the JSON; exits 1 with a message if a required key is missing or a shortlist/drilldown slug is unknown (the cheap schema check). Page, in order:
- Header: week, generated, step, counts; one line of sources (cache date, previous review, latest week plan).
- Gaps: table
# | Kind | Subject | Evidence | Suggested fix | Accepted. - Projects: table
Initiative | Project | Priority (last) | Phase | Status | Owner | Epics (ready/open) | Latest note, active group only, sorted by initiative tree order then priority desc then phase order. Initiative repeated on every row (its own column, no nesting). Priority as stars with source, e.g.★★★★ (record),★★★ (review 19 Oct),—. Owner↑when inherited. Project links tofile. - Ice box: one line per project:
- Project (initiative) — status, owner, since <note_date>. - Shortlist: numbered,
**Project** (initiative, owner) — ★ priority — reason, thenQuestions:bullets;set_by: rufusrows get a✓mark. - Drill-down: one short block per shortlisted project: readiness flag, planning checks, route (epic and ready count), key tasks with IDs, note.
- Harvest: four sub-sections (Permanent record edits; This week: goal, focus, hard dates, not this week; Beads; People), each entry showing
applied. - Feedback log: the
feedbackentries.
Empty sections render as a one-line placeholder ("not yet"), so the page is readable at every step.
3. Skill: review mode in skills/planning/SKILL.md
Add a third mode after week. The week mode stays for a quick personal/team plan without a portfolio pass; review is the full weekly pass and ends by writing the week plan in the same format.
Mode: review — from every project to an agreed week plan. Scripts do sweep, gaps, priority history and rendering; the AI judges, suggests and asks. Rufus steers at two gates. Default output reviews/<Monday>.json and .md.
- Sweep.
.venv/bin/python scripts/review_prepare.py --week <Monday> --refresh(--outif told to). Work from the JSON, not the Markdown table. Note repos reported as not checked out or unreachable. - Gaps. Read
gaps[]. For each, keep or sharpenfix(one line: file or command), using the record andbd showwhere needed; don't invent owners. Add any gap the AI sees that the script can't (e.g. two records for one piece of work). Setmeta.step: gaps. Re-render. No gate: gaps are presented with the shortlist. - Shortlist. Read the latest
weeks/plan, the roadmap decisions (roadmap.jsondecisions,milestones) and the projects' latest notes. Writeshortlist[]: 3–7 projects, ordered, each with a star priority, a one-line reason and open questions;set_by: ai. Move anything clearly iced togroup: ice. Setmeta.step: shortlist, re-render, then present in chat: the gap count with the top three, the shortlist with reasons, the questions. Gate 1. Rufus answers in prose. For each instruction: edit the JSON (reorder, change priority, add or drop, flipgroup, answer a question intoreason), setset_by: rufuson rows he touched, append afeedbackentry (gate: shortlist, what he said, what changed), re-render, show the diff in one line each. Repeat until he says it's agreed. - Drill down. For each shortlisted project, fill
drilldown[slug]:planningfrom the record (record exists,done_whennon-empty, an issue tree or plan section in the body);routefromepics[]plusbd -C ../<repo> list --parent <epic> --ready --json;key_tasks: 1–3 existing IDs, no invented tasks;readiness:needs-planningwhen planning checks fail,needs-next-actionwhen the epic has no ready task, elseready. Setmeta.step: drilldown, re-render, present one line per project (flag, key tasks). Gate 2. Rufus confirms or adjusts; same feedback loop as gate 1. - Harvest. Fill
harvestfrom the agreed shortlist and drill-down:permanent(phase/status/owner changes, dated status notes naming "Rufus, weekly review", new records for accepted gaps),week(goal, focus with owners and outcomes, hard dates, not this week = dropped and iced),beads(create/update with repo and command text),people(one line each). Setmeta.step: harvest, re-render, present the harvest split. Then:- Inside planning, on "go": write
weeks/<Monday>.mdin the existing week-note format fromharvest.week(focus areas, owners, hard dates, not this week, fix in trackers =harvest.beads); setmeta.step: done. - Outside planning (other repos, Beads), only on explicit confirmation per batch: apply
permanentedits andbeadscommands, markapplied: true, re-render. Never commit or push other repos without being told. Nothing is written outside the review files before Rufus confirms.
- Inside planning, on "go": write
Common mistakes to add: editing the .md instead of the JSON; dropping script-owned fields when editing; inventing task IDs; writing the week plan before gate 2 is agreed; re-running review_prepare and losing hand edits (it preserves them, but say so); treating priority.source: record as current truth.
4. Test plan
Run by a controller agent in this repo on real data. The runner is a fresh Sonnet (then Opus) subagent whose whole prompt is: "Use the planning skill at skills/planning/SKILL.md and run the weekly review for the week of 2026-10-26. Write the review files to docs/plans/2026-10-26-weekly-review-trial.json (pass --out). Stop at each gate and report; I am Rufus. Do not write weeks/2026-10-26.md or anything outside this repository." The controller continues the runner with SendMessage at each gate using this script:
- Gate 1 (Rufus): "Put the Collective Action course first, it starts Wednesday. Drop comms infrastructure this week. Ice Film and Retreat. Keep the SoR launch second. Why is the Wisdom paper not on the list?"
- Gate 2 (Rufus): "The course is ready, fine. Mark the SoR launch as needs a next action unless you found a ready task with an owner. Keep only one key task for the weekly review project."
- Harvest: "Go on the week plan to the trial file; don't apply anything else."
Pass/fail checks (controller runs them; each maps to the brief's done-when):
| # | Check | Pass when |
|---|---|---|
| 1 | Files exist | …trial.json and …trial.md; review_render.py exits 0 on the JSON |
| 2 | Complete table | rows in the page's Projects table + Ice box lines = non-closed, non-shadowed projects in projects-cache.json; every row has an initiative and a priority cell |
| 3 | Gaps with fixes | gaps[] non-empty, every entry has a non-empty fix; includes epic-no-project for ais-61b/ais-q7s (ai-wise epics) and record-missing for 2626-forummagnum-deployment (no done_when) unless records changed |
| 4 | Shortlist | 3–7 entries with reason and at least one questions entry overall; meta.step reached shortlist before gate 1 |
| 5 | Gate 1 applied | after feedback: course order: 1, 2612 absent from shortlist and in week.not_this_week or feedback, 2628-film/2627-retreat group: ice, a feedback entry with gate: shortlist, and the .md shows the new order (mtime after JSON) |
| 6 | Drill-down | every shortlisted slug has readiness, planning, route, key_tasks with IDs that exist (bd show in the right repo succeeds) |
| 7 | Gate 2 applied | 2620 has exactly one key task; a feedback entry with gate: drilldown |
| 8 | Harvest split | harvest.permanent, harvest.week.goal, harvest.week.focus, harvest.beads, harvest.people all non-empty; every applied is false |
| 9 | No side effects | git status --porcelain in each sibling repo unchanged from before; bd list --all --json hash per repo unchanged; weeks/2026-10-26.md absent; the trial week plan is in docs/plans/ |
| 10 | From the skill alone | the runner asked no clarifying question before gate 1 other than those in the shortlist |
Record the outcome for each run (model, checks passed, what broke) in a short pilot note in docs/plans/; fix and rerun until Sonnet passes all ten.
5. Out of the MVP
- Live view or board; the rendered
.mdis the view. - Parsing
weeks/*.mdprose for priorities or ice decisions; reading Asana, GitHub Issues, calendars or the Google Doc tab. - Setting initiative star priorities (stays a manual, dated record edit).
- Auto-applying harvest edits, commits or pushes in other repos; creating records for gaps (harvest proposes, a person or a later session does it with the portfolio skill).
- Per-person reviews (
--person), month planning, a JSON Schema file or validation library (the renderer's key check is enough). - Changing
project_sweep.py,build_index.pyor the portfolio page.