$ cat node-template.py

P

Plan aggregate

// Collect the tasks every Plan review produced into one ordered, de-duplicated list. Several reviews wire into its one Tasks input; it drops the duplicates four lenses inevitably raise, sorts by priority then by lens, and emits both the JSON a Task node is built from and the markdown checklist a design doc carries.

Process
Data
#plan#tasks#aggregate#vibe
template.py
1"""Plan aggregate — every review's tasks, de-duplicated and ordered.23Replicates gstack's `tasks-aggregator`: four lenses read the same plan, so four4lenses raise overlapping work, and the useful artefact is the union with the5overlap collapsed — not four separate lists to reconcile by hand.67The de-dup key is gstack's: `(component, sorted(files), title)`, exact match8only. Fuzzy matching was deliberately not copied. Two lenses phrase the same9task differently often enough that near-matching would silently drop one of10them, and a duplicate the human deletes costs a second — a dropped P1 costs11whatever it was guarding.1213Ordering is priority first, then lens. Priority is what a reader acts on;14`ceo, design, eng, devex` breaks the ties in the order the lenses conventionally15run, so the list is stable across re-runs rather than reflecting dict order.16"""1718import json19import sys2021PRIORITY_RANK = {"P1": 0, "P2": 1, "P3": 2}22LENS_RANK = {"ceo": 0, "design": 1, "eng": 2, "devex": 3}2324#: Where an unrecognised value sorts. Last, never first — a task with a25#: priority nobody recognises must not outrank a real P1 by accident.26UNRANKED = 99272829def flatten(raw):30    """One list, whatever arity the board delivered.3132    A single wired edge arrives as a bare list; several arrive as a list of33    lists. Both shapes must work, or adding a second reviewer to a working34    board breaks it.35    """36    if not isinstance(raw, list):37        return []38    out = []39    for item in raw:40        if isinstance(item, list):41            out.extend(x for x in item if isinstance(x, dict))42        elif isinstance(item, dict):43            out.append(item)44    return out454647def dedup_key(task):48    files = task.get("files")49    files = sorted(str(f) for f in files) if isinstance(files, list) else []50    return (51        str(task.get("component", "")).strip().lower(),52        tuple(files),53        str(task.get("title", "")).strip().lower(),54    )555657def sort_key(task):58    return (59        PRIORITY_RANK.get(str(task.get("priority", "")).upper(), UNRANKED),60        LENS_RANK.get(str(task.get("lens", "")).lower(), UNRANKED),61        str(task.get("title", "")),62    )636465def aggregate(tasks):66    """Collapse duplicates, keeping the most urgent copy of each."""67    groups = {}68    for task in tasks:69        key = dedup_key(task)70        current = groups.get(key)71        if current is None or sort_key(task) < sort_key(current):72            # The surviving copy is the highest-priority one, so a task raised73            # as P3 by design and P1 by eng lands as the P1. Recording the74            # lenses that agreed is the point of the merge — a task three75            # lenses independently asked for is different evidence from one.76            merged = dict(task)77            merged["lenses"] = sorted(78                {str(t.get("lens", "")) for t in tasks if dedup_key(t) == key} - {""}79            )80            groups[key] = merged81    return sorted(groups.values(), key=sort_key)828384def to_markdown(tasks):85    """The checklist format this repo's design docs already use."""86    if not tasks:87        return "_No tasks were raised by any review._"88    lines = []89    for task in tasks:90        lenses = task.get("lenses") or ([task["lens"]] if task.get("lens") else [])91        raised = ", ".join(lenses) if lenses else "unknown"92        lines.append(93            f"- [ ] **{task.get('id', '?')} "94            f"({task.get('priority', 'P?')}, "95            f"human: {task.get('effort_human', '?')} / "96            f"CC: {task.get('effort_cc', '?')}) — "97            f"{task.get('component', 'unknown')}** — {task.get('title', '')}\n"98            f"  - Raised by: {raised} — {task.get('source_finding', 'no finding recorded')}"99        )100        files = task.get("files")101        if isinstance(files, list) and files:102            lines.append(f"  - Files: {', '.join(str(f) for f in files)}")103    return "\n".join(lines)104105106def main():107    try:108        envelope = json.loads(sys.stdin.read() or "{}")109        inputs = envelope.get("inputs", {}) if isinstance(envelope, dict) else {}110111        tasks = flatten(inputs.get("tasks"))112        findings = flatten(inputs.get("findings"))113        blocking = sum(1 for f in findings if f.get("severity") == "blocking")114115        merged = aggregate(tasks)116117        print(118            f"[plan-aggregate] in={len(tasks)} out={len(merged)} "119            f"dropped={len(tasks) - len(merged)} blocking={blocking}",120            file=sys.stderr,121        )122123        json.dump({124            "tasks": merged,125            "markdown": to_markdown(merged),126            "count": len(merged),127            "blocking": blocking,128        }, sys.stdout)129    except Exception as e:130        print(json.dumps({"error": str(e), "errorType": type(e).__name__}), file=sys.stderr)131        sys.exit(1)132133134if __name__ == "__main__":135    main()136

$ git log --oneline

v1.0.0
HEAD
2026-10-14

First marketplace publish: vibe-coding lanes and the gstack plan flow (PR #1313).