Skip to content

feat(research): add vis-lens family, plan-visualization, output_mode, and report bundling#781

Merged
Trecek merged 39 commits intointegrationfrom
research-recipe-local-report-bundle-vis-lens-family/741
Apr 13, 2026
Merged

feat(research): add vis-lens family, plan-visualization, output_mode, and report bundling#781
Trecek merged 39 commits intointegrationfrom
research-recipe-local-report-bundle-vis-lens-family/741

Conversation

@Trecek
Copy link
Copy Markdown
Collaborator

@Trecek Trecek commented Apr 13, 2026

Summary

This PR completes the full visualization-planning infrastructure for the research recipe, shipping a 12-lens vis-lens skill family plus plan-visualization orchestrator, a bundle-local-report skill that renders self-contained HTML reports with inlined Mermaid diagrams, and an output_mode ingredient (local | pr, default local) that bifurcates the research pipeline between offline HTML export and GitHub PR creation. Supporting work includes vendoring Mermaid v11 UMD, fixing a stale-tarball lifecycle bug, renaming write-reportgenerate-report, dual-layer output_mode validation (static recipe rule + runtime MCP gate), a 0.7.77-to-0.8.0.yaml migration note, and a version bump to 0.8.0.

Individual Group Plans

Group A: Infrastructure & Assets

Land the two foundational infrastructure assets required by downstream groups in issue #741:

  1. Vendor mermaid.min.js v11 (UMD) into src/autoskillit/assets/mermaid/ — with sibling LICENSE.mermaid (MIT attribution) and VERSION metadata — and add a repeatable task vendor-mermaid target for future updates.
  2. Mark the blob binary in a new repo-root .gitattributes so git diff never attempts to text-diff it.
  3. Declare markdown-it-py>=3.0 in pyproject.toml and regenerate uv.lock.

No recipe, skill, or server logic is touched. This group is the smallest-blast-radius starting point; all other groups in track A, B, C converge on these assets.

Group B: Bundle Lifecycle Refactor (Stale-Tarball Bug Fix)

Fix a pre-existing bug in the research recipe where commit_research_artifacts compresses and commits the artifact directory before the review/rewrite loop runs, causing re_write_report to drop a fresh report.md next to an already-frozen README.md and artifacts.tar.gz — leaving the archival branch with stale conclusions paired with a stale tarball.

The fix splits commit_research_artifacts into three narrowly-scoped steps:

  • stage_bundle — idempotent file organizer (no compression, no rename, no commit)
  • re_stage_bundle — identical body, called after re_write_report before re_test
  • finalize_bundle — single compression point, runs exactly once at the very end

Group C: vis-lens Pack Registration + P0 Lenses

Register the vis-lens pack in PACK_REGISTRY and ship 5 P0 vis-lens skills (vis-lens-chart-select, vis-lens-uncertainty, vis-lens-antipattern, vis-lens-domain-norms, vis-lens-always-on). Each skill is a single SKILL.md file in skills_extended/. Supporting work: canonical yaml:figure-spec schema embedded in vis-lens-chart-select, 5 entries in skill_contracts.yaml, updated test counts, a new parametrized structural test file, an updated write-recipe/SKILL.md bundled skills catalog, and a corrected docs/skills/visibility.md total count. No recipe wiring — deferred to groupF.

Group D: vis-lens P1 Skills

Ship 4 P1 vis-lens skills: vis-lens-multi-compare, vis-lens-temporal, vis-lens-color-access, and vis-lens-figure-table. Each is a leaf skill following the identical P0 shape from groupC. The work is: (1) extend structural test parametrization to cover the new slugs, (2) add 4 SKILL.md files, (3) register 4 contract entries, and (4) update three count assertions and one doc string. No recipe wiring, no catalog update, no helper Python files.

Group E: vis-lens P2 Lenses + Catalog Finalization

Ship the final 3 P2 visualization-planning lenses (vis-lens-caption-annot, vis-lens-story-arc, vis-lens-reproducibility), completing the 12-lens vis-lens family. Update all supporting infrastructure: skill registration (skill_contracts.yaml, defaults.yaml), test parametrization, documentation (catalog.md, visibility.md), and the bundled-skill enumeration in write-recipe/SKILL.md.

Group F (Part A): plan-visualization + Recipe Wiring

Part A covers: creating the plan-visualization orchestrator skill, updating generate-report/SKILL.md body (the skill renamed in Part B), and all research.yaml changes (new step, rerouted GO edge, create_worktree cmd extension, requires_packs update, and write_report → generate_report step renames).

Group F (Part B): Rename + Test Updates

Part B covers: the write-report/generate-report/ directory rename, all mechanical test file updates (rename, step assertions), the skill_contracts.yaml key rename (write-reportgenerate-report), the _analysis.py exemption update, updating skill reference files (plan-experiment/SKILL.md, run-experiment/SKILL.md), and final grep verification.

Group G: bundle-local-report Skill + Recipe Wiring

Create the bundle-local-report skill that converts a research markdown report into a self-contained report.html with inlined exp-lens mermaid diagrams and inserted plot images from yaml:figure-spec blocks. Wire it as a new finalize_bundle_render recipe step that always runs (on_success and on_failure from finalize_bundle) and routes to begin_archival.

Group H (Part A): output_mode Ingredient + Local/PR Mode Branching

Add an output_mode ingredient (local | pr, default "local") to research.yaml that controls whether the recipe opens a GitHub PR. In local mode the recipe bypasses the PR/review/archival pipeline and exports the finalized HTML bundle to ${source_dir}/research-bundles/{date-slug}/. In pr mode the existing GitHub flow runs on top of the always-generated HTML report.

Group H (Part B): Semantic Validator + Runtime Gate + Version Bump

Supporting infrastructure for the output_mode ingredient added in Part A. This part covers the semantic validator rule (research_output_mode_enum in rules_inputs.py), runtime output_mode value check in tools_kitchen.py, generate-report/SKILL.md update for issue_url injection in local mode, migration note 0.7.77-to-0.8.0.yaml, and version bump 0.7.77 → 0.8.0.

Architecture Impact

Process Flow Diagram

