From bae136f0f626faed9bd954dac6c5ad2546a19ddf Mon Sep 17 00:00:00 2001 From: codejunkie99 Date: Thu, 21 May 2026 01:17:06 +0530 Subject: [PATCH] feat: add GateFlow terminal console --- .claude-plugin/marketplace.json | 6 +- README.md | 28 ++- agents/gf-auditor.md | 1 + agents/gf-pluginfixer.md | 1 + agents/pcb-designer.md | 1 + agents/sv-formal.md | 1 + agents/sv-ip-scanner.md | 1 + agents/sv-pinmap.md | 1 + agents/sv-synth.md | 1 + agents/vhdl-codegen.md | 1 + agents/vhdl-testbench.md | 1 + docs/gateflow.index | 101 ++++++-- plugins/gateflow/.claude-plugin/plugin.json | 6 +- plugins/gateflow/README.md | 16 +- plugins/gateflow/commands/gf-release.md | 47 ++++ plugins/gateflow/commands/gf-tui.md | 43 ++++ plugins/gateflow/skills/gf-release/SKILL.md | 101 ++++++++ plugins/gateflow/skills/gf-tui/SKILL.md | 64 +++++ releases.md | 29 ++- skills/gf-cocotb/SKILL.md | 1 + skills/gf-errors/SKILL.md | 1 + skills/gf-formal/SKILL.md | 1 + skills/gf-fusesoc/SKILL.md | 1 + skills/gf-ip-detect/SKILL.md | 1 + skills/gf-ip/SKILL.md | 1 + skills/gf-learn-ctx/SKILL.md | 1 + skills/gf-pcb/SKILL.md | 1 + skills/gf-pinmap/SKILL.md | 1 + skills/gf-pnr/SKILL.md | 1 + skills/gf-project/SKILL.md | 1 + skills/gf-protocols/SKILL.md | 1 + skills/gf-release/SKILL.md | 1 + skills/gf-synth/SKILL.md | 1 + skills/gf-tui/SKILL.md | 1 + tests/__init__.py | 1 + tests/test_gateflow_tui.py | 60 +++++ tests/test_validate_gateflow.py | 57 +++++ tools/gateflow_tui.py | 250 ++++++++++++++++++++ tools/validate_gateflow.py | 176 ++++++++++++++ 39 files changed, 974 insertions(+), 35 deletions(-) create mode 120000 agents/gf-auditor.md create mode 120000 agents/gf-pluginfixer.md create mode 120000 agents/pcb-designer.md create mode 120000 agents/sv-formal.md create mode 120000 agents/sv-ip-scanner.md create mode 120000 agents/sv-pinmap.md create mode 120000 agents/sv-synth.md create mode 120000 agents/vhdl-codegen.md create mode 120000 agents/vhdl-testbench.md create mode 100644 plugins/gateflow/commands/gf-release.md create mode 100644 plugins/gateflow/commands/gf-tui.md create mode 100644 plugins/gateflow/skills/gf-release/SKILL.md create mode 100644 plugins/gateflow/skills/gf-tui/SKILL.md create mode 120000 skills/gf-cocotb/SKILL.md create mode 120000 skills/gf-errors/SKILL.md create mode 120000 skills/gf-formal/SKILL.md create mode 120000 skills/gf-fusesoc/SKILL.md create mode 120000 skills/gf-ip-detect/SKILL.md create mode 120000 skills/gf-ip/SKILL.md create mode 120000 skills/gf-learn-ctx/SKILL.md create mode 120000 skills/gf-pcb/SKILL.md create mode 120000 skills/gf-pinmap/SKILL.md create mode 120000 skills/gf-pnr/SKILL.md create mode 120000 skills/gf-project/SKILL.md create mode 120000 skills/gf-protocols/SKILL.md create mode 120000 skills/gf-release/SKILL.md create mode 120000 skills/gf-synth/SKILL.md create mode 120000 skills/gf-tui/SKILL.md create mode 100644 tests/__init__.py create mode 100644 tests/test_gateflow_tui.py create mode 100644 tests/test_validate_gateflow.py create mode 100755 tools/gateflow_tui.py create mode 100755 tools/validate_gateflow.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d647c63..7479787 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,8 +9,8 @@ "plugins": [ { "name": "gateflow", - "description": "AI-powered hardware development platform \u2014 design, verify, synthesize, and deploy working RTL with natural language. 18 agents, 25 skills, 8 IP blocks.", - "version": "2.3.0", + "description": "AI-powered hardware development platform \u2014 design, verify, synthesize, release, and deploy working RTL with natural language. 20 agents, 27 skills, 8 IP blocks.", + "version": "2.5.0", "author": { "name": "codejunkie99", "github": "https://github.com/codejunkie99" @@ -19,4 +19,4 @@ "category": "development" } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index 5b4cbb3..380ec27 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ Skills activate automatically based on context: | `/gf-scan` | Index project files | | `/gf-map` | Map codebase architecture | | `/gf-doctor` | Check environment and dependencies | +| `/gf-tui` | Open the local GateFlow terminal console | +| `/gf-release` | Validate plugin release readiness | ### Example Session @@ -253,7 +255,7 @@ GateFlow watches your workflow and helps proactively: ## Components -### Skills (26) +### Skills (27) | Skill | Description | Source | |-------|-------------|--------| @@ -282,8 +284,10 @@ GateFlow watches your workflow and helps proactively: | `gf-fusesoc` | FuseSoC build system integration | [SKILL.md](plugins/gateflow/skills/gf-fusesoc/SKILL.md) | | `gf-learn-ctx` | Contextual learning — micro-lessons in workflows | [SKILL.md](plugins/gateflow/skills/gf-learn-ctx/SKILL.md) | | `gf-ip-detect` | **Auto-detect IP blocks** — scan, match, auto-fill gaps | [SKILL.md](plugins/gateflow/skills/gf-ip-detect/SKILL.md) | +| `gf-tui` | **Terminal console** — local OpenClaw-style GateFlow dashboard | [SKILL.md](plugins/gateflow/skills/gf-tui/SKILL.md) | +| `gf-release` | **Release readiness** — validate manifests, docs, index, mirrors | [SKILL.md](plugins/gateflow/skills/gf-release/SKILL.md) | -### Agents (18) +### Agents (20) | Agent | Expertise | Source | |-------|-----------|--------| @@ -305,8 +309,10 @@ GateFlow watches your workflow and helps proactively: | `vhdl-testbench` | **VHDL testbench** — GHDL-compatible verification | [vhdl-testbench.md](plugins/gateflow/agents/vhdl-testbench.md) | | `pcb-designer` | **KiCad PCB** — AI-verified schematics and layouts | [pcb-designer.md](plugins/gateflow/agents/pcb-designer.md) | | `sv-ip-scanner` | **IP scanner** — detect missing modules, auto-fill | [sv-ip-scanner.md](plugins/gateflow/agents/sv-ip-scanner.md) | +| `gf-auditor` | **Plugin auditor** — find package gaps and stale docs | [gf-auditor.md](plugins/gateflow/agents/gf-auditor.md) | +| `gf-pluginfixer` | **Plugin fixer** — repair audited plugin gaps | [gf-pluginfixer.md](plugins/gateflow/agents/gf-pluginfixer.md) | -### Commands (18) +### Commands (21) | Command | Description | Source | |---------|-------------|--------| @@ -328,6 +334,9 @@ GateFlow watches your workflow and helps proactively: | `/gf-pinmap` | Generate pin constraint file for board | [gf-pinmap.md](plugins/gateflow/commands/gf-pinmap.md) | | `/gf-cocotb` | Generate Python testbench (Cocotb) | [gf-cocotb.md](plugins/gateflow/commands/gf-cocotb.md) | | `/gf-fusesoc` | Generate FuseSoC .core file | [gf-fusesoc.md](plugins/gateflow/commands/gf-fusesoc.md) | +| `/gf-audit` | Audit plugin quality and optionally auto-fix issues | [gf-audit.md](plugins/gateflow/commands/gf-audit.md) | +| `/gf-tui` | Open the local GateFlow terminal console | [gf-tui.md](plugins/gateflow/commands/gf-tui.md) | +| `/gf-release` | Validate plugin release readiness | [gf-release.md](plugins/gateflow/commands/gf-release.md) | ### IP Library (8 verified blocks) @@ -521,9 +530,9 @@ clock_freq: 100MHz Gateflow-Plugin/ ├── plugins/gateflow/ # Main plugin source │ ├── .claude-plugin/ # Plugin manifest -│ ├── agents/ # 18 specialized AI agents -│ ├── commands/ # 18 slash commands -│ ├── skills/ # 26 auto-activating skills +│ ├── agents/ # 20 specialized AI agents +│ ├── commands/ # 21 slash commands +│ ├── skills/ # 27 auto-activating skills │ ├── hooks/ # Automation hooks + session tracking │ ├── boards/ # Curated FPGA board database (4 boards) │ ├── ip/ # Verified IP block library (8 blocks) @@ -581,6 +590,13 @@ For detailed release notes, see [`releases.md`](releases.md). | Version | Date | What Changed | |---------|------|-------------| +| **2.5.0** | 2026-05-21 | OpenClaw-style CLI/TUI, release readiness workflow, deterministic validators, synced marketplace/docs/index/mirrors | +| **2.4.0** | 2026-04-11 | Deep skill enrichment across verification, synthesis, orchestration, architecture, learning, IP, and planning | +| **2.3.0** | 2026-03-27 | Quality pass, expanded IP docs, new commands, and component reference fixes | +| **2.2.1** | 2026-03-26 | IP auto-detection, auto-fill, CDC scanning, and `sv-ip-scanner` | +| **2.2.0** | 2026-03-26 | Community guides, KiCad, Cocotb, FuseSoC, CI templates, and ecosystem integrations | +| **2.1.0** | 2026-03-26 | VHDL, pin mapping, place and route, FPGA flash, and protocol scaffolding | +| **2.0.0** | 2026-03-26 | Formal verification, synthesis, IP library, and board database | | **1.6.0** | 2026-03-26 | Version sync across plugin.json and marketplace.json; BSL-1.1 license confirmed | | **1.5.3** | 2026-02-18 | Replace prompt-based PostToolUse hook with deterministic Python script | | **1.5.2** | 2026-02-15 | Fix Stop hook JSON validation: replace prompt hook with deterministic command hook (non-blocking reminder) | diff --git a/agents/gf-auditor.md b/agents/gf-auditor.md new file mode 120000 index 0000000..91998d0 --- /dev/null +++ b/agents/gf-auditor.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/gf-auditor.md \ No newline at end of file diff --git a/agents/gf-pluginfixer.md b/agents/gf-pluginfixer.md new file mode 120000 index 0000000..a621ccb --- /dev/null +++ b/agents/gf-pluginfixer.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/gf-pluginfixer.md \ No newline at end of file diff --git a/agents/pcb-designer.md b/agents/pcb-designer.md new file mode 120000 index 0000000..fade499 --- /dev/null +++ b/agents/pcb-designer.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/pcb-designer.md \ No newline at end of file diff --git a/agents/sv-formal.md b/agents/sv-formal.md new file mode 120000 index 0000000..3b46d2f --- /dev/null +++ b/agents/sv-formal.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/sv-formal.md \ No newline at end of file diff --git a/agents/sv-ip-scanner.md b/agents/sv-ip-scanner.md new file mode 120000 index 0000000..5d7b0b3 --- /dev/null +++ b/agents/sv-ip-scanner.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/sv-ip-scanner.md \ No newline at end of file diff --git a/agents/sv-pinmap.md b/agents/sv-pinmap.md new file mode 120000 index 0000000..67286fb --- /dev/null +++ b/agents/sv-pinmap.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/sv-pinmap.md \ No newline at end of file diff --git a/agents/sv-synth.md b/agents/sv-synth.md new file mode 120000 index 0000000..0c9af02 --- /dev/null +++ b/agents/sv-synth.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/sv-synth.md \ No newline at end of file diff --git a/agents/vhdl-codegen.md b/agents/vhdl-codegen.md new file mode 120000 index 0000000..6c631f4 --- /dev/null +++ b/agents/vhdl-codegen.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/vhdl-codegen.md \ No newline at end of file diff --git a/agents/vhdl-testbench.md b/agents/vhdl-testbench.md new file mode 120000 index 0000000..86356c1 --- /dev/null +++ b/agents/vhdl-testbench.md @@ -0,0 +1 @@ +../plugins/gateflow/agents/vhdl-testbench.md \ No newline at end of file diff --git a/docs/gateflow.index b/docs/gateflow.index index 12ca01d..f8bda79 100644 --- a/docs/gateflow.index +++ b/docs/gateflow.index @@ -3,41 +3,104 @@ # Use: Agent retrieval, codebase navigation primary|CLAUDE.md|SV patterns, always_ff/comb, FSM, CDC, lint fixes, Spear/Tumbush book index -primary|README.md|Installation, usage, features +primary|README.md|Installation, usage, features, component inventory +primary|plugins/gateflow/README.md|Plugin-local overview and quick start +primary|releases.md|Release notes and version history -commands|commands/gf-doctor.md|Env check -commands|commands/gf-fix.md|Fix lint -commands|commands/gf-gen.md|Generate scaffolds -commands|commands/gf-lint.md|Run lint -commands|commands/gf-map.md|Map codebase -commands|commands/gf-scan.md|Index project -commands|commands/gf-sim.md|Run sim +commands|commands/gf-audit.md|Audit plugin quality and optionally auto-fix issues +commands|commands/gf-boards.md|List supported FPGA boards and query pinouts +commands|commands/gf-cocotb.md|Generate Cocotb Python testbenches +commands|commands/gf-demo.md|Create a zero-config demo project +commands|commands/gf-detect.md|Scan for missing IP blocks, stubs, and CDC issues +commands|commands/gf-doctor.md|Check environment and hardware tool dependencies +commands|commands/gf-fix.md|Fix lint errors +commands|commands/gf-flash.md|Program FPGA boards with openFPGALoader +commands|commands/gf-formal.md|Run formal verification +commands|commands/gf-fusesoc.md|Generate FuseSoC core files +commands|commands/gf-gen.md|Generate RTL and testbench scaffolds +commands|commands/gf-ip.md|Manage verified IP blocks +commands|commands/gf-lint.md|Run Verilator lint +commands|commands/gf-map.md|Map codebase architecture +commands|commands/gf-pcb.md|Generate KiCad schematic and PCB drafts +commands|commands/gf-pinmap.md|Generate board constraint files +commands|commands/gf-pnr.md|Run place and route workflows +commands|commands/gf-release.md|Validate and prepare GateFlow releases +commands|commands/gf-scan.md|Index project files +commands|commands/gf-sim.md|Run simulation +commands|commands/gf-tui.md|Open the local GateFlow terminal console skills|skills/gf/SKILL.md|Main orchestrator, verification loops -skills|skills/gf-plan/SKILL.md|Hardware planner, Mermaid diagrams, FSM design skills|skills/gf-architect/SKILL.md|Codebase mapping, hierarchy analysis skills|skills/gf-build/SKILL.md|Parallel multi-component build orchestrator -skills|skills/gf-expand/SKILL.md|Design expansion and requirement elaboration -skills|skills/gf-learn/SKILL.md|Learning-oriented SV guidance and explanations +skills|skills/gf-cocotb/SKILL.md|Cocotb testbench generation and execution +skills|skills/gf-errors/SKILL.md|Three-layer hardware tool error translation +skills|skills/gf-expand/SKILL.md|Requirement expansion and clarifying questions +skills|skills/gf-formal/SKILL.md|Formal verification, SVA, SymbiYosys +skills|skills/gf-fusesoc/SKILL.md|FuseSoC and Edalize integration +skills|skills/gf-ip/SKILL.md|Verified IP block library operations +skills|skills/gf-ip-detect/SKILL.md|Missing IP, stub, pattern, and CDC detection +skills|skills/gf-learn/SKILL.md|Interactive SV learning mode +skills|skills/gf-learn-ctx/SKILL.md|Contextual micro-lessons and concept tracking skills|skills/gf-lint/SKILL.md|Lint analysis and remediation workflow +skills|skills/gf-pcb/SKILL.md|KiCad schematic and PCB workflow +skills|skills/gf-pinmap/SKILL.md|Board-aware pin mapping +skills|skills/gf-plan/SKILL.md|Hardware planner, Mermaid diagrams, FSM design +skills|skills/gf-pnr/SKILL.md|Synthesis, place and route flow +skills|skills/gf-project/SKILL.md|Project context and .gateflow/project.yaml management +skills|skills/gf-protocols/SKILL.md|Protocol references and scaffolds +skills|skills/gf-release/SKILL.md|Release readiness validation and version prep skills|skills/gf-router/SKILL.md|Task routing and intent classification skills|skills/gf-sim/SKILL.md|Simulation flow and debug loops skills|skills/gf-summary/SKILL.md|Summarization and progress reporting +skills|skills/gf-synth/SKILL.md|Yosys synthesis and resource reports +skills|skills/gf-tui/SKILL.md|OpenClaw-style local terminal console skills|skills/gf-viz/SKILL.md|Terminal visualization, hierarchy and FSM diagrams -skills|skills/tb-best-practices/SKILL.md|Testbench best practices, layered TB, randomization, coverage, OOP patterns +skills|skills/tb-best-practices/SKILL.md|Testbench best practices, coverage, OOP patterns +agents|agents/gf-auditor.md|Plugin quality audit +agents|agents/gf-pluginfixer.md|Automatic plugin gap fixing +agents|agents/pcb-designer.md|KiCad schematic and PCB design agents|agents/sv-codegen.md|RTL generation, modules, FSMs, FIFOs -agents|agents/sv-testbench.md|Testbench creation, stimulus, self-checking agents|agents/sv-debug.md|Simulation failures, X-values, timing -agents|agents/sv-verification.md|SVA assertions, coverage, formal -agents|agents/sv-understanding.md|Code explanation, signal tracing +agents|agents/sv-developer.md|Multi-file development and complex features +agents|agents/sv-formal.md|Formal proofs and SVA properties +agents|agents/sv-ip-scanner.md|Missing IP, stubs, and CDC scanning +agents|agents/sv-orchestrator.md|Parallel component orchestration +agents|agents/sv-pinmap.md|Pin assignments and constraints agents|agents/sv-planner.md|Architecture planning, design plans, diagrams -agents|agents/sv-orchestrator.md|Parallel component orchestration, multi-module builds agents|agents/sv-refactor.md|Lint fixes, cleanup, optimization -agents|agents/sv-developer.md|Multi-file development, complex features +agents|agents/sv-synth.md|Yosys synthesis optimization +agents|agents/sv-testbench.md|Testbench creation, stimulus, self-checking agents|agents/sv-tutor.md|SV tutoring and concept explanations -agents|agents/sv-viz.md|Terminal visualization, hierarchy diagrams, FSM rendering +agents|agents/sv-understanding.md|Code explanation and signal tracing +agents|agents/sv-verification.md|SVA assertions, coverage, formal +agents|agents/sv-viz.md|Terminal visualization and diagrams +agents|agents/vhdl-codegen.md|VHDL-2008 generation +agents|agents/vhdl-testbench.md|VHDL testbenches hooks|hooks/hooks.json|Automation hooks configuration +hooks|hooks/scripts/check-dependencies.sh|Session dependency check +hooks|hooks/scripts/session-tracker.py|Session tracking +hooks|hooks/scripts/userpromptsubmit-sv-nudge.py|SystemVerilog prompt nudges +hooks|hooks/scripts/pretooluse-bash-guard.py|Bash safety guard +hooks|hooks/scripts/posttooluse-sv-lint-nudge.py|Post-edit lint reminder +hooks|hooks/scripts/stop-hook.sh|Session-end verification reminder + +ip|ip/axi4lite_slave/block.yaml|AXI4-Lite register slave metadata +ip|ip/cdc_2ff/block.yaml|Two-flop synchronizer metadata +ip|ip/cdc_handshake/block.yaml|Multi-bit CDC handshake metadata +ip|ip/debouncer/block.yaml|Button debouncer metadata +ip|ip/fifo_async/block.yaml|Asynchronous FIFO metadata +ip|ip/fifo_sync/block.yaml|Synchronous FIFO metadata +ip|ip/spi_master/block.yaml|SPI master metadata +ip|ip/uart/block.yaml|UART TX/RX metadata + +boards|boards/arty-a7-35t/board.yaml|Digilent Arty A7-35T board definition +boards|boards/basys3/board.yaml|Digilent Basys 3 board definition +boards|boards/icebreaker/board.yaml|1BitSquared iCEBreaker board definition +boards|boards/tang-nano-9k/board.yaml|Sipeed Tang Nano 9K board definition + +tools|tools/validate_gateflow.py|Release metadata and package wiring validator +tools|tools/gateflow_tui.py|Local terminal console for GateFlow status and actions -reference|CLAUDE.md#external-references|Spear/Tumbush SV Verification 3rd ed (ch 1-12) +reference|CLAUDE.md#external-references|Spear/Tumbush SV Verification 3rd ed and SV for Design references diff --git a/plugins/gateflow/.claude-plugin/plugin.json b/plugins/gateflow/.claude-plugin/plugin.json index 7084021..5dcbb2b 100644 --- a/plugins/gateflow/.claude-plugin/plugin.json +++ b/plugins/gateflow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gateflow", - "version": "2.4.0", - "description": "AI-powered hardware development platform \u2014 design, verify, synthesize, and deploy working RTL with natural language. 20 agents, 25 skills, 8 IP blocks.", + "version": "2.5.0", + "description": "AI-powered hardware development platform \u2014 design, verify, synthesize, release, and deploy working RTL with natural language. 20 agents, 27 skills, 8 IP blocks.", "author": { "name": "codejunkie99", "github": "https://github.com/codejunkie99", @@ -22,4 +22,4 @@ ], "license": "BSL-1.1", "repository": "https://github.com/codejunkie99/Gateflow-Plugin" -} \ No newline at end of file +} diff --git a/plugins/gateflow/README.md b/plugins/gateflow/README.md index 120b45a..52fda62 100644 --- a/plugins/gateflow/README.md +++ b/plugins/gateflow/README.md @@ -1,6 +1,6 @@ # GateFlow -**The open-source AI hardware platform.** Design, verify, synthesize, and deploy working RTL from natural language. 20 agents. 25 skills. 8 verified IP blocks. One command. +**The open-source AI hardware platform.** Design, verify, synthesize, release, and deploy working RTL from natural language. 20 agents. 27 skills. 21 commands. 8 verified IP blocks. ```bash claude plugin add codejunkie99/Gateflow-Plugin @@ -66,7 +66,7 @@ claude plugin add codejunkie99/Gateflow-Plugin | `gf-auditor` | Plugin health checks | | `gf-pluginfixer` | Auto-fix plugin issues | -### 25 Skills +### 27 Skills | Category | Skills | |----------|--------| @@ -77,9 +77,11 @@ claude plugin add codejunkie99/Gateflow-Plugin | **Hardware** | `/gf-pcb`, `/gf-pnr`, `/gf-pinmap`, `/gf-fusesoc` | | **Learning** | `/gf-learn`, `/gf-learn-ctx` | | **Visualization** | `/gf-viz` | +| **Terminal** | `/gf-tui` | +| **Release** | `/gf-release` | | **Reference** | `tb-best-practices`, `/gf-build` | -### 19 Commands +### 21 Commands | Command | Description | |---------|-------------| @@ -102,6 +104,8 @@ claude plugin add codejunkie99/Gateflow-Plugin | `/gf-fusesoc` | FuseSoC .core files | | `/gf-demo` | Interactive demo | | `/gf-audit` | Plugin health audit | +| `/gf-tui` | Local terminal console | +| `/gf-release` | Release readiness validation | ### 8 Verified IP Blocks @@ -195,6 +199,12 @@ claude plugin add codejunkie99/Gateflow-Plugin # Target real hardware /gf-pnr synthesize and place for iCEBreaker + +# Prepare plugin release +/gf-release --check-only + +# Open terminal console +/gf-tui ``` --- diff --git a/plugins/gateflow/commands/gf-release.md b/plugins/gateflow/commands/gf-release.md new file mode 100644 index 0000000..7eea969 --- /dev/null +++ b/plugins/gateflow/commands/gf-release.md @@ -0,0 +1,47 @@ +--- +name: gf-release +description: Validate GateFlow plugin readiness and prepare a versioned release +argument-hint: "[--version X.Y.Z] [--check-only]" +allowed-tools: + - Bash + - Read + - Edit + - Write + - Glob + - Grep +--- + +# GateFlow Release Command + +Prepare and validate a GateFlow plugin release. + +## Usage + +``` +/gf-release --check-only +/gf-release --version 2.5.0 +``` + +## Workflow + +1. Invoke the `gf-release` skill. +2. Run the deterministic validator: + +```bash +python3 tools/validate_gateflow.py --version +``` + +3. If validation fails, fix the reported package wiring issues before tagging. +4. Confirm `plugins/gateflow/.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, + `README.md`, `plugins/gateflow/README.md`, `docs/gateflow.index`, and `releases.md` + all describe the same version and component counts. +5. For a real release, create a git tag and GitHub release only after validation passes. + +## Output + +Report: +- version being prepared +- component inventory +- validation failures, if any +- files that must change before release +- final tag command when ready diff --git a/plugins/gateflow/commands/gf-tui.md b/plugins/gateflow/commands/gf-tui.md new file mode 100644 index 0000000..42af760 --- /dev/null +++ b/plugins/gateflow/commands/gf-tui.md @@ -0,0 +1,43 @@ +--- +name: gf-tui +description: Open the GateFlow terminal console +argument-hint: "[--snapshot] [--json] [--plain]" +allowed-tools: + - Bash + - Read +--- + +# GateFlow TUI Command + +Open the local GateFlow terminal console. + +## Usage + +``` +/gf-tui +/gf-tui --snapshot +/gf-tui --json +``` + +## Execution + +Run from the repository root: + +```bash +python3 tools/gateflow_tui.py +``` + +Use snapshot mode when running in a non-interactive terminal: + +```bash +python3 tools/gateflow_tui.py --snapshot --plain +``` + +## What It Shows + +- plugin version and workspace path +- component inventory +- local hardware tool health +- map/release readiness +- quick actions for `/gf-doctor`, `/gf-map`, `/gf-viz`, `/gf-lint`, `/gf-sim`, + `/gf-formal`, and `/gf-release` diff --git a/plugins/gateflow/skills/gf-release/SKILL.md b/plugins/gateflow/skills/gf-release/SKILL.md new file mode 100644 index 0000000..06dccd9 --- /dev/null +++ b/plugins/gateflow/skills/gf-release/SKILL.md @@ -0,0 +1,101 @@ +--- +name: gf-release +description: > + GateFlow release readiness workflow. Validates plugin manifests, marketplace + metadata, docs index coverage, root mirrors, release notes, and component + counts before a version tag is created. Use when preparing, checking, or + cutting a GateFlow plugin release. +user-invocable: true +triggers: + - prepare GateFlow release + - cut a GateFlow release + - validate plugin release + - check marketplace metadata + - bump GateFlow version +--- +allowed-tools: + - Bash + - Read + - Edit + - Write + - Glob + - Grep + +# GF-Release -- Release Readiness + +Use this workflow before tagging or publishing GateFlow. + +## Required Inputs + +- Target version, for example `2.5.0` +- Whether this is check-only or a release-prep edit + +If no version is provided, inspect `plugins/gateflow/.claude-plugin/plugin.json` +and propose the next semver version based on the changes: + +| Change Type | Version Bump | +|---|---| +| Metadata, docs, or packaging fix | Patch | +| New command, skill, agent, IP, or board | Minor | +| Breaking plugin layout or workflow change | Major | + +## Release Checks + +Run the deterministic validator from the repo root: + +```bash +python3 tools/validate_gateflow.py --version +``` + +The validator must pass before creating a tag. It checks: +- plugin and marketplace JSON version consistency +- component counts in descriptions and READMEs +- `docs/gateflow.index` coverage for every command, skill, and agent +- root-level mirrors for every command-adjacent skill and agent file +- a release note entry for the target version + +## Prep Workflow + +1. Count actual components: + +```bash +find plugins/gateflow/agents -maxdepth 1 -name '*.md' | wc -l +find plugins/gateflow/skills -maxdepth 2 -name 'SKILL.md' | wc -l +find plugins/gateflow/commands -maxdepth 1 -name '*.md' | wc -l +``` + +2. Update version strings: +- `plugins/gateflow/.claude-plugin/plugin.json` +- `.claude-plugin/marketplace.json` + +3. Update release-facing docs: +- `README.md` +- `plugins/gateflow/README.md` +- `docs/gateflow.index` +- `releases.md` + +4. Run validator and focused tests: + +```bash +python3 -m unittest tests/test_validate_gateflow.py +python3 tools/validate_gateflow.py --version +``` + +5. Only after validation passes, tag and release: + +```bash +git tag v +gh release create v --title "GateFlow v" --notes-file +``` + +## Report Format + +```text +---GATEFLOW-RESULT--- +STATUS: PASS | FAIL +VERSION: +INVENTORY: agents, skills, commands, IP, boards +FILES: +DETAILS: +---END-GATEFLOW-RESULT--- +``` diff --git a/plugins/gateflow/skills/gf-tui/SKILL.md b/plugins/gateflow/skills/gf-tui/SKILL.md new file mode 100644 index 0000000..c2a5ac5 --- /dev/null +++ b/plugins/gateflow/skills/gf-tui/SKILL.md @@ -0,0 +1,64 @@ +--- +name: gf-tui +description: > + GateFlow terminal console inspired by OpenClaw local TUI workflows. Shows + workspace status, component inventory, tool health, map readiness, release + readiness, and command shortcuts from one terminal surface. +user-invocable: true +triggers: + - open GateFlow TUI + - show GateFlow dashboard + - terminal console + - CLI TUI + - OpenClaw-style TUI +--- +allowed-tools: + - Bash + - Read + +# GF-TUI -- Terminal Console + +Launch a local terminal console for GateFlow. + +## Modes + +| Mode | Command | Use When | +|---|---|---| +| Interactive | `python3 tools/gateflow_tui.py` | You are in a real TTY and want keyboard navigation | +| Snapshot | `python3 tools/gateflow_tui.py --snapshot --plain` | Logs, CI, or non-interactive terminals | +| JSON | `python3 tools/gateflow_tui.py --json` | Scripts need machine-readable state | + +## OpenClaw-Inspired Behavior + +- Local workspace mode by default; no gateway is required. +- TTY-aware styling with plain and JSON fallbacks. +- Health/status surfaces are visible before action. +- Commands are shown as operator shortcuts rather than hidden docs. +- Release and config repair loops stay inside the terminal workflow. + +## Console Sections + +1. **Workspace** — root path and plugin version. +2. **Inventory** — agents, skills, commands, IP blocks, and boards. +3. **Health** — doctor command, release validation, map readiness, Verilator, + Yosys, and SymbiYosys availability. +4. **Actions** — launch points for doctor, map, viz, lint, sim, formal, and + release workflows. + +## Guardrails + +- The TUI does not silently run destructive hardware actions. +- `/gf-flash` remains outside the default action list because programming a + board should stay explicit. +- Missing tools are shown as warnings, not failures, because GateFlow can still + generate and review RTL without every optional tool installed. + +## Verification + +Run: + +```bash +python3 -m unittest tests/test_gateflow_tui.py +python3 tools/gateflow_tui.py --snapshot --plain +python3 tools/gateflow_tui.py --json +``` diff --git a/releases.md b/releases.md index d8cc4ee..a783894 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,33 @@ # Releases +## 2.5.0 (2026-05-21) — CLI/TUI + Release Readiness + +GateFlow now ships with a deterministic release-prep path so versioned plugin +updates can be checked before tagging, plus a local terminal console for the +same status-first workflow users expect from modern agent CLIs. + +### New Features +- Added `/gf-tui` command for a local OpenClaw-style terminal console. +- Added `gf-tui` skill covering interactive, snapshot, and JSON console modes. +- Added `tools/gateflow_tui.py`, a stdlib Python TUI with TTY-aware rendering, + plain output, JSON output, component inventory, tool health, map readiness, + release readiness, and command shortcuts. +- Added `/gf-release` command for release validation and version-prep workflow. +- Added `gf-release` skill with semver guidance, required release checks, and + structured `GATEFLOW-RESULT` reporting. +- Added `tools/validate_gateflow.py` to validate plugin/marketplace versions, + README counts, docs index coverage, root mirrors, and release notes. +- Added focused unittest suites for the release validator and terminal console. + +### Package Consistency +- Synced plugin and marketplace metadata to `2.5.0`. +- Refreshed `docs/gateflow.index` so every current command, skill, agent, IP + block, board, hook, and release tool is discoverable. +- Completed root-level mirrors for all 20 agents and all 27 skills so + cross-tool installs have the same entrypoint coverage as the Claude plugin. +- Updated root and plugin READMEs to reflect 20 agents, 27 skills, 21 commands, + 8 IP blocks, and 4 boards. + ## 2.4.0 (2026-04-11) — Deep Skill Enrichment The biggest content update in GateFlow history. Every skill in the plugin has been enriched with research-backed reference material, actionable templates, and structured return formats. 37 files changed, +3,139 lines added. @@ -166,4 +194,3 @@ The biggest content update in GateFlow history. Every skill in the plugin has be ## 1.5.1 (2025-02-12) - Prompt-based hooks: PreToolUse (SV file safety), PostToolUse (lint nudge), Stop (smart completion gate). - diff --git a/skills/gf-cocotb/SKILL.md b/skills/gf-cocotb/SKILL.md new file mode 120000 index 0000000..fed562e --- /dev/null +++ b/skills/gf-cocotb/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-cocotb/SKILL.md \ No newline at end of file diff --git a/skills/gf-errors/SKILL.md b/skills/gf-errors/SKILL.md new file mode 120000 index 0000000..32c1239 --- /dev/null +++ b/skills/gf-errors/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-errors/SKILL.md \ No newline at end of file diff --git a/skills/gf-formal/SKILL.md b/skills/gf-formal/SKILL.md new file mode 120000 index 0000000..d3d3f76 --- /dev/null +++ b/skills/gf-formal/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-formal/SKILL.md \ No newline at end of file diff --git a/skills/gf-fusesoc/SKILL.md b/skills/gf-fusesoc/SKILL.md new file mode 120000 index 0000000..27d9b6a --- /dev/null +++ b/skills/gf-fusesoc/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-fusesoc/SKILL.md \ No newline at end of file diff --git a/skills/gf-ip-detect/SKILL.md b/skills/gf-ip-detect/SKILL.md new file mode 120000 index 0000000..acbbb37 --- /dev/null +++ b/skills/gf-ip-detect/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-ip-detect/SKILL.md \ No newline at end of file diff --git a/skills/gf-ip/SKILL.md b/skills/gf-ip/SKILL.md new file mode 120000 index 0000000..c6ec52e --- /dev/null +++ b/skills/gf-ip/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-ip/SKILL.md \ No newline at end of file diff --git a/skills/gf-learn-ctx/SKILL.md b/skills/gf-learn-ctx/SKILL.md new file mode 120000 index 0000000..ecbcae9 --- /dev/null +++ b/skills/gf-learn-ctx/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-learn-ctx/SKILL.md \ No newline at end of file diff --git a/skills/gf-pcb/SKILL.md b/skills/gf-pcb/SKILL.md new file mode 120000 index 0000000..2b44a91 --- /dev/null +++ b/skills/gf-pcb/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-pcb/SKILL.md \ No newline at end of file diff --git a/skills/gf-pinmap/SKILL.md b/skills/gf-pinmap/SKILL.md new file mode 120000 index 0000000..7994def --- /dev/null +++ b/skills/gf-pinmap/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-pinmap/SKILL.md \ No newline at end of file diff --git a/skills/gf-pnr/SKILL.md b/skills/gf-pnr/SKILL.md new file mode 120000 index 0000000..e0c61a4 --- /dev/null +++ b/skills/gf-pnr/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-pnr/SKILL.md \ No newline at end of file diff --git a/skills/gf-project/SKILL.md b/skills/gf-project/SKILL.md new file mode 120000 index 0000000..8030f22 --- /dev/null +++ b/skills/gf-project/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-project/SKILL.md \ No newline at end of file diff --git a/skills/gf-protocols/SKILL.md b/skills/gf-protocols/SKILL.md new file mode 120000 index 0000000..57e620f --- /dev/null +++ b/skills/gf-protocols/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-protocols/SKILL.md \ No newline at end of file diff --git a/skills/gf-release/SKILL.md b/skills/gf-release/SKILL.md new file mode 120000 index 0000000..2e4948f --- /dev/null +++ b/skills/gf-release/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-release/SKILL.md \ No newline at end of file diff --git a/skills/gf-synth/SKILL.md b/skills/gf-synth/SKILL.md new file mode 120000 index 0000000..ee8bba2 --- /dev/null +++ b/skills/gf-synth/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-synth/SKILL.md \ No newline at end of file diff --git a/skills/gf-tui/SKILL.md b/skills/gf-tui/SKILL.md new file mode 120000 index 0000000..96dc656 --- /dev/null +++ b/skills/gf-tui/SKILL.md @@ -0,0 +1 @@ +../../plugins/gateflow/skills/gf-tui/SKILL.md \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..f839d02 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""GateFlow test package.""" diff --git a/tests/test_gateflow_tui.py b/tests/test_gateflow_tui.py new file mode 100644 index 0000000..1115f83 --- /dev/null +++ b/tests/test_gateflow_tui.py @@ -0,0 +1,60 @@ +import importlib.util +import subprocess +import sys +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +TUI = ROOT / "tools" / "gateflow_tui.py" + + +def load_tui(): + spec = importlib.util.spec_from_file_location("gateflow_tui", TUI) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class GateFlowTuiTests(unittest.TestCase): + def test_snapshot_contains_openclaw_style_operator_surfaces(self): + tui = load_tui() + + snapshot = tui.render_snapshot(ROOT, plain=True) + + self.assertIn("GateFlow Terminal", snapshot) + self.assertIn("Workspace", snapshot) + self.assertIn("Health", snapshot) + self.assertIn("Actions", snapshot) + self.assertIn("/gf-doctor", snapshot) + self.assertIn("/gf-release", snapshot) + self.assertIn("OpenClaw-style local mode", snapshot) + + def test_json_mode_returns_machine_readable_inventory(self): + tui = load_tui() + + payload = tui.build_payload(ROOT) + + self.assertEqual("gateflow", payload["plugin"]["name"]) + self.assertEqual("2.5.0", payload["plugin"]["version"]) + self.assertEqual(20, payload["inventory"]["agents"]) + self.assertEqual(27, payload["inventory"]["skills"]) + self.assertEqual(21, payload["inventory"]["commands"]) + self.assertIn("doctor", payload["health"]) + + def test_cli_snapshot_mode(self): + completed = subprocess.run( + [sys.executable, str(TUI), "--snapshot", "--plain"], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + ) + + self.assertEqual("", completed.stderr) + self.assertEqual(0, completed.returncode) + self.assertIn("GateFlow Terminal", completed.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_validate_gateflow.py b/tests/test_validate_gateflow.py new file mode 100644 index 0000000..500cfa2 --- /dev/null +++ b/tests/test_validate_gateflow.py @@ -0,0 +1,57 @@ +import importlib.util +import subprocess +import sys +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +VALIDATOR = ROOT / "tools" / "validate_gateflow.py" + + +def load_validator(): + spec = importlib.util.spec_from_file_location("validate_gateflow", VALIDATOR) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class GateFlowValidatorTests(unittest.TestCase): + def test_inventory_matches_release_target(self): + validator = load_validator() + + inventory = validator.discover_inventory(ROOT) + + self.assertEqual(20, len(inventory.agents)) + self.assertEqual(27, len(inventory.skills)) + self.assertEqual(21, len(inventory.commands)) + self.assertEqual(8, len(inventory.ip_blocks)) + self.assertEqual(4, len(inventory.boards)) + self.assertIn("gf-release", inventory.skills) + self.assertIn("gf-release", inventory.commands) + self.assertIn("gf-tui", inventory.skills) + self.assertIn("gf-tui", inventory.commands) + + def test_repository_passes_release_checks(self): + validator = load_validator() + + result = validator.run_checks(ROOT, expected_version="2.5.0") + + self.assertEqual([], result.errors) + + def test_cli_reports_success(self): + completed = subprocess.run( + [sys.executable, str(VALIDATOR), "--version", "2.5.0"], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + ) + + self.assertEqual("", completed.stderr) + self.assertEqual(0, completed.returncode) + self.assertIn("PASS GateFlow validation", completed.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/gateflow_tui.py b/tools/gateflow_tui.py new file mode 100755 index 0000000..0501b24 --- /dev/null +++ b/tools/gateflow_tui.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""OpenClaw-style terminal console for GateFlow.""" + +from __future__ import annotations + +import argparse +import curses +import importlib.util +import json +import os +import shutil +import sys +from pathlib import Path + + +ACCENT = "\033[38;2;255;90;45m" +SUCCESS = "\033[38;2;47;191;113m" +WARN = "\033[38;2;255;176;32m" +ERROR = "\033[38;2;226;61;45m" +MUTED = "\033[38;2;139;127;119m" +RESET = "\033[0m" + + +def _load_validator(): + path = Path(__file__).with_name("validate_gateflow.py") + spec = importlib.util.spec_from_file_location("validate_gateflow", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _read_json(path: Path) -> dict: + try: + return json.loads(path.read_text(encoding="utf-8")) + except (FileNotFoundError, json.JSONDecodeError): + return {} + + +def _tool_status(name: str) -> str: + return "installed" if shutil.which(name) else "missing" + + +def _map_status(root: Path) -> dict: + map_file = root / ".gateflow" / "map" / "CODEBASE.md" + if map_file.exists(): + return {"status": "ready", "detail": str(map_file)} + return {"status": "missing", "detail": "run /gf-map"} + + +def build_payload(root: Path) -> dict: + root = root.resolve() + validator = _load_validator() + inventory = validator.discover_inventory(root) + release = validator.run_checks(root, expected_version="2.5.0") + plugin = _read_json(root / "plugins" / "gateflow" / ".claude-plugin" / "plugin.json") + + health = { + "doctor": "ready" if (root / "plugins/gateflow/commands/gf-doctor.md").exists() else "missing", + "release": "ready" if not release.errors else f"{len(release.errors)} issues", + "map": _map_status(root), + "verilator": _tool_status("verilator"), + "yosys": _tool_status("yosys"), + "sby": _tool_status("sby"), + } + + actions = [ + ("/gf-doctor", "Check local hardware toolchain"), + ("/gf-map", "Build RTL architecture map"), + ("/gf-viz", "Explore hierarchy and FSM views"), + ("/gf-lint", "Run Verilator lint"), + ("/gf-sim", "Run simulation"), + ("/gf-formal", "Run SymbiYosys proofs"), + ("/gf-release", "Validate plugin release readiness"), + ] + + return { + "mode": "OpenClaw-style local mode", + "workspace": str(root), + "plugin": {"name": plugin.get("name", "gateflow"), "version": plugin.get("version", "unknown")}, + "inventory": { + "agents": len(inventory.agents), + "skills": len(inventory.skills), + "commands": len(inventory.commands), + "ip_blocks": len(inventory.ip_blocks), + "boards": len(inventory.boards), + }, + "health": health, + "actions": [{"command": command, "description": description} for command, description in actions], + } + + +def _color(text: str, color: str, plain: bool) -> str: + return text if plain or os.environ.get("NO_COLOR") else f"{color}{text}{RESET}" + + +def _status(value: str | dict, plain: bool) -> str: + if isinstance(value, dict): + value = value.get("status", "unknown") + if value in {"ready", "installed"}: + return _color(value, SUCCESS, plain) + if value in {"missing", "unknown"} or "issues" in value: + return _color(value, WARN, plain) + return value + + +def render_snapshot(root: Path, plain: bool = False) -> str: + payload = build_payload(root) + inv = payload["inventory"] + health = payload["health"] + actions = payload["actions"] + + lines = [ + _color("GateFlow Terminal", ACCENT, plain), + f"{payload['mode']}", + "", + "Workspace", + f" path {payload['workspace']}", + f" plugin {payload['plugin']['name']} {payload['plugin']['version']}", + "", + "Inventory", + f" agents {inv['agents']}", + f" skills {inv['skills']}", + f" commands {inv['commands']}", + f" IP blocks {inv['ip_blocks']}", + f" boards {inv['boards']}", + "", + "Health", + f" doctor {_status(health['doctor'], plain)}", + f" release {_status(health['release'], plain)}", + f" map {_status(health['map'], plain)} ({health['map']['detail']})", + f" verilator {_status(health['verilator'], plain)}", + f" yosys {_status(health['yosys'], plain)}", + f" sby {_status(health['sby'], plain)}", + "", + "Actions", + ] + width = max(len(action["command"]) for action in actions) + for index, action in enumerate(actions, start=1): + lines.append(f" {index}. {action['command']:<{width}} {action['description']}") + lines.extend(["", "Run without --snapshot in a TTY for interactive navigation. Press q to exit."]) + return "\n".join(lines) + "\n" + + +def _draw(stdscr, payload: dict, selected: int, message: str) -> None: + stdscr.erase() + height, width = stdscr.getmaxyx() + actions = payload["actions"] + inv = payload["inventory"] + health = payload["health"] + left_width = min(34, max(24, width // 3)) + + def add(y: int, x: int, text: str, attr=0) -> None: + if 0 <= y < height: + stdscr.addnstr(y, x, text, max(0, width - x - 1), attr) + + curses.init_pair(1, curses.COLOR_RED, -1) + curses.init_pair(2, curses.COLOR_GREEN, -1) + curses.init_pair(3, curses.COLOR_YELLOW, -1) + curses.init_pair(4, curses.COLOR_CYAN, -1) + accent = curses.color_pair(1) | curses.A_BOLD + ok = curses.color_pair(2) + warn = curses.color_pair(3) + muted = curses.A_DIM + + add(0, 0, " GateFlow Terminal ", accent) + add(0, 20, payload["mode"], muted) + add(1, 0, "─" * (width - 1), muted) + + add(3, 2, "Actions", curses.A_BOLD) + for idx, action in enumerate(actions): + attr = curses.A_REVERSE if idx == selected else 0 + add(5 + idx, 2, f"{idx + 1}. {action['command']}", attr) + add(5 + idx, 16, action["description"], attr) + + x = left_width + 2 + add(3, x, "Workspace", curses.A_BOLD) + add(5, x, payload["workspace"]) + add(6, x, f"{payload['plugin']['name']} {payload['plugin']['version']}", accent) + + add(8, x, "Inventory", curses.A_BOLD) + add(10, x, f"{inv['agents']} agents {inv['skills']} skills {inv['commands']} commands") + add(11, x, f"{inv['ip_blocks']} IP blocks {inv['boards']} boards") + + add(13, x, "Health", curses.A_BOLD) + rows = [ + ("doctor", health["doctor"]), + ("release", health["release"]), + ("map", health["map"]["status"]), + ("verilator", health["verilator"]), + ("yosys", health["yosys"]), + ("sby", health["sby"]), + ] + for offset, (name, value) in enumerate(rows): + attr = ok if value in {"ready", "installed"} else warn + add(15 + offset, x, f"{name:<10} {value}", attr) + + footer = message or "↑/↓ select Enter show command r refresh q quit" + add(height - 2, 0, "─" * (width - 1), muted) + add(height - 1, 1, footer, curses.color_pair(4)) + stdscr.refresh() + + +def run_interactive(root: Path) -> int: + payload = build_payload(root) + + def wrapped(stdscr) -> None: + curses.curs_set(0) + stdscr.keypad(True) + selected = 0 + message = "" + while True: + _draw(stdscr, payload, selected, message) + key = stdscr.getch() + if key in {ord("q"), 27}: + break + if key in {curses.KEY_UP, ord("k")}: + selected = max(0, selected - 1) + elif key in {curses.KEY_DOWN, ord("j")}: + selected = min(len(payload["actions"]) - 1, selected + 1) + elif key in {ord("\n"), curses.KEY_ENTER, 10, 13}: + action = payload["actions"][selected] + message = f"Run in Claude Code: {action['command']} ({action['description']})" + elif key == ord("r"): + payload.update(build_payload(root)) + message = "Refreshed" + + curses.wrapper(wrapped) + return 0 + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path.cwd(), help="Repository root") + parser.add_argument("--snapshot", action="store_true", help="Print a static console view") + parser.add_argument("--json", action="store_true", help="Print machine-readable state") + parser.add_argument("--plain", action="store_true", help="Disable ANSI styling") + parser.add_argument("--local", action="store_true", help="Use local workspace mode") + args = parser.parse_args(argv) + + if args.json: + print(json.dumps(build_payload(args.root), indent=2, sort_keys=True)) + return 0 + if args.snapshot or not sys.stdin.isatty(): + print(render_snapshot(args.root, plain=args.plain), end="") + return 0 + return run_interactive(args.root) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/validate_gateflow.py b/tools/validate_gateflow.py new file mode 100755 index 0000000..c366bf6 --- /dev/null +++ b/tools/validate_gateflow.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Validate GateFlow plugin release metadata and package wiring.""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +from pathlib import Path +from typing import NamedTuple + + +class Inventory(NamedTuple): + agents: list[str] + skills: list[str] + commands: list[str] + ip_blocks: list[str] + boards: list[str] + + +class ValidationResult(NamedTuple): + inventory: Inventory + errors: list[str] + warnings: list[str] + + +def discover_inventory(root: Path) -> Inventory: + plugin = root / "plugins" / "gateflow" + agents = sorted(path.stem for path in (plugin / "agents").glob("*.md")) + skills = sorted(path.parent.name for path in (plugin / "skills").glob("*/SKILL.md")) + commands = sorted(path.stem for path in (plugin / "commands").glob("*.md")) + ip_blocks = sorted(path.name for path in (plugin / "ip").iterdir() if path.is_dir()) + boards = sorted(path.name for path in (plugin / "boards").iterdir() if path.is_dir()) + return Inventory(agents, skills, commands, ip_blocks, boards) + + +def _read_json(path: Path, errors: list[str]) -> dict: + try: + with path.open(encoding="utf-8") as handle: + return json.load(handle) + except FileNotFoundError: + errors.append(f"missing JSON file: {path}") + except json.JSONDecodeError as exc: + errors.append(f"invalid JSON in {path}: {exc}") + return {} + + +def _read_text(path: Path, errors: list[str]) -> str: + try: + return path.read_text(encoding="utf-8") + except FileNotFoundError: + errors.append(f"missing text file: {path}") + return "" + + +def _expect_text(path: Path, text: str, needle: str, errors: list[str]) -> None: + if needle not in text: + errors.append(f"{path} is missing expected text: {needle}") + + +def _check_manifest(root: Path, inventory: Inventory, version: str, errors: list[str]) -> None: + plugin_path = root / "plugins" / "gateflow" / ".claude-plugin" / "plugin.json" + market_path = root / ".claude-plugin" / "marketplace.json" + plugin_json = _read_json(plugin_path, errors) + market_json = _read_json(market_path, errors) + + if plugin_json.get("version") != version: + errors.append(f"{plugin_path} version is {plugin_json.get('version')}, expected {version}") + + market_plugin = (market_json.get("plugins") or [{}])[0] + if market_plugin.get("version") != version: + errors.append(f"{market_path} plugin version is {market_plugin.get('version')}, expected {version}") + + fragments = [ + f"{len(inventory.agents)} agents", + f"{len(inventory.skills)} skills", + f"{len(inventory.ip_blocks)} IP blocks", + ] + for fragment in fragments: + if fragment not in plugin_json.get("description", ""): + errors.append(f"{plugin_path} description missing '{fragment}'") + if fragment not in market_plugin.get("description", ""): + errors.append(f"{market_path} description missing '{fragment}'") + + +def _check_docs(root: Path, inventory: Inventory, version: str, errors: list[str]) -> None: + readme_path = root / "README.md" + plugin_readme_path = root / "plugins" / "gateflow" / "README.md" + releases_path = root / "releases.md" + index_path = root / "docs" / "gateflow.index" + + readme = _read_text(readme_path, errors) + plugin_readme = _read_text(plugin_readme_path, errors) + releases = _read_text(releases_path, errors) + index = _read_text(index_path, errors) + + _expect_text(readme_path, readme, f"### Skills ({len(inventory.skills)})", errors) + _expect_text(readme_path, readme, f"### Agents ({len(inventory.agents)})", errors) + _expect_text(readme_path, readme, f"### Commands ({len(inventory.commands)})", errors) + _expect_text(plugin_readme_path, plugin_readme, f"### {len(inventory.agents)} Agents", errors) + _expect_text(plugin_readme_path, plugin_readme, f"### {len(inventory.skills)} Skills", errors) + _expect_text(plugin_readme_path, plugin_readme, f"### {len(inventory.commands)} Commands", errors) + _expect_text(releases_path, releases, f"## {version} ", errors) + + for agent in inventory.agents: + _expect_text(index_path, index, f"agents/{agent}.md", errors) + for skill in inventory.skills: + _expect_text(index_path, index, f"skills/{skill}/SKILL.md", errors) + for command in inventory.commands: + _expect_text(index_path, index, f"commands/{command}.md", errors) + + +def _check_root_mirrors(root: Path, inventory: Inventory, errors: list[str]) -> None: + for agent in inventory.agents: + mirror = root / "agents" / f"{agent}.md" + expected = f"../plugins/gateflow/agents/{agent}.md" + _check_symlink(mirror, expected, errors) + + for skill in inventory.skills: + mirror = root / "skills" / skill / "SKILL.md" + expected = f"../../plugins/gateflow/skills/{skill}/SKILL.md" + _check_symlink(mirror, expected, errors) + + +def _check_symlink(path: Path, expected_target: str, errors: list[str]) -> None: + if not path.is_symlink(): + errors.append(f"{path} is missing or is not a symlink") + return + target = os.readlink(path) + if target != expected_target: + errors.append(f"{path} points to {target}, expected {expected_target}") + + +def run_checks(root: Path, expected_version: str = "2.5.0") -> ValidationResult: + root = root.resolve() + inventory = discover_inventory(root) + errors: list[str] = [] + warnings: list[str] = [] + + _check_manifest(root, inventory, expected_version, errors) + _check_docs(root, inventory, expected_version, errors) + _check_root_mirrors(root, inventory, errors) + + return ValidationResult(inventory, errors, warnings) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path.cwd(), help="Repository root") + parser.add_argument("--version", default="2.5.0", help="Expected release version") + args = parser.parse_args(argv) + + result = run_checks(args.root, expected_version=args.version) + inventory = result.inventory + print( + "GateFlow inventory: " + f"{len(inventory.agents)} agents, " + f"{len(inventory.skills)} skills, " + f"{len(inventory.commands)} commands, " + f"{len(inventory.ip_blocks)} IP blocks, " + f"{len(inventory.boards)} boards" + ) + + if result.errors: + print("FAIL GateFlow validation") + for error in result.errors: + print(f"- {error}") + return 1 + + print("PASS GateFlow validation") + return 0 + + +if __name__ == "__main__": + sys.exit(main())