%%{init: {'flowchart': {'nodeSpacing': 42, 'rankSpacing': 54, 'curve': 'basis'}}}%%
flowchart TB
    %% CLASS DEFINITIONS %%
    classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;
    classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff;
    classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff;
    classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff;
    classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff;

    %% TERMINALS %%
    START([START])
    COMPLETE([research_complete])
    REJECTED([design_rejected])
    ERROR([escalate_stop])

    subgraph Design ["Design & Planning Phase"]
        direction TB
        Scope["scope<br/>━━━━━━━━━━<br/>Scope experiment"]
        PlanExp["plan_experiment<br/>━━━━━━━━━━<br/>optional_context_refs: revision_guidance"]
        ReviewDesign{"review_design<br/>━━━━━━━━━━<br/>skip_when_false: inputs.review_design<br/>retries: 2"}
        ReviseDesign["revise_design<br/>━━━━━━━━━━<br/>action: route → plan_experiment"]
        ResolveDesign["resolve_design_review<br/>━━━━━━━━━━<br/>retries: 1"]
        PlanViz["★ plan_visualization<br/>━━━━━━━━━━<br/>Tier A: vis-lens-always-on (mandatory)<br/>Tier B: 1-2 experiment-type lenses<br/>Tier C: 0-1 domain lens<br/>Writes: visualization-plan.md<br/>Writes: report-plan.md<br/>Captures: visualization_plan_path, report_plan_path"]
    end

    subgraph ImplPhase ["Implementation Phase"]
        direction TB
        CreateWT["create_worktree<br/>━━━━━━━━━━<br/>Commit plan + scope to research/"]
        ImplLoop["decompose_phases → plan_phase<br/>→ implement_phase (stale: 2400)<br/>━━━━━━━━━━<br/>troubleshoot_implement_failure loop<br/>route: is_fixable → plan_phase"]
        NextPhase{"next_phase_or_experiment<br/>━━━━━━━━━━<br/>more phases?"}
    end

    subgraph ExpExec ["Experiment Execution"]
        direction TB
        RunExp["run_experiment<br/>━━━━━━━━━━<br/>retries: 2, stale_threshold: 2400"]
        AdjustExp["adjust_experiment<br/>━━━━━━━━━━<br/>--adjust flag, stale: 2400"]
        EnsureResults["ensure_results<br/>━━━━━━━━━━<br/>Write placeholder inconclusive file"]
        GenReport["● generate_report<br/>━━━━━━━━━━<br/>renamed from write_report<br/>--output-mode inputs.output_mode<br/>Step 1.5: local mode injects issue blockquote<br/>Step 2.5: run visualization plotting scripts"]
        GenReportInc["● generate_report_inconclusive<br/>━━━━━━━━━━<br/>--inconclusive + --output-mode flags"]
        TestFlow["test → fix_tests → retest<br/>━━━━━━━━━━<br/>push_branch on pass"]
        PrepLenses["prepare_research_pr<br/>+ run_experiment_lenses<br/>━━━━━━━━━━<br/>Parallel lens execution<br/>Captures: selected_lenses, lens_context_paths"]
    end

    subgraph BundleStage ["★ Bundle Staging — NEW"]
        direction TB
        StageBundle["★ stage_bundle<br/>━━━━━━━━━━<br/>Copy phase-groups, phase-plans,<br/>exp-lens diagrams → research_dir/artifacts/<br/>Idempotent, no commit<br/>Non-fatal: always → route_pr_or_local"]
        RoutePRLocal{"★ route_pr_or_local<br/>━━━━━━━━━━<br/>inputs.output_mode == local?"}
    end

    subgraph PRReview ["PR Review Loop  (output_mode == pr)"]
        direction TB
        ComposePR["compose_research_pr<br/>━━━━━━━━━━<br/>Opens GitHub PR<br/>Captures: pr_url"]
        GuardPR{"guard_pr_url<br/>━━━━━━━━━━<br/>context.pr_url truthy?"}
        ReviewAudit["review_research_pr (skip_when_false)<br/>→ audit_claims (skip_when_false, retries: 1)<br/>━━━━━━━━━━<br/>review_verdict + audit_verdict captured"]
        MergeEsc{"merge_escalations<br/>━━━━━━━━━━<br/>needs_rerun?"}
        RerunLoop["re_run_experiment → re_generate_report<br/>→ re_stage_bundle → re_test<br/>━━━━━━━━━━<br/>Full experiment rerun with --adjust<br/>--output-mode flag propagated"]
        RePush["re_push_research<br/>━━━━━━━━━━<br/>git push → finalize_bundle"]
    end

    subgraph FinalizePhase ["★ Finalize Phase — NEW"]
        direction TB
        FinalizeBundle["★ finalize_bundle<br/>━━━━━━━━━━<br/>pr mode: rename report.md → README.md<br/>          archive artifacts, git commit<br/>local mode: keep report.md, skip commit<br/>Captures: report_path_after_finalize"]
        FinalizeRender["★ finalize_bundle_render<br/>━━━━━━━━━━<br/>skill: bundle-local-report (ALWAYS RUNS)<br/>Inline validated mermaid diagrams<br/>Insert figure imgs from visualization-plan.md<br/>Copy mermaid.min.js (UMD, file:// safe)<br/>Writes: report.html<br/>Captures: html_path<br/>Non-fatal: both outcomes → route_archive_or_export"]
        RouteArchExp{"★ route_archive_or_export<br/>━━━━━━━━━━<br/>inputs.output_mode == local?"}
    end

    ExportLocal["★ export_local_bundle<br/>━━━━━━━━━━<br/>Copy research_dir → source_dir/research-bundles/date-slug/<br/>Captures: local_bundle_path<br/>Non-fatal terminal for local mode"]

    subgraph Archival ["Archival (output_mode == pr)"]
        direction TB
        ArchFlow["begin_archival → capture_experiment_branch<br/>→ create_artifact_branch (research/ only)<br/>→ open_artifact_pr → tag_experiment_branch<br/>→ close_experiment_pr (structured comment)"]
    end

    %% MAIN FLOW %%
    START --> Scope --> PlanExp --> ReviewDesign
    ReviewDesign -->|"REVISE"| ReviseDesign --> PlanExp
    ReviewDesign -->|"STOP"| ResolveDesign --> REJECTED
    ReviewDesign -->|"GO — NEW: routes to ★plan_visualization"| PlanViz
    PlanViz --> CreateWT

    CreateWT --> ImplLoop --> NextPhase
    NextPhase -->|"more phases"| ImplLoop
    NextPhase -->|"done"| RunExp
    ImplLoop -->|"fatal failure"| ERROR

    RunExp -->|"success"| GenReport
    RunExp -->|"failure"| AdjustExp
    AdjustExp --> RunExp
    RunExp -->|"exhausted"| EnsureResults
    EnsureResults --> GenReportInc
    GenReport --> TestFlow
    GenReportInc --> TestFlow
    TestFlow -->|"pass"| PrepLenses
    TestFlow -->|"fatal"| ERROR
    GenReport -->|"failure"| ERROR

    PrepLenses --> StageBundle
    StageBundle --> RoutePRLocal

    RoutePRLocal -->|"local — skip entire PR pipeline"| FinalizeBundle
    RoutePRLocal -->|"pr — full PR flow"| ComposePR

    ComposePR --> GuardPR
    GuardPR -->|"no pr_url"| COMPLETE
    GuardPR -->|"has pr_url"| ReviewAudit
    ReviewAudit --> MergeEsc
    MergeEsc -->|"needs rerun"| RerunLoop
    MergeEsc -->|"no rerun"| RePush
    RerunLoop --> RePush
    RePush --> FinalizeBundle

    FinalizeBundle --> FinalizeRender
    FinalizeRender --> RouteArchExp

    RouteArchExp -->|"local"| ExportLocal --> COMPLETE
    RouteArchExp -->|"pr"| ArchFlow --> COMPLETE

    %% CLASS ASSIGNMENTS %%
    class START,COMPLETE,REJECTED,ERROR terminal;
    class Scope,PlanExp,CreateWT,ImplLoop handler;
    class RunExp,AdjustExp,EnsureResults,GenReport,GenReportInc handler;
    class TestFlow,PrepLenses handler;
    class ReviewDesign,GuardPR,MergeEsc stateNode;
    class ReviseDesign,ResolveDesign,NextPhase phase;
    class ReviewAudit,ComposePR,RerunLoop,RePush phase;
    class PlanViz,StageBundle,FinalizeBundle,FinalizeRender,ExportLocal newComponent;
    class RoutePRLocal,RouteArchExp newComponent;
    class ArchFlow output;
Loading

Module Dependency Diagram

%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 70, 'curve': 'basis'}}}%%
graph TB
    %% CLASS DEFINITIONS %%
    classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;
    classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff;
    classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff;
    classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff;
    classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff;
    classDef integration fill:#c62828,stroke:#ef9a9a,stroke-width:2px,color:#fff;

    %% ─────────────────────────────────────────────
    %% L3 APPLICATION
    %% ─────────────────────────────────────────────
    subgraph L3 ["L3 — APPLICATION  (server/)"]
        direction LR
        TKITCHEN["● server/tools_kitchen.py<br/>━━━━━━━━━━<br/>+runtime output_mode guard<br/>(pr | local enum check<br/>before load_and_validate)"]
    end

    %% ─────────────────────────────────────────────
    %% L2 DOMAIN
    %% ─────────────────────────────────────────────
    subgraph L2 ["L2 — DOMAIN  (recipe/)"]
        direction TB

        subgraph L2_RULES ["Semantic Rules  (fan-out → _analysis)"]
            direction LR
            RINPUTS["● recipe/rules_inputs.py<br/>━━━━━━━━━━<br/>+research_output_mode_enum<br/>(static enum guard: pr|local)"]
            RPACKS["recipe/rules_packs.py<br/>━━━━━━━━━━<br/>unknown-required-pack<br/>(checks PACK_REGISTRY)"]
            RSKILLS["recipe/rules_skills.py<br/>━━━━━━━━━━<br/>unknown-skill-command<br/>lazy→ workspace.SkillResolver"]
        end

        RANALYSIS["● recipe/_analysis.py<br/>━━━━━━━━━━<br/>ValidationContext  [fan-in: 3]<br/>+generate-report exemption"]
        RCONTRACTS["recipe/contracts.py<br/>━━━━━━━━━━<br/>get_skill_contract<br/>load_bundled_manifest"]
        RSCHEMA["recipe/schema.py<br/>━━━━━━━━━━<br/>RecipeIngredient.default<br/>Recipe.ingredients"]
        RREGISTRY["recipe/registry.py<br/>━━━━━━━━━━<br/>@semantic_rule  [fan-in: 3]"]
    end

    %% ─────────────────────────────────────────────
    %% L1 INFRASTRUCTURE
    %% ─────────────────────────────────────────────
    subgraph L1 ["L1 — INFRASTRUCTURE  (config/ | workspace/ | migration/)"]
        direction LR
        CONFIGDEFAULTS["● config/defaults.yaml<br/>━━━━━━━━━━<br/>+vis-lens in tier2<br/>+plan-visualization<br/>+bundle-local-report"]
        WSKILLS["workspace/skills.py<br/>━━━━━━━━━━<br/>DefaultSkillResolver<br/>scans skills_extended/"]
        MIGLOADER["migration/loader.py<br/>━━━━━━━━━━<br/>applicable_migrations()<br/>via pkg_root()/migrations"]
    end

    %% ─────────────────────────────────────────────
    %% L0 CORE
    %% ─────────────────────────────────────────────
    subgraph L0 ["L0 — CORE  (core/)"]
        direction LR
        TYPECONSTANTS["● core/_type_constants.py<br/>━━━━━━━━━━<br/>PACK_REGISTRY  [fan-in: 5]<br/>+vis-lens PackDef(False)<br/>SKILL_TOOLS, CATEGORY_TAGS"]
        COREPATHS["core/paths.py<br/>━━━━━━━━━━<br/>pkg_root()  [fan-in: 4]<br/>is_git_worktree()"]
    end

    %% ─────────────────────────────────────────────
    %% CONTENT / ASSETS
    %% ─────────────────────────────────────────────
    subgraph CONTENT ["CONTENT / ASSETS  (skills_extended/ | recipes/ | migrations/ | assets/)"]
        direction TB

        subgraph NEWSKILLS ["★ New Skill Content"]
            direction LR
            VISL["★ skills_extended/vis-lens/*<br/>━━━━━━━━━━<br/>12 SKILL.md files<br/>always-on, antipattern,<br/>chart-select, color-access,<br/>domain-norms, figure-table,<br/>multi-compare, temporal,<br/>uncertainty, caption-annot,<br/>story-arc, reproducibility"]
            PLANVIS["★ skills_extended/<br/>plan-visualization<br/>━━━━━━━━━━<br/>orchestrates 2–4 vis-lens skills<br/>(always-on mandatory)"]
            BUNDLEREP["★ skills_extended/<br/>bundle-local-report<br/>━━━━━━━━━━<br/>renders self-contained HTML<br/>copies mermaid.min.js<br/>uses markdown-it-py"]
        end

        subgraph MODCONTENT ["● Modified Recipe / Contracts"]
            direction LR
            RESEARCHYAML["● recipes/research.yaml<br/>━━━━━━━━━━<br/>+output_mode ingredient<br/>default: local (pr|local)<br/>requires_packs: vis-lens"]
            SKILLCONTRACTS["● recipe/skill_contracts.yaml<br/>━━━━━━━━━━<br/>+plan-visualization contract<br/>+bundle-local-report contract<br/>+12 vis-lens contracts"]
        end

        subgraph NEWASSETS ["★ New Assets / Migrations"]
            direction LR
            ASSETS["★ assets/mermaid/<br/>━━━━━━━━━━<br/>mermaid.min.js (UMD)<br/>VERSION, LICENSE"]
            MIGFILE["★ migrations/<br/>0.7.77-to-0.8.0.yaml<br/>━━━━━━━━━━<br/>migration note for 0.8.0"]
        end
    end

    %% ─────────────────────────────────────────────
    %% EXTERNAL
    %% ─────────────────────────────────────────────
    subgraph EXT ["EXTERNAL DEPENDENCIES"]
        MARKDOWNIT["★ markdown-it-py<br/>━━━━━━━━━━<br/>HTML rendering<br/>(graceful ImportError fallback)"]
    end

    %% ═══════════════════════════════════════════
    %% IMPORT RELATIONSHIPS  (solid = import)
    %% ═══════════════════════════════════════════

    %% L3 → L0
    TKITCHEN -->|"imports core"| TYPECONSTANTS

    %% L2 rules → L2 shared (fan-in on _analysis and registry)
    RINPUTS -->|"imports ValidationContext"| RANALYSIS
    RPACKS -->|"imports ValidationContext"| RANALYSIS
    RSKILLS -->|"imports ValidationContext"| RANALYSIS

    RINPUTS -->|"imports @semantic_rule"| RREGISTRY
    RPACKS -->|"imports @semantic_rule"| RREGISTRY
    RSKILLS -->|"imports @semantic_rule"| RREGISTRY

    %% L2 rules → L0 (PACK_REGISTRY / SKILL_TOOLS)
    RINPUTS -->|"imports SKILL_TOOLS"| TYPECONSTANTS
    RPACKS -->|"imports PACK_REGISTRY"| TYPECONSTANTS
    RSKILLS -->|"imports SKILL_TOOLS"| TYPECONSTANTS

    %% L2 rules → contracts / schema
    RINPUTS -->|"imports"| RCONTRACTS
    RSKILLS -->|"imports"| RCONTRACTS
    RANALYSIS -->|"imports"| RCONTRACTS
    RANALYSIS -->|"imports RecipeIngredient"| RSCHEMA
    RINPUTS -->|"imports _TERMINAL_TARGETS"| RSCHEMA

    %% L2 → L0 (core constants)
    RANALYSIS -->|"imports SKILL_TOOLS"| TYPECONSTANTS

    %% L2 lazy cross-layer (L2 → L1)  — valid downward, deferred to avoid circular
    RSKILLS -.->|"lazy import<br/>DefaultSkillResolver"| WSKILLS

    %% L1 → L0
    WSKILLS -->|"imports pkg_root"| COREPATHS
    MIGLOADER -->|"imports pkg_root"| COREPATHS

    %% ═══════════════════════════════════════════
    %% RUNTIME / CONTENT DISCOVERY (dashed)
    %% ═══════════════════════════════════════════

    %% contracts reads skill_contracts.yaml
    RCONTRACTS -.->|"reads"| SKILLCONTRACTS

    %% tools_kitchen validates research.yaml at runtime
    TKITCHEN -.->|"runtime validates<br/>output_mode enum"| RESEARCHYAML

    %% rules_inputs validates research.yaml statically
    RINPUTS -.->|"static rule validates"| RESEARCHYAML

    %% rules_packs validates research.yaml requires_packs
    RPACKS -.->|"validates requires_packs"| RESEARCHYAML

    %% workspace/skills discovers new skills
    WSKILLS -.->|"scans skills_extended/"| VISL
    WSKILLS -.->|"scans skills_extended/"| PLANVIS
    WSKILLS -.->|"scans skills_extended/"| BUNDLEREP

    %% migration loader discovers new migration note
    MIGLOADER -.->|"discovers"| MIGFILE

    %% bundle-local-report copies mermaid asset at runtime
    BUNDLEREP -.->|"copies at runtime"| ASSETS

    %% bundle-local-report imports markdown-it-py
    BUNDLEREP -.->|"imports (optional)"| MARKDOWNIT

    %% plan-visualization orchestrates vis-lens skills
    PLANVIS -.->|"orchestrates 2–4 skills"| VISL

    %% config/defaults.yaml feeds PACK_REGISTRY context
    CONFIGDEFAULTS -.->|"tier2 list mirrors"| TYPECONSTANTS

    %% ═══════════════════════════════════════════
    %% CLASS ASSIGNMENTS
    %% ═══════════════════════════════════════════
    class TKITCHEN cli;
    class RANALYSIS,RINPUTS,RPACKS,RSKILLS phase;
    class RCONTRACTS,RSCHEMA,RREGISTRY handler;
    class TYPECONSTANTS,COREPATHS stateNode;
    class CONFIGDEFAULTS,WSKILLS,MIGLOADER handler;
    class VISL,PLANVIS,BUNDLEREP,MIGFILE,ASSETS newComponent;
    class RESEARCHYAML,SKILLCONTRACTS output;
    class MARKDOWNIT integration;
Loading

Operational Diagram

%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 65, 'curve': 'basis'}}}%%
flowchart TB
    %% CLASS DEFINITIONS %%
    classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;
    classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff;
    classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff;
    classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff;
    classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff;
    classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff;
    classDef gap fill:#ff6f00,stroke:#ffa726,stroke-width:2px,color:#000;
    classDef integration fill:#c62828,stroke:#ef9a9a,stroke-width:2px,color:#fff;
    classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff;

    subgraph CLI_Entry ["CLI / TASK ENTRY POINTS"]
        direction TB
        ORDER["autoskillit order research<br/>━━━━━━━━━━<br/>output_mode: local | pr<br/>Validates recipe before launch"]
        VENDORMERMAID["★ task vendor-mermaid<br/>━━━━━━━━━━<br/>Downloads mermaid v11 UMD<br/>Writes mermaid.min.js + VERSION"]
        MIGRATE["autoskillit migrate<br/>━━━━━━━━━━<br/>Reports pending migrations<br/>--check for CI gating"]
    end

    subgraph Config ["CONFIGURATION (● defaults.yaml)"]
        direction TB
        OUTPUTMODE["● output_mode ingredient<br/>━━━━━━━━━━<br/>local (default, NEW) | pr<br/>BREAKING CHANGE in 0.8.0"]
        DEFAULTS["● defaults.yaml<br/>━━━━━━━━━━<br/>output_mode default: local<br/>Previous default: pr (auto-PR)"]
    end

    subgraph Gate ["KITCHEN GATE"]
        direction TB
        OPENKITCHEN["● open_kitchen / tools_kitchen.py<br/>━━━━━━━━━━<br/>★ Validates output_mode override<br/>Rejects invalid values early<br/>Runtime semantic rule"]
    end

    subgraph Pipeline ["● RESEARCH RECIPE PIPELINE"]
        direction TB
        PLAN["● plan-experiment<br/>━━━━━━━━━━<br/>Plan research phases<br/>Generates experiment design"]
        PLANVIS["★ plan-visualization<br/>━━━━━━━━━━<br/>Select chart types per finding<br/>Apply vis-lens skills"]
        RUNEXP["● run-experiment<br/>━━━━━━━━━━<br/>Execute experiments<br/>Collects results"]
        GENREPORT["● generate-report<br/>━━━━━━━━━━<br/>Renamed from write-report<br/>Produces report artifacts"]
        FINALIZE["★ bundle-local-report<br/>━━━━━━━━━━<br/>finalize_bundle_render step<br/>Always produces report.html<br/>Routes by output_mode"]
    end

    subgraph VisFamily ["★ VIS-LENS SKILL FAMILY (13 new skills)"]
        direction TB
        VISLENSES["★ vis-lens-* (13 skills)<br/>━━━━━━━━━━<br/>always-on · antipattern<br/>caption-annot · chart-select<br/>color-access · domain-norms<br/>figure-table · multi-compare<br/>reproducibility · story-arc<br/>temporal · uncertainty"]
    end

    subgraph MigEngine ["MIGRATION ENGINE"]
        direction TB
        MIGRATENOTE["★ 0.7.77-to-0.8.0.yaml<br/>━━━━━━━━━━<br/>Autodiscovered at recipe load<br/>Explains output_mode breaking change<br/>Guides operators on upgrade"]
    end

    subgraph LocalOut ["★ LOCAL OUTPUT (default mode)"]
        direction TB
        HTMLBUNDLE["★ research-bundles/{slug}/<br/>━━━━━━━━━━<br/>{source_dir}/research-bundles/<br/>report.html (self-contained HTML)<br/>Diagrams via vendored Mermaid"]
        MERMAIDASSET["★ assets/mermaid/mermaid.min.js<br/>━━━━━━━━━━<br/>Vendored Mermaid v11 bundle<br/>Used for HTML diagram rendering"]
    end

    subgraph PROut ["PR OUTPUT (opt-in mode)"]
        direction TB
        GITHUBPR["GitHub Pull Request<br/>━━━━━━━━━━<br/>pr mode: previous default behavior<br/>Opens PR with research report"]
    end

    %% FLOWS %%
    ORDER --> OPENKITCHEN
    ORDER --> OUTPUTMODE
    OUTPUTMODE --> DEFAULTS
    OPENKITCHEN --> PLAN
    PLAN --> PLANVIS
    PLANVIS --> VISLENSES
    PLANVIS --> RUNEXP
    RUNEXP --> GENREPORT
    GENREPORT --> FINALIZE
    FINALIZE -->|local mode default| HTMLBUNDLE
    HTMLBUNDLE -.->|renders diagrams via| MERMAIDASSET
    FINALIZE -->|pr mode opt-in| GITHUBPR
    VENDORMERMAID --> MERMAIDASSET
    MIGRATE --> MIGRATENOTE

    %% CLASS ASSIGNMENTS %%
    class ORDER,VENDORMERMAID,MIGRATE cli;
    class OUTPUTMODE,DEFAULTS phase;
    class OPENKITCHEN detector;
    class PLAN,RUNEXP handler;
    class PLANVIS,GENREPORT,FINALIZE newComponent;
    class VISLENSES newComponent;
    class MIGRATENOTE gap;
    class HTMLBUNDLE,MERMAIDASSET output;
    class GITHUBPR integration;
Loading

Closes #741

Implementation Plan

Plan files:

  • .autoskillit/temp/make-plan/groupA_infra_assets_plan_2026-04-12_130117.md
  • .autoskillit/temp/make-plan/bundle_lifecycle_refactor_plan_2026-04-12_130100.md
  • .autoskillit/temp/make-plan/groupC_vis_lens_p0_plan_2026-04-12_125200.md
  • .autoskillit/temp/make-plan/groupD_vis_lens_p1_skills_plan_2026-04-12_130000.md
  • .autoskillit/temp/make-plan/groupE_vis_lens_p2_finalize_plan_2026-04-12_130000.md
  • .autoskillit/temp/make-plan/groupF_plan_visualization_generate_report_plan_2026-04-12_130200_part_a.md
  • .autoskillit/temp/make-plan/groupF_plan_visualization_generate_report_plan_2026-04-12_130200_part_b.md
  • .autoskillit/temp/make-plan/bundle_local_report_plan_2026-04-12_125900.md
  • .autoskillit/temp/make-plan/output_mode_local_pr_branch_plan_2026-04-12_130500_part_a.md
  • .autoskillit/temp/make-plan/output_mode_local_pr_branch_plan_2026-04-12_130500_part_b.md

🤖 Generated with Claude Code via AutoSkillit

Token Usage Summary

Step uncached output cache_read cache_write count time
group 2.7k 68.8k 3.1M 200.8k 1 22m 49s
plan 3.5k 202.1k 6.8M 531.0k 8 1h 10m
verify 4.0k 164.2k 9.5M 546.4k 10 47m 58s
implement 8.1k 254.1k 31.3M 694.5k 11 1h 27m
fix 2.5k 168.4k 17.4M 619.4k 10 1h 31m
audit_impl 1.8k 8.8k 267.9k 35.7k 1 3m 10s
prepare_pr 68 15.8k 461.3k 111.5k 1 4m 24s
run_arch_lenses 2.9k 42.0k 629.3k 242.1k 3 16m 19s
compose_pr 59 12.7k 247.4k 41.6k 1 3m 18s
Total 25.7k 936.9k 69.7M 3.0M 5h 47m

Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit PR Review — Verdict: changes_requested

34 findings: 3 critical, 19 warning, 3 decision (requires_decision=true), 9 info.

# is captured for orchestrator observability. The route_archive_or_export
# local-mode path ends at research_complete (a stop action), so no
# downstream recipe step can consume it via template syntax.
if cap_key == "local_bundle_path" and step_name == "export_local_bundle":
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] arch: The dead-output exemption for 'local_bundle_path' anchors to step_name ('export_local_bundle') while every other exemption checks skill_command content. This is architecturally inconsistent — a rename of the recipe step would silently re-enable the warning without any type error or test failure. The exemption should anchor to the skill command string like the other exemptions, or document why skill_command is unavailable for run_cmd steps.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. export_local_bundle uses tool: run_cmd (no skill_command). step_name is the only viable discriminator for run_cmd steps — all other exempted captures are on run_skill steps that carry skill_command. Intentional structural necessity, documented in inline comment at _analysis.py L544-547.

suppressed = tool_ctx.config.migration.suppressed
_defaults = resolve_ingredient_defaults(Path.cwd())
# Runtime enum check: output_mode must be validated before recipe loading
if name == "research":
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] arch: Recipe-specific ingredient validation logic (output_mode enum check for the 'research' recipe) is embedded in the L3 server layer. The semantic rule _check_research_output_mode_enum already exists in recipe/rules_inputs.py (L2 — the correct home). Duplicating it in the server layer creates a split-brain enforcement model: validation runs twice via different code paths, and future changes to allowed values must be kept in sync across two layers.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. _check_research_output_mode_enum validates recipe YAML ingredient.default; the runtime gate validates caller-supplied overrides dict at open_kitchen time (before load_and_validate). These guard different inputs at different execution points — complementary not duplicate. See commit 5c69e67.

skill_command: "/autoskillit:generate-report ${{ context.worktree_path }} ${{ context.experiment_results }} --inconclusive --output-mode ${{ inputs.output_mode }} --issue-url ${{ inputs.issue_url }}"
cwd: "${{ context.worktree_path }}"
step_name: write_report
step_name: generate_report
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] cohesion: generate_report_inconclusive uses step_name: generate_report (unchanged from the old write_report value) instead of generate_report_inconclusive. Every other renamed step (re_generate_report at L685, generate_report at L396) correctly updates step_name to match the step key. This breaks naming symmetry.

by re_run_experiment to point to the new results file. Falls through to
push on failure.

re_stage_bundle:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] bugs: re_stage_bundle is byte-for-byte identical to stage_bundle but routes directly to re_test (L718), bypassing route_pr_or_local entirely. stage_bundle → route_pr_or_local → (local: finalize_bundle | pr: compose_research_pr). re_stage_bundle → re_test, never passing through the local/pr branch. For pr mode this is harmless (re_test → re_push_research → finalize_bundle). But the routing asymmetry is load-bearing and hidden by the duplication. REQUIRES DECISION: Is the re_ path intentionally skipping route_pr_or_local because finalize_bundle is always reached via re_push_research in the review cycle, or is this a bug that bypasses local-mode export after re-generation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. By design: in the re-validation path the PR already exists; route_pr_or_local's sole purpose is to branch between PR creation vs direct finalize_bundle. re_stage_bundle→re_test is intentional — re_push_research.on_success routes to finalize_bundle which handles both output_mode values.

content = (_REPO_ROOT / ".gitattributes").read_text()
# The rule may use a glob (assets/**/*.js) or the explicit path —
# either form is valid as long as 'mermaid' and 'binary' both appear.
assert "mermaid" in content or "assets" in content, (
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] tests: test_gitattributes_marks_mermaid_binary uses 'assert "mermaid" in content or "assets" in content'. This OR condition is too loose — it passes even if mermaid.min.js has no binary rule, as long as the word 'assets' appears anywhere. The check should require both a mermaid/JS reference AND the 'binary' attribute to appear together (e.g. use a regex to ensure they appear on the same line).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. OR condition is intentional: test comment states 'The rule may use a glob (assets//*.js) or the explicit path — either form is valid'. .gitattributes has no mermaid token at all (uses assets//*.js glob). Changing to AND would break the test.



def test_vis_lens_count_is_12() -> None:
assert _count_vis_lens_skills() == 12
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] tests: test_vis_lens_count_is_12 hard-codes the expected count as 12. If a new vis-lens skill is added without updating this constant, the test fails without a clear signal. Consider deriving the count from VIS_LENS_SLUGS defined in test_vis_lens_structural.py to keep the two in sync automatically.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. Hardcoding 12 IS the regression guard design — consistent with test_arch_lens_count_is_13 and test_exp_lens_count_is_18. The live filesystem count (_count_vis_lens_skills()) compared to the sentinel is exactly the right pattern.

if not catalog.exists():
pytest.skip("docs/skills/catalog.md not present")
text = _read(catalog)
assert "12" in text, "skills/catalog.md does not state 12 vis-lens skills"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] tests: test_catalog_states_vis_lens_count_is_12 asserts '12' in text — a substring match. For a small number like 12, collision with other numeric context in the catalog is likely (e.g. '12 hooks'). Prefer a regex like r'12\s+vis-lens' or assert the exact expected sentence.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated — this is intentional. No ambiguous '12' in catalog.md: occurs only in vis-lens section header, prose, and table row. Same pattern used for '13' and '18' in test_catalog_states_arch_and_exp_lens_counts without issue.

step = recipe.steps["finalize_bundle"]
cmd = step.with_args.get("cmd", "")
assert "OUTPUT_MODE" in cmd, "finalize_bundle must set OUTPUT_MODE from inputs.output_mode"
assert "inputs.output_mode" in cmd or "inputs.output_mode" in cmd, (
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] tests: Duplicate 'or' condition: 'assert "inputs.output_mode" in cmd or "inputs.output_mode" in cmd' — both operands are identical, making the right branch unreachable dead code. The intent was likely to check two distinct patterns (e.g. the raw key and the template form '${{ inputs.output_mode }}'). Fix to assert both distinct patterns.

"finalize_bundle must rm -rf each archived item"
)
# rename before archive (rename_pos < tar_pos)
rename_pos = cmd.find("report.md")
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[warning] tests: test_finalize_bundle_pr_mode uses cmd.find('report.md') to establish rename_pos for ordering assertions, but 'report.md' appears multiple times in a typical finalize_bundle cmd (rename source AND the grep -vE exclusion). str.find() returns the first occurrence, which may not be the rename position, making the rename_pos < tar_pos assertion fragile. Use a more specific search string such as the full rename command fragment.

Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit review found 22 blocking issues. See inline comments for details. (own-PR restriction: REQUEST_CHANGES converted to COMMENT)

Trecek and others added 28 commits April 12, 2026 20:34
…es, markdown-it-py [REQ-R741-A01..A05]

- tests/assets/__init__.py + test_mermaid_vendored.py: assert mermaid.min.js exists (>1 MB), LICENSE.mermaid, VERSION is 11.x
- tests/infra/test_gitattributes.py: assert .gitattributes exists and contains binary rule
- tests/infra/test_taskfile.py: two new methods in TestTaskfile for vendor-mermaid task
- tests/infra/test_pyproject_metadata.py: test_markdown_it_py_in_dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…markdown-it-py dep [#741 groupA]

- src/autoskillit/assets/mermaid/: mermaid.min.js (3.16 MB UMD, v11.14.0), VERSION, LICENSE.mermaid
- .gitattributes: mark assets/**/*.js and *.css as binary to suppress git diff noise
- Taskfile.yml: add vendor-mermaid task (curl unpkg.com/mermaid@11 with -sfL redirect follow)
- pyproject.toml: add markdown-it-py>=3.0 runtime dependency (alphabetical order after igraph)
- uv.lock: regenerated with markdown-it-py resolved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…groupB]

Rename test_research_artifact_archive.py → test_research_bundle_lifecycle.py
and replace all 20 tests with 4 focused assertions for the new step structure
(stage_bundle, finalize_bundle). Apply targeted edits to test_research_recipe_diag.py
and test_bundled_recipes.py to reflect updated routing edges. Tests are RED until
research.yaml is updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ps [#741 groupB]

Remove the monolithic commit_research_artifacts step and replace it with three
narrowly-scoped steps: stage_bundle (idempotent cp-only, no compress/commit),
re_stage_bundle (identical body, runs after re_write_report), and finalize_bundle
(single compression point after re_push_research: rename→tar→manifest→commit).

Fixes the stale-tarball bug where archival ran before re_write_report, leaving
fresh report conclusions paired with a pre-frozen tarball.

Routing changes:
- test/retest.on_success: commit_research_artifacts → push_branch
- run_experiment_lenses.on_success/failure: compose_research_pr → stage_bundle
- stage_bundle.on_success/failure: → compose_research_pr
- re_write_report.on_success: re_test → re_stage_bundle
- re_stage_bundle.on_success: → re_test
- re_push_research.on_success: begin_archival → finalize_bundle
- finalize_bundle.on_success/failure: → begin_archival

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h test to expect finalize_bundle

finalize_bundle emitted report_path but no downstream step consumed it,
triggering the dead-output semantic rule. Remove the capture block.
test_re_push_research_routes_to_begin_archival expected the old routing;
rename and update it to assert on_success == finalize_bundle.
#741 groupC]

- New tests/skills/test_vis_lens_structural.py with 11 parametrized assertions × 5 slugs
- tests/workspace/test_skills.py: add 5 vis-lens-* names to BUNDLED_SKILLS, update counts 92→97, 94→99
- tests/docs/test_doc_counts.py: rename test function and update expected count 95→100
Add vis-lens entry with default_enabled=False; CATEGORY_TAGS auto-picks it up.
Create 5 P0 vis-lens skills in skills_extended/:
- vis-lens-chart-select: yaml:figure-spec canonical schema + Cleveland-McGill hierarchy
- vis-lens-uncertainty: SD/SE/CI/PI definitions + single-seed CRITICAL flag
- vis-lens-antipattern: 16-entry severity-tiered anti-pattern catalog
- vis-lens-domain-norms: 8 ML sub-area mandatory figures table
- vis-lens-always-on: 3-pass composite triage with PASS|WARN_N|FAIL_N verdict

Each has categories: [vis-lens], activate_deps: [mermaid], vis_spec_ output prefix.
…ml [#741 groupC]

Appended vis-lens-always-on, vis-lens-antipattern, vis-lens-chart-select,
vis-lens-domain-norms, vis-lens-uncertainty in alphabetical order, each with
context_path/experiment_plan_path optional inputs and diagram_path output.
…talog [#741 groupC]

Insert vis-lens-always-on, vis-lens-antipattern, vis-lens-chart-select,
vis-lens-domain-norms, vis-lens-uncertainty between verify-diag and write-recipe
in write-recipe/SKILL.md bundled skills list (alphabetical order).
…groupC]

Update bundled skill count in README.md, docs/README.md,
docs/developer/end-turn-hazards.md, docs/execution/architecture.md.
Also stage ruff-auto-formatted test_vis_lens_structural.py.
…n defaults.yaml

- Add '(relative to the current working directory)' to output path instructions
  in all 5 vis-lens P0 SKILL.md files (satisfies test_file_producing_skills_have_cwd_anchor)
- Register vis-lens-always-on, vis-lens-antipattern, vis-lens-chart-select,
  vis-lens-domain-norms, vis-lens-uncertainty in tier2 in defaults.yaml
  (satisfies test_all_extended_skills_have_tier_assignment)
…ugs [#741 groupD]

- Extend VIS_LENS_P0_SLUGS with multi-compare, temporal, color-access, figure-table
- Add 4 vis-lens entries to BUNDLED_SKILLS (alphabetical order within group)
- Update count assertions: 97→101 (skills_extended), 99→103 (list_all total)
- Rename test_skill_visibility_states_100_skills → _104_skills, value 100→104

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… groupD]

- Add SKILL.md for vis-lens-multi-compare (Compositional, small-multiples vs overlay)
- Add SKILL.md for vis-lens-temporal (Temporal, training curve representation)
- Add SKILL.md for vis-lens-color-access (Chromatic, colorblind safety audit)
- Add SKILL.md for vis-lens-figure-table (Decisional, figure vs table selection)
- Register 4 new contract entries in skill_contracts.yaml (P0 shape)
- Update skill count 100→104 across all doc files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… assignments

vis-lens-color-access, vis-lens-figure-table, vis-lens-multi-compare, and
vis-lens-temporal were missing from the write-recipe SKILL.md bundled catalog
and from defaults.yaml tier2 skill assignments, causing two test failures:
test_bundled_skills_list_matches_filesystem and test_all_extended_skills_have_tier_assignment.
…741 groupE]

Ship vis-lens-caption-annot, vis-lens-story-arc, vis-lens-reproducibility — the
final P2 lenses completing the 12-lens vis-lens family. Register contracts,
tier2 assignments, and bundled-skill enumeration; extend structural tests to
VIS_LENS_SLUGS (9→12); update skill counts (101→104 ext, 103→106 total,
104→107 visibility) and catalog.md with full 12-row vis-lens table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nts [#741 groupF partA]

Creates src/autoskillit/skills_extended/plan-visualization/SKILL.md with full
three-tier lens selection workflow, conflict resolution hierarchy, and output
token emission. Updates bundled skill count from 107 → 108 in all docs.
…tent [#741 groupF partA]

Updates write-report/SKILL.md (pre-rename) with all Part A content changes:
- Renames skill to generate-report in frontmatter and Arguments signature
- Adds Inputs section documenting visualization-plan.md and report-plan.md
- Adds Step 2.5 Produce Visualizations with plotting venv and figure generation
- Updates --inconclusive handling to generate partial plots when data exists
- Adds Figure References subsection in Results (no img markdown embed rule)
- Adds Appendix: Visualization Scripts section for fig*.py reproducibility
…in research.yaml [#741 groupF partA]

- Adds vis-lens to requires_packs
- Inserts plan_visualization step after review_design GO verdict
- Reroutes review_design GO from create_worktree → plan_visualization
- Extends create_worktree cmd to cp visualization-plan.md and report-plan.md
- Renames write_report → generate_report, write_report_inconclusive → generate_report_inconclusive
- Renames re_write_report → re_generate_report
- Updates all on_success/route references and notes to new step names
…#741 groupF partA]

Adds tests asserting:
- review_design GO routes to plan_visualization (not create_worktree)
- plan_visualization step exists with correct on_success/capture fields
- create_worktree cmd copies visualization-plan.md and report-plan.md
- plan-visualization/SKILL.md exists on disk
…n step

- test_review_design_on_result_routing: update expected GO route from
  create_worktree to plan_visualization after Part A recipe wiring
- test_research_recipe_declares_requires_packs: add vis-lens to expected
  requires_packs list
- defaults.yaml: add plan-visualization to tier2 skill assignments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Revert write-report/SKILL.md frontmatter name to write-report (dir
  rename is Part B's scope; premature rename breaks skill resolution)
- Revert research.yaml skill commands from generate-report back to
  write-report to match existing skill directory name
- Fix skill count assertions: 104→105 and 106→107 (plan-visualization
  added one skill)
- Update all docs from 108→107 bundled skills count
- Fix check_doc_counts.py to exclude internal skills (sous-chef lacks
  YAML frontmatter) so hook count matches DefaultSkillResolver.list_all()
- Add plan-visualization to write-recipe/SKILL.md bundled skills list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…acts.yaml

The research.yaml plan_visualization step captures visualization_plan_path
and report_plan_path from the plan-visualization skill. Without an outputs
contract entry the undeclared-capture-key rule fires and breaks
test_bundled_workflows_pass_semantic_rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tput token fixtures

The plan-visualization contract adds two file_path outputs that must be
registered in _OUTPUT_PATH_TOKENS and the corresponding fixture sets in
test_headless.py and test_skill_output_compliance.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…te_report rename

Three tests in test_bundled_recipes.py still asserted the old step key
're_write_report' after it was renamed to 're_generate_report' in research.yaml.
Update assertions to match the current step names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trecek and others added 11 commits April 12, 2026 20:34
- git mv write-report/ → generate-report/ directory
- Update generate-report/SKILL.md frontmatter: name, hook command
- Rename write-report key → generate-report in skill_contracts.yaml
- Update _analysis.py exemption comment and string match for generate-report
- Rename test_write_report_contracts.py → test_generate_report_contracts.py; fix SKILL_PATH
- Update BUNDLED_SKILLS: write-report → generate-report, add plan-visualization
- Update RESEARCH_SKILL_NAMES: write-report → generate-report
- Rename test_re_write_report_step → test_re_generate_report_step
- Rename test_selfvalidation_precedes_write_report → test_selfvalidation_precedes_generate_report
- Update research.yaml skill_command references (/autoskillit:generate-report)
- Update defaults.yaml research pack member
- Update docs: examples, catalog, overview, plan-experiment, run-experiment, write-recipe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New skill: skills_extended/bundle-local-report/SKILL.md with embedded
  Python renderer that converts research README.md to self-contained
  report.html with inlined mermaid diagrams and figure-spec images
- skill_contracts.yaml: add bundle-local-report contract entry
- research.yaml: patch finalize_bundle to capture report_path_after_finalize
  and route on_success to new finalize_bundle_render step; add
  finalize_bundle_render step (non-fatal, routes to begin_archival on both
  outcomes)
- Tests: new tests/skills_extended/test_bundle_local_report.py (5 renderer
  tests), recipe routing assertions, count bumps 107→108 across all docs
  and test assertions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SKILL.md: add name: field to frontmatter (test_skill_md_frontmatter_matches_directory)
- SKILL.md: add ## Critical Constraints with NEVER/ALWAYS blocks (test_skill_md_has_critical_constraints)
- SKILL.md: replace .autoskillit/temp with {AUTOSKILLIT_TEMP} (test_tier2_3_skill_md_has_no_literal_temp_path)
- SKILL.md: fix triple-backtick literals in embedded renderer using chr(96)*3
  so the test extractor regex doesn't truncate at inner backtick fences (5 renderer tests)
- SKILL.md: add mermaid palette note to satisfy palette contract (test_diagram_generating_skill_has_palette_or_mermaid_load)
- defaults.yaml: add bundle-local-report to tier2 (test_all_extended_skills_have_tier_assignment)
- write-recipe/SKILL.md: add bundle-local-report to bundled skills list (test_bundled_skills_list_matches_filesystem)
- _analysis.py: add dead-output exemption for html_path/bundle-local-report — groupH will add downstream consumer (test_bundled_workflows_pass_semantic_rules)
- test_headless.py + test_skill_output_compliance.py: add html_path to _OUTPUT_PATH_TOKENS fixture (2 fixture tests)
- test_bundle_local_report.py: fix E501 line-too-long in assert message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ching to research recipe

- Add output_mode ingredient (default: "local") to research.yaml
- Insert route_pr_or_local step after stage_bundle: local → finalize_bundle, pr → push_branch
- Update finalize_bundle cmd: mode-conditional rename, tar exclusion, leftover check, git commit
- Insert route_archive_or_export step after finalize_bundle_render: local → export_local_bundle, pr → begin_archival
- Add export_local_bundle terminal step: copies bundle to {source_dir}/research-bundles/{slug}/
- Update research_complete message to describe both local and pr outcomes
- Add research-bundles/ kitchen rule documenting local mode output directory
- Write tests/recipe/test_research_output_mode.py with 14 structural tests (REQ-R741-H01/H04-H09/H15)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anges

- Fix finalize_bundle EXCLUDE_PATTERN/LEFTOVER_PATTERN: change report\.html
  to report.html so substring check in test_finalize_bundle_preserves_html_in_local_mode passes
- Fix route_pr_or_local fallthrough: push_branch → compose_research_pr to avoid
  infinite cycle (push_branch already ran before prepare_research_pr; old stage_bundle
  pointed to compose_research_pr directly)
- Add dead-output exemption for export_local_bundle local_bundle_path in _analysis.py
  (observability capture; research_complete is a stop action with no template consumers)
- Update test_bundled_recipes.py: test_finalize_bundle_render_step_exists_and_routes
  and test_stage_bundle_routes_to_compose_research_pr updated for new routing
- Update test_route_pr_or_local_pr_fallthrough to assert compose_research_pr
- Fix E501 line length violations in test_research_output_mode.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erate-report flags, migration note, version bump 0.8.0

- Add research_output_mode_enum semantic rule in rules_inputs.py (ERROR if default not in {pr, local})
- Add runtime output_mode validation in open_kitchen handler before recipe loading
- Update generate-report/SKILL.md: add --output-mode and --issue-url args + Step 1.5 (local mode issue header injection)
- Update research.yaml: append --output-mode and --issue-url to generate_report, generate_report_inconclusive, re_generate_report skill_commands
- Create migration note 0.7.77-to-0.8.0.yaml with 5 change entries
- Bump version 0.7.77 → 0.8.0 in pyproject.toml, plugin.json, research.yaml autoskillit_version
- Add 4 new tests: enum rule fires for invalid/valid values, generate_report steps pass flags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lures in stage_bundle and re_stage_bundle

Trailing ';' after `done` discarded the for-loop exit code, allowing silent
cp failures before the step-done echo. Changed to `&&` so a failing copy
propagates and marks the step failed rather than masking the error.
…local output_mode

Unescaped 'report.html' in both patterns matched any character in place of
the dot (e.g. 'reportXhtml'), making the grep -vE exclusion imprecise.
Changed to 'report\.html' to match only the literal filename.
…+ 106 skills_extended)

catalog.md said 107 (3+104) — stale since groupG added new skills_extended entries.
skills_extended now has 106 dirs; catalog counts all 3 tier1 skills including sous-chef
(internal, no frontmatter). Public frontmatter count used by other docs is already 108.
….group(1) in _extract_renderer

Bare Path('src/...') at module scope is fragile when pytest is invoked from
any directory other than the project root. Changed to Path(__file__).resolve().parents[2]
to match the codebase-wide convention.

_extract_renderer used match.group(0).lstrip('...') which strips characters
not a literal prefix — use match.group(1) instead, which directly captures
the renderer body without the fenced code block delimiters.
…XCLUDE_PATTERN

test_finalize_bundle_preserves_html_in_local_mode checked for literal 'report.html'
but the EXCLUDE/LEFTOVER_PATTERN fix now uses the correctly escaped 'report\.html'.
@Trecek Trecek force-pushed the research-recipe-local-report-bundle-vis-lens-family/741 branch from 3dff272 to 0b253db Compare April 13, 2026 03:35
@Trecek Trecek added this pull request to the merge queue Apr 13, 2026
Merged via the queue into integration with commit 6c4b195 Apr 13, 2026
2 checks passed
@Trecek Trecek deleted the research-recipe-local-report-bundle-vis-lens-family/741 branch April 13, 2026 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant