first commit
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
// This file holds the first-run bootstrap: on the first launch (per pigo home),
|
||||
// the built-in skill collections in Manifest are copied into the user's skills
|
||||
// directory so they load as /skill-name commands with no manual install.
|
||||
//
|
||||
// The flow is designed to be silent and non-blocking (a failed bootstrap must
|
||||
// never stop pigo from starting) and idempotent (skills already present are left
|
||||
// untouched, so a user's edits are never clobbered). A state file under the pigo
|
||||
// home records which collections+versions have been installed, so a completed
|
||||
// bootstrap is skipped on later launches and a bumped collection Version
|
||||
// re-triggers installation of any still-missing skills.
|
||||
package builtinskills
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// stateFileName is the bootstrap record kept under the pigo home directory. It
|
||||
// maps a collection name to the version last installed, so Bootstrap can decide
|
||||
// per collection whether work remains.
|
||||
const stateFileName = "builtin-skills.json"
|
||||
|
||||
// state is the on-disk bootstrap record: collection name -> installed version.
|
||||
type state struct {
|
||||
Installed map[string]string `json:"installed"`
|
||||
}
|
||||
|
||||
// Bootstrap installs any not-yet-installed built-in skill collections into
|
||||
// skillsDir, recording progress under pigoHome. It is safe to call on every
|
||||
// launch: collections already recorded at their current Version are skipped, and
|
||||
// individual skills whose target directory already exists are left untouched.
|
||||
//
|
||||
// It never returns an error — bootstrap is best-effort and must not block
|
||||
// startup — but writes a one-line note per failure to logw when logw is non-nil
|
||||
// (callers pass a writer only in debug/verbose mode, keeping normal runs silent).
|
||||
// Empty pigoHome or skillsDir disables bootstrap (home unresolved): nothing is
|
||||
// installed and nothing is logged.
|
||||
func Bootstrap(pigoHome, skillsDir string, logw io.Writer) {
|
||||
bootstrap(Manifest(), pigoHome, skillsDir, logw)
|
||||
}
|
||||
|
||||
// bootstrap is the testable core of Bootstrap, parameterized on the manifest so
|
||||
// tests can inject synthetic collections without touching the embedded set.
|
||||
func bootstrap(sets []Set, pigoHome, skillsDir string, logw io.Writer) {
|
||||
logf := func(format string, a ...any) {
|
||||
if logw != nil {
|
||||
fmt.Fprintf(logw, format, a...)
|
||||
}
|
||||
}
|
||||
if pigoHome == "" || skillsDir == "" {
|
||||
return // home unresolved; nothing we can safely do
|
||||
}
|
||||
|
||||
st := loadState(filepath.Join(pigoHome, stateFileName))
|
||||
|
||||
changed := false
|
||||
for _, set := range sets {
|
||||
// Per-collection version gate: once recorded at this Version the whole
|
||||
// set is skipped, so a skill the user later *deletes* is not restored
|
||||
// (only a Version bump re-triggers install of still-missing skills).
|
||||
// This is deliberate — silently re-adding a removed skill would fight
|
||||
// the user's choice. An empty Version is never "already installed"
|
||||
// (the zero-value lookup would otherwise equal it and skip forever),
|
||||
// so a set with a blank Version always attempts install.
|
||||
if set.Version != "" && st.Installed[set.Name] == set.Version {
|
||||
continue
|
||||
}
|
||||
// installSet reports whether the collection is fully installed (every
|
||||
// skill now present on disk). Only then do we record the version, so a
|
||||
// partial failure re-runs next launch (satisfying "retry on next run").
|
||||
if installSet(set, skillsDir, logf) {
|
||||
if st.Installed == nil {
|
||||
st.Installed = map[string]string{}
|
||||
}
|
||||
st.Installed[set.Name] = set.Version
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := saveState(pigoHome, st); err != nil {
|
||||
logf("builtinskills: could not save state: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// installSet copies each skill in the set into skillsDir/<name>/, skipping any
|
||||
// whose target directory already exists (never clobbering a user's copy). A
|
||||
// single skill's failure does not abort the rest. It returns true only when
|
||||
// every named skill is present on disk afterward, so the caller can decide
|
||||
// whether to record the collection as fully installed.
|
||||
func installSet(set Set, skillsDir string, logf func(string, ...any)) bool {
|
||||
if err := os.MkdirAll(skillsDir, 0o755); err != nil {
|
||||
logf("builtinskills: create skills dir %q: %v\n", skillsDir, err)
|
||||
return false
|
||||
}
|
||||
allPresent := true
|
||||
for _, name := range set.Skills {
|
||||
dest := filepath.Join(skillsDir, name)
|
||||
if _, err := os.Stat(dest); err == nil {
|
||||
continue // already present (user copy or prior install): leave it
|
||||
}
|
||||
src := path.Join(set.Root, name)
|
||||
if err := copyTree(set.FS, src, dest); err != nil {
|
||||
logf("builtinskills: install %q: %v\n", name, err)
|
||||
// Remove a half-written tree so a later run starts clean.
|
||||
_ = os.RemoveAll(dest)
|
||||
allPresent = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
return allPresent
|
||||
}
|
||||
|
||||
// copyTree copies the directory tree rooted at src within srcFS to the on-disk
|
||||
// directory dest, creating parents as needed. Files are written 0o644 and
|
||||
// directories 0o755.
|
||||
func copyTree(srcFS fs.FS, src, dest string) error {
|
||||
entries, err := fs.ReadDir(srcFS, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range entries {
|
||||
srcPath := path.Join(src, e.Name())
|
||||
destPath := filepath.Join(dest, e.Name())
|
||||
if e.IsDir() {
|
||||
if err := copyTree(srcFS, srcPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
data, err := fs.ReadFile(srcFS, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(destPath, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadState reads the bootstrap record at p. A missing or malformed file yields
|
||||
// an empty state (treated as "nothing installed"), so a corrupt record just
|
||||
// re-triggers a — idempotent — install rather than failing.
|
||||
func loadState(p string) state {
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return state{}
|
||||
}
|
||||
var st state
|
||||
if json.Unmarshal(data, &st) != nil {
|
||||
return state{}
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
// saveState writes the bootstrap record under pigoHome, creating the home
|
||||
// directory if needed.
|
||||
func saveState(pigoHome string, st state) error {
|
||||
if err := os.MkdirAll(pigoHome, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(st, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filepath.Join(pigoHome, stateFileName), data, 0o644)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package builtinskills
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
// mapSet builds a Set backed by an in-memory tree so bootstrap can be exercised
|
||||
// without the embedded FS. Each skill gets a SKILL.md plus one support file.
|
||||
func mapSet(name, version string, skills ...string) Set {
|
||||
files := fstest.MapFS{}
|
||||
for _, s := range skills {
|
||||
files["skills/"+s+"/SKILL.md"] = &fstest.MapFile{Data: []byte("---\nname: " + s + "\n---\nbody")}
|
||||
files["skills/"+s+"/support.txt"] = &fstest.MapFile{Data: []byte("aux for " + s)}
|
||||
}
|
||||
return Set{Name: name, Version: version, Root: "skills", Skills: skills, FS: files}
|
||||
}
|
||||
|
||||
// TestBootstrapInstallsSkills verifies a fresh bootstrap lays every named skill
|
||||
// down under skillsDir with its SKILL.md and support files intact.
|
||||
func TestBootstrapInstallsSkills(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
set := mapSet("test-set", "v1", "alpha", "beta")
|
||||
|
||||
bootstrap([]Set{set}, home, skills, nil)
|
||||
|
||||
for _, name := range []string{"alpha", "beta"} {
|
||||
md := filepath.Join(skills, name, "SKILL.md")
|
||||
if _, err := os.Stat(md); err != nil {
|
||||
t.Errorf("expected %s installed: %v", md, err)
|
||||
}
|
||||
aux := filepath.Join(skills, name, "support.txt")
|
||||
if _, err := os.Stat(aux); err != nil {
|
||||
t.Errorf("expected support file %s: %v", aux, err)
|
||||
}
|
||||
}
|
||||
// State recorded so a re-run is a no-op.
|
||||
if _, err := os.Stat(filepath.Join(home, stateFileName)); err != nil {
|
||||
t.Errorf("expected state file written: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapSkipsWhenAlreadyInstalled verifies a second bootstrap at the same
|
||||
// version does not touch an existing (possibly user-edited) skill.
|
||||
func TestBootstrapSkipsWhenAlreadyInstalled(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
set := mapSet("test-set", "v1", "alpha")
|
||||
|
||||
bootstrap([]Set{set}, home, skills, nil)
|
||||
|
||||
// Simulate a user edit, then re-run: the edit must survive.
|
||||
md := filepath.Join(skills, "alpha", "SKILL.md")
|
||||
if err := os.WriteFile(md, []byte("user edited"), 0o644); err != nil {
|
||||
t.Fatalf("edit: %v", err)
|
||||
}
|
||||
bootstrap([]Set{set}, home, skills, nil)
|
||||
|
||||
got, err := os.ReadFile(md)
|
||||
if err != nil {
|
||||
t.Fatalf("read: %v", err)
|
||||
}
|
||||
if string(got) != "user edited" {
|
||||
t.Errorf("SKILL.md = %q, want user edit preserved", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapDoesNotClobberPreexistingSkill verifies a skill directory that
|
||||
// already exists before the first bootstrap is left untouched (never overwritten).
|
||||
func TestBootstrapDoesNotClobberPreexistingSkill(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
// Pre-place a user's own "alpha" skill.
|
||||
if err := os.MkdirAll(filepath.Join(skills, "alpha"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mine := filepath.Join(skills, "alpha", "SKILL.md")
|
||||
if err := os.WriteFile(mine, []byte("mine"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bootstrap([]Set{mapSet("test-set", "v1", "alpha", "beta")}, home, skills, nil)
|
||||
|
||||
got, _ := os.ReadFile(mine)
|
||||
if string(got) != "mine" {
|
||||
t.Errorf("preexisting alpha overwritten: %q", got)
|
||||
}
|
||||
// The other skill still installs.
|
||||
if _, err := os.Stat(filepath.Join(skills, "beta", "SKILL.md")); err != nil {
|
||||
t.Errorf("beta should install alongside preexisting alpha: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapVersionBumpReinstallsMissing verifies bumping a set's Version
|
||||
// re-triggers installation of skills that are missing, without disturbing ones
|
||||
// already present.
|
||||
func TestBootstrapVersionBumpReinstallsMissing(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
|
||||
bootstrap([]Set{mapSet("test-set", "v1", "alpha")}, home, skills, nil)
|
||||
|
||||
// v2 adds "beta"; alpha is already present and must stay.
|
||||
bootstrap([]Set{mapSet("test-set", "v2", "alpha", "beta")}, home, skills, nil)
|
||||
|
||||
if _, err := os.Stat(filepath.Join(skills, "beta", "SKILL.md")); err != nil {
|
||||
t.Errorf("version bump should install newly added beta: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapEmptyHomeIsNoop verifies an unresolved home/skills dir disables
|
||||
// bootstrap without panicking or erroring.
|
||||
func TestBootstrapEmptyHomeIsNoop(t *testing.T) {
|
||||
bootstrap([]Set{mapSet("s", "v1", "alpha")}, "", "", nil)
|
||||
bootstrap([]Set{mapSet("s", "v1", "alpha")}, t.TempDir(), "", nil)
|
||||
}
|
||||
|
||||
// TestBootstrapEmptyVersionInstalls verifies a Set with a blank Version is not
|
||||
// mistaken for "already installed" (the zero-value state lookup also equals "")
|
||||
// and so its skills are installed on a fresh run.
|
||||
func TestBootstrapEmptyVersionInstalls(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
|
||||
bootstrap([]Set{mapSet("test-set", "", "alpha")}, home, skills, nil)
|
||||
|
||||
if _, err := os.Stat(filepath.Join(skills, "alpha", "SKILL.md")); err != nil {
|
||||
t.Errorf("blank-version set should still install: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestManifestEmbedsAllSkills verifies every skill named in the real manifest is
|
||||
// actually embedded (each has a SKILL.md), catching a missing-copy regression.
|
||||
func TestManifestEmbedsAllSkills(t *testing.T) {
|
||||
for _, set := range Manifest() {
|
||||
for _, name := range set.Skills {
|
||||
md := path.Join(set.Root, name, "SKILL.md")
|
||||
if _, err := set.FS.Open(md); err != nil {
|
||||
t.Errorf("%s/%s: embedded SKILL.md missing: %v", set.Name, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapInstallsRealManifest verifies the embedded manifest installs its
|
||||
// full skill set into a temp skills dir — the end-to-end offline install path.
|
||||
func TestBootstrapInstallsRealManifest(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
skills := t.TempDir()
|
||||
|
||||
Bootstrap(home, skills, nil)
|
||||
|
||||
for _, set := range Manifest() {
|
||||
for _, name := range set.Skills {
|
||||
if _, err := os.Stat(filepath.Join(skills, name, "SKILL.md")); err != nil {
|
||||
t.Errorf("skill %q not installed: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Package builtinskills embeds a curated set of skills into the pigo binary and
|
||||
// installs them into the user's skills directory on first run. This makes a
|
||||
// baseline of workflow skills (the goal-workflow set plus a couple of standalone
|
||||
// skills) available as /skill-name commands out of the box, with no manual
|
||||
// `pigo install` step and no network access — the skill trees are compiled into
|
||||
// the binary via //go:embed.
|
||||
//
|
||||
// The design is deliberately generic (a manifest of skill *sets*) so future
|
||||
// collections can be added by appending a Set to Manifest and embedding their
|
||||
// files, without touching the bootstrap/first-run logic in Bootstrap.
|
||||
package builtinskills
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
// skillsFS holds the embedded skill trees under skills/<name>/. The `all:`
|
||||
// prefix is required so files whose names begin with '_' or '.' (e.g.
|
||||
// weather/_meta.json) are embedded too — the default pattern skips them.
|
||||
//
|
||||
//go:embed all:skills
|
||||
var skillsFS embed.FS
|
||||
|
||||
// Set is one collection of built-in skills sharing a provenance and version.
|
||||
// Bootstrap installs every skill named in a Set from FS, rooted at Root (the
|
||||
// path within FS holding the per-skill directories). Adding a new collection is
|
||||
// a matter of appending a Set here and embedding its files — the install and
|
||||
// first-run machinery is collection-agnostic.
|
||||
type Set struct {
|
||||
// Name identifies the collection (e.g. "goal-workflow"), used in the
|
||||
// bootstrap state record and diagnostics.
|
||||
Name string
|
||||
// Version marks the collection's revision. It is recorded in the bootstrap
|
||||
// state file; a Version newer than the recorded one re-triggers install of
|
||||
// any still-missing skills on the next run.
|
||||
Version string
|
||||
// Root is the directory within FS that contains the per-skill subdirectories.
|
||||
Root string
|
||||
// Skills lists the skill directory names under Root to install. Each must be
|
||||
// a "<name>/" directory holding a SKILL.md.
|
||||
Skills []string
|
||||
// FS is the filesystem the skills are read from. Production sets use the
|
||||
// embedded skillsFS; tests can supply an fstest.MapFS with a synthetic tree.
|
||||
FS fs.FS
|
||||
}
|
||||
|
||||
// goalWorkflowSkills is the goal-workflow set (https://goal.rpcx.io) minus
|
||||
// humanize-it (intentionally excluded), plus the two standalone skills
|
||||
// architecture-diagram and weather.
|
||||
var goalWorkflowSkills = []string{
|
||||
// goal-workflow core
|
||||
"prd", "prd-to-spec", "to-issues", "review-it", "ship-it",
|
||||
// goal-workflow bonus (humanize-it intentionally excluded)
|
||||
"insight-diagram", "refactor", "modern-go", "note-it",
|
||||
"code-to-spec", "smell", "loop-it", "to-design", "graph",
|
||||
// standalone additions
|
||||
"architecture-diagram", "weather",
|
||||
}
|
||||
|
||||
// Manifest is the single source of truth for the built-in skill collections
|
||||
// installed on first run. It seeds one collection today; adding a Set is all
|
||||
// that is needed to bundle another collection.
|
||||
func Manifest() []Set {
|
||||
return []Set{
|
||||
{
|
||||
Name: "goal-workflow",
|
||||
Version: "2026-07-24",
|
||||
Root: "skills",
|
||||
Skills: goalWorkflowSkills,
|
||||
FS: skillsFS,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: architecture-diagram
|
||||
description: Create professional, dark-themed architecture diagrams as standalone HTML files with SVG graphics. Use when the user asks for system architecture diagrams, infrastructure diagrams, cloud architecture visualizations, security diagrams, network topology diagrams, or any technical diagram showing system components and their relationships.
|
||||
license: MIT
|
||||
metadata:
|
||||
version: "1.0"
|
||||
author: Cocoon AI (hello@cocoon-ai.com)
|
||||
---
|
||||
|
||||
# Architecture Diagram Skill
|
||||
|
||||
Create professional technical architecture diagrams as self-contained HTML files with inline SVG graphics and CSS styling.
|
||||
|
||||
## Design System
|
||||
|
||||
### Color Palette
|
||||
|
||||
Use these semantic colors for component types:
|
||||
|
||||
| Component Type | Fill (rgba) | Stroke |
|
||||
|---------------|-------------|--------|
|
||||
| Frontend | `rgba(8, 51, 68, 0.4)` | `#22d3ee` (cyan-400) |
|
||||
| Backend | `rgba(6, 78, 59, 0.4)` | `#34d399` (emerald-400) |
|
||||
| Database | `rgba(76, 29, 149, 0.4)` | `#a78bfa` (violet-400) |
|
||||
| AWS/Cloud | `rgba(120, 53, 15, 0.3)` | `#fbbf24` (amber-400) |
|
||||
| Security | `rgba(136, 19, 55, 0.4)` | `#fb7185` (rose-400) |
|
||||
| Message Bus | `rgba(251, 146, 60, 0.3)` | `#fb923c` (orange-400) |
|
||||
| External/Generic | `rgba(30, 41, 59, 0.5)` | `#94a3b8` (slate-400) |
|
||||
|
||||
### Typography
|
||||
|
||||
Use JetBrains Mono for all text (monospace, technical aesthetic):
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
Font sizes: 12px for component names, 9px for sublabels, 8px for annotations, 7px for tiny labels.
|
||||
|
||||
### Visual Elements
|
||||
|
||||
**Background:** `#020617` (slate-950) with subtle grid pattern:
|
||||
```svg
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
```
|
||||
|
||||
**Component boxes:** Rounded rectangles (`rx="6"`) with 1.5px stroke, semi-transparent fills.
|
||||
|
||||
**Security groups:** Dashed stroke (`stroke-dasharray="4,4"`), transparent fill, rose color.
|
||||
|
||||
**Region boundaries:** Larger dashed stroke (`stroke-dasharray="8,4"`), amber color, `rx="12"`.
|
||||
|
||||
**Arrows:** Use SVG marker for arrowheads:
|
||||
```svg
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
|
||||
</marker>
|
||||
```
|
||||
|
||||
**Arrow z-order:** Draw connection arrows early in the SVG (after the background grid) so they render behind component boxes. SVG elements are painted in document order, so arrows drawn first will appear behind shapes drawn later.
|
||||
|
||||
**Masking arrows behind transparent fills:** Since component boxes use semi-transparent fills (`rgba(..., 0.4)`), arrows behind them will show through. To fully mask arrows, draw an opaque background rect (e.g., `fill="#0f172a"`) at the same position before drawing the semi-transparent styled rect on top:
|
||||
```svg
|
||||
<!-- Opaque background to mask arrows -->
|
||||
<rect x="X" y="Y" width="W" height="H" rx="6" fill="#0f172a"/>
|
||||
<!-- Styled component on top -->
|
||||
<rect x="X" y="Y" width="W" height="H" rx="6" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
```
|
||||
|
||||
**Auth/security flows:** Dashed lines in rose color (`#fb7185`).
|
||||
|
||||
**Message buses / Event buses:** Small connector elements between services. Use orange color (`#fb923c` stroke, `rgba(251, 146, 60, 0.3)` fill):
|
||||
```svg
|
||||
<rect x="X" y="Y" width="120" height="20" rx="4" fill="rgba(251, 146, 60, 0.3)" stroke="#fb923c" stroke-width="1"/>
|
||||
<text x="CENTER_X" y="Y+14" fill="#fb923c" font-size="7" text-anchor="middle">Kafka / RabbitMQ</text>
|
||||
```
|
||||
|
||||
### Spacing Rules
|
||||
|
||||
**CRITICAL:** When stacking components vertically, ensure proper spacing to avoid overlaps:
|
||||
|
||||
- **Standard component height:** 60px for services, 80-120px for larger components
|
||||
- **Minimum vertical gap between components:** 40px
|
||||
- **Inline connectors (message buses):** Place IN the gap between components, not overlapping
|
||||
|
||||
**Example vertical layout:**
|
||||
```
|
||||
Component A: y=70, height=60 → ends at y=130
|
||||
Gap: y=130 to y=170 → 40px gap, place bus at y=140 (20px tall)
|
||||
Component B: y=170, height=60 → ends at y=230
|
||||
```
|
||||
|
||||
**Wrong:** Placing a message bus at y=160 when Component B starts at y=170 (causes overlap)
|
||||
**Right:** Placing a message bus at y=140, centered in the 40px gap (y=130 to y=170)
|
||||
|
||||
### Legend Placement
|
||||
|
||||
**CRITICAL:** Place legends OUTSIDE all boundary boxes (region boundaries, cluster boundaries, security groups).
|
||||
|
||||
- Calculate where all boundaries end (y position + height)
|
||||
- Place legend at least 20px below the lowest boundary
|
||||
- Expand SVG viewBox height if needed to accommodate
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Kubernetes Cluster: y=30, height=460 → ends at y=490
|
||||
Legend should start at: y=510 or below
|
||||
SVG viewBox height: at least 560 to fit legend
|
||||
```
|
||||
|
||||
**Wrong:** Legend at y=470 inside a cluster boundary that ends at y=490
|
||||
**Right:** Legend at y=510, below the cluster boundary, with viewBox height extended
|
||||
|
||||
### Layout Structure
|
||||
|
||||
1. **Header** - Title with pulsing dot indicator, subtitle
|
||||
2. **Main SVG diagram** - Contained in rounded border card
|
||||
3. **Summary cards** - Grid of 3 cards below diagram with key details
|
||||
4. **Footer** - Minimal metadata line
|
||||
|
||||
### Component Box Pattern
|
||||
|
||||
```svg
|
||||
<rect x="X" y="Y" width="W" height="H" rx="6" fill="FILL_COLOR" stroke="STROKE_COLOR" stroke-width="1.5"/>
|
||||
<text x="CENTER_X" y="Y+20" fill="white" font-size="11" font-weight="600" text-anchor="middle">LABEL</text>
|
||||
<text x="CENTER_X" y="Y+36" fill="#94a3b8" font-size="9" text-anchor="middle">sublabel</text>
|
||||
```
|
||||
|
||||
### Info Card Pattern
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot COLOR"></div>
|
||||
<h3>Title</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Item one</li>
|
||||
<li>• Item two</li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Template
|
||||
|
||||
Copy and customize the template at `assets/template.html`. Key customization points:
|
||||
|
||||
1. Update the `<title>` and header text
|
||||
2. Modify SVG viewBox dimensions if needed (default: `1000 x 680`)
|
||||
3. Add/remove/reposition component boxes
|
||||
4. Draw connection arrows between components
|
||||
5. Update the three summary cards
|
||||
6. Update footer metadata
|
||||
|
||||
## Output
|
||||
|
||||
Always produce a single self-contained `.html` file with:
|
||||
- Embedded CSS (no external stylesheets except Google Fonts)
|
||||
- Inline SVG (no external images)
|
||||
- No JavaScript required (pure CSS animations)
|
||||
|
||||
The file should render correctly when opened directly in any modern browser.
|
||||
@@ -0,0 +1,319 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>[PROJECT NAME] Architecture Diagram</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
background: #020617;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pulse-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #22d3ee;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #94a3b8;
|
||||
font-size: 0.875rem;
|
||||
margin-left: 1.75rem;
|
||||
}
|
||||
|
||||
.diagram-container {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid #1e293b;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
min-width: 900px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #1e293b;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.card-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.card-dot.cyan { background: #22d3ee; }
|
||||
.card-dot.emerald { background: #34d399; }
|
||||
.card-dot.violet { background: #a78bfa; }
|
||||
.card-dot.amber { background: #fbbf24; }
|
||||
.card-dot.rose { background: #fb7185; }
|
||||
|
||||
.card h3 {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card ul {
|
||||
list-style: none;
|
||||
color: #94a3b8;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.card li {
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
color: #475569;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-row">
|
||||
<div class="pulse-dot"></div>
|
||||
<h1>[PROJECT NAME] Architecture</h1>
|
||||
</div>
|
||||
<p class="subtitle">[Subtitle description]</p>
|
||||
</div>
|
||||
|
||||
<!-- Main Diagram -->
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 680">
|
||||
<!-- Definitions -->
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b" />
|
||||
</marker>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- Background Grid -->
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
<!-- =================================================================
|
||||
COMPONENT EXAMPLES - Copy and customize these patterns
|
||||
================================================================= -->
|
||||
|
||||
<!-- External/Generic Component -->
|
||||
<rect x="30" y="280" width="100" height="50" rx="6" fill="rgba(30, 41, 59, 0.5)" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="80" y="300" fill="white" font-size="11" font-weight="600" text-anchor="middle">Users</text>
|
||||
<text x="80" y="316" fill="#94a3b8" font-size="9" text-anchor="middle">Browser/Mobile</text>
|
||||
|
||||
<!-- Security Component -->
|
||||
<rect x="30" y="80" width="100" height="60" rx="6" fill="rgba(136, 19, 55, 0.4)" stroke="#fb7185" stroke-width="1.5"/>
|
||||
<text x="80" y="105" fill="white" font-size="11" font-weight="600" text-anchor="middle">Auth Provider</text>
|
||||
<text x="80" y="121" fill="#94a3b8" font-size="9" text-anchor="middle">OAuth 2.0</text>
|
||||
|
||||
<!-- Region/Cloud Boundary -->
|
||||
<rect x="160" y="40" width="820" height="620" rx="12" fill="rgba(251, 191, 36, 0.05)" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
|
||||
<text x="172" y="58" fill="#fbbf24" font-size="10" font-weight="600">AWS Region: us-west-2</text>
|
||||
|
||||
<!-- AWS/Cloud Service -->
|
||||
<rect x="200" y="280" width="110" height="50" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="255" y="300" fill="white" font-size="11" font-weight="600" text-anchor="middle">CloudFront</text>
|
||||
<text x="255" y="316" fill="#94a3b8" font-size="9" text-anchor="middle">CDN</text>
|
||||
|
||||
<!-- Multi-line AWS Component (S3 Buckets example) -->
|
||||
<rect x="200" y="380" width="110" height="100" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="255" y="400" fill="white" font-size="11" font-weight="600" text-anchor="middle">S3 Buckets</text>
|
||||
<text x="255" y="420" fill="#94a3b8" font-size="8" text-anchor="middle">• bucket-one</text>
|
||||
<text x="255" y="434" fill="#94a3b8" font-size="8" text-anchor="middle">• bucket-two</text>
|
||||
<text x="255" y="448" fill="#94a3b8" font-size="8" text-anchor="middle">• bucket-three</text>
|
||||
<text x="255" y="466" fill="#fbbf24" font-size="7" text-anchor="middle">OAI Protected</text>
|
||||
|
||||
<!-- Security Group (dashed boundary) -->
|
||||
<rect x="350" y="265" width="120" height="80" rx="8" fill="transparent" stroke="#fb7185" stroke-width="1" stroke-dasharray="4,4"/>
|
||||
<text x="358" y="279" fill="#fb7185" font-size="8">sg-name :port</text>
|
||||
|
||||
<!-- Component inside security group -->
|
||||
<rect x="360" y="280" width="100" height="50" rx="6" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text x="410" y="300" fill="white" font-size="11" font-weight="600" text-anchor="middle">Load Balancer</text>
|
||||
<text x="410" y="316" fill="#94a3b8" font-size="9" text-anchor="middle">HTTPS :443</text>
|
||||
|
||||
<!-- Backend Component -->
|
||||
<rect x="510" y="280" width="110" height="50" rx="6" fill="rgba(6, 78, 59, 0.4)" stroke="#34d399" stroke-width="1.5"/>
|
||||
<text x="565" y="300" fill="white" font-size="11" font-weight="600" text-anchor="middle">API Server</text>
|
||||
<text x="565" y="316" fill="#94a3b8" font-size="9" text-anchor="middle">FastAPI :8000</text>
|
||||
|
||||
<!-- Database Component -->
|
||||
<rect x="700" y="280" width="120" height="50" rx="6" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<text x="760" y="300" fill="white" font-size="11" font-weight="600" text-anchor="middle">Database</text>
|
||||
<text x="760" y="316" fill="#94a3b8" font-size="9" text-anchor="middle">PostgreSQL</text>
|
||||
|
||||
<!-- Frontend Component -->
|
||||
<rect x="200" y="520" width="200" height="110" rx="8" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1.5"/>
|
||||
<text x="300" y="545" fill="white" font-size="12" font-weight="600" text-anchor="middle">Frontend</text>
|
||||
<text x="300" y="565" fill="#94a3b8" font-size="9" text-anchor="middle">React + TypeScript</text>
|
||||
<text x="300" y="580" fill="#94a3b8" font-size="9" text-anchor="middle">Additional detail</text>
|
||||
<text x="300" y="595" fill="#94a3b8" font-size="9" text-anchor="middle">More info</text>
|
||||
<text x="300" y="615" fill="#22d3ee" font-size="8" text-anchor="middle">domain.example.com</text>
|
||||
|
||||
<!-- =================================================================
|
||||
ARROW EXAMPLES
|
||||
================================================================= -->
|
||||
|
||||
<!-- Standard arrow with label -->
|
||||
<line x1="130" y1="305" x2="198" y2="305" stroke="#22d3ee" stroke-width="1.5" marker-end="url(#arrowhead)"/>
|
||||
<text x="164" y="299" fill="#94a3b8" font-size="9" text-anchor="middle">HTTPS</text>
|
||||
|
||||
<!-- Simple arrow (no label) -->
|
||||
<line x1="310" y1="305" x2="358" y2="305" stroke="#22d3ee" stroke-width="1.5" marker-end="url(#arrowhead)"/>
|
||||
|
||||
<!-- Vertical arrow -->
|
||||
<line x1="255" y1="330" x2="255" y2="378" stroke="#fbbf24" stroke-width="1.5" marker-end="url(#arrowhead)"/>
|
||||
<text x="270" y="358" fill="#94a3b8" font-size="9">OAI</text>
|
||||
|
||||
<!-- Dashed arrow (for auth/security flows) -->
|
||||
<line x1="460" y1="305" x2="508" y2="305" stroke="#34d399" stroke-width="1.5" marker-end="url(#arrowhead)"/>
|
||||
<line x1="620" y1="305" x2="698" y2="305" stroke="#a78bfa" stroke-width="1.5" marker-end="url(#arrowhead)"/>
|
||||
<text x="655" y="299" fill="#94a3b8" font-size="9">TLS</text>
|
||||
|
||||
<!-- Curved path for auth flow -->
|
||||
<path d="M 80 140 L 80 200 Q 80 220 100 220 L 200 220 Q 220 220 220 240 L 220 278" fill="none" stroke="#fb7185" stroke-width="1.5" stroke-dasharray="5,5"/>
|
||||
<text x="150" y="210" fill="#fb7185" font-size="8">JWT + PKCE</text>
|
||||
|
||||
<!-- =================================================================
|
||||
LEGEND
|
||||
================================================================= -->
|
||||
<text x="720" y="70" fill="white" font-size="10" font-weight="600">Legend</text>
|
||||
|
||||
<rect x="720" y="82" width="16" height="10" rx="2" fill="rgba(8, 51, 68, 0.4)" stroke="#22d3ee" stroke-width="1"/>
|
||||
<text x="742" y="90" fill="#94a3b8" font-size="8">Frontend</text>
|
||||
|
||||
<rect x="720" y="98" width="16" height="10" rx="2" fill="rgba(6, 78, 59, 0.4)" stroke="#34d399" stroke-width="1"/>
|
||||
<text x="742" y="106" fill="#94a3b8" font-size="8">Backend</text>
|
||||
|
||||
<rect x="720" y="114" width="16" height="10" rx="2" fill="rgba(120, 53, 15, 0.3)" stroke="#fbbf24" stroke-width="1"/>
|
||||
<text x="742" y="122" fill="#94a3b8" font-size="8">Cloud Service</text>
|
||||
|
||||
<rect x="720" y="130" width="16" height="10" rx="2" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1"/>
|
||||
<text x="742" y="138" fill="#94a3b8" font-size="8">Database</text>
|
||||
|
||||
<rect x="720" y="146" width="16" height="10" rx="2" fill="rgba(136, 19, 55, 0.4)" stroke="#fb7185" stroke-width="1"/>
|
||||
<text x="742" y="154" fill="#94a3b8" font-size="8">Security</text>
|
||||
|
||||
<line x1="720" y1="168" x2="736" y2="168" stroke="#fb7185" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="742" y="171" fill="#94a3b8" font-size="8">Auth Flow</text>
|
||||
|
||||
<rect x="720" y="178" width="16" height="10" rx="2" fill="transparent" stroke="#fb7185" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="742" y="186" fill="#94a3b8" font-size="8">Security Group</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards -->
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot rose"></div>
|
||||
<h3>Card Title 1</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Item one</li>
|
||||
<li>• Item two</li>
|
||||
<li>• Item three</li>
|
||||
<li>• Item four</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot amber"></div>
|
||||
<h3>Card Title 2</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Item one</li>
|
||||
<li>• Item two</li>
|
||||
<li>• Item three</li>
|
||||
<li>• Item four</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-dot violet"></div>
|
||||
<h3>Card Title 3</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>• Item one</li>
|
||||
<li>• Item two</li>
|
||||
<li>• Item three</li>
|
||||
<li>• Item four</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<p class="footer">
|
||||
[Project Name] • [Additional metadata]
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,341 @@
|
||||
---
|
||||
name: code-to-spec
|
||||
description: "Reverse-engineer a SPEC document from an existing project. Analyzes code, config, tests, and structure to produce a comprehensive specification. Triggers on: code-to-spec, reverse spec, generate spec, 逆向规格, 生成规格文档, 生成设计文档, 生成设计方案, extract spec, document this project, what does this project do."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# to-spec — Reverse-Engineer Project Specification
|
||||
|
||||
Analyze an existing codebase and produce a structured SPEC document that captures what the project does, how it's built, and what contracts it exposes. The output is a living specification that could be used to rebuild the project from scratch or onboard new contributors.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
- You want a comprehensive understanding of an existing project
|
||||
- Onboarding new team members who need a high-level overview
|
||||
- Documenting a project that was built without a spec
|
||||
- Comparing actual implementation against intended design
|
||||
- Preparing for a rewrite or major refactor
|
||||
- Auditing what a project actually does vs. what people think it does
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. **Scope confirmation** — ask user what to analyze (entire repo, specific directory, or specific aspect)
|
||||
2. **Deep scan** — systematically read project structure, entry points, config, tests, and core logic
|
||||
3. **Synthesize** — produce a structured SPEC document
|
||||
4. **Review** — present to user for feedback and iteration
|
||||
5. **Save** — write final SPEC to agreed location
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Scope Confirmation
|
||||
|
||||
Before scanning, ask the user:
|
||||
|
||||
```
|
||||
What should I analyze?
|
||||
|
||||
A. Entire repository (recommended for small-medium projects)
|
||||
B. Specific directory or module: [path]
|
||||
C. Specific aspect only (e.g., API surface, data model, auth flow)
|
||||
|
||||
Depth level:
|
||||
1. Overview — high-level architecture + tech stack + key features (fast, ~5 min)
|
||||
2. Standard — includes API contracts, data models, config, dependencies (default)
|
||||
3. Deep — adds internal module interactions, error handling patterns, test coverage analysis
|
||||
```
|
||||
|
||||
If the project is large (>500 files), recommend starting with Overview or a specific module.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Deep Scan
|
||||
|
||||
Systematically analyze the following (adapt to what exists):
|
||||
|
||||
### 2.1 Project Identity
|
||||
- `package.json`, `go.mod`, `Cargo.toml`, `pyproject.toml`, `pom.xml`, etc.
|
||||
- README, LICENSE
|
||||
- Git history (first commit date, recent activity, contributor count)
|
||||
|
||||
### 2.2 Architecture
|
||||
- Directory structure and organization pattern (monorepo, layered, hexagonal, etc.)
|
||||
- Entry points (main files, CLI commands, server bootstrap)
|
||||
- Module boundaries and dependency graph (internal)
|
||||
|
||||
### 2.3 Tech Stack
|
||||
- Language(s) and version constraints
|
||||
- Frameworks and major libraries
|
||||
- Build tools and bundlers
|
||||
- Runtime requirements (Node version, Docker, etc.)
|
||||
|
||||
### 2.4 Features & Behavior
|
||||
- Route definitions / CLI commands / exported functions
|
||||
- Business logic modules and their responsibilities
|
||||
- Background jobs, cron tasks, event handlers
|
||||
|
||||
### 2.5 Data Model
|
||||
- Database schemas, migrations, ORMs
|
||||
- Key data structures and their relationships
|
||||
- State management approach
|
||||
|
||||
### 2.6 API Surface
|
||||
- HTTP endpoints (method, path, request/response shapes)
|
||||
- GraphQL schema / gRPC protos / WebSocket events
|
||||
- CLI interface (commands, flags, arguments)
|
||||
- Exported library API (public functions, classes, types)
|
||||
|
||||
### 2.7 Configuration & Environment
|
||||
- Environment variables and their purpose
|
||||
- Config files and their schema
|
||||
- Feature flags, toggles
|
||||
|
||||
### 2.8 External Dependencies
|
||||
- Third-party services (databases, queues, APIs)
|
||||
- Infrastructure requirements (cloud services, storage)
|
||||
- Authentication/authorization providers
|
||||
|
||||
### 2.9 Testing & Quality
|
||||
- Test framework and approach (unit, integration, e2e)
|
||||
- Coverage patterns (what's tested, what's not)
|
||||
- Linting, formatting, type checking setup
|
||||
|
||||
### 2.10 Deployment & Operations
|
||||
- CI/CD configuration
|
||||
- Deployment targets and strategies
|
||||
- Monitoring, logging, health checks
|
||||
|
||||
---
|
||||
|
||||
## Step 3: SPEC Document Structure
|
||||
|
||||
Generate the SPEC with these sections. Omit sections that don't apply.
|
||||
|
||||
```markdown
|
||||
# SPEC: [Project Name]
|
||||
|
||||
> Reverse-engineered specification — generated [date] from commit [short-hash]
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Purpose
|
||||
[One paragraph: what problem this project solves and for whom]
|
||||
|
||||
### 1.2 Key Capabilities
|
||||
- [Bullet list of what the system can do, from a user's perspective]
|
||||
|
||||
### 1.3 Architecture Style
|
||||
[e.g., "Monolithic Express.js API with React SPA frontend", "CLI tool with plugin system", "Microservices communicating over gRPC"]
|
||||
|
||||
---
|
||||
|
||||
## 2. Tech Stack
|
||||
|
||||
| Layer | Technology | Version |
|
||||
|-------|-----------|---------|
|
||||
| Language | ... | ... |
|
||||
| Framework | ... | ... |
|
||||
| Database | ... | ... |
|
||||
| Build | ... | ... |
|
||||
| Test | ... | ... |
|
||||
| Deploy | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## 3. Project Structure
|
||||
|
||||
[Directory tree with annotations explaining each top-level directory's purpose]
|
||||
|
||||
---
|
||||
|
||||
## 4. Data Model
|
||||
|
||||
### 4.1 Core Entities
|
||||
[For each entity: name, fields, relationships, constraints]
|
||||
|
||||
### 4.2 State Transitions
|
||||
[If applicable: lifecycle states and valid transitions]
|
||||
|
||||
---
|
||||
|
||||
## 5. API Surface
|
||||
|
||||
### 5.1 [Interface Type: REST / CLI / Library / etc.]
|
||||
|
||||
[For each endpoint/command/function:]
|
||||
| Method | Path/Command | Description | Auth |
|
||||
|--------|-------------|-------------|------|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### 5.2 Request/Response Schemas
|
||||
[Key request/response shapes with field types]
|
||||
|
||||
---
|
||||
|
||||
## 6. Configuration
|
||||
|
||||
| Variable / Key | Required | Default | Description |
|
||||
|---------------|----------|---------|-------------|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## 7. External Dependencies
|
||||
|
||||
| Service | Purpose | Failure Impact |
|
||||
|---------|---------|----------------|
|
||||
| ... | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## 8. Business Rules & Constraints
|
||||
|
||||
- [Numbered list of invariants, validation rules, and business logic constraints discovered in the code]
|
||||
|
||||
---
|
||||
|
||||
## 9. Non-Functional Characteristics
|
||||
|
||||
### 9.1 Performance
|
||||
[Observed patterns: caching, pagination, batch processing, etc.]
|
||||
|
||||
### 9.2 Security
|
||||
[Auth mechanism, input validation patterns, secrets management]
|
||||
|
||||
### 9.3 Error Handling
|
||||
[Error strategy: custom error types, error codes, retry policies]
|
||||
|
||||
---
|
||||
|
||||
## 10. Testing Strategy
|
||||
|
||||
| Type | Framework | Coverage Pattern |
|
||||
|------|-----------|-----------------|
|
||||
| Unit | ... | ... |
|
||||
| Integration | ... | ... |
|
||||
| E2E | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## 11. Known Gaps & Assumptions
|
||||
|
||||
- [Things that are unclear from the code alone]
|
||||
- [Assumptions made during analysis]
|
||||
- [Areas with no tests or documentation]
|
||||
|
||||
---
|
||||
|
||||
## 12. Appendix
|
||||
|
||||
### A. Dependency Graph
|
||||
[Key module dependencies, import relationships]
|
||||
|
||||
### B. Environment Setup
|
||||
[Steps to run the project locally, derived from config and scripts]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Review & Iteration
|
||||
|
||||
After generating the SPEC, present it and ask:
|
||||
|
||||
```
|
||||
SPEC generated. Please review:
|
||||
|
||||
- Are there sections that need more detail?
|
||||
- Are there inaccuracies I should correct?
|
||||
- Should I add/remove any sections?
|
||||
- Is the depth level appropriate?
|
||||
|
||||
Reply OK to save, or provide feedback for iteration.
|
||||
```
|
||||
|
||||
Apply feedback and re-present until user confirms.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Save
|
||||
|
||||
Ask user for save location:
|
||||
|
||||
```
|
||||
Where should I save the SPEC?
|
||||
|
||||
A. docs/SPEC.md (recommended)
|
||||
B. SPEC.md (project root)
|
||||
C. Custom path: [specify]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Analysis Heuristics
|
||||
|
||||
### Identifying Purpose
|
||||
- Look at README first line, package description field, CLI help text
|
||||
- Check the main entry point — what does it bootstrap?
|
||||
- Look at test descriptions — they often describe expected behavior in plain language
|
||||
|
||||
### Discovering Architecture
|
||||
- Map `import`/`require` statements to build dependency graph
|
||||
- Identify layers by directory naming: `controllers`, `services`, `models`, `routes`, `handlers`, `domain`, `infra`
|
||||
- Check for dependency injection patterns, middleware chains, plugin registrations
|
||||
|
||||
### Extracting Business Rules
|
||||
- Look for validation functions, guard clauses, assertion statements
|
||||
- Check error messages — they often describe what went wrong in business terms
|
||||
- Examine test assertions — they encode expected behavior
|
||||
|
||||
### Finding API Contracts
|
||||
- Route registrations (Express: `app.get()`, FastAPI: `@app.get()`, Go: `mux.HandleFunc()`)
|
||||
- OpenAPI/Swagger files if present
|
||||
- Request validation schemas (Joi, Zod, Pydantic, struct tags)
|
||||
- CLI flag/argument definitions (cobra, argparse, yargs)
|
||||
|
||||
### Detecting Data Models
|
||||
- ORM model definitions (Prisma, SQLAlchemy, GORM, TypeORM)
|
||||
- Migration files (in chronological order)
|
||||
- Type/interface definitions for core domain objects
|
||||
- Database seed files
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| Project has no README or documentation | Note this in "Known Gaps"; infer purpose from code |
|
||||
| Monorepo with multiple services | Ask user which service(s) to analyze; produce one SPEC per service or a unified SPEC with clear boundaries |
|
||||
| Project uses code generation | Document the generated code's purpose but focus on the source of truth (schemas, proto files, templates) |
|
||||
| Legacy project with mixed patterns | Document all observed patterns, note inconsistencies in "Known Gaps" |
|
||||
| Project is a library (no runtime) | Focus on exported API surface, type contracts, and usage patterns from tests |
|
||||
| Incomplete or broken code | Document what exists, mark broken/incomplete areas explicitly |
|
||||
| Project >1000 files | Start with entry points and trace key flows; don't exhaustively read every file |
|
||||
| Multiple languages in one repo | Document each language's role and how they interact |
|
||||
|
||||
---
|
||||
|
||||
## Quality Criteria
|
||||
|
||||
A good reverse-engineered SPEC should pass these checks:
|
||||
|
||||
- [ ] A developer unfamiliar with the project could understand its purpose in 60 seconds
|
||||
- [ ] The tech stack section is complete enough to set up a dev environment
|
||||
- [ ] API contracts are specific enough to write a client against
|
||||
- [ ] Data models are complete enough to recreate the schema
|
||||
- [ ] Business rules are explicit (not buried in "see code")
|
||||
- [ ] Known gaps are honestly listed (don't invent what you can't determine)
|
||||
- [ ] The SPEC matches the actual code (not aspirational documentation)
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
- **Don't invent intent.** If you can't determine WHY something exists, say so. Don't fabricate rationale.
|
||||
- **Don't copy code into the SPEC.** Describe behavior and contracts, don't paste implementations.
|
||||
- **Don't include transient state.** The SPEC describes the system's design, not its current runtime state.
|
||||
- **Don't over-specify internals.** Focus on boundaries, contracts, and behavior. Internal implementation details belong in code comments, not specs.
|
||||
- **Don't assume the README is accurate.** READMEs often lag behind code. Verify claims against actual implementation.
|
||||
@@ -0,0 +1,329 @@
|
||||
---
|
||||
name: graph
|
||||
description: "Graph engineering for parallel task execution: convert a task, PRD, SPEC, or issue set into a dependency graph (DAG), layer it into supersteps, then implement each independent node concurrently with subagents — each node runs /goal → /review-it → /ship-it in an isolated git worktree, with a fan-in barrier between waves. Triggers on: graph, graph engineering, build a graph, task graph, dependency graph, DAG, parallel implement, 并发实现, 并行实现, 任务图, 把任务变成图, fan-out fan-in, superstep, dynamic workflow."
|
||||
user-invocable: true
|
||||
allowed-tools:
|
||||
- Bash(git:*)
|
||||
- Bash(gh:*)
|
||||
- Bash(cat:*)
|
||||
- Bash(mkdir:*)
|
||||
- Bash(grep:*)
|
||||
- Bash(python3:*)
|
||||
---
|
||||
|
||||
# graph — Task/PRD to Parallel Execution Graph
|
||||
|
||||
Turn a task (or PRD / SPEC / issue set) into a **directed acyclic graph** of work units, layer it into **supersteps (waves)**, and implement each wave's independent nodes **concurrently** using subagents. Each node runs the full `/goal → /review-it → /ship-it` pipeline inside its **own git worktree**, so parallel nodes never clobber each other's working tree. Between waves, a **fan-in barrier** merges results and re-plans the next wave.
|
||||
|
||||
This is the parallel sibling of `/loop-it`. `/loop-it` is strictly sequential (one worktree, one issue at a time). `/graph` fans out every independent node in a wave at once.
|
||||
|
||||
---
|
||||
|
||||
## Mental Model (borrowed from LangGraph / graph engineering)
|
||||
|
||||
| Concept | Here |
|
||||
|---------|------|
|
||||
| **Node** | One implementable unit of work (an issue / subtask) |
|
||||
| **Edge** | A dependency: `B depends on A` → edge `A → B` |
|
||||
| **Superstep / wave** | A set of nodes whose deps are all satisfied — run concurrently |
|
||||
| **Fan-out** | Dispatch one subagent per node in the current wave |
|
||||
| **Fan-in (barrier)** | Wait for **all** nodes in the wave before starting the next |
|
||||
| **State channel** | `.graph_state` — shared checkpoint, rewritten between waves (resume source) |
|
||||
| **Live tracker** | `graph.html` — Claude-style light-theme dashboard, re-rendered from `.graph_state` at every checkpoint |
|
||||
| **Dynamic re-plan** | After a wave, revise the graph if new work/deps emerged |
|
||||
|
||||
**Core principle:** Independent nodes in the same wave have *no shared state and no ordering dependency*, so they can run in true parallel. Dependencies define the *only* ordering. Everything else runs at once.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
Input (task / PRD / SPEC / issues)
|
||||
│
|
||||
▼
|
||||
1. Decompose into nodes ─────────► nodes = {id, title, deps, criteria, scope}
|
||||
│
|
||||
▼
|
||||
2. Build DAG + validate ─────────► detect cycles, orphan deps
|
||||
│
|
||||
▼
|
||||
3. Topological layering ─────────► waves = [[n1,n2,n3], [n4,n5], [n6]]
|
||||
│
|
||||
▼
|
||||
4. Render graph + confirm with user
|
||||
│
|
||||
▼ (write .graph_state + graph.html — open graph.html to watch live)
|
||||
┌──────────── per wave (superstep) ────────────┐
|
||||
│ │
|
||||
│ FAN-OUT: 1 subagent per node (parallel) │
|
||||
│ each subagent, in its own git worktree: │
|
||||
│ /goal (inline implement) → /review-it │
|
||||
│ → /ship-it │
|
||||
│ │
|
||||
│ FAN-IN barrier: wait for ALL nodes │
|
||||
│ integrate, update .graph_state │
|
||||
│ re-render graph.html │
|
||||
│ re-plan next wave if graph changed │
|
||||
│ │
|
||||
└───────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
All waves done → final summary
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Locate & Decompose Input
|
||||
|
||||
Accept any of: a free-form task description, a PRD/SPEC file, or an existing issue set (GitHub / local `.md` / iCafe).
|
||||
|
||||
- **PRD/SPEC** → reuse `/to-issues` decomposition rules (one node per User Story; split large, merge tiny).
|
||||
- **Existing issues** → each issue is a node; parse dependencies from issue bodies (`Depends on: #3`, `Dependencies: #3, #5`).
|
||||
- **Free-form task** → break into the smallest independently-shippable units yourself.
|
||||
|
||||
Each node MUST have:
|
||||
|
||||
```
|
||||
Node #N
|
||||
title: short imperative title
|
||||
deps: [list of node ids] or []
|
||||
criteria: acceptance criteria (checklist) — how the subagent knows it's done
|
||||
type: backend | frontend | fullstack | ui | infra | docs
|
||||
scope_hint: which files/dirs this node is expected to touch (for conflict analysis)
|
||||
```
|
||||
|
||||
`scope_hint` matters: two nodes with no dependency edge but overlapping file scope are **not** truly independent — see Step 3.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Build the DAG & Validate
|
||||
|
||||
Construct edges from `deps`. Then validate:
|
||||
|
||||
| Check | Action on failure |
|
||||
|-------|-------------------|
|
||||
| **Cycle** (`A → B → A`) | Print `⚠️ 循环依赖: #A ↔ #B`. Break by node id order, warn user, ask to confirm or fix. |
|
||||
| **Dangling dep** (`#7 depends on #99`, no such node) | Print warning, drop the phantom edge. |
|
||||
| **Scope collision** (two dep-free nodes edit same files) | Add a *soft edge* to serialize them (lower id first), OR flag for user. Never let two parallel worktrees fight over the same files. |
|
||||
|
||||
**Hot-file exception:** A shared *wiring* file that nearly every node must touch (e.g. `router.go`, `main.go`, `mod.rs`, a DI container, an `__init__` re-export) does NOT count as a scope collision — treating it as one would serialize the entire graph into a chain. For such files, assume append-only edits merge cleanly, and prefer one of: (a) designate a single node that *owns* wiring and have others expose a registration hook, or (b) do a tiny follow-up "wire everything" node in the last wave. Reserve the collision rule for nodes that edit the *same logic* in the same file (e.g. two handlers rewriting the same function).
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Topological Layering into Waves
|
||||
|
||||
Compute waves via Kahn's algorithm:
|
||||
|
||||
1. **Wave 0** = all nodes with `deps == []` and no scope collision among themselves.
|
||||
2. Remove wave-0 nodes; **Wave 1** = nodes whose deps are now all satisfied.
|
||||
3. Repeat until all nodes placed.
|
||||
4. Within a wave, if two nodes edit the **same logic in the same file** (real collision, per the hot-file exception in Step 2), push the higher-id one to the next wave. Bare wiring-file overlap does not trigger this.
|
||||
|
||||
**ID conventions (used consistently):** lower id wins — cycles break by lowest id first (Step 2), and scope collisions serialize with the lower id first (higher id deferred to the next wave).
|
||||
|
||||
Print the layered plan:
|
||||
|
||||
```
|
||||
📊 Graph: 6 nodes, 3 waves
|
||||
|
||||
Wave 0 (parallel ×3): #1 db schema #2 config loader #3 logging util
|
||||
Wave 1 (parallel ×2): #4 API handler (deps #1) #5 CLI flags (deps #2)
|
||||
Wave 2 (parallel ×1): #6 integration (deps #4,#5)
|
||||
|
||||
Max parallelism: 3 subagents in Wave 0.
|
||||
```
|
||||
|
||||
Also emit a Mermaid diagram for the user:
|
||||
|
||||
````
|
||||
```mermaid
|
||||
graph LR
|
||||
n1[#1 db schema] --> n4[#4 API handler]
|
||||
n2[#2 config loader] --> n5[#5 CLI flags]
|
||||
n3[#3 logging util]
|
||||
n4 --> n6[#6 integration]
|
||||
n5 --> n6
|
||||
```
|
||||
````
|
||||
|
||||
**Wait for user confirmation** before dispatching any subagent. Let them adjust nodes, deps, or the max-parallelism cap.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Pre-flight Checks
|
||||
|
||||
Before the first wave (same spirit as `/loop-it`):
|
||||
|
||||
```bash
|
||||
git rev-parse --is-inside-work-tree # in a repo?
|
||||
git status --porcelain # clean tree? (dirty → stash/abort)
|
||||
git branch --show-current # on main/master?
|
||||
git ls-remote --heads origin # remote reachable?
|
||||
gh auth status # if shipping to GitHub
|
||||
```
|
||||
|
||||
Any hard failure → print the error and stop. Confirm a **max concurrency cap** with the user (default 3–4 parallel subagents; more risks rate limits and review noise).
|
||||
|
||||
Then **initialize the state channel + live tracker** (do this once, right after the plan is confirmed and before the first fan-out):
|
||||
|
||||
```bash
|
||||
# 1. Write the initial checkpoint (all nodes pending, current_wave 0).
|
||||
cat > .graph_state <<'JSON'
|
||||
{ "version": 1, "task": "...", "repo": "owner/repo",
|
||||
"waves": [[1,2,3],[4,5],[6]], "current_wave": 0,
|
||||
"nodes": { "1": {"title":"...","deps":[],"status":"pending","wave":0}, ... } }
|
||||
JSON
|
||||
|
||||
# 2. Keep it out of git.
|
||||
grep -qxF '.graph_state' .gitignore || printf '.graph_state\ngraph.html\n' >> .gitignore
|
||||
|
||||
# 3. Render the Claude-style light-theme dashboard.
|
||||
python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html
|
||||
```
|
||||
|
||||
Tell the user: **open `graph.html` in a browser** — it auto-refreshes every 5s, so it tracks execution live (waves, node statuses, progress bar, and a Mermaid DAG colored by status). Re-run the render command at every checkpoint (see Step 5b) to push updates.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Execute Wave by Wave (fan-out → fan-in)
|
||||
|
||||
For each wave, in order:
|
||||
|
||||
### 5a. FAN-OUT — one subagent per node, in parallel
|
||||
|
||||
**Dispatch all nodes of the wave in a single response** (multiple Agent/subagent calls in one message = concurrent). Each subagent works in its **own git worktree** so parallel file edits never collide:
|
||||
|
||||
```bash
|
||||
# The orchestrator creates a worktree per node BEFORE dispatching:
|
||||
git worktree add -b feat/node-{N}-{slug} ../.graph-worktrees/node-{N} main
|
||||
```
|
||||
|
||||
Each subagent receives a **self-contained** prompt (it does NOT inherit orchestrator context):
|
||||
|
||||
```markdown
|
||||
You are implementing ONE node of a task graph, working in an ISOLATED git worktree.
|
||||
|
||||
Worktree: ../.graph-worktrees/node-{N} (already created on branch feat/node-{N}-{slug})
|
||||
Node #{N}: {title}
|
||||
Type: {type}
|
||||
Scope: {scope_hint} — stay within these files; do not touch other nodes' scope
|
||||
|
||||
Acceptance criteria (all must pass):
|
||||
- [ ] {criterion 1}
|
||||
- [ ] {criterion 2}
|
||||
|
||||
Context (deps already merged into main, pull first):
|
||||
{summaries of dependency nodes' outputs, or the referenced PRD/SPEC excerpt}
|
||||
|
||||
Your pipeline (run all three, in order):
|
||||
1. IMPLEMENT (inline /goal): read the node + any referenced PRD/SPEC, read adjacent
|
||||
code, implement to satisfy EVERY acceptance criterion, run build + tests + lint
|
||||
(e.g. go build ./... && go vet ./... && go test ./...). Iterate until all green.
|
||||
2. REVIEW (/review-it): run code review on your changes, apply accepted findings,
|
||||
re-run focused tests, repeat until review is clean (max 2 rounds).
|
||||
3. SHIP (/ship-it): commit (message references the node/issue), push branch,
|
||||
create PR, merge, close the issue.
|
||||
|
||||
Constraints:
|
||||
- Work ONLY inside your worktree. Do NOT edit files outside {scope_hint}.
|
||||
- Do NOT try to call `goal` via the Skill tool (it's a UI command, not a skill) —
|
||||
"implement" means you write the code yourself. /review-it and /ship-it ARE skills.
|
||||
- If you cannot satisfy a criterion, STOP and report what's blocking — don't fake it.
|
||||
|
||||
Return: node id, PASS/FAIL, PR/commit refs, files changed, and — if you discovered new required work or a dependency the graph didn't capture — a `NEW_WORK:` line describing it (title + which nodes it blocks). Emit `NEW_WORK: none` if there's nothing.
|
||||
```
|
||||
|
||||
> **Why worktrees, not branches alone:** `/goal` mutates the working tree. Two subagents editing the same checkout would corrupt each other. A worktree per node gives each its own filesystem checkout on its own branch — that's what makes the wave genuinely parallel and safe.
|
||||
|
||||
### 5b. FAN-IN — barrier, integrate, re-plan
|
||||
|
||||
Wait for **every** subagent in the wave to return (BSP barrier — the next wave cannot start until this one commits). Then:
|
||||
|
||||
1. Read each subagent's summary. Mark node `shipped` or `failed`.
|
||||
2. `git checkout main && git pull` — dependency outputs are now on main for the next wave.
|
||||
3. Remove finished worktrees: `git worktree remove ../.graph-worktrees/node-{N}` (keep failed ones for investigation).
|
||||
4. Write checkpoint to `.graph_state`, then re-render the tracker:
|
||||
`python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html` (the open `graph.html` picks it up on its next auto-refresh).
|
||||
5. **Dynamic re-plan** (LangGraph-style conditional edge): scan each subagent's `NEW_WORK:` line. If any is not `none`, add the new node(s)/edge(s) and re-layer the *remaining* nodes before starting the next wave. Show the user the delta.
|
||||
6. If any node in the wave **failed**, mark all nodes that depend on it as `blocked` and skip them (their inputs aren't ready).
|
||||
|
||||
Proceed to the next wave.
|
||||
|
||||
---
|
||||
|
||||
## State File: `.graph_state` (+ live tracker `graph.html`)
|
||||
|
||||
`.graph_state` lives at the repo root and **must be in `.gitignore`**. It's the single source of truth: checkpoint it after every wave so a crash resumes at the wave boundary, and re-render `graph.html` from it so the browser dashboard stays live. `graph.html` is a *derived* view — never hand-edit it; regenerate it from `.graph_state`.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"updated_at": "2026-07-21T10:30:00Z",
|
||||
"task": "Add user auth",
|
||||
"repo": "owner/repo",
|
||||
"waves": [[1, 2, 3], [4, 5], [6]],
|
||||
"current_wave": 1,
|
||||
"nodes": {
|
||||
"1": { "title": "db schema", "deps": [], "status": "shipped", "branch": "feat/node-1-db-schema", "pr": 43, "wave": 0 },
|
||||
"2": { "title": "config loader", "deps": [], "status": "shipped", "wave": 0 },
|
||||
"3": { "title": "logging util", "deps": [], "status": "failed", "wave": 0, "error": "test TestLog failed", "attempts": 2 },
|
||||
"4": { "title": "API handler", "deps": [1], "status": "in_progress", "wave": 1 },
|
||||
"6": { "title": "integration", "deps": [4, 5], "status": "blocked", "wave": 2, "reason": "depends on #3 (failed)" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `pending | in_progress | shipped | failed | blocked | skipped`. Each node carries `title` + `deps` so `graph.html` can draw the DAG and cards straight from the checkpoint.
|
||||
|
||||
Render the tracker any time with:
|
||||
|
||||
```bash
|
||||
python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html
|
||||
```
|
||||
|
||||
On resume: read `.graph_state`, skip `shipped`, ask about `failed` (retry/skip), re-derive remaining waves, and re-render `graph.html`.
|
||||
|
||||
---
|
||||
|
||||
## Safety Guards
|
||||
|
||||
- **Worktree isolation is mandatory** — never run two parallel `/goal` sessions in the same checkout.
|
||||
- **Fan-in barrier is mandatory** — never start wave N+1 before every node in wave N returns and merges.
|
||||
- **Scope collisions serialize** — dep-free nodes touching the same files go in different waves.
|
||||
- **Never skip /review-it** before `/ship-it`.
|
||||
- **Cap concurrency** — default 3–4; more invites rate limits and merge contention.
|
||||
- **Never force-push to main.** Each node ships via its own branch/PR.
|
||||
- **Failed node blocks its dependents** — don't ship on top of unmet inputs.
|
||||
- **Max retries per node** — reuse `/loop-it`'s error classes; don't loop forever.
|
||||
- **Confirm the plan** before the first fan-out.
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Fix |
|
||||
|---------|-----|
|
||||
| Dispatching subagents in separate responses | One response, multiple calls = parallel. Separate = sequential. |
|
||||
| No worktree → parallel edits corrupt the tree | One `git worktree` per node. |
|
||||
| Two "independent" nodes edit the same file | Add a soft edge; put them in different waves. |
|
||||
| Starting the next wave before all nodes merge | Enforce the fan-in barrier. |
|
||||
| Over-decomposing into 20 trivial nodes | Merge tiny units; a node should be a meaningful shippable unit. |
|
||||
| Ignoring a failed node's dependents | Mark them `blocked`, skip them. |
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Other Skills
|
||||
|
||||
```
|
||||
/prd → /prd-to-spec → /to-issues ─┬─► /loop-it (sequential: one node at a time)
|
||||
└─► /graph (parallel: whole wave at once)
|
||||
│
|
||||
each node: inline /goal → /review-it → /ship-it (in its own worktree)
|
||||
```
|
||||
|
||||
- **`/to-issues`** — decomposition rules reused for building nodes.
|
||||
- **`/loop-it`** — sequential counterpart; use it when nodes heavily share files or serial safety matters.
|
||||
- **`/graph`** — this skill; use it when the DAG has genuine parallelism (independent subsystems).
|
||||
- **`/review-it`, `/ship-it`** — real skills each node's subagent invokes.
|
||||
```
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render a Claude-style light-theme graph.html dashboard from a .graph_state state file.
|
||||
|
||||
Usage:
|
||||
render_graph_html.py [.graph_state] [graph.html]
|
||||
|
||||
Defaults to reading ./.graph_state and writing ./graph.html.
|
||||
Called by the /graph skill at every checkpoint (initial plan + each fan-in barrier),
|
||||
so opening graph.html in a browser (it self-refreshes) tracks execution live.
|
||||
No third-party dependencies — stdlib only.
|
||||
"""
|
||||
import html
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
STATUS = {
|
||||
"pending": ("Pending", "#8C8579", "#EFECE3"),
|
||||
"in_progress": ("In Progress", "#CC785C", "#F7E9E2"),
|
||||
"shipped": ("Shipped", "#3D7A5A", "#DFEEE4"),
|
||||
"failed": ("Failed", "#B54A3E", "#F6DEDA"),
|
||||
"blocked": ("Blocked", "#9A6C3A", "#F2E6D4"),
|
||||
"skipped": ("Skipped", "#8C8579", "#EFECE3"),
|
||||
}
|
||||
|
||||
|
||||
def esc(s):
|
||||
return html.escape(str(s if s is not None else ""))
|
||||
|
||||
|
||||
def node_card(nid, n):
|
||||
st = n.get("status", "pending")
|
||||
label, fg, bg = STATUS.get(st, STATUS["pending"])
|
||||
deps = n.get("deps") or []
|
||||
deps_str = ", ".join(f"#{d}" for d in deps) if deps else "no deps"
|
||||
meta = []
|
||||
if n.get("pr"):
|
||||
meta.append(f'PR #{esc(n["pr"])}')
|
||||
if n.get("branch"):
|
||||
meta.append(f'<code>{esc(n["branch"])}</code>')
|
||||
if n.get("attempts"):
|
||||
meta.append(f'attempt {esc(n["attempts"])}')
|
||||
meta_html = " · ".join(meta)
|
||||
err = f'<div class="err">{esc(n["error"])}</div>' if n.get("error") else ""
|
||||
return f"""
|
||||
<div class="node" style="border-left:4px solid {fg}">
|
||||
<div class="node-top">
|
||||
<span class="nid">#{esc(nid)}</span>
|
||||
<span class="badge" style="color:{fg};background:{bg}">{label}</span>
|
||||
</div>
|
||||
<div class="title">{esc(n.get('title','(untitled)'))}</div>
|
||||
<div class="deps">{esc(deps_str)}</div>
|
||||
{f'<div class="meta">{meta_html}</div>' if meta_html else ''}
|
||||
{err}
|
||||
</div>"""
|
||||
|
||||
|
||||
def mermaid(state):
|
||||
lines = ["graph LR"]
|
||||
nodes = state.get("nodes", {})
|
||||
for nid, n in nodes.items():
|
||||
t = n.get("title", "")
|
||||
lines.append(f' n{nid}["#{nid} {t}"]')
|
||||
for nid, n in nodes.items():
|
||||
for d in (n.get("deps") or []):
|
||||
lines.append(f" n{d} --> n{nid}")
|
||||
# color by status
|
||||
for st, (_, fg, bg) in STATUS.items():
|
||||
ids = [f"n{nid}" for nid, n in nodes.items() if n.get("status") == st]
|
||||
if ids:
|
||||
lines.append(f" classDef {st} fill:{bg},stroke:{fg},color:#33312B;")
|
||||
lines.append(f" class {','.join(ids)} {st};")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render(state):
|
||||
nodes = state.get("nodes", {})
|
||||
total = len(nodes)
|
||||
counts = {k: 0 for k in STATUS}
|
||||
for n in nodes.values():
|
||||
counts[n.get("status", "pending")] = counts.get(n.get("status", "pending"), 0) + 1
|
||||
shipped = counts.get("shipped", 0)
|
||||
pct = int(shipped / total * 100) if total else 0
|
||||
waves = state.get("waves", [])
|
||||
cur = state.get("current_wave", 0)
|
||||
|
||||
legend = "".join(
|
||||
f'<span class="lg"><i style="background:{bg};border-color:{fg}"></i>{label}</span>'
|
||||
for label, fg, bg in STATUS.values()
|
||||
)
|
||||
|
||||
wave_html = ""
|
||||
for wi, wave in enumerate(waves):
|
||||
state_cls = "cur" if wi == cur else ("done" if wi < cur else "future")
|
||||
cards = "".join(node_card(str(nid), nodes.get(str(nid), {"title": f"#{nid}"})) for nid in wave)
|
||||
wave_html += f"""
|
||||
<section class="wave {state_cls}">
|
||||
<h2>Wave {wi} <span class="wcount">×{len(wave)} parallel</span>
|
||||
{'<span class="pill">running</span>' if wi == cur else ''}</h2>
|
||||
<div class="nodes">{cards}</div>
|
||||
</section>"""
|
||||
|
||||
stat = lambda k: f'<b style="color:{STATUS[k][1]}">{counts.get(k,0)}</b> {STATUS[k][0].lower()}'
|
||||
stats = " · ".join(stat(k) for k in ["shipped", "in_progress", "failed", "blocked", "skipped", "pending"])
|
||||
|
||||
return f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="5">
|
||||
<title>graph · {esc(state.get('task','execution'))}</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
<style>
|
||||
:root {{ --paper:#F5F4EE; --card:#FFFFFF; --ink:#33312B; --muted:#8C8579;
|
||||
--coral:#CC785C; --line:#E7E3D9; }}
|
||||
* {{ box-sizing:border-box; }}
|
||||
body {{ margin:0; background:var(--paper); color:var(--ink);
|
||||
font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; }}
|
||||
.wrap {{ max-width:1080px; margin:0 auto; padding:32px 24px 64px; }}
|
||||
header {{ border-bottom:1px solid var(--line); padding-bottom:20px; margin-bottom:24px; }}
|
||||
h1 {{ font-size:22px; margin:0 0 4px; font-weight:650; }}
|
||||
.sub {{ color:var(--muted); font-size:13px; }}
|
||||
.bar {{ height:8px; background:var(--line); border-radius:99px; margin:16px 0 8px; overflow:hidden; }}
|
||||
.bar>i {{ display:block; height:100%; width:{pct}%; background:var(--coral); border-radius:99px; }}
|
||||
.stats {{ font-size:13px; color:var(--muted); }}
|
||||
.legend {{ display:flex; gap:14px; flex-wrap:wrap; margin:14px 0 4px; font-size:12px; color:var(--muted); }}
|
||||
.lg {{ display:inline-flex; align-items:center; gap:6px; }}
|
||||
.lg i {{ width:12px; height:12px; border-radius:3px; border:1px solid; display:inline-block; }}
|
||||
.diagram {{ background:var(--card); border:1px solid var(--line); border-radius:14px; padding:18px; margin:20px 0; overflow:auto; }}
|
||||
.wave {{ margin:22px 0; }}
|
||||
.wave h2 {{ font-size:15px; margin:0 0 12px; display:flex; align-items:center; gap:10px; }}
|
||||
.wcount {{ font-weight:400; color:var(--muted); font-size:12px; }}
|
||||
.pill {{ font-size:11px; color:#fff; background:var(--coral); padding:2px 9px; border-radius:99px; }}
|
||||
.wave.future {{ opacity:.55; }}
|
||||
.nodes {{ display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:12px; }}
|
||||
.node {{ background:var(--card); border:1px solid var(--line); border-radius:12px; padding:12px 14px; }}
|
||||
.node-top {{ display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }}
|
||||
.nid {{ font-weight:650; color:var(--muted); font-size:13px; }}
|
||||
.badge {{ font-size:11px; padding:2px 8px; border-radius:99px; font-weight:600; }}
|
||||
.title {{ font-weight:550; margin-bottom:6px; }}
|
||||
.deps {{ font-size:12px; color:var(--muted); }}
|
||||
.meta {{ font-size:12px; color:var(--muted); margin-top:6px; }}
|
||||
.meta code, .node code {{ background:var(--paper); padding:1px 5px; border-radius:5px; font-size:11px; }}
|
||||
.err {{ font-size:12px; color:#B54A3E; margin-top:6px; white-space:pre-wrap; }}
|
||||
footer {{ margin-top:32px; color:var(--muted); font-size:12px; text-align:center; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<h1>{esc(state.get('task','Task Graph Execution'))}</h1>
|
||||
<div class="sub">{esc(state.get('repo',''))} · wave {cur} of {max(len(waves)-1,0)} · updated {esc(state.get('updated_at',''))}</div>
|
||||
<div class="bar"><i></i></div>
|
||||
<div class="stats">{shipped}/{total} shipped ({pct}%) — {stats}</div>
|
||||
<div class="legend">{legend}</div>
|
||||
</header>
|
||||
<div class="diagram"><pre class="mermaid">{esc(mermaid(state))}</pre></div>
|
||||
{wave_html}
|
||||
<footer>Auto-refreshes every 5s · generated by /graph from <code>.graph_state</code></footer>
|
||||
</div>
|
||||
<script>mermaid.initialize({{ startOnLoad:true, theme:"neutral" }});</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
def main():
|
||||
src = sys.argv[1] if len(sys.argv) > 1 else ".graph_state"
|
||||
dst = sys.argv[2] if len(sys.argv) > 2 else "graph.html"
|
||||
with open(src, encoding="utf-8") as f:
|
||||
state = json.load(f)
|
||||
state.setdefault("updated_at", datetime.now().isoformat(timespec="seconds"))
|
||||
with open(dst, "w", encoding="utf-8") as f:
|
||||
f.write(render(state))
|
||||
print(f"wrote {dst} from {src}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,273 @@
|
||||
---
|
||||
name: insight-diagram
|
||||
description: 为任意项目生成 UML 图、架构图和流程图。分析代码库后让用户选择要生成的图表类型,使用 architecture-diagram skill 渲染为 HTML+SVG,保存到 docs/ 目录。适用于任何软件项目的文档可视化。
|
||||
---
|
||||
|
||||
# Insight Diagram — 项目图表生成技能
|
||||
|
||||
分析任意代码库,自动生成 UML 14种图 + 架构图 + 流程图,使用 `/architecture-diagram` 渲染为 HTML+SVG。
|
||||
|
||||
## 图表分类与清单
|
||||
|
||||
### 结构性图形 (Structural Diagrams — 静态)
|
||||
描述系统的物理组成和静态结构。
|
||||
|
||||
| 编号 | 图表类型 | 英文标识 | 关注点 |
|
||||
|------|---------|---------|--------|
|
||||
| 1 | 系统架构图 | architecture | 组件关系、全局视角(非UML,最常用) |
|
||||
| 2 | 类图 | class | 定义类、属性、操作及关系 |
|
||||
| 3 | 对象图 | object | 特定时刻的对象实例及其关系 |
|
||||
| 4 | 组件图 | component | 系统组件及其依赖关系 |
|
||||
| 5 | 部署图 | deployment | 物理硬件、节点及软件部署 |
|
||||
| 6 | 包图 | package | 将模型元素分组组织 |
|
||||
| 7 | 复合结构图 | composite-structure | 类的内部结构 |
|
||||
| 8 | 剖面图 | profile | 扩展UML元模型、自定义构造型 |
|
||||
|
||||
### 行为性图形 (Behavioral Diagrams — 动态)
|
||||
描述系统与外部参与者或系统内部的交互过程。
|
||||
|
||||
| 编号 | 图表类型 | 英文标识 | 关注点 |
|
||||
|------|---------|---------|--------|
|
||||
| 9 | 流程图 | flowchart | 主流程与分支(非UML,最常用) |
|
||||
| 10 | 用例图 | usecase | 从用户角度展示系统功能 |
|
||||
| 11 | 活动图 | activity | 过程的流程或步骤 |
|
||||
| 12 | 状态机图 | state-machine | 对象生命周期的状态变迁 |
|
||||
| 13 | 序列图 | sequence | 按时间顺序展示对象间交互 |
|
||||
| 14 | 通信图 | communication | 侧重于对象间的组织关系 |
|
||||
| 15 | 定时图 | timing | 侧重于状态变化的时间约束 |
|
||||
| 16 | 交互概览图 | interaction-overview | 结合活动图和时序图 |
|
||||
| 17 | 泳道图 | swimlane | 跨组件/角色职责流程(活动图变体) |
|
||||
|
||||
## 示例参考
|
||||
|
||||
本技能的 `examples/` 目录包含 13 个已完成的图表 HTML 文件,作为视觉样式和内容结构的参考模板。**生成任何图表前,必须先阅读对应的示例文件**,以确保风格一致、结构规范。
|
||||
|
||||
### 示例文件清单
|
||||
|
||||
| 文件 | 图表类型 | 英文标识 |
|
||||
|------|---------|---------|
|
||||
| `examples/architecture.html` | 系统架构图 | architecture |
|
||||
| `examples/class.html` | 类图 | class |
|
||||
| `examples/object.html` | 对象图 | object |
|
||||
| `examples/component.html` | 组件图 | component |
|
||||
| `examples/deployment.html` | 部署图 | deployment |
|
||||
| `examples/flowchart.html` | 流程图 | flowchart |
|
||||
| `examples/usecase.html` | 用例图 | usecase |
|
||||
| `examples/activity.html` | 活动图 | activity |
|
||||
| `examples/sequence.html` | 序列图 | sequence |
|
||||
| `examples/communication.html` | 通信图 | communication |
|
||||
| `examples/dfd.html` | 数据流图 | dfd |
|
||||
| `examples/interaction-overview.html` | 交互概览图 | interaction-overview |
|
||||
| `examples/swimlane.html` | 泳道图 | swimlane |
|
||||
|
||||
### 参考规则
|
||||
|
||||
1. **生成前必读**: 调用 `/architecture-diagram` 前,先用 Read 工具阅读对应类型的示例文件,从中提取:
|
||||
- SVG 布局策略(节点间距、分组方式、箭头走向)
|
||||
- 节点样式层级(核心节点 accent 高亮、普通节点实线边框、可选节点虚线边框)
|
||||
- 标注风格(阶段标签、Legend 图例、卡片摘要)
|
||||
- 信息密度(每个节点显示多少字段/属性)
|
||||
|
||||
2. **结构对齐**: 生成的图表应与示例保持相同的结构层次:
|
||||
- 页面顶部:标题 + 副标题 + 图表类型说明
|
||||
- 中间主体:SVG 图表区域(带浅色边框容器)
|
||||
- 底部:信息摘要卡片 + 页脚
|
||||
|
||||
3. **内容替换而非照搬**: 示例中的业务数据(NovaShield 风控系统)是虚构的参考案例,生成时需替换为目标项目的真实架构信息。只参考布局和样式,不复制业务内容。
|
||||
|
||||
4. **无对应示例的类型**: 对于包图 (package)、复合结构图 (composite-structure)、剖面图 (profile)、状态机图 (state-machine)、定时图 (timing) 这 5 种没有示例文件的图表类型,参考最相近的已有示例(如包图参考组件图,状态机图参考活动图),并沿用相同的视觉语言。
|
||||
|
||||
## 执行流程
|
||||
|
||||
### 步骤 1:分析代码库
|
||||
|
||||
读取项目关键文件,提取架构信息:
|
||||
|
||||
1. 读取项目根目录的 `CLAUDE.md`(如存在)获取项目概览
|
||||
2. 读取各子目录的 `CLAUDE.md`(如存在)获取模块细节
|
||||
3. 用 Glob 扫描源码文件结构(`**/*.go`, `**/*.py`, `**/*.ts` 等)
|
||||
4. 读取入口文件(`main.go`, `app.py`, `index.ts` 等)识别顶层组件
|
||||
5. 用 Grep 搜索关键模式:接口定义、函数签名、依赖注入、配置项
|
||||
|
||||
从以上信息中提炼出:
|
||||
- **组件清单**: 服务、模块、外部依赖
|
||||
- **关系图**: 谁调用谁、谁依赖谁、数据流向
|
||||
- **核心类型**: 结构体/类、接口、枚举
|
||||
- **流程**: 主业务流程、异常处理流程
|
||||
- **部署**: 进程、中间件、外部服务
|
||||
|
||||
### 步骤 2:选择图表
|
||||
|
||||
使用 AskUserQuestion 让用户选择要生成的图表(multiSelect: true),分4组展示:
|
||||
|
||||
**第1组 — 结构性图形(静态):**
|
||||
- 系统架构图 (architecture)
|
||||
- 类图 (class)
|
||||
- 对象图 (object)
|
||||
- 组件图 (component)
|
||||
|
||||
**第2组 — 结构性图形续 + 部署:**
|
||||
- 部署图 (deployment)
|
||||
- 包图 (package)
|
||||
- 复合结构图 (composite-structure)
|
||||
- 剖面图 (profile)
|
||||
|
||||
**第3组 — 行为性图形(动态):**
|
||||
- 流程图 (flowchart)
|
||||
- 用例图 (usecase)
|
||||
- 活动图 (activity)
|
||||
- 状态机图 (state-machine)
|
||||
|
||||
**第4组 — 交互图 + 常用非UML:**
|
||||
- 序列图 (sequence)
|
||||
- 通信图 (communication)
|
||||
- 交互概览图 (interaction-overview)
|
||||
- 泳道图 (swimlane)
|
||||
- 全部生成 (all)
|
||||
|
||||
默认推荐:architecture + sequence + flowchart
|
||||
|
||||
### 步骤 3:逐个生成
|
||||
|
||||
对每个选中的图表类型:
|
||||
|
||||
1. **先读示例**: 用 Read 工具阅读 `examples/<标识>.html`(如 `examples/architecture.html`),提取布局模式、节点样式、标注方式
|
||||
2. 根据步骤 1 提取的架构信息,整理出该图表应展示的元素和关系
|
||||
3. 调用 `/architecture-diagram` skill,传入图表类型、标题、内容描述、输出路径,**必须指定 light 风格**
|
||||
4. 输出文件保存到 `docs/<标识>.html`(如 `docs/architecture.html`)
|
||||
5. **生成后必须 review**: 运行几何校验脚本,按结果修正后再继续下一张(见下方「步骤 3.5」)
|
||||
6. 简要报告完成状态
|
||||
|
||||
**生成规则:**
|
||||
- **风格**: 必须使用 light Claude 风格(暖白背景 #FAF9F6、terracotta/sage/plum/rose 配色、Inter 字体、白色卡片容器),与 Anthropic Claude 品牌视觉一致
|
||||
- **防遮盖**: 所有 SVG 元素(节点、箭头、标签)不得互相遮盖。具体做法:
|
||||
- 计算每个元素的边界框,确保无重叠
|
||||
- 箭头绘制在节点下方(SVG 中先画箭头再画节点)
|
||||
- 节点间留足间距(垂直最少 40px,水平最少 30px)
|
||||
- 文字不超出所在节点边界,超长文字截断或换行
|
||||
- 连接线的标签放置在线段中点偏移处,避免覆盖线段或节点
|
||||
- 如果元素过多导致图表拥挤,拆分为多个子图或缩小元素尺寸
|
||||
|
||||
批量生成顺序(宏观→微观):
|
||||
architecture → component → deployment → package → composite-structure → profile → class → object → usecase → flowchart → activity → state-machine → swimlane → sequence → communication → timing → interaction-overview
|
||||
|
||||
### 步骤 3.5:几何 review(每张图生成后必做)
|
||||
|
||||
生成的 SVG 常见三类几何缺陷,必须用脚本逐张校验并修正:
|
||||
|
||||
```bash
|
||||
python3 skills/insight-diagram/scripts/review_svg.py docs/<标识>.html --min-gap 8
|
||||
# 批量: python3 skills/insight-diagram/scripts/review_svg.py docs/*.html --min-gap 8
|
||||
```
|
||||
|
||||
脚本检查(与三条核心要求一一对应):
|
||||
|
||||
1. **箭头落点**:每个带箭头的端点必须恰好落在目标框/椭圆/菱形的**边缘线**上(容差 6px)。
|
||||
- `ERROR 深入框内`:端点穿入框内部 >8px → 缩短连线,让它止于边缘。
|
||||
- `WARNING 空接`:端点悬空、距最近框 >8px 且不汇入任何其它连线 → 把端点对齐到框边或汇合点。
|
||||
- 合法情形:端点落在框边、生命线、或与另一条连线交汇(分支/汇聚)——脚本不会误报。
|
||||
2. **框重叠**(`ERROR`):非嵌套的两个框在水平、垂直两个方向都有交叠 → 必须移开其中一个。嵌套(一个完全包住另一个,如分组边界框包子节点)是允许的。
|
||||
3. **框间距**(`WARNING`):投影相邻的两框净间距 < `--min-gap`(默认 8px)→ 拉开距离。
|
||||
|
||||
处理原则:
|
||||
- **ERROR 必须修复**后再进入下一张;修完重跑脚本确认归零。
|
||||
- **WARNING 逐条核对**:序列图生命线底部的消息、泳道边界、紧贴的分组等可能是设计本意,确认无误可保留;其余应调整坐标。
|
||||
- 修正方式是直接编辑 `docs/<标识>.html` 里对应的 `<rect>/<line>/<path>` 坐标,而非重新生成整张图。
|
||||
- 退出码:有 ERROR 返回 1,干净返回 0;CI 中可加 `--strict` 让 WARNING 也阻断。
|
||||
|
||||
### 步骤 4:报告
|
||||
|
||||
全部完成后输出:
|
||||
- 生成的文件列表
|
||||
- 每个图表的简要描述
|
||||
|
||||
## 各图表的内容指南
|
||||
|
||||
### 系统架构图 (architecture) — 非UML,最常用
|
||||
- 展示系统顶层组件及其连接关系
|
||||
- 区分内部模块与外部依赖
|
||||
- 标注核心数据流方向
|
||||
|
||||
### 类图 (class)
|
||||
- 核心类型为类节点(名称+字段+方法)
|
||||
- 继承、组合、依赖关系
|
||||
- 接口与实现分离
|
||||
- 限制在 10-15 个核心类型
|
||||
|
||||
### 对象图 (object)
|
||||
- 选取一个典型运行时场景
|
||||
- 展示对象实例及其属性值
|
||||
- 对象间的链接关系
|
||||
|
||||
### 组件图 (component)
|
||||
- 每个组件为一个节点
|
||||
- 箭头表示依赖/调用方向
|
||||
- 标注接口名称
|
||||
|
||||
### 部署图 (deployment)
|
||||
- 物理节点(服务器、容器、Serverless)
|
||||
- 中间件(消息队列、缓存、数据库)
|
||||
- 外部服务(第三方 API)
|
||||
- 标注通信协议
|
||||
|
||||
### 包图 (package)
|
||||
- 按模块/命名空间分组
|
||||
- 包间依赖关系
|
||||
- 体现分层架构
|
||||
|
||||
### 复合结构图 (composite-structure)
|
||||
- 类/组件的内部结构
|
||||
- 部件(Part)与连接器(Connector)
|
||||
- 端口(Port)与接口
|
||||
|
||||
### 剖面图 (profile)
|
||||
- 自定义构造型(Stereotype)
|
||||
- 扩展元模型的标签定义(Tagged Values)
|
||||
- 领域特定建模约束
|
||||
|
||||
### 流程图 (flowchart) — 非UML,最常用
|
||||
- 主流程 + 关键分支
|
||||
- 失败/异常路径
|
||||
- 起止节点清晰
|
||||
|
||||
### 用例图 (usecase)
|
||||
- 参与者(人/外部系统)
|
||||
- 用例椭圆
|
||||
- include/extend 关系
|
||||
|
||||
### 活动图 (activity)
|
||||
- 阶段/步骤为活动节点
|
||||
- 并行分支用 fork/join
|
||||
- 决策点用菱形
|
||||
|
||||
### 状态机图 (state-machine)
|
||||
- 对象的关键状态
|
||||
- 触发状态变迁的事件
|
||||
- 动作/守卫条件
|
||||
- 初始态和终态
|
||||
|
||||
### 序列图 (sequence)
|
||||
- 参与者为纵向生命线
|
||||
- 水平箭头为消息调用
|
||||
- 标注关键返回值
|
||||
- 关注 2-5 个核心交互场景
|
||||
|
||||
### 通信图 (communication)
|
||||
- 组件为节点,消息为连线
|
||||
- 标注消息序号
|
||||
- 强调协作关系而非时序
|
||||
|
||||
### 定时图 (timing)
|
||||
- 时间轴横向展开
|
||||
- 状态变化的时间约束
|
||||
- 持续时间标注
|
||||
|
||||
### 交互概览图 (interaction-overview)
|
||||
- 控制流节点内嵌交互片段
|
||||
- 展示条件分支和循环
|
||||
- 宏观概览各交互场景
|
||||
|
||||
### 泳道图 (swimlane) — 活动图变体
|
||||
- 按组件/角色分泳道
|
||||
- 流程步骤在对应泳道内
|
||||
- 跨泳道箭头表示交互
|
||||
@@ -0,0 +1,62 @@
|
||||
# 图表类型内容提取策略
|
||||
|
||||
每种图表需要从代码库中提取不同维度的信息。以下是通用的提取策略,适用于任何语言/框架的项目。
|
||||
|
||||
## 通用提取规则
|
||||
|
||||
### 组件识别
|
||||
- 入口文件中的初始化/注册代码
|
||||
- 依赖注入容器(Wire, Spring, 等)
|
||||
- 包/模块的公开接口
|
||||
- 配置文件中引用的外部服务
|
||||
|
||||
### 关系识别
|
||||
- import/require 语句
|
||||
- 函数调用链(谁调用了谁)
|
||||
- 接口实现关系
|
||||
- 事件发布/订阅
|
||||
|
||||
### 数据流识别
|
||||
- 函数参数和返回值
|
||||
- 消息队列的 topic/producer/consumer
|
||||
- API endpoint 的 request/response
|
||||
- 数据库读写操作
|
||||
|
||||
### 流程识别
|
||||
- 主循环 / 事件循环
|
||||
- 中间件链 / handler 链
|
||||
- 状态机转换
|
||||
- 错误处理 / 重试逻辑
|
||||
|
||||
## 按语言的搜索模式
|
||||
|
||||
### Go
|
||||
- 接口: `type \w+ interface`
|
||||
- 结构体: `type \w+ struct`
|
||||
- 函数签名: `func \([^)]+\) \w+`
|
||||
- 依赖注入: `New\w+\(.*\w+Client`
|
||||
- Goroutine/Channel: `go func`, `chan `
|
||||
- 错误处理: `if err != nil`
|
||||
|
||||
### Python
|
||||
- 类: `class \w+`
|
||||
- 函数: `def \w+`
|
||||
- 装饰器: `@\w+`(路由、依赖注入)
|
||||
- 异步: `async def`, `await `
|
||||
- 导入: `from .* import`, `import `
|
||||
|
||||
### TypeScript/JavaScript
|
||||
- 类: `class \w+`
|
||||
- 接口: `interface \w+`
|
||||
- 导入: `import .* from`
|
||||
- 路由: `app\.(get|post|put|delete)`
|
||||
- 中间件: `\.use\(`
|
||||
|
||||
## 信息提取深度
|
||||
|
||||
- **架构图/组件图/部署图**: 只需包级/模块级信息,读 CLAUDE.md + 入口文件即可
|
||||
- **序列图/通信图**: 需要函数调用链,读关键源码文件
|
||||
- **类图/对象图**: 需要类型定义,读 types/model 文件
|
||||
- **流程图/活动图/泳道图**: 需要主流程代码,读 pipeline/orchestrator/handler 文件
|
||||
- **数据流图**: 需要数据结构 + 变换逻辑,读 processor/converter 文件
|
||||
- **用例图**: 读 CLAUDE.md + router/api 文件
|
||||
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/env python3
|
||||
"""审查 insight-diagram 生成的 SVG 图,做几何校验。
|
||||
|
||||
检查项(对应三条要求):
|
||||
1. 箭头两端是否落在框图边缘线上 —— 既不"深入"框内,也不"空接"悬空。
|
||||
2. 非嵌套框图之间是否重叠。
|
||||
3. 框图之间是否留出足够间距。
|
||||
|
||||
用法:
|
||||
python3 review_svg.py docs/architecture.html [more.html ...]
|
||||
python3 review_svg.py docs/*.html --min-gap 8 --json
|
||||
|
||||
退出码: 发现 ERROR 时为 1;仅 WARNING(或干净)为 0;加 --strict 让 WARNING 也返回 1。
|
||||
|
||||
实现说明:用 html.parser(而非 XML 解析器)遍历,以兼容 SVG-in-HTML 中
|
||||
未转义的 & 或文本里的 <<include>> 等;箭头端点允许落在任意图形边缘、
|
||||
任意连线/生命线上(汇合点),故序列图/通信图等不会误报"空接"。
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
from html.parser import HTMLParser
|
||||
|
||||
# ---- 几何容差(像素)----
|
||||
BOUNDARY_TOL = 6.0 # 端点距图形边 <= 此值视为"落在边上",正常
|
||||
PENETRATION = 8.0 # 端点在框内且距最近边 > 此值视为"深入"(ERROR)
|
||||
FLOATING = 8.0 # 端点距所有图形 > 此值且不接任何连线 → "空接"(WARNING)
|
||||
JUNCTION_TOL = 6.0 # 端点距其他连线/生命线 <= 此值视为合法汇合点
|
||||
OVERLAP_EPS = 2.0 # 两方向交叠都 > 此值才算重叠
|
||||
CONTAIN_MARGIN = 2.0 # 判定包含时允许内框略微超出
|
||||
SAME_BOX_TOL = 3.0 # 各边相差 <= 此值视为同一个框(去重)
|
||||
|
||||
# 只有 w/h 同时达到阈值的图形才算"框图节点",参与重叠/间距校验;
|
||||
# 更小的(标签底衬、终止圆点、图例色块)仅作为箭头落点目标。
|
||||
MIN_BOX_W = 36.0
|
||||
MIN_BOX_H = 22.0
|
||||
|
||||
# 这些子树内的图形只是装饰/定义,不收集
|
||||
SKIP_SUBTREES = {'defs', 'marker', 'pattern', 'clippath', 'lineargradient',
|
||||
'radialgradient', 'symbol', 'mask'}
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# 基础工具
|
||||
# =====================================================================
|
||||
def _floats(s):
|
||||
return [float(x) for x in re.findall(r'-?\d+(?:\.\d+)?', s or '')]
|
||||
|
||||
|
||||
def _num(v):
|
||||
"""解析 SVG 数值属性;含 '%' 或无法解析时返回 None。"""
|
||||
if v is None or '%' in v:
|
||||
return None
|
||||
m = re.match(r'\s*(-?\d+(?:\.\d+)?)', v)
|
||||
return float(m.group(1)) if m else None
|
||||
|
||||
|
||||
def _parse_translate(transform):
|
||||
"""累加 transform 中的 translate 偏移,返回 (dx, dy)。"""
|
||||
dx = dy = 0.0
|
||||
for m in re.finditer(r'translate\(\s*([-\d.]+)[\s,]*([-\d.]+)?\s*\)', transform or ''):
|
||||
dx += float(m.group(1))
|
||||
dy += float(m.group(2)) if m.group(2) is not None else 0.0
|
||||
return dx, dy
|
||||
|
||||
|
||||
def _point_seg_dist(px, py, ax, ay, bx, by):
|
||||
"""点到线段的最短距离。"""
|
||||
dx, dy = bx - ax, by - ay
|
||||
if dx == 0 and dy == 0:
|
||||
return math.hypot(px - ax, py - ay)
|
||||
t = ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)
|
||||
t = max(0.0, min(1.0, t))
|
||||
return math.hypot(px - (ax + t * dx), py - (ay + t * dy))
|
||||
|
||||
|
||||
def parse_path_points(d):
|
||||
"""提取 path 各命令的落点(曲线只取终点),用于求首/末锚点。"""
|
||||
pts = []
|
||||
cx = cy = 0.0
|
||||
cmd, nums = None, []
|
||||
tokens = re.findall(r'([MmLlHhVvCcSsQqTtAaZz])|(-?\d+(?:\.\d+)?)', d or '')
|
||||
|
||||
def flush():
|
||||
nonlocal cx, cy
|
||||
if cmd in ('M', 'L', 'T'):
|
||||
for i in range(0, len(nums) - 1, 2):
|
||||
cx, cy = nums[i], nums[i + 1]; pts.append((cx, cy))
|
||||
elif cmd in ('m', 'l', 't'):
|
||||
for i in range(0, len(nums) - 1, 2):
|
||||
cx, cy = cx + nums[i], cy + nums[i + 1]; pts.append((cx, cy))
|
||||
elif cmd == 'H':
|
||||
for v in nums: cx = v; pts.append((cx, cy))
|
||||
elif cmd == 'h':
|
||||
for v in nums: cx += v; pts.append((cx, cy))
|
||||
elif cmd == 'V':
|
||||
for v in nums: cy = v; pts.append((cx, cy))
|
||||
elif cmd == 'v':
|
||||
for v in nums: cy += v; pts.append((cx, cy))
|
||||
elif cmd in ('C', 'S', 'Q') and len(nums) >= 2:
|
||||
cx, cy = nums[-2], nums[-1]; pts.append((cx, cy))
|
||||
elif cmd in ('c', 's', 'q') and len(nums) >= 2:
|
||||
cx, cy = cx + nums[-2], cy + nums[-1]; pts.append((cx, cy))
|
||||
for tok_cmd, tok_num in tokens:
|
||||
if tok_cmd:
|
||||
if cmd is not None:
|
||||
flush()
|
||||
cmd, nums = tok_cmd, []
|
||||
else:
|
||||
nums.append(float(tok_num))
|
||||
if cmd is not None:
|
||||
flush()
|
||||
return pts
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# 图形对象
|
||||
# =====================================================================
|
||||
class Shape:
|
||||
"""任意几何图形的统一表示,用 bbox + 类型描述。"""
|
||||
__slots__ = ('kind', 'x0', 'y0', 'x1', 'y1', 'cx', 'cy', 'rx', 'ry')
|
||||
|
||||
def __init__(self, kind, x0, y0, x1, y1):
|
||||
self.kind = kind
|
||||
self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1
|
||||
self.cx, self.cy = (x0 + x1) / 2, (y0 + y1) / 2
|
||||
self.rx, self.ry = (x1 - x0) / 2, (y1 - y0) / 2
|
||||
|
||||
@property
|
||||
def w(self): return self.x1 - self.x0
|
||||
@property
|
||||
def h(self): return self.y1 - self.y0
|
||||
|
||||
def is_box(self):
|
||||
return self.w >= MIN_BOX_W and self.h >= MIN_BOX_H
|
||||
|
||||
def signed_dist(self, px, py):
|
||||
"""点到边界的有符号距离:内部为负、外部为正、≈0 在边上。"""
|
||||
if self.kind == 'ellipse' and self.rx > 0 and self.ry > 0:
|
||||
nx, ny = (px - self.cx) / self.rx, (py - self.cy) / self.ry
|
||||
return (math.hypot(nx, ny) - 1.0) * ((self.rx + self.ry) / 2.0)
|
||||
if self.kind == 'circle' and self.rx > 0:
|
||||
return math.hypot(px - self.cx, py - self.cy) - self.rx
|
||||
dx = max(self.x0 - px, 0, px - self.x1)
|
||||
dy = max(self.y0 - py, 0, py - self.y1)
|
||||
if dx > 0 or dy > 0:
|
||||
return math.hypot(dx, dy)
|
||||
return -min(px - self.x0, self.x1 - px, py - self.y0, self.y1 - py)
|
||||
|
||||
def bbox_key(self):
|
||||
return (round(self.x0, 1), round(self.y0, 1),
|
||||
round(self.x1, 1), round(self.y1, 1))
|
||||
|
||||
|
||||
def _contains(a, b):
|
||||
"""框 a 是否(在容差内)包含框 b 且二者不等大。"""
|
||||
return (b.x0 >= a.x0 - CONTAIN_MARGIN and b.x1 <= a.x1 + CONTAIN_MARGIN and
|
||||
b.y0 >= a.y0 - CONTAIN_MARGIN and b.y1 <= a.y1 + CONTAIN_MARGIN and
|
||||
not (abs(a.x0 - b.x0) < SAME_BOX_TOL and abs(a.x1 - b.x1) < SAME_BOX_TOL and
|
||||
abs(a.y0 - b.y0) < SAME_BOX_TOL and abs(a.y1 - b.y1) < SAME_BOX_TOL))
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# 用 HTMLParser 遍历 SVG(容忍未转义字符)
|
||||
# =====================================================================
|
||||
class SvgCollector(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__(convert_charrefs=True)
|
||||
self.stack = [{'ox': 0.0, 'oy': 0.0, 'skip': False}]
|
||||
self.shapes = [] # 所有图形(含小图形),用于箭头落点目标
|
||||
self.connectors = [] # 带 marker 的 line/path
|
||||
self.segments = [] # 所有 line/path 折线段,用于汇合点判定
|
||||
|
||||
# void/自闭合元素
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
self._emit(tag.lower(), dict(attrs))
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
tag = tag.lower()
|
||||
a = dict(attrs)
|
||||
parent = self.stack[-1]
|
||||
dx, dy = _parse_translate(a.get('transform', ''))
|
||||
node = {'ox': parent['ox'] + dx, 'oy': parent['oy'] + dy,
|
||||
'skip': parent['skip'] or tag in SKIP_SUBTREES}
|
||||
self.stack.append(node)
|
||||
self._emit(tag, a, ctx=node)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if len(self.stack) > 1:
|
||||
self.stack.pop()
|
||||
|
||||
def _emit(self, tag, a, ctx=None):
|
||||
ctx = ctx or self.stack[-1]
|
||||
if ctx['skip'] or tag in SKIP_SUBTREES:
|
||||
return
|
||||
ox, oy = ctx['ox'], ctx['oy']
|
||||
dx, dy = _parse_translate(a.get('transform', '')) # 自闭合元素自身的 translate
|
||||
if tag in ('rect', 'ellipse', 'circle', 'polygon'):
|
||||
ox, oy = ox + dx, oy + dy
|
||||
|
||||
if tag == 'rect':
|
||||
x, y = _num(a.get('x', '0')), _num(a.get('y', '0'))
|
||||
w, h = _num(a.get('width')), _num(a.get('height'))
|
||||
if None not in (x, y, w, h):
|
||||
self.shapes.append(Shape('rect', ox + x, oy + y, ox + x + w, oy + y + h))
|
||||
elif tag == 'ellipse':
|
||||
cx, cy = _num(a.get('cx', '0')), _num(a.get('cy', '0'))
|
||||
rx, ry = _num(a.get('rx')), _num(a.get('ry'))
|
||||
if None not in (cx, cy, rx, ry):
|
||||
self.shapes.append(Shape('ellipse', ox + cx - rx, oy + cy - ry,
|
||||
ox + cx + rx, oy + cy + ry))
|
||||
elif tag == 'circle':
|
||||
cx, cy = _num(a.get('cx', '0')), _num(a.get('cy', '0'))
|
||||
r = _num(a.get('r'))
|
||||
if None not in (cx, cy, r):
|
||||
self.shapes.append(Shape('circle', ox + cx - r, oy + cy - r,
|
||||
ox + cx + r, oy + cy + r))
|
||||
elif tag == 'polygon':
|
||||
nums = _floats(a.get('points', ''))
|
||||
pts = list(zip(nums[0::2], nums[1::2]))
|
||||
if len(pts) >= 3:
|
||||
xs = [ox + p[0] for p in pts]; ys = [oy + p[1] for p in pts]
|
||||
self.shapes.append(Shape('polygon', min(xs), min(ys), max(xs), max(ys)))
|
||||
elif tag == 'line':
|
||||
x1, y1 = _num(a.get('x1', '0')), _num(a.get('y1', '0'))
|
||||
x2, y2 = _num(a.get('x2', '0')), _num(a.get('y2', '0'))
|
||||
if None not in (x1, y1, x2, y2):
|
||||
seg = [(ox + x1, oy + y1), (ox + x2, oy + y2)]
|
||||
self.segments.append(seg)
|
||||
if a.get('marker-end') or a.get('marker-start'):
|
||||
self.connectors.append({
|
||||
'a': seg[0], 'b': seg[-1], 'seg': seg,
|
||||
'arrow_a': bool(a.get('marker-start')),
|
||||
'arrow_b': bool(a.get('marker-end'))})
|
||||
elif tag == 'path':
|
||||
pts = [(ox + px, oy + py) for px, py in parse_path_points(a.get('d', ''))]
|
||||
if len(pts) >= 2:
|
||||
self.segments.append(pts)
|
||||
if a.get('marker-end') or a.get('marker-start'):
|
||||
self.connectors.append({
|
||||
'a': pts[0], 'b': pts[-1], 'seg': pts,
|
||||
'arrow_a': bool(a.get('marker-start')),
|
||||
'arrow_b': bool(a.get('marker-end'))})
|
||||
|
||||
|
||||
def collect(svg_text):
|
||||
"""返回 (boxes, all_shapes, connectors, segments)。"""
|
||||
p = SvgCollector()
|
||||
p.feed(svg_text)
|
||||
# 框去重(描边 + 遮罩底衬常画两层完全重合的 rect)
|
||||
seen, boxes = set(), []
|
||||
for s in p.shapes:
|
||||
if s.is_box():
|
||||
k = s.bbox_key()
|
||||
if k not in seen:
|
||||
seen.add(k)
|
||||
boxes.append(s)
|
||||
return boxes, p.shapes, p.connectors, p.segments
|
||||
|
||||
|
||||
def extract_svg(text):
|
||||
m = re.search(r'<svg\b.*?</svg>', text, re.DOTALL | re.IGNORECASE)
|
||||
return m.group(0) if m else None
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# 三项检查
|
||||
# =====================================================================
|
||||
def check_arrow_endpoints(shapes, connectors, segments):
|
||||
"""检查 1:箭头端点应恰好落在某图形边缘,或合法汇入另一连线。
|
||||
|
||||
会先剔除"装饰性"连线:两端都既不贴任何图形边、也不汇入其它线段
|
||||
(典型如图例 Legend 里的示例箭头 / 独立标注线),不参与判定。
|
||||
"""
|
||||
issues = []
|
||||
|
||||
def status(px, py):
|
||||
dists = [s.signed_dist(px, py) for s in shapes]
|
||||
on_edge = bool(dists) and any(abs(d) <= BOUNDARY_TOL for d in dists)
|
||||
deepest = min(dists) if dists else 0.0
|
||||
nearest_out = min((d for d in dists if d >= 0), default=None)
|
||||
return on_edge, deepest, nearest_out
|
||||
|
||||
for i, c in enumerate(connectors):
|
||||
sa = status(*c['a'])
|
||||
sb = status(*c['b'])
|
||||
a_anchored = sa[0] or _near_other_segment(*c['a'], segments, c['seg'])
|
||||
b_anchored = sb[0] or _near_other_segment(*c['b'], segments, c['seg'])
|
||||
# 两端都不锚定 → 视为图例/装饰线,跳过
|
||||
if not a_anchored and not b_anchored:
|
||||
continue
|
||||
|
||||
ends = []
|
||||
if c['arrow_b']:
|
||||
ends.append(('终点', c['b'], sb))
|
||||
if c['arrow_a']:
|
||||
ends.append(('起点', c['a'], sa))
|
||||
for label, (px, py), (on_edge, deepest, nearest_out) in ends:
|
||||
if on_edge:
|
||||
continue # 落在某图形边上:正常
|
||||
if deepest < -PENETRATION:
|
||||
issues.append(('ERROR',
|
||||
f'连线#{i+1} {label}({px:.0f},{py:.0f}) 深入框内 '
|
||||
f'{-deepest:.0f}px,应止于框边缘'))
|
||||
continue
|
||||
if nearest_out is not None and nearest_out > FLOATING:
|
||||
if _near_other_segment(px, py, segments, c['seg']):
|
||||
continue # 汇入另一连线/生命线
|
||||
issues.append(('WARNING',
|
||||
f'连线#{i+1} {label}({px:.0f},{py:.0f}) 悬空,'
|
||||
f'距最近框边 {nearest_out:.0f}px(空接)'))
|
||||
return issues
|
||||
|
||||
|
||||
def _near_other_segment(px, py, segments, own):
|
||||
for seg in segments:
|
||||
if seg is own:
|
||||
continue
|
||||
for k in range(len(seg) - 1):
|
||||
if _point_seg_dist(px, py, *seg[k], *seg[k + 1]) <= JUNCTION_TOL:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_overlap(boxes):
|
||||
"""检查 2:非嵌套框之间不得重叠。"""
|
||||
issues = []
|
||||
for i in range(len(boxes)):
|
||||
for j in range(i + 1, len(boxes)):
|
||||
a, b = boxes[i], boxes[j]
|
||||
if _contains(a, b) or _contains(b, a):
|
||||
continue
|
||||
ox = min(a.x1, b.x1) - max(a.x0, b.x0)
|
||||
oy = min(a.y1, b.y1) - max(a.y0, b.y0)
|
||||
if ox > OVERLAP_EPS and oy > OVERLAP_EPS:
|
||||
issues.append(('ERROR',
|
||||
f'框[{a.x0:.0f},{a.y0:.0f} {a.w:.0f}x{a.h:.0f}] 与 '
|
||||
f'[{b.x0:.0f},{b.y0:.0f} {b.w:.0f}x{b.h:.0f}] '
|
||||
f'重叠 {ox:.0f}x{oy:.0f}px'))
|
||||
return issues
|
||||
|
||||
|
||||
def check_spacing(boxes, min_gap):
|
||||
"""检查 3:投影相邻、不嵌套、不重叠的框,净间距需 >= min_gap。"""
|
||||
issues = []
|
||||
for i in range(len(boxes)):
|
||||
for j in range(i + 1, len(boxes)):
|
||||
a, b = boxes[i], boxes[j]
|
||||
if _contains(a, b) or _contains(b, a):
|
||||
continue
|
||||
xo = min(a.x1, b.x1) - max(a.x0, b.x0)
|
||||
yo = min(a.y1, b.y1) - max(a.y0, b.y0)
|
||||
if xo > OVERLAP_EPS and yo > OVERLAP_EPS:
|
||||
continue # 重叠交给 check_overlap
|
||||
gap, axis = None, ''
|
||||
if xo > OVERLAP_EPS:
|
||||
gap, axis = max(a.y0, b.y0) - min(a.y1, b.y1), '垂直'
|
||||
elif yo > OVERLAP_EPS:
|
||||
gap, axis = max(a.x0, b.x0) - min(a.x1, b.x1), '水平'
|
||||
if gap is not None and 0 <= gap < min_gap:
|
||||
issues.append(('WARNING',
|
||||
f'框[{a.x0:.0f},{a.y0:.0f}] 与 [{b.x0:.0f},{b.y0:.0f}] '
|
||||
f'{axis}间距仅 {gap:.0f}px (< {min_gap:.0f}px)'))
|
||||
return issues
|
||||
|
||||
|
||||
def review_file(path, min_gap):
|
||||
try:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
except OSError as e:
|
||||
return {'file': path, 'issues': [('ERROR', f'无法读取: {e}')],
|
||||
'boxes': 0, 'connectors': 0}
|
||||
svg = extract_svg(text)
|
||||
if not svg:
|
||||
return {'file': path, 'issues': [('ERROR', '未找到 <svg> 块')],
|
||||
'boxes': 0, 'connectors': 0}
|
||||
boxes, shapes, connectors, segments = collect(svg)
|
||||
issues = (check_arrow_endpoints(shapes, connectors, segments)
|
||||
+ check_overlap(boxes)
|
||||
+ check_spacing(boxes, min_gap))
|
||||
return {'file': path, 'issues': issues,
|
||||
'boxes': len(boxes), 'connectors': len(connectors)}
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# CLI
|
||||
# =====================================================================
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(
|
||||
description='审查 insight-diagram 生成的 SVG(箭头落点 / 框重叠 / 框间距)')
|
||||
ap.add_argument('files', nargs='+', help='待检查的 HTML/SVG 文件')
|
||||
ap.add_argument('--min-gap', type=float, default=8.0,
|
||||
help='相邻框最小净间距阈值 px,默认 8')
|
||||
ap.add_argument('--json', action='store_true', help='以 JSON 输出')
|
||||
ap.add_argument('--strict', action='store_true',
|
||||
help='存在 WARNING 时也以非零码退出')
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
results = [review_file(p, args.min_gap) for p in args.files]
|
||||
|
||||
if args.json:
|
||||
print(json.dumps([
|
||||
{'file': r['file'], 'boxes': r['boxes'], 'connectors': r['connectors'],
|
||||
'issues': [{'level': lv, 'message': m} for lv, m in r['issues']]}
|
||||
for r in results], ensure_ascii=False, indent=2))
|
||||
else:
|
||||
for r in results:
|
||||
errs = [m for lv, m in r['issues'] if lv == 'ERROR']
|
||||
warns = [m for lv, m in r['issues'] if lv == 'WARNING']
|
||||
mark = '✗' if errs else ('⚠' if warns else '✓')
|
||||
print(f'\n{mark} {r["file"]} '
|
||||
f'({r["boxes"]} 框 / {r["connectors"]} 箭头连线)')
|
||||
for m in errs:
|
||||
print(f' ERROR {m}')
|
||||
for m in warns:
|
||||
print(f' WARNING {m}')
|
||||
if not errs and not warns:
|
||||
print(' 通过:箭头落点、框重叠、框间距均无异常')
|
||||
|
||||
total_err = sum(1 for r in results for lv, _ in r['issues'] if lv == 'ERROR')
|
||||
total_warn = sum(1 for r in results for lv, _ in r['issues'] if lv == 'WARNING')
|
||||
if not args.json:
|
||||
print(f'\n汇总:{total_err} 个 ERROR,{total_warn} 个 WARNING,'
|
||||
f'共 {len(results)} 个文件')
|
||||
return 1 if (total_err or (args.strict and total_warn)) else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,562 @@
|
||||
---
|
||||
name: loop-it
|
||||
description: "Automated issue loop with checkpoint/resume: fetch open GitHub issues → dependency-aware topological sort → implement each issue end-to-end → review with /review-it → document with /note-it → ship with /ship-it → repeat. Persists state to .loop-state.json for crash recovery. Triggers on: loop-it, loop issues, auto implement, 批量实现, 循环实现, 实现所有issue, 恢复循环, resume loop."
|
||||
user-invocable: true
|
||||
allowed-tools:
|
||||
- Bash(gh:*)
|
||||
- Bash(git:*)
|
||||
- Bash(cat:*)
|
||||
---
|
||||
|
||||
# loop-it — 带检查点恢复的自动化 Issue 实现循环
|
||||
|
||||
Fetch all open GitHub issues, resolve dependency order, implement each through the full pipeline (内联实现 → `/review-it` → `/note-it` → `/ship-it`), persist progress to state file, and resume from checkpoint on crash.
|
||||
|
||||
> **⚠️ 关键前提:实现步骤由 agent 内联自主完成,不依赖任何外部 `/goal` 命令。**
|
||||
> 本环境中不存在可调用的 `goal` 命令或 skill。因此「实现 issue」这一步**必须由 agent 内联完成**:直接读取该 issue 的标题与正文(含其引用的 PRD/SPEC 与验收条件),自主完成"理解需求 → 写/改代码 → 跑测试与 lint → 满足全部验收条件"的闭环,持续工作直到该 issue 的验收条件全部满足且测试/构建通过。**不要**尝试用 Skill 工具调用 `goal`(会报 `goal is a UI command, not a skill`),也**不要**因为找不到 `/goal` 而中止循环。`/review-it`、`/note-it`、`/ship-it` 仍是真实 skill,经 Skill 工具调用。
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
前置检查 → 读取状态文件 → Fetch Issues → 构建依赖图 → 拓扑排序
|
||||
|
|
||||
┌───────────────────────────────────────────────────────────┘
|
||||
|
|
||||
v
|
||||
┌──────────────── 单 Issue 循环 ────────────────┐
|
||||
| |
|
||||
| 从检查点恢复?—— 跳过已完成/失败的 |
|
||||
| |
|
||||
| 分支准备 (checkout main, pull, create branch) |
|
||||
| | |
|
||||
| Skip/Blocked? ── 是 → 标记 skipped/blocked, 写检查点 |
|
||||
| | |
|
||||
| 否 |
|
||||
| | |
|
||||
| 内联实现 → 出错?→ 分类 → 恢复 → 重试 |
|
||||
| | | |
|
||||
| | 失败 → 检查点, 下一个 |
|
||||
| | |
|
||||
| /review-it → 有问题?→ 修复 → 重跑 review |
|
||||
| | |
|
||||
| /note-it (捕获实现笔记, best-effort) |
|
||||
| | |
|
||||
| /ship-it → 出错?→ 分类 → 恢复 |
|
||||
| | |
|
||||
| 分支清理 (checkout main, pull, delete branch) |
|
||||
| | |
|
||||
| 检查点 (标记 shipped) |
|
||||
| | |
|
||||
└────────┴──────────────────────────────────────┘
|
||||
|
|
||||
v
|
||||
全部完成 → 最终 Summary
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前置检查
|
||||
|
||||
开始循环前,按顺序验证所有前提条件。任何检查失败则停止并打印错误。
|
||||
|
||||
### Check 1: gh CLI 认证
|
||||
|
||||
```bash
|
||||
gh auth status
|
||||
```
|
||||
|
||||
失败 → 打印 `❌ gh CLI 未认证。运行: gh auth login`,退出。
|
||||
|
||||
### Check 2: Git 仓库
|
||||
|
||||
```bash
|
||||
git rev-parse --is-inside-work-tree
|
||||
```
|
||||
|
||||
失败 → 打印 `❌ 不在 git 仓库中`,退出。
|
||||
|
||||
### Check 3: Git 工作树清洁度
|
||||
|
||||
```bash
|
||||
git status --porcelain
|
||||
```
|
||||
|
||||
有输出(dirty)→ 打印 `⚠️ 工作树有未提交的更改`,提供选项:
|
||||
- A. `git stash` 暂存后继续
|
||||
- B. 中止,让用户自行处理
|
||||
- C. 强制继续(不推荐)
|
||||
|
||||
默认 B。
|
||||
|
||||
### Check 4: 在默认分支上
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
不在 main/master → 打印 `⚠️ 当前在 {branch} 分支`,提供选项:
|
||||
- A. `git checkout main && git pull` 切换
|
||||
- B. 继续在当前分支
|
||||
|
||||
### Check 5: 远程可达
|
||||
|
||||
```bash
|
||||
git ls-remote --heads origin
|
||||
```
|
||||
|
||||
失败 → 打印 `❌ 无法访问远程仓库。检查网络和权限`,退出。
|
||||
|
||||
### Check 6: 状态文件存在?
|
||||
|
||||
```bash
|
||||
cat .loop-state.json
|
||||
```
|
||||
|
||||
存在 → 打印进度摘要,提供选项:
|
||||
- A. 从检查点恢复
|
||||
- B. 从头开始(删除状态文件)
|
||||
- C. 中止
|
||||
|
||||
---
|
||||
|
||||
## 状态文件
|
||||
|
||||
### 位置
|
||||
|
||||
`.loop-state.json`,放在 repo 根目录。**必须添加到 `.gitignore`**。如果文件被 git 跟踪,打印警告并建议用户添加到 `.gitignore`。
|
||||
|
||||
### 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"started_at": "2025-06-09T10:00:00Z",
|
||||
"updated_at": "2025-06-09T10:30:00Z",
|
||||
"repo": "owner/repo-name",
|
||||
"total_issues": 8,
|
||||
"issues": {
|
||||
"3": {
|
||||
"status": "shipped",
|
||||
"branch": "feat/issue-3-add-priority",
|
||||
"started_at": "2025-06-09T10:00:00Z",
|
||||
"completed_at": "2025-06-09T10:15:00Z",
|
||||
"attempts": 1
|
||||
},
|
||||
"4": {
|
||||
"status": "failed",
|
||||
"phase": "goal",
|
||||
"error_class": "build_failure",
|
||||
"branch": "feat/issue-4-filter-tasks",
|
||||
"started_at": "2025-06-09T10:15:00Z",
|
||||
"updated_at": "2025-06-09T10:30:00Z",
|
||||
"attempts": 3,
|
||||
"last_error": "test TestFilterPriority failed: expected 3, got 0"
|
||||
},
|
||||
"7": {
|
||||
"status": "pending"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态值
|
||||
|
||||
`pending` | `in_progress` | `skipped` | `shipped` | `failed` | `blocked`
|
||||
|
||||
### 写入规则
|
||||
|
||||
- 每次状态转换后立即写入(`pending` → `in_progress`、`in_progress` → `shipped`/`failed`/`skipped` 等)
|
||||
- 写入使用 `cat > .loop-state.json << 'LOOPSTATE'\n{json}\nLOOPSTATE`
|
||||
- 如果状态文件已存在但内容损坏(非法 JSON),打印警告,提供从头开始或中止的选项。**绝不自动覆盖损坏文件**
|
||||
- 循环完成后保留状态文件(作为记录),用户可手动删除
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Fetch Issues & Build Dependency Graph
|
||||
|
||||
Fetch all open issues:
|
||||
|
||||
```bash
|
||||
gh issue list --state open --json number,title,labels,body --limit 100
|
||||
```
|
||||
|
||||
### Parse Dependencies
|
||||
|
||||
Read each issue body, look for patterns:
|
||||
- `Dependencies: #3, #5` or `Depends on: #3`
|
||||
- `depends on #3` or `requires #3` (in body text)
|
||||
|
||||
Build a dependency graph. Sort using topological order:
|
||||
|
||||
1. Issues with no dependencies first (sorted by number ascending)
|
||||
2. Issues whose dependencies are all shipped/closed next
|
||||
3. Blocked issues (depend on other open issues) last
|
||||
4. Circular dependencies → print warning `⚠️ 循环依赖检测到: #A ↔ #B,按编号顺序处理`,break cycle by number order
|
||||
|
||||
If no dependency patterns found in any issue body, fall back to number-ascending sort.
|
||||
|
||||
Print ordered list:
|
||||
|
||||
```
|
||||
📋 Found N open issues (topological sort):
|
||||
#1: Add priority field (无依赖)
|
||||
#3: Display indicator (依赖 #1)
|
||||
#5: Add selector (依赖 #1)
|
||||
#7: Filter view (依赖 #1, #3)
|
||||
```
|
||||
|
||||
If no open issues → print `✅ No open issues found. Nothing to do.` and exit.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Resume or Initialize
|
||||
|
||||
### If `.loop-state.json` exists (from 前置检查 Check 6)
|
||||
|
||||
1. Read the file
|
||||
2. Print progress summary:
|
||||
|
||||
```
|
||||
📊 从检查点恢复 (上次更新: {updated_at})
|
||||
✅ Shipped: #1, #3
|
||||
⏭️ Skipped: #2 (question)
|
||||
❌ Failed: #4 (build_failure — 3 attempts)
|
||||
📋 Remaining: #5, #7
|
||||
```
|
||||
|
||||
3. For each `failed` issue: ask user — retry or skip?
|
||||
4. For `in_progress` issues: check if branch exists, changes exist → decide resume from current state or restart
|
||||
5. Skip all `shipped`/`skipped` issues
|
||||
6. Continue from first pending/retryable issue
|
||||
|
||||
### If no state file
|
||||
|
||||
1. Initialize new state file with all fetched issues as `pending`
|
||||
2. Start from first issue in topological order
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Process Single Issue
|
||||
|
||||
For each issue, print a banner:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔄 Processing Issue #{number}: {title}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
### 3a. Branch Prep
|
||||
|
||||
Prepare a clean git environment for this issue:
|
||||
|
||||
```bash
|
||||
# 确认在 main 上
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# 创建功能分支
|
||||
git checkout -b feat/issue-{N}-{short-desc}
|
||||
```
|
||||
|
||||
Branch naming: `feat/issue-N-short-desc` or `fix/issue-N-short-desc`(与 /ship-it 保持一致)
|
||||
|
||||
### 3b. Skip or Implement
|
||||
|
||||
Read the issue title and body. Decide if it needs code implementation:
|
||||
|
||||
**Skip if the issue is:**
|
||||
- A question / discussion / clarification
|
||||
- Documentation-only (typos, wording)
|
||||
- Already implemented (check codebase)
|
||||
- A duplicate of another issue
|
||||
- Clearly labeled `wontfix`, `question`, `discussion`, or `invalid`
|
||||
- Not actionable (no clear acceptance criteria and cannot infer any)
|
||||
|
||||
**Skip (blocked) if the issue has unresolved dependencies:**
|
||||
- Check the dependency graph from Step 1
|
||||
- If any dependency issue is not `shipped` (still `pending`, `failed`, `blocked`, or not in state file) → skip as blocked
|
||||
- The dependency issue itself may have failed or been skipped — in either case, this issue cannot proceed safely
|
||||
|
||||
When skipping:
|
||||
|
||||
```
|
||||
⏭️ Skipping Issue #{number}: {title}
|
||||
Reason: {why}
|
||||
```
|
||||
|
||||
When blocked:
|
||||
|
||||
```
|
||||
🔒 Blocking Issue #{number}: {title}
|
||||
Reason: dependency #{dep_number} not shipped ({status})
|
||||
```
|
||||
|
||||
Update state: `pending` → `skipped` or `pending` → `blocked`, write checkpoint, run **3h Branch Cleanup**, proceed to next issue.
|
||||
|
||||
### 3c. Implement (内联自主实现)
|
||||
|
||||
Update state: `pending` → `in_progress`, `phase: "implement"`, write checkpoint.
|
||||
|
||||
**由 agent 内联完成实现**(本环境无 `goal` 命令/skill 可调用,必须自己干):
|
||||
|
||||
1. 读取该 issue 的标题与正文,提取需求与全部验收条件(Acceptance Criteria);若正文引用了 PRD/SPEC 文件(如 `tasks/prd-*.md`),一并读取作为上下文
|
||||
2. 阅读相关现有代码,遵循项目既有风格、命名与依赖约定
|
||||
3. 实现/修改代码以满足全部验收条件
|
||||
4. 跑项目的构建、测试与 lint(如 `go build ./...`、`go vet ./...`、`go test ./...`)
|
||||
5. 持续工作直到该 issue 的验收条件**全部满足**且测试/构建/lint 通过
|
||||
|
||||
> 不要尝试用 Skill 工具调用 `goal`(会报 `goal is a UI command, not a skill`),也不要因找不到 `/goal` 而中止——实现就是你自己内联完成的工作。
|
||||
|
||||
**On success:**
|
||||
|
||||
```
|
||||
✅ Issue #{number} implementation complete
|
||||
```
|
||||
|
||||
Write checkpoint with `phase: "implement_done"`.
|
||||
|
||||
**On failure** — classify error (see 错误分类与恢复), apply recovery strategy, retry up to max attempts. If all retries exhausted:
|
||||
|
||||
```
|
||||
⚠️ Issue #{number} failed: {error_class} after {N} attempts
|
||||
Manual intervention required.
|
||||
```
|
||||
|
||||
Update state: `in_progress` → `failed`, write checkpoint, run **3h Branch Cleanup**, proceed to next issue.
|
||||
|
||||
### 3d. Review with /review-it
|
||||
|
||||
Write checkpoint with `phase: "review"`.
|
||||
|
||||
```
|
||||
/review-it
|
||||
```
|
||||
|
||||
**If review finds actionable issues:**
|
||||
|
||||
```
|
||||
🔍 Review found N issue(s) for #{number}. Fixing...
|
||||
```
|
||||
|
||||
Fix each accepted finding, re-run `/review-it`. Repeat until clean or max 2 review rounds.
|
||||
|
||||
**If review is clean:**
|
||||
|
||||
```
|
||||
✅ Review clean for Issue #{number}
|
||||
```
|
||||
|
||||
Write checkpoint with `phase: "review_done"`.
|
||||
|
||||
### 3e. Document with /note-it
|
||||
|
||||
After review, before ship — capture implementation notes:
|
||||
|
||||
```
|
||||
/note-it
|
||||
```
|
||||
|
||||
This creates `docs/issue#XXXX.html` with design decisions, deviations, tradeoffs, and open questions.
|
||||
|
||||
**On success:**
|
||||
|
||||
```
|
||||
📝 Issue #{number} notes captured
|
||||
```
|
||||
|
||||
**On failure** (can't determine issue number, etc.) — print warning but **do not block shipping**:
|
||||
|
||||
```
|
||||
⚠️ /note-it failed for Issue #{number}: {reason}. Continuing to ship.
|
||||
```
|
||||
|
||||
Write checkpoint with `phase: "note_done"`.
|
||||
|
||||
### 3f. Ship with /ship-it
|
||||
|
||||
```
|
||||
/ship-it
|
||||
```
|
||||
|
||||
This commits, pushes, creates PR, merges, and closes the issue.
|
||||
|
||||
**On success:**
|
||||
|
||||
```
|
||||
🚀 Issue #{number} shipped successfully!
|
||||
```
|
||||
|
||||
**On failure** — classify error (see 错误分类与恢复), apply recovery. If unresolvable:
|
||||
|
||||
```
|
||||
⚠️ Issue #{number} ship failed: {error}. Manual merge required.
|
||||
```
|
||||
|
||||
Update state: `in_progress` → `failed`, `phase: "ship"`, write checkpoint, run **3h Branch Cleanup**, proceed to next issue.
|
||||
|
||||
### 3g. Checkpoint
|
||||
|
||||
After successful ship, update state: `in_progress` → `shipped`, set `completed_at`, write checkpoint.
|
||||
|
||||
Print progress (see 进度可观测性).
|
||||
|
||||
### 3h. Branch Cleanup
|
||||
|
||||
After each issue (shipped, skipped, or failed):
|
||||
|
||||
```bash
|
||||
# 切回 main
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# 删除本地功能分支(仅当 shipped 时)
|
||||
git branch -d feat/issue-{N}-{short-desc}
|
||||
```
|
||||
|
||||
**For failed issues**: do NOT delete the branch. Keep it for investigation.
|
||||
|
||||
### Next Issue
|
||||
|
||||
Return to Step 3 for the next issue in topological order.
|
||||
|
||||
When all issues processed, print final summary:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📊 Loop Complete — Summary
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✅ Shipped: N issues (#1, #3, ...)
|
||||
⏭️ Skipped: N issues (#2 — reason, #5 — reason, ...)
|
||||
🔒 Blocked: N issues (#7 — depends on #4, ...)
|
||||
❌ Failed: N issues (#4 — error, ...)
|
||||
📋 Total: N issues
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误分类与恢复
|
||||
|
||||
当错误发生时,先分类,再按策略恢复。
|
||||
|
||||
| 错误类别 | 检测信号 | 恢复策略 | 最大重试 |
|
||||
|----------|---------|---------|---------|
|
||||
| build_failure | 编译错误、undefined、类型错误 | 读错误,修代码,重新构建 | 3 |
|
||||
| test_failure | 断言失败、test failed | 读测试输出,修实现,重跑测试 | 3 |
|
||||
| lint_failure | lint 错误、格式问题 | 自动修复 (lint --fix),重跑 | 2 |
|
||||
| merge_conflict | CONFLICT 标记 | rebase origin/main,解决冲突,push | 2 |
|
||||
| ci_failure | gh pr checks 失败 | 读 CI 日志,本地修复,push | 2 |
|
||||
| auth_failure | 403、401、认证错误 | 停止,告知用户重新认证 | 0 |
|
||||
| rate_limit | rate limit、secondary abuse | 等待 60s,重试 | 3 |
|
||||
| issue_unclear | issue 无验收条件且无法推断需求 | 跳过,标记 failed | 0 |
|
||||
| network_error | timeout、connection refused | 等待 30s,重试 | 3 |
|
||||
| unknown | 其他情况 | 记录完整错误,跳过 | 0 |
|
||||
|
||||
**恢复协议:**
|
||||
|
||||
1. 匹配错误类别
|
||||
2. 匹配成功 → 应用恢复策略,重试最多 N 次
|
||||
3. 重试全部失败 → 标记 `failed`,写检查点,继续下一个 issue
|
||||
4. 无法匹配 → 标记 `failed`(error_class: `unknown`),继续
|
||||
5. **绝不无限重试。绝不未经确认 force-push。**
|
||||
|
||||
---
|
||||
|
||||
## 进度可观测性
|
||||
|
||||
每完成一个 issue 后,打印结构化进度:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📊 Progress: 3/8 issues (37%)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✅ Shipped: #1, #3
|
||||
⏭️ Skipped: #2 (question), #6 (duplicate)
|
||||
🔒 Blocked: #7 (depends on #4 — failed)
|
||||
❌ Failed: #4 (build_failure — 3 attempts)
|
||||
📋 Remaining: #5, #8
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging Rules
|
||||
|
||||
Every key step MUST print a log line with emoji prefix:
|
||||
|
||||
| Emoji | Meaning |
|
||||
|-------|---------|
|
||||
| 📋 | Fetch / list |
|
||||
| 🔄 | Processing issue |
|
||||
| ⏭️ | Skip |
|
||||
| 🔒 | Blocked (dependency not shipped) |
|
||||
| ✅ | Success |
|
||||
| ❌ | Failure |
|
||||
| 🔍 | Review |
|
||||
| 📝 | Notes (/note-it) |
|
||||
| 🚀 | Ship |
|
||||
| ⚠️ | Warning / retry |
|
||||
| 📊 | Progress / summary |
|
||||
|
||||
---
|
||||
|
||||
## Safety Guards
|
||||
|
||||
- **Never force-push to main/master** — always use feature branches
|
||||
- **Never skip review** — always run `/review-it` before `/ship-it`
|
||||
- **Never skip notes** — always run `/note-it` before `/ship-it`(best-effort,不阻塞)
|
||||
- **Max retries per error class** — 参见错误分类与恢复表,不无限重试
|
||||
- **Max 2 review rounds** — don't over-polish
|
||||
- **Pause on CI failure** — log and continue, don't auto-override branch protection
|
||||
- **Preserve issue labels** — only close issues that were actually shipped
|
||||
- **Never auto-delete failed branches** — 保留供调查
|
||||
- **Checkpoint at every transition** — 每次状态变更写检查点,不仅仅在 ship 时
|
||||
- **State file integrity** — 损坏时警告用户,绝不自动覆盖
|
||||
- **State file in .gitignore** — 提醒用户添加 `.loop-state.json`
|
||||
- **Strictly sequential** — 一次只处理一个 issue(实现会修改工作树,不能并行)
|
||||
- **Skip dependency-blocked issues** — 依赖的 issue 未 shipped 时标记 `blocked`
|
||||
- **实现由 agent 内联完成** — 本环境无 `goal` 命令/skill 可调用;「实现 issue」必须由 agent 自己读 issue、写代码、跑测试完成。报 `goal is a UI command, not a skill` 时不要中止,直接内联实现
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| No open issues | Print "nothing to do" and exit |
|
||||
| All issues are questions | Skip all, report summary |
|
||||
| `gh` not authenticated | Print error, suggest `gh auth login`, exit |
|
||||
| Issue has no body | Use title only to decide skip/implement |
|
||||
| Issue references PRD/SPEC | 读取被引用的 PRD/SPEC 作为上下文,agent 内联实现 |
|
||||
| Multiple issues depend on each other | Topological sort; dependencies already shipped first |
|
||||
| Git repo is dirty before starting | 前置检查 Check 3: stash/abort/force |
|
||||
| State file corrupted (invalid JSON) | 警告用户,提供从头开始或中止选项。绝不自动覆盖 |
|
||||
| State file from different repo | 检测 repo 字段不匹配,警告,提供从头开始选项 |
|
||||
| Issue `in_progress` from previous run | 检查分支是否存在、是否有变更 → 恢复或重新开始 |
|
||||
| User aborts mid-loop | 状态文件已包含最新检查点,下次运行可恢复 |
|
||||
| New issues created during loop | 不重新获取。完成当前批次后运行新 `/loop-it` |
|
||||
| Circular dependencies | 打印警告,按编号顺序打破循环 |
|
||||
| `/note-it` can't find issue number | 打印警告,跳过 /note-it,继续 /ship-it |
|
||||
| `.loop-state.json` is git-tracked | 警告用户添加到 .gitignore,继续 |
|
||||
| 误以为需要外部 `goal` 命令 | 本环境无此命令;「实现 issue」由 agent 内联完成(读 issue → 写代码 → 测试),不要中止循环 |
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Other Skills
|
||||
|
||||
```
|
||||
/loop-it
|
||||
├── 内联实现 ← implement each issue(agent 自主读 issue、写代码、测试;非外部命令)
|
||||
├── /review-it ← review code before shipping(skill)
|
||||
├── /note-it ← capture implementation notes (best-effort)(skill)
|
||||
└── /ship-it ← commit, PR, merge, close(skill)
|
||||
```
|
||||
|
||||
Part of the goal-workflow pipeline:
|
||||
|
||||
```
|
||||
/prd → /prd-to-spec → /to-issues → /loop-it (→ 内联实现 → /review-it → /note-it → /ship-it)×N
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
---
|
||||
name: note-it
|
||||
description: "Capture implementation notes after code implementation and review/fix. Records design decisions, deviations, tradeoffs, and open questions to docs/issue#XXXX.html. Triggers on: /note-it, 记录笔记, implementation notes."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Implementation Notes
|
||||
|
||||
After completing implementation and review/fix for an Issue, capture a running implementation notes file that documents how the implementation diverges from or interprets the spec.
|
||||
|
||||
## Triggers
|
||||
|
||||
Use when:
|
||||
- After `/goal` implementation and `/review-it` are both complete
|
||||
- User says "记录笔记", "implementation notes", "note-it", "/note-it"
|
||||
- Before `/ship-it` (as a final checkpoint)
|
||||
- Any time the user wants to capture design rationale
|
||||
|
||||
## The Job
|
||||
|
||||
1. Determine the Issue number from context (branch name, `/goal` target, or user input)
|
||||
2. Review the implementation against the Issue spec / PRD
|
||||
3. Generate an HTML notes file at `docs/issue#XXXX.html`
|
||||
4. Present a summary to the user
|
||||
|
||||
## Notes Structure
|
||||
|
||||
The HTML file must cover these four categories. If a category has nothing to report, write "None" with a brief explanation.
|
||||
|
||||
### 1. Design Decisions
|
||||
Choices made where the spec was ambiguous or silent:
|
||||
- What was the ambiguity?
|
||||
- What choice did you make?
|
||||
- What was the rationale?
|
||||
|
||||
### 2. Deviations
|
||||
Places where you intentionally departed from the spec:
|
||||
- What did the spec say?
|
||||
- What did you implement instead?
|
||||
- Why was the deviation necessary or better?
|
||||
|
||||
### 3. Tradeoffs
|
||||
Alternatives you considered and why you picked what you did:
|
||||
- What were the viable alternatives?
|
||||
- What were the pros/cons of each?
|
||||
- Why did the chosen approach win?
|
||||
|
||||
### 4. Open Questions
|
||||
Anything you'd want confirmed or revised:
|
||||
- What assumption are you unsure about?
|
||||
- What should the user verify?
|
||||
- What might need follow-up?
|
||||
|
||||
## Output
|
||||
|
||||
- **Format:** HTML
|
||||
- **Location:** `docs/`
|
||||
- **Filename:** `issue#XXXX.html` (where XXXX is the zero-padded Issue number, e.g., `issue#0042.html`)
|
||||
|
||||
## HTML Template
|
||||
|
||||
Use this exact HTML structure:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Implementation Notes — Issue #XXXX</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: #FAF9F6;
|
||||
color: #1a1a1a;
|
||||
padding: 2.5rem 2rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
h1 { font-size: 1.375rem; font-weight: 700; margin-bottom: 0.25rem; }
|
||||
.meta { color: #8B8680; font-size: 0.8125rem; margin-bottom: 2rem; }
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.375rem;
|
||||
border-bottom: 1px solid #E8E4DE;
|
||||
display: flex; align-items: center; gap: 0.5rem;
|
||||
}
|
||||
.dot {
|
||||
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
|
||||
}
|
||||
.dot.design { background: #5B8A72; }
|
||||
.dot.deviation { background: #D97757; }
|
||||
.dot.tradeoff { background: #4A6FA5; }
|
||||
.dot.question { background: #D4A843; }
|
||||
.item {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E8E4DE;
|
||||
border-radius: 8px;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||||
}
|
||||
.item h3 { font-size: 0.875rem; font-weight: 600; margin-bottom: 0.375rem; }
|
||||
.item p { font-size: 0.8125rem; color: #4A4540; margin-bottom: 0.375rem; }
|
||||
.label {
|
||||
display: inline-block;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
.label-design { background: #F5F8F6; color: #5B8A72; border: 1px solid #5B8A72; }
|
||||
.label-deviation { background: #FFF5F0; color: #D97757; border: 1px solid #D97757; }
|
||||
.label-tradeoff { background: #F0F4F8; color: #4A6FA5; border: 1px solid #4A6FA5; }
|
||||
.label-question { background: #FDF8F0; color: #D4A843; border: 1px solid #D4A843; }
|
||||
.none { color: #B0AAA4; font-style: italic; font-size: 0.8125rem; }
|
||||
.footer {
|
||||
text-align: center; margin-top: 2.5rem; color: #B0AAA4;
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Implementation Notes</h1>
|
||||
<p class="meta">Issue <a href="{{ISSUE_URL}}">#{{ISSUE_NUMBER}}</a> — {{ISSUE_TITLE}} — {{DATE}}</p>
|
||||
|
||||
<h2><span class="dot design"></span> Design Decisions</h2>
|
||||
<!-- One .item per decision, or .none if none -->
|
||||
|
||||
<h2><span class="dot deviation"></span> Deviations</h2>
|
||||
<!-- One .item per deviation, or .none if none -->
|
||||
|
||||
<h2><span class="dot tradeoff"></span> Tradeoffs</h2>
|
||||
<!-- One .item per tradeoff, or .none if none -->
|
||||
|
||||
<h2><span class="dot question"></span> Open Questions</h2>
|
||||
<!-- One .item per question, or .none if none -->
|
||||
|
||||
<p class="footer">Generated by goal-workflow /note-it</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Example Item
|
||||
|
||||
```html
|
||||
<div class="item">
|
||||
<h3><span class="label label-design">Decision</span> Used interface-based polymorphism instead of switch</h3>
|
||||
<p><strong>Ambiguity:</strong> The spec said "handle different types" without specifying how.</p>
|
||||
<p><strong>Choice:</strong> Defined a <code>Handler</code> interface with per-type implementations.</p>
|
||||
<p><strong>Rationale:</strong> Adding new types requires no changes to existing code (Open/Closed Principle). A switch would grow unboundedly.</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
## How to Determine the Issue Number
|
||||
|
||||
1. If the user provides it directly (e.g., `/note-it #42`), use it
|
||||
2. If on a branch named `feat/issue-42-*` or `fix/issue-42-*`, extract `42`
|
||||
3. If the last `/goal` target was `#42`, use `42`
|
||||
4. Otherwise, ask the user: "Which Issue number should I use for the notes file?"
|
||||
|
||||
## Edge Cases
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| No Issue number found | Ask the user to specify |
|
||||
| `docs/` directory does not exist | Auto-create it |
|
||||
| Notes file already exists for this Issue | Ask: "Update existing notes or overwrite?" — default to update (append new items) |
|
||||
| No deviations or open questions | Write "None — implementation followed the spec as written." |
|
||||
| Spec/PRD file not found | Note in Open Questions: "No PRD found at tasks/prd-*.md — verify against original requirements." |
|
||||
|
||||
## Checklist
|
||||
|
||||
Before saving:
|
||||
- [ ] Issue number identified
|
||||
- [ ] All four categories reviewed (even if some are "None")
|
||||
- [ ] Design decisions explain rationale, not just what was done
|
||||
- [ ] Deviations clearly contrast spec vs implementation
|
||||
- [ ] Tradeoffs mention specific alternatives considered
|
||||
- [ ] Open questions are actionable (user can answer yes/no or give direction)
|
||||
- [ ] HTML is well-formed and renders correctly
|
||||
@@ -0,0 +1,409 @@
|
||||
---
|
||||
name: prd-to-spec
|
||||
description: "Transform a PRD into a technical SPEC document — architecture, API design, data model, error handling, and implementation contracts. Triggers on: prd-to-spec, prd to spec, prd转spec, 需求转设计, 需求转规格, generate spec from prd, design from prd, 技术方案, 设计方案."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# prd-to-spec — PRD to Technical Specification
|
||||
|
||||
Transform a Product Requirements Document (PRD) into a detailed technical SPEC that an engineer or AI agent can implement against. The PRD says *what* to build; the SPEC says *how* to build it.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
- A `/prd` has been generated and you need to bridge the gap to implementation
|
||||
- You want architecture decisions documented before coding starts
|
||||
- Multiple developers/agents will implement the feature and need a shared contract
|
||||
- You need to validate technical feasibility before committing to a PRD
|
||||
- You want to catch design issues early — before code is written
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. **Locate PRD** — find or receive the PRD document
|
||||
2. **Analyze context (optional)** — if a codebase exists, scan it to understand current architecture, patterns, and constraints
|
||||
3. **Ask clarifying questions** — resolve technical ambiguities (max 3-5 questions)
|
||||
4. **Generate SPEC** — produce a structured technical specification
|
||||
5. **Review** — present to user for feedback and iteration
|
||||
6. **Save** — write final SPEC to agreed location
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Locate PRD
|
||||
|
||||
Find the input PRD in one of these ways:
|
||||
|
||||
```
|
||||
Provide the PRD to convert:
|
||||
|
||||
A. File path (e.g., tasks/prd-priority-system.md)
|
||||
B. GitHub Issue URL
|
||||
C. Paste PRD content directly
|
||||
D. Auto-detect: scan tasks/ directory for recent PRDs
|
||||
```
|
||||
|
||||
If auto-detecting, list available PRDs and let the user choose:
|
||||
|
||||
```
|
||||
Found PRDs in tasks/:
|
||||
1. tasks/prd-priority-system.md (2024-03-15)
|
||||
2. tasks/prd-user-auth.md (2024-03-10)
|
||||
|
||||
Which PRD should I convert? [1/2]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Analyze Context (Optional)
|
||||
|
||||
**Skip this step if no codebase exists yet** (greenfield project). In that case, the SPEC will propose architecture from scratch based on the PRD requirements and clarifying questions.
|
||||
|
||||
If a codebase exists, scan it to understand:
|
||||
|
||||
- **Existing architecture** — how the current system is structured
|
||||
- **Tech stack** — languages, frameworks, libraries already in use
|
||||
- **Patterns** — naming conventions, file organization, error handling approach
|
||||
- **Database** — current schema, migration tool, ORM
|
||||
- **API style** — REST/GraphQL/gRPC, authentication method, response format
|
||||
- **Testing** — test framework, coverage patterns, test utilities
|
||||
|
||||
This ensures the SPEC aligns with the existing system rather than proposing incompatible solutions.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Clarifying Questions
|
||||
|
||||
Ask only when the PRD leaves technical decisions ambiguous. Focus on:
|
||||
|
||||
- **Architecture choices** — where does this feature live? New service or extend existing?
|
||||
- **Data storage** — new table? Extend existing? Cache strategy?
|
||||
- **API design** — new endpoints? Extend existing? Breaking changes?
|
||||
- **Dependencies** — any new libraries needed? Version constraints?
|
||||
- **Performance** — expected load? Latency requirements? Batch size limits?
|
||||
|
||||
Format:
|
||||
```
|
||||
Technical questions before I generate the SPEC:
|
||||
|
||||
1. Where should the priority logic live?
|
||||
A. Extend existing TaskService
|
||||
B. New PriorityService
|
||||
C. Inline in controller
|
||||
D. Let me decide based on the codebase
|
||||
|
||||
2. Database migration approach?
|
||||
A. Add column to existing tasks table
|
||||
B. New priority table with FK
|
||||
C. JSON field on tasks
|
||||
D. Let me decide based on current schema
|
||||
|
||||
3. API versioning concern?
|
||||
A. Add to existing v1 endpoints
|
||||
B. New v2 endpoints
|
||||
C. No versioning needed
|
||||
```
|
||||
|
||||
If user selects "let me decide" options, make the best choice based on codebase analysis and document the rationale in the SPEC.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: SPEC Document Structure
|
||||
|
||||
```markdown
|
||||
# SPEC: [Feature Name]
|
||||
|
||||
> Technical specification derived from: [PRD filename/link]
|
||||
> Generated: [date] | Target branch: [branch] | Commit: [short-hash]
|
||||
|
||||
## 1. Summary
|
||||
|
||||
### 1.1 What This SPEC Covers
|
||||
[One paragraph: what feature this specifies and the scope of implementation]
|
||||
|
||||
### 1.2 PRD Reference
|
||||
- Source: [path or URL to PRD]
|
||||
- User Stories covered: [US-001, US-002, ...]
|
||||
- Functional Requirements covered: [FR-1, FR-2, ...]
|
||||
|
||||
### 1.3 Design Decisions Summary
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| ... | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture
|
||||
|
||||
### 2.1 System Context
|
||||
[Where this feature fits in the overall system — diagram or description]
|
||||
|
||||
### 2.2 Component Design
|
||||
[New components/modules introduced, their responsibilities, and boundaries]
|
||||
|
||||
### 2.3 Module Interactions
|
||||
[How new components interact with existing ones — sequence or data flow]
|
||||
|
||||
### 2.4 File Structure
|
||||
[New files to create and existing files to modify]
|
||||
|
||||
```
|
||||
src/
|
||||
├── services/
|
||||
│ └── priority.service.ts [NEW]
|
||||
├── controllers/
|
||||
│ └── task.controller.ts [MODIFY: add priority endpoints]
|
||||
├── models/
|
||||
│ └── priority.model.ts [NEW]
|
||||
└── migrations/
|
||||
└── 20240315_add_priority.ts [NEW]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Data Model
|
||||
|
||||
### 3.1 Schema Changes
|
||||
[New tables, columns, indexes — with SQL or ORM notation]
|
||||
|
||||
### 3.2 Entity Definitions
|
||||
[TypeScript interfaces / Go structs / Python dataclasses for new entities]
|
||||
|
||||
### 3.3 Relationships
|
||||
[How new entities relate to existing ones — FK, embedded, reference]
|
||||
|
||||
### 3.4 Migration Plan
|
||||
[Migration steps, backward compatibility, rollback strategy]
|
||||
|
||||
---
|
||||
|
||||
## 4. API Design
|
||||
|
||||
### 4.1 Endpoints
|
||||
|
||||
| Method | Path | Description | Auth | Request | Response |
|
||||
|--------|------|-------------|------|---------|----------|
|
||||
| ... | ... | ... | ... | ... | ... |
|
||||
|
||||
### 4.2 Request/Response Schemas
|
||||
[Detailed shapes with field types, validation rules, and examples]
|
||||
|
||||
### 4.3 Error Responses
|
||||
[Error codes, messages, and HTTP status codes for each failure mode]
|
||||
|
||||
### 4.4 Breaking Changes
|
||||
[Any backward-incompatible changes and migration path for consumers]
|
||||
|
||||
---
|
||||
|
||||
## 5. Business Logic
|
||||
|
||||
### 5.1 Core Algorithms
|
||||
[Step-by-step logic for key operations — pseudocode or structured description]
|
||||
|
||||
### 5.2 Validation Rules
|
||||
[Input validation, business rule validation, with specific constraints]
|
||||
|
||||
### 5.3 State Machine
|
||||
[If applicable: states, transitions, guards, and side effects]
|
||||
|
||||
### 5.4 Edge Cases
|
||||
[Known edge cases and how they should be handled]
|
||||
|
||||
---
|
||||
|
||||
## 6. Error Handling
|
||||
|
||||
### 6.1 Error Taxonomy
|
||||
| Error Code | HTTP Status | Condition | User Message |
|
||||
|------------|-------------|-----------|--------------|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### 6.2 Retry Strategy
|
||||
[Which operations are retryable, backoff policy, max attempts]
|
||||
|
||||
### 6.3 Failure Modes
|
||||
[What happens when dependencies fail — graceful degradation plan]
|
||||
|
||||
---
|
||||
|
||||
## 7. Security
|
||||
|
||||
### 7.1 Authentication & Authorization
|
||||
[Who can access what, permission model, role checks]
|
||||
|
||||
### 7.2 Input Validation
|
||||
[Sanitization rules, injection prevention, size limits]
|
||||
|
||||
### 7.3 Data Protection
|
||||
[Sensitive fields, encryption at rest/transit, audit logging]
|
||||
|
||||
---
|
||||
|
||||
## 8. Performance
|
||||
|
||||
### 8.1 Expected Load
|
||||
[Estimated QPS, data volume, growth projection]
|
||||
|
||||
### 8.2 Optimization Strategy
|
||||
[Caching, pagination, lazy loading, batch processing]
|
||||
|
||||
### 8.3 Database Considerations
|
||||
[Index strategy, query patterns, N+1 prevention]
|
||||
|
||||
---
|
||||
|
||||
## 9. Testing Strategy
|
||||
|
||||
### 9.1 Unit Tests
|
||||
[What to test, test boundaries, mock strategy]
|
||||
|
||||
### 9.2 Integration Tests
|
||||
[API tests, database tests, service interaction tests]
|
||||
|
||||
### 9.3 Edge Case Tests
|
||||
[Specific scenarios to cover based on Section 5.4]
|
||||
|
||||
### 9.4 Acceptance Criteria Mapping
|
||||
| US/FR | Test | Type | Description |
|
||||
|-------|------|------|-------------|
|
||||
| US-001 | ... | unit | ... |
|
||||
| FR-2 | ... | integration | ... |
|
||||
|
||||
---
|
||||
|
||||
## 10. Implementation Plan
|
||||
|
||||
### 10.1 Phases
|
||||
[Order of implementation — what to build first, dependencies between steps]
|
||||
|
||||
### 10.2 Issue Mapping
|
||||
[Map SPEC sections to PRD Issues for implementation tracking]
|
||||
|
||||
| Issue | SPEC Sections | Priority | Depends On |
|
||||
|-------|--------------|----------|------------|
|
||||
| #1 | 3.1, 3.4 | high | — |
|
||||
| #2 | 4.1, 4.2, 5.1 | high | #1 |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### 10.3 Incremental Delivery
|
||||
[How to ship incrementally — feature flags, dark launches, gradual rollout]
|
||||
|
||||
---
|
||||
|
||||
## 11. Open Questions & Risks
|
||||
|
||||
### 11.1 Unresolved Questions
|
||||
- [Questions that need product/engineering input before implementation]
|
||||
|
||||
### 11.2 Technical Risks
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|-----------|
|
||||
| ... | ... | ... |
|
||||
|
||||
### 11.3 Assumptions
|
||||
- [Technical assumptions made during SPEC creation — validate before implementing]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Review & Iteration
|
||||
|
||||
After generating the SPEC, present it and ask:
|
||||
|
||||
```
|
||||
SPEC generated from PRD. Please review:
|
||||
|
||||
- Are the architecture choices appropriate?
|
||||
- Are there missing edge cases or error scenarios?
|
||||
- Is the API design consistent with existing patterns?
|
||||
- Should any section have more/less detail?
|
||||
|
||||
Reply OK to save, or provide feedback for iteration.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Save
|
||||
|
||||
Ask user for save location:
|
||||
|
||||
```
|
||||
Where should I save the SPEC?
|
||||
|
||||
A. tasks/spec-[feature-name].md (alongside PRD, recommended)
|
||||
B. docs/spec-[feature-name].md
|
||||
C. Custom path: [specify]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapping Strategy: PRD → SPEC
|
||||
|
||||
How PRD elements translate to SPEC sections:
|
||||
|
||||
| PRD Section | SPEC Section(s) | Transformation |
|
||||
|-------------|-----------------|----------------|
|
||||
| User Stories | 5. Business Logic, 9.4 Acceptance Mapping | Stories → algorithms + test cases |
|
||||
| Functional Requirements | 4. API Design, 5. Business Logic | FRs → endpoints + logic |
|
||||
| Acceptance Criteria | 9. Testing Strategy | Criteria → specific test scenarios |
|
||||
| Non-Goals | 11.1 Open Questions | Clarify what's explicitly excluded |
|
||||
| Technical Considerations | 2. Architecture, 8. Performance | Constraints → design decisions |
|
||||
| Success Metrics | 8.1 Expected Load, 10.3 Delivery | Metrics → monitoring + rollout plan |
|
||||
|
||||
---
|
||||
|
||||
## Quality Criteria
|
||||
|
||||
A good SPEC should pass these checks:
|
||||
|
||||
- [ ] Every PRD User Story has corresponding SPEC sections
|
||||
- [ ] Every Functional Requirement maps to an API endpoint or business logic rule
|
||||
- [ ] Every Acceptance Criterion maps to at least one test case
|
||||
- [ ] Architecture choices are justified with rationale
|
||||
- [ ] API schemas are specific enough to generate client code
|
||||
- [ ] Error handling covers all identified failure modes
|
||||
- [ ] Implementation order respects dependencies
|
||||
- [ ] No "TBD" or "TODO" items — resolve or move to Open Questions
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Fallback
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| PRD is vague or incomplete | Generate SPEC with best-effort choices, mark assumptions in Section 11.3 |
|
||||
| PRD conflicts with existing code | Flag conflicts explicitly, propose resolution in Section 11.1 |
|
||||
| Feature is too large for one SPEC | Split into multiple SPECs (one per service boundary), link them |
|
||||
| No existing codebase (greenfield) | Skip Step 2, propose architecture from scratch based on PRD + clarifying questions |
|
||||
| PRD has no User Stories (just bullet points) | Infer structure, map bullets to SPEC sections, note in Summary |
|
||||
| User wants SPEC without reading codebase | Skip Step 2, note that assumptions about existing code are unverified |
|
||||
| Multiple PRDs need one SPEC | Merge PRD inputs, deduplicate requirements, note source for each |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
- **Don't restate the PRD.** The SPEC adds technical depth, not a copy of requirements in different words.
|
||||
- **Don't over-specify trivial operations.** CRUD with no special logic doesn't need a full algorithm section.
|
||||
- **Don't pick technologies without context.** Always check what the project already uses before suggesting new tools.
|
||||
- **Don't design in isolation.** The SPEC must fit the existing system — same patterns, same conventions, same style.
|
||||
- **Don't leave decisions implicit.** If you made a choice (e.g., "add column to existing table"), state it and say why.
|
||||
- **Don't write implementation code.** The SPEC describes contracts and behavior, not code. Pseudocode is acceptable for complex algorithms.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Other Skills
|
||||
|
||||
```
|
||||
/prd → /prd-to-spec → /goal → /review-it → /ship-it
|
||||
│ │ │
|
||||
│ Requirements │ Technical │ Implementation
|
||||
│ (what) │ (how) │ (code)
|
||||
```
|
||||
|
||||
- **/prd** produces the PRD (input to this skill)
|
||||
- **/prd-to-spec** produces the SPEC (this skill)
|
||||
- **/goal** implements Issues with SPEC as the technical reference
|
||||
- **/code-to-spec** reverse-engineers SPEC from existing code (complementary — forward vs. reverse)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,43 @@
|
||||
# PRD Generator Skill
|
||||
|
||||
Generate structured Product Requirements Documents (PRD) for new features. Focused solely on producing a clear, implementable PRD — Issue decomposition and technical design are handled by separate skills.
|
||||
|
||||
## Features
|
||||
|
||||
- Asks 3-5 clarifying questions with lettered options for quick iteration
|
||||
- Generates a well-structured PRD with user stories, numbered functional requirements, non-goals, success metrics, and more
|
||||
- Enforces verifiable acceptance criteria (observable / testable / verifiable)
|
||||
- Supports user review and adjustment before saving
|
||||
- Saves output to `tasks/prd-[feature-name].md`
|
||||
- Bilingual (Chinese & English) edge case handling
|
||||
|
||||
## Workflow
|
||||
|
||||
The PRD skill is the first step in a three-stage pipeline:
|
||||
|
||||
| Stage | Skill | Purpose |
|
||||
|-------|-------|---------|
|
||||
| 1. Requirements | `/prd` (this skill) | Define *what* to build |
|
||||
| 2. Technical design (optional) | `/prd-to-spec` | Define *how* to build it |
|
||||
| 3. Decomposition | `/to-issues` | Break into implementable tickets (GitHub / Local / Baidu iCafe) |
|
||||
|
||||
After a PRD is confirmed, run `/prd-to-spec` for complex features, then `/to-issues` — or go straight to `/to-issues`.
|
||||
|
||||
## Usage
|
||||
|
||||
Trigger with prompts like:
|
||||
|
||||
- "create a prd for..."
|
||||
- "write prd for..."
|
||||
- "写PRD"
|
||||
- "需求文档"
|
||||
- "需求分析"
|
||||
|
||||
## Files
|
||||
|
||||
- `SKILL.md` — Skill definition and instructions
|
||||
- `test-prompts.json` — Test prompts for validation
|
||||
|
||||
## Attribution
|
||||
|
||||
This skill is adapted from [ralph/skills/prd](https://github.com/snarktank/ralph/tree/main/skills/prd).
|
||||
@@ -0,0 +1,278 @@
|
||||
---
|
||||
name: prd
|
||||
description: "Generate a Product Requirements Document (PRD) for a new feature. Use when planning a feature, starting a new project, or when asked to create a PRD. After PRD is confirmed, use /prd-to-spec (optional) for technical design, then /to-issues to create implementable tickets. Triggers on: create a prd, write prd for, plan this feature, requirements for, spec out, 写PRD, 需求文档, 需求分析, 规格说明."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# PRD Generator
|
||||
|
||||
Create detailed Product Requirements Documents that are clear, actionable, and suitable for implementation. After PRD is confirmed, use `/to-issues` to decompose it into Issues, and optionally `/prd-to-spec` for technical design before that.
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. Receive a feature description from the user
|
||||
2. Ask clarifying questions to cover key ambiguities — scale the count to complexity, not a fixed number (see Step 1)
|
||||
3. Generate a structured PRD based on answers
|
||||
4. **Present PRD to user for review** — ask "Please review the PRD. Let me know if any adjustments are needed, or reply OK to confirm."
|
||||
5. Apply any adjustments, then save to `tasks/prd-[feature-name].md`
|
||||
6. **Suggest next steps** (see Step 3)
|
||||
|
||||
**Important:** Do NOT start implementing. Just create the PRD.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Clarifying Questions
|
||||
|
||||
Ask only critical questions where the initial prompt is ambiguous. **Scale the number of questions to the feature's complexity — the goal is covering key ambiguities, not hitting a fixed count:**
|
||||
|
||||
- **Simple, well-scoped feature:** 2-3 questions
|
||||
- **Typical feature:** 3-5 questions
|
||||
- **Complex feature** (multiple user roles, cross-system integration, significant ambiguity): 6-8 questions
|
||||
|
||||
If a dimension is already unambiguous from the user's input, skip it — don't ask filler questions just to reach a number. Focus on:
|
||||
|
||||
- **Problem/Goal:** What problem does this solve?
|
||||
- **Core Functionality:** What are the key actions?
|
||||
- **Scope/Boundaries:** What should it NOT do?
|
||||
- **Success Criteria:** How do we know it's done?
|
||||
|
||||
### Format Questions Like This:
|
||||
|
||||
```
|
||||
1. What is the primary goal of this feature?
|
||||
A. Improve user onboarding experience
|
||||
B. Increase user retention
|
||||
C. Reduce support burden
|
||||
D. Other: [please specify]
|
||||
|
||||
2. Who is the target user?
|
||||
A. New users only
|
||||
B. Existing users only
|
||||
C. All users
|
||||
D. Admin users only
|
||||
|
||||
3. What is the scope?
|
||||
A. Minimal viable version
|
||||
B. Full-featured implementation
|
||||
C. Just the backend/API
|
||||
D. Just the UI
|
||||
```
|
||||
|
||||
This lets users respond with "1A, 2C, 3B" for quick iteration. Remember to indent the options.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Fallback
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| User skips clarifying questions (e.g., replies "whatever", "just write it") | Fill with reasonable defaults, mark with `[Assumption]` in PRD, prompt user to confirm during review |
|
||||
| User input is too vague (e.g., "add a feature") | Ask once for specifics; if still vague, infer from project context and mark assumptions |
|
||||
| `tasks/` directory does not exist | Auto-create `tasks/` directory |
|
||||
| feature-name is hard to extract from input | Ask the user directly: "Suggested PRD filename is prd-XXX.md, please confirm or modify" |
|
||||
| User requests PRD changes after review | Apply changes and re-save without re-running the clarification flow |
|
||||
| PRD content exceeds 500 lines | Suggest the user consider splitting into multiple sub-feature PRDs |
|
||||
| User declines to proceed | Just save the PRD, user can run `/to-issues` later |
|
||||
| Issue creation needed later | Suggest running `/to-issues` with the saved PRD file |
|
||||
|
||||
---
|
||||
|
||||
## Step 2: PRD Structure
|
||||
|
||||
Generate the PRD with these sections:
|
||||
|
||||
### 1. Introduction/Overview
|
||||
Brief description of the feature and the problem it solves. Use plain language — avoid jargon or explain it. Assume the reader may be a junior developer or AI agent.
|
||||
|
||||
### 2. Goals
|
||||
Specific, measurable objectives (bullet list).
|
||||
|
||||
### 3. User Stories
|
||||
Each story needs:
|
||||
- **Title:** Short descriptive name
|
||||
- **Description:** "As a [user], I want [feature] so that [benefit]"
|
||||
- **Acceptance Criteria:** Verifiable checklist of what "done" means
|
||||
|
||||
**Numbering rule:** US-001, US-002, US-003... (three digits, starting from 001). Each US should be independently implementable and small enough to complete within one focused agent session.
|
||||
|
||||
**Acceptance criteria self-check template:** Each criterion must satisfy at least one of the following, otherwise it is considered "vague" and must be rewritten:
|
||||
- Observable: describes a specific UI state or API response (e.g., "button shows confirmation dialog")
|
||||
- Testable: has clear input/output pairs (e.g., "entering an empty email shows a red warning")
|
||||
- Verifiable: can be checked by tools (e.g., "Typecheck/lint passes")
|
||||
- ❌ Bad example: "works correctly", "good user experience", "excellent performance" → these are unverifiable
|
||||
|
||||
**Format:**
|
||||
```markdown
|
||||
### US-001: [Title]
|
||||
**Description:** As a [user], I want [feature] so that [benefit].
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Specific verifiable criterion
|
||||
- [ ] Another criterion
|
||||
- [ ] Typecheck/lint passes
|
||||
- [ ] **[UI stories only]** Verify in a browser (e.g., via the `run` skill)
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Acceptance criteria must be verifiable, not vague. "Works correctly" is bad. "Button shows confirmation dialog before deleting" is good.
|
||||
- **For any story with UI changes:** Always include "Verify in a browser" as acceptance criteria (e.g., via the `run` skill). This ensures visual verification of frontend work.
|
||||
|
||||
### 4. Functional Requirements
|
||||
Numbered list of specific functionalities:
|
||||
- "FR-1: The system must allow users to..."
|
||||
- "FR-2: When a user clicks X, the system must..."
|
||||
|
||||
**FR specification:** Each FR starts with `FR-N:` (N increments from 1), uses "system must / system shall" phrasing, and describes **one** specific behavior. Avoid combining multiple "and"-linked behaviors in a single FR.
|
||||
|
||||
### 5. Non-Goals (Out of Scope)
|
||||
What this feature will NOT include. Critical for managing scope.
|
||||
|
||||
### 6. Design Considerations (Optional)
|
||||
- UI/UX requirements
|
||||
- Link to mockups if available
|
||||
- Relevant existing components to reuse
|
||||
|
||||
### 7. Technical Considerations (Optional)
|
||||
- Known constraints or dependencies
|
||||
- Integration points with existing systems
|
||||
- Performance requirements
|
||||
|
||||
### 8. Success Metrics
|
||||
How will success be measured?
|
||||
- "Reduce time to complete X by 50%"
|
||||
- "Increase conversion rate by 10%"
|
||||
|
||||
### 9. Open Questions
|
||||
Remaining questions or areas needing clarification.
|
||||
|
||||
---
|
||||
|
||||
## Output
|
||||
|
||||
- **Format:** Markdown (`.md`)
|
||||
- **Location:** `tasks/`
|
||||
- **Filename:** `prd-[feature-name].md` (kebab-case)
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Next Steps
|
||||
|
||||
After the PRD is saved, suggest the user:
|
||||
|
||||
```
|
||||
✅ PRD saved to tasks/prd-[feature-name].md
|
||||
|
||||
Next steps:
|
||||
/prd-to-spec → Generate technical SPEC (optional — for complex features)
|
||||
/to-issues → Decompose into Issues and create tickets
|
||||
|
||||
Or go straight to implementation:
|
||||
/to-issues → Create Issues, then /goal to implement
|
||||
```
|
||||
|
||||
If the user wants to proceed, invoke the corresponding skill.
|
||||
|
||||
---
|
||||
|
||||
## Example PRD
|
||||
|
||||
```markdown
|
||||
# PRD: Task Priority System
|
||||
|
||||
## Introduction
|
||||
|
||||
Add priority levels to tasks so users can focus on what matters most. Tasks can be marked as high, medium, or low priority, with visual indicators and filtering to help users manage their workload effectively.
|
||||
|
||||
## Goals
|
||||
|
||||
- Allow assigning priority (high/medium/low) to any task
|
||||
- Provide clear visual differentiation between priority levels
|
||||
- Enable filtering and sorting by priority
|
||||
- Default new tasks to medium priority
|
||||
|
||||
## User Stories
|
||||
|
||||
### US-001: Add priority field to database
|
||||
**Description:** As a developer, I need to store task priority so it persists across sessions.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Add priority column to tasks table: 'high' | 'medium' | 'low' (default 'medium')
|
||||
- [ ] Generate and run migration successfully
|
||||
- [ ] Typecheck passes
|
||||
|
||||
### US-002: Display priority indicator on task cards
|
||||
**Description:** As a user, I want to see task priority at a glance so I know what needs attention first.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Each task card shows colored priority badge (red=high, yellow=medium, gray=low)
|
||||
- [ ] Priority visible without hovering or clicking
|
||||
- [ ] Typecheck passes
|
||||
- [ ] Verify in a browser (e.g., via the `run` skill)
|
||||
|
||||
### US-003: Add priority selector to task edit
|
||||
**Description:** As a user, I want to change a task's priority when editing it.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Priority dropdown in task edit modal
|
||||
- [ ] Shows current priority as selected
|
||||
- [ ] Saves immediately on selection change
|
||||
- [ ] Typecheck passes
|
||||
- [ ] Verify in a browser (e.g., via the `run` skill)
|
||||
|
||||
### US-004: Filter tasks by priority
|
||||
**Description:** As a user, I want to filter the task list to see only high-priority items when I'm focused.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Filter dropdown with options: All | High | Medium | Low
|
||||
- [ ] Filter persists in URL params
|
||||
- [ ] Empty state message when no tasks match filter
|
||||
- [ ] Typecheck passes
|
||||
- [ ] Verify in a browser (e.g., via the `run` skill)
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
- FR-1: Add `priority` field to tasks table ('high' | 'medium' | 'low', default 'medium')
|
||||
- FR-2: Display colored priority badge on each task card
|
||||
- FR-3: Include priority selector in task edit modal
|
||||
- FR-4: Add priority filter dropdown to task list header
|
||||
- FR-5: Sort by priority within each status column (high to medium to low)
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No priority-based notifications or reminders
|
||||
- No automatic priority assignment based on due date
|
||||
- No priority inheritance for subtasks
|
||||
|
||||
## Technical Considerations
|
||||
|
||||
- Reuse existing badge component with color variants
|
||||
- Filter state managed via URL search params
|
||||
- Priority stored in database, not computed
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Users can change priority in under 2 clicks
|
||||
- High-priority tasks immediately visible at top of lists
|
||||
- No regression in task list performance
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should priority affect task ordering within a column?
|
||||
- Should we add keyboard shortcuts for priority changes?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
Before saving the PRD:
|
||||
|
||||
- [ ] Asked clarifying questions with lettered options
|
||||
- [ ] Incorporated user's answers
|
||||
- [ ] User stories are small and specific
|
||||
- [ ] Functional requirements are numbered and unambiguous
|
||||
- [ ] Non-goals section defines clear boundaries
|
||||
- [ ] Saved to `tasks/prd-[feature-name].md`
|
||||
- [ ] Suggested next steps: `/prd-to-spec` (optional) and `/to-issues`
|
||||
@@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "帮我写一个用户登录功能的PRD",
|
||||
"expected": "应先询问3-5个澄清问题(登录方式、目标用户、安全要求等),用户回答后生成结构化PRD,包含用户故事、编号功能需求、非目标、验收标准等完整章节;结尾建议下一步运行 /prd-to-spec(可选)和 /to-issues"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "我需要给电商App加一个购物车功能,支持多规格商品",
|
||||
"expected": "应识别'多规格'的歧义(SKU选择 vs 自定义规格),在澄清问题中覆盖;PRD中需体现规格选择、数量修改、价格联动等核心逻辑,验收标准需可验证"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "创建一个PRD,给我们的内部工具加批量导入功能",
|
||||
"expected": "应区分内部工具场景(无普通C端用户),PRD应聚焦批量操作的错误处理、进度反馈、部分失败策略等企业场景关注点"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"prompt": "随便写个功能的PRD吧",
|
||||
"expected": "输入过于模糊时应先追问一次具体功能;若用户仍不明确(如回复'whatever'),用合理默认值填充并在PRD中用[Assumption]标注,在review阶段提示用户确认"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"prompt": "给任务列表加一个优先级功能的PRD,包含前端展示",
|
||||
"expected": "含UI改动的用户故事,验收标准必须包含'Verify in a browser (e.g., via the run skill)'一项;生成后展示PRD供用户review,确认后保存到 tasks/prd-*.md"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,674 @@
|
||||
---
|
||||
name: refactor
|
||||
description: "Expert code refactoring based on Martin Fowler's catalog — improve maintainability without changing behavior. Covers code smells, composing methods, moving features, organizing data, simplifying conditionals, method calls, and generalization. Triggers on: refactor, 重构, clean up, improve code, code smell, extract method, rename, simplify."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Refactor — Expert Code Restructuring
|
||||
|
||||
Surgical code refactoring based on Martin Fowler's <Refactoring> (2nd Edition) catalog. Improve structure, readability, and maintainability without changing external behavior. Gradual evolution, not revolution.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
This skill activates when:
|
||||
- Code is hard to understand or maintain
|
||||
- Functions/classes have grown too large
|
||||
- Code smells are detected
|
||||
- Adding features is difficult due to poor structure
|
||||
- User explicitly requests refactoring, cleanup, or improvement
|
||||
- User says: refactor, 重构, clean up, improve code, code smell, extract method, rename, simplify
|
||||
|
||||
---
|
||||
|
||||
## The Golden Rules
|
||||
|
||||
These five rules are non-negotiable. Violating any of them turns refactoring into reckless editing.
|
||||
|
||||
### 1. Behavior is Preserved
|
||||
|
||||
Only *how* the code works changes, never *what* it does. If tests existed before, they must pass after. If the refactoring introduces a behavioral change, it's not refactoring — it's rewriting.
|
||||
|
||||
### 2. Small Steps
|
||||
|
||||
Each change should be the smallest possible transformation that compiles and passes tests. If a step breaks, you know exactly which change caused it. Refactoring is a series of tiny, safe transformations, not one big rewrite.
|
||||
|
||||
### 3. Version Control is Your Friend
|
||||
|
||||
Commit before starting. Commit after each successful step. This gives you infinite undo. Branch from a clean state so you can abandon the refactoring without consequences.
|
||||
|
||||
### 4. Tests are Essential
|
||||
|
||||
"Without tests, you're not refactoring — you're just editing." If tests don't exist for the target code, write characterization tests first. These tests capture the current behavior so you can detect regressions.
|
||||
|
||||
### 5. One Thing at a Time
|
||||
|
||||
Never mix refactoring with feature changes. Never refactor two unrelated things simultaneously. Each commit should contain exactly one refactoring operation.
|
||||
|
||||
---
|
||||
|
||||
## When NOT to Refactor
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Code works and won't change again | Leave it alone |
|
||||
| Critical production path with no tests | Write characterization tests first |
|
||||
| Under tight deadline pressure | Document the smell, refactor later |
|
||||
| No clear purpose or benefit | Don't refactor for refactoring's sake |
|
||||
| Code is fundamentally wrong | This is a rewrite, not a refactoring |
|
||||
|
||||
---
|
||||
|
||||
## Code Smells Catalog
|
||||
|
||||
Based on Fowler's taxonomy. Before refactoring, identify which smell is present.
|
||||
|
||||
### Bloaters
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Long Method** | Method > 10-15 lines, doing multiple things | Extract Method, Replace Temp with Query |
|
||||
| **Large Class** | Class with too many fields/methods (God Object) | Extract Class, Extract Subclass |
|
||||
| **Primitive Obsession** | Using primitives instead of small objects | Replace Data Value with Object, Replace Type Code with Class |
|
||||
| **Long Parameter List** | Method with > 3-4 parameters | Introduce Parameter Object, Preserve Whole Object |
|
||||
| **Data Clumps** | Same group of data appearing together | Extract Class, Introduce Parameter Object |
|
||||
|
||||
### Object-Orientation Abusers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Switch Statements** | Repeated switch/if-else on type codes | Replace Conditional with Polymorphism, Replace Type Code with Subclasses |
|
||||
| **Temporary Field** | Field only set in certain circumstances | Extract Class, Introduce Null Object |
|
||||
| **Refused Bequest** | Subclass doesn't use inherited members | Replace Inheritance with Delegation, Push Down Method/Field |
|
||||
| **Alternative Classes with Different Interfaces** | Classes doing similar things with different names | Rename Method, Move Method, Extract Superclass |
|
||||
|
||||
### Change Preventers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Divergent Change** | One class changed for different reasons | Extract Class |
|
||||
| **Shotgun Surgery** | One change requires many small changes across classes | Move Method, Move Field, Inline Class |
|
||||
| **Parallel Inheritance Hierarchies** | Adding a subclass to one hierarchy forces adding to another | Move Method, Move Field |
|
||||
|
||||
### Dispensables
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Comments** | Comments explaining what code does (not why) | Extract Method, Rename Variable, Introduce Assertion |
|
||||
| **Duplicate Code** | Same code structure in multiple places | Extract Method, Pull Up Method, Form Template Method |
|
||||
| **Lazy Class** | Class doing too little to justify existence | Inline Class, Collapse Hierarchy |
|
||||
| **Data Class** | Class with only fields and getters/setters | Move Method, Encapsulate Field, Encapsulate Collection |
|
||||
| **Dead Code** | Unused code, imports, commented-out blocks | Delete it (git history has it) |
|
||||
| **Speculative Generality** | Code built for "someday" that never came | Inline Class, Collapse Hierarchy, Remove Parameter |
|
||||
|
||||
### Couplers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Feature Envy** | Method uses another class's data more than its own | Move Method, Extract Method + Move Method |
|
||||
| **Inappropriate Intimacy** | Classes know too much about each other's internals | Move Method, Move Field, Replace Delegation with Hidden Delegate |
|
||||
| **Message Chains** | `a.getB().getC().getD().doSomething()` | Hide Delegate, Extract Method |
|
||||
| **Middle Man** | Class delegates everything to another class | Remove Middle Man, Inline Method |
|
||||
| **Incomplete Library Class** | Library missing methods you need | Introduce Foreign Method, Introduce Local Extension |
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Techniques Catalog
|
||||
|
||||
Organized by category, from Fowler's catalog. Each technique includes its mechanical steps.
|
||||
|
||||
### Composing Methods
|
||||
|
||||
#### Extract Method
|
||||
Turn a code fragment into a method whose name explains its purpose.
|
||||
|
||||
**Mechanics:**
|
||||
1. Create a new method named after what the fragment does (not how)
|
||||
2. Copy the extracted code into the new method
|
||||
3. Identify local variables: read-only become parameters, modified become return values
|
||||
4. Pass parameters and handle return values
|
||||
5. Replace the original fragment with a call to the new method
|
||||
6. Test
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
void printOwing() {
|
||||
printBanner();
|
||||
// Print details
|
||||
System.out.println("name: " + _name);
|
||||
System.out.println("amount: " + getOutstanding());
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
void printOwing() {
|
||||
printBanner();
|
||||
printDetails(getOutstanding());
|
||||
}
|
||||
|
||||
void printDetails(double outstanding) {
|
||||
System.out.println("name: " + _name);
|
||||
System.out.println("amount: " + outstanding);
|
||||
}
|
||||
```
|
||||
|
||||
#### Inline Method
|
||||
Replace a method call with its body when the method body is as clear as the name.
|
||||
|
||||
**Mechanics:**
|
||||
1. Check the method is not polymorphic (no subclasses override it)
|
||||
2. Find all callers
|
||||
3. Replace each call with the method body
|
||||
4. Delete the method definition
|
||||
5. Test
|
||||
|
||||
#### Extract Variable
|
||||
Put the result of an expression (or part of it) in a self-explanatory variable.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
if (platform.toUpperCase().indexOf("MAC") > -1 &&
|
||||
browser.toUpperCase().indexOf("IE") > -1 &&
|
||||
wasInitialized() && resize > 0) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
|
||||
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
|
||||
final boolean wasResized = resize > 0;
|
||||
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Inline Temp
|
||||
Replace a temp variable with its expression when the temp is only used once and the expression is clear.
|
||||
|
||||
#### Replace Temp with Query
|
||||
Extract the expression into a method. Temps that are computed once and reused are replaced with method calls.
|
||||
|
||||
#### Split Temporary Variable
|
||||
A temp assigned more than once (not loop/collecting) should be split into separate variables, one per responsibility.
|
||||
|
||||
#### Remove Assignments to Parameters
|
||||
Don't assign to parameters. Use a local variable instead.
|
||||
|
||||
#### Replace Method with Method Object
|
||||
When a long method uses many local variables that make Extract Method hard, turn the method into its own class, with locals as fields.
|
||||
|
||||
#### Substitute Algorithm
|
||||
Replace an algorithm with a clearer one.
|
||||
|
||||
---
|
||||
|
||||
### Moving Features Between Objects
|
||||
|
||||
#### Move Method
|
||||
Move a method to the class where it's used most.
|
||||
|
||||
**Mechanics:**
|
||||
1. Check all features used by the method on its current class
|
||||
2. Check for polymorphism (subclass/superclass methods)
|
||||
3. Create the method on the target class, adapting as needed
|
||||
4. Reference the target object from the source
|
||||
5. Turn the source method into a delegating method, or remove it
|
||||
6. Test
|
||||
|
||||
#### Move Field
|
||||
Move a field to the class where it's used most.
|
||||
|
||||
#### Extract Class
|
||||
When a class does the work of two, split it. Create a new class and move relevant fields and methods.
|
||||
|
||||
#### Inline Class
|
||||
When a class does almost nothing, absorb it into the class that uses it most.
|
||||
|
||||
#### Hide Delegate
|
||||
Create methods on the server to hide the delegate chain. `manager = person.getDepartment().getManager()` → `manager = person.getManager()`.
|
||||
|
||||
#### Remove Middle Man
|
||||
When a class is doing too much delegation, call the delegate directly.
|
||||
|
||||
#### Introduce Foreign Method
|
||||
When a server class needs an additional method but you can't modify it, create a method on the client with the server instance as the first argument.
|
||||
|
||||
#### Introduce Local Extension
|
||||
When you need multiple foreign methods, create an extension class (subclass or wrapper).
|
||||
|
||||
---
|
||||
|
||||
### Organizing Data
|
||||
|
||||
#### Self Encapsulate Field
|
||||
Access fields through getters and setters, even within the owning class.
|
||||
|
||||
#### Replace Data Value with Object
|
||||
When a data item needs additional data or behavior, turn it into an object.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
class Order {
|
||||
private String customer; // Just a string
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
class Order {
|
||||
private Customer customer; // Rich object with name, address, credit rating
|
||||
}
|
||||
```
|
||||
|
||||
#### Change Value to Reference
|
||||
When you need to share one instance of an object across multiple places.
|
||||
|
||||
#### Change Reference to Value
|
||||
When a reference object is small, immutable, and you want value semantics.
|
||||
|
||||
#### Replace Array with Object
|
||||
When an array holds heterogeneous data (`String[] row = new String[3]` — name, score, wins), replace with an object.
|
||||
|
||||
#### Duplicate Observed Data
|
||||
Domain data lives in a GUI control but domain logic needs it. Copy the data into a domain object and set up an observer to keep the two in sync (Observer pattern). Separates presentation from domain so each can evolve independently.
|
||||
|
||||
#### Change Unidirectional Association to Bidirectional
|
||||
Two classes need each other's features but only one holds a reference. Add a back-pointer and make the modifiers on both ends keep the link consistent. Add the reference only when genuinely needed — bidirectional links raise coupling and risk inconsistency.
|
||||
|
||||
#### Change Bidirectional Association to Unidirectional
|
||||
A two-way link exists but one side no longer uses the other. Drop the unneeded direction. Reduces coupling, simplifies lifecycle management, and avoids "zombie" objects kept alive only by a stale back-pointer.
|
||||
|
||||
#### Replace Magic Number with Symbolic Constant
|
||||
Replace literal numbers/strings with named constants.
|
||||
|
||||
#### Encapsulate Field
|
||||
Make public fields private and provide accessors.
|
||||
|
||||
#### Encapsulate Collection
|
||||
Never return the raw collection. Return a read-only view and provide add/remove methods.
|
||||
|
||||
#### Replace Type Code with Class
|
||||
Replace a numeric/string type code with a class that has meaningful behavior.
|
||||
|
||||
#### Replace Type Code with Subclasses
|
||||
When type code affects behavior, use polymorphism instead of conditionals.
|
||||
|
||||
#### Replace Type Code with State/Strategy
|
||||
Similar to subclasses but uses composition when the type can change at runtime.
|
||||
|
||||
#### Replace Subclass with Fields
|
||||
When subclasses vary only in constant data, replace them with fields on a single class.
|
||||
|
||||
---
|
||||
|
||||
### Simplifying Conditional Expressions
|
||||
|
||||
#### Decompose Conditional
|
||||
Extract the condition, then-part, and else-part into separate methods.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
|
||||
charge = quantity * _winterRate + _winterServiceCharge;
|
||||
} else {
|
||||
charge = quantity * _summerRate;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
if (isSummer(date)) {
|
||||
charge = summerCharge(quantity);
|
||||
} else {
|
||||
charge = winterCharge(quantity);
|
||||
}
|
||||
```
|
||||
|
||||
#### Consolidate Conditional Expression
|
||||
Combine multiple conditionals that have the same result.
|
||||
|
||||
#### Consolidate Duplicate Conditional Fragments
|
||||
Move code that appears in every branch outside the conditional.
|
||||
|
||||
#### Remove Control Flag
|
||||
Replace control flags with break, continue, or return.
|
||||
|
||||
#### Replace Nested Conditional with Guard Clauses
|
||||
Use early returns for special cases instead of deep nesting.
|
||||
|
||||
**Before (arrow code):**
|
||||
```java
|
||||
double getPayAmount() {
|
||||
double result;
|
||||
if (_isDead) {
|
||||
result = deadAmount();
|
||||
} else {
|
||||
if (_isSeparated) {
|
||||
result = separatedAmount();
|
||||
} else {
|
||||
if (_isRetired) {
|
||||
result = retiredAmount();
|
||||
} else {
|
||||
result = normalPayAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
double getPayAmount() {
|
||||
if (_isDead) return deadAmount();
|
||||
if (_isSeparated) return separatedAmount();
|
||||
if (_isRetired) return retiredAmount();
|
||||
return normalPayAmount();
|
||||
}
|
||||
```
|
||||
|
||||
#### Replace Conditional with Polymorphism
|
||||
When a conditional chooses different behavior based on the type of an object, use subclasses.
|
||||
|
||||
#### Introduce Null Object
|
||||
Replace null checks with a null object that provides default behavior.
|
||||
|
||||
#### Introduce Assertion
|
||||
State assumptions explicitly with assertions.
|
||||
|
||||
---
|
||||
|
||||
### Making Method Calls Simpler
|
||||
|
||||
#### Rename Method
|
||||
The name should say what the method does. If you can't think of a good name, the method may have multiple responsibilities.
|
||||
|
||||
#### Add Parameter / Remove Parameter
|
||||
Add parameters when a method needs more info. Remove parameters when the method can get the info another way.
|
||||
|
||||
#### Separate Query from Modifier
|
||||
A method should either return a value OR change state, never both.
|
||||
|
||||
#### Parameterize Method
|
||||
Several methods doing similar things with different values → one method with a parameter.
|
||||
|
||||
#### Replace Parameter with Explicit Methods
|
||||
The inverse: when a parameter essentially selects different behavior, create separate methods.
|
||||
|
||||
#### Preserve Whole Object
|
||||
Pass the whole object instead of pulling individual fields from it.
|
||||
|
||||
#### Replace Parameter with Method
|
||||
*(refactoring.guru: Replace Parameter with Method Call)* When a parameter can be computed from data the object already has, remove the parameter and let the method call the query itself.
|
||||
|
||||
#### Introduce Parameter Object
|
||||
Group parameters that naturally go together into an object.
|
||||
|
||||
#### Remove Setting Method
|
||||
Make a field immutable by removing its setter and setting it in the constructor.
|
||||
|
||||
#### Hide Method
|
||||
Make methods private when they're not used outside the class.
|
||||
|
||||
#### Replace Constructor with Factory Method
|
||||
When you need more flexibility than a simple constructor call.
|
||||
|
||||
#### Replace Error Code with Exception
|
||||
Throw an exception instead of returning an error code.
|
||||
|
||||
#### Replace Exception with Test
|
||||
Check the condition first instead of catching an exception.
|
||||
|
||||
---
|
||||
|
||||
### Dealing with Generalization
|
||||
|
||||
#### Pull Up Field/Method/Constructor Body
|
||||
Move identical fields/methods/constructor code from subclasses to superclass.
|
||||
|
||||
#### Push Down Method/Field
|
||||
Move behavior from superclass to only the subclasses that use it.
|
||||
|
||||
#### Extract Subclass
|
||||
Create a subclass for a subset of features used in some instances.
|
||||
|
||||
#### Extract Superclass
|
||||
Create a superclass for shared features of similar classes.
|
||||
|
||||
#### Extract Interface
|
||||
Create an interface from a subset of a class's public methods.
|
||||
|
||||
#### Collapse Hierarchy
|
||||
Merge a superclass and subclass when they're not different enough.
|
||||
|
||||
#### Form Template Method
|
||||
Generalize an algorithm in the superclass, letting subclasses fill in the specifics.
|
||||
|
||||
#### Replace Inheritance with Delegation
|
||||
When a subclass only uses part of the superclass, use composition instead.
|
||||
|
||||
#### Replace Delegation with Inheritance
|
||||
When a delegating class needs access to all of the delegate's behavior.
|
||||
|
||||
---
|
||||
|
||||
## The Refactoring Process
|
||||
|
||||
### Phase 1: Prepare
|
||||
|
||||
1. **Write characterization tests** if they don't exist. These capture current behavior — they don't need to be elegant, just comprehensive enough to catch regressions.
|
||||
2. **Commit** current state. Start from a clean working tree.
|
||||
3. **Create a branch** for the refactoring. Keep it separate from feature work.
|
||||
|
||||
### Phase 2: Identify
|
||||
|
||||
1. **Smell the code.** Use the smell catalog above to classify what's wrong.
|
||||
2. **Understand the code.** Read it thoroughly. You must understand what it does before changing it.
|
||||
3. **Choose the right refactoring.** Pick from the technique catalog. Know what the result looks like before you start.
|
||||
|
||||
### Phase 3: Refactor (Small Steps)
|
||||
|
||||
For each step:
|
||||
1. **Make one small change.** One refactoring technique at a time.
|
||||
2. **Compile.** The code should compile after every change.
|
||||
3. **Run tests.** All tests must pass. If they don't, you've changed behavior.
|
||||
4. **Commit.** Create a commit with a message like `refactor: extract validateEmail method`.
|
||||
|
||||
Repeat until the smell is resolved.
|
||||
|
||||
### Phase 4: Verify
|
||||
|
||||
1. **All tests pass.** Non-negotiable.
|
||||
2. **Manual check.** Briefly run the application or review the diff for unintended changes.
|
||||
3. **Performance.** Ensure no performance regression. Simple refactorings rarely cause them, but check.
|
||||
|
||||
### Phase 5: Clean Up
|
||||
|
||||
1. **Remove stale comments.** If a refactoring made a comment obvious, delete the comment.
|
||||
2. **Check for dead code.** After refactorings, unused code may emerge.
|
||||
3. **Final commit.** Summarize the refactoring sequence.
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Checklist
|
||||
|
||||
### Code Quality
|
||||
- [ ] Functions are small (< 20 lines preferred, < 50 lines max)
|
||||
- [ ] Each function does one thing (single responsibility)
|
||||
- [ ] No duplicated code (DRY)
|
||||
- [ ] Names describe what, not how
|
||||
- [ ] No magic numbers or strings
|
||||
- [ ] Dead code removed
|
||||
|
||||
### Structure
|
||||
- [ ] Related code is grouped together
|
||||
- [ ] Module boundaries are clear
|
||||
- [ ] Dependencies flow in one direction (no cycles)
|
||||
- [ ] No circular dependencies
|
||||
|
||||
### Conditionals
|
||||
- [ ] Guard clauses replace deep nesting
|
||||
- [ ] Complex conditions extracted to named methods
|
||||
- [ ] Polymorphism replaces type-switching conditionals
|
||||
- [ ] Null Object pattern where appropriate
|
||||
|
||||
### Type Safety (typed languages)
|
||||
- [ ] Types defined for all public APIs
|
||||
- [ ] No `any` usage without qualification
|
||||
- [ ] Nullable types explicitly marked
|
||||
- [ ] Type codes replaced with classes/enums
|
||||
|
||||
### Testing
|
||||
- [ ] Refactored code is tested
|
||||
- [ ] Edge cases are covered
|
||||
- [ ] All tests pass after each step
|
||||
- [ ] Characterization tests capture pre-refactoring behavior
|
||||
|
||||
---
|
||||
|
||||
## Language-Specific Guidance
|
||||
|
||||
### Java
|
||||
- Prefer `final` for locals that shouldn't change
|
||||
- Use IDE automated refactorings (Eclipse/IntelliJ) for mechanical steps
|
||||
- Leverage the type system: enums, records (Java 14+), sealed classes (Java 17+)
|
||||
|
||||
### JavaScript/TypeScript
|
||||
- Use destructuring to reduce parameter count
|
||||
- Prefer `const` over `let` for immutable bindings
|
||||
- Use TypeScript union types instead of type codes
|
||||
- Nullish coalescing (`??`) and optional chaining (`?.`) eliminate null-check noise
|
||||
|
||||
### Python
|
||||
- Use type hints for documenting intent during refactoring
|
||||
- Use `dataclasses` to replace tuple/data-class patterns
|
||||
- Use `@property` to replace getters
|
||||
- Context managers for resource cleanup patterns
|
||||
|
||||
### Go
|
||||
- Small interfaces preferred: accept interfaces, return structs
|
||||
- Use named return values when they improve clarity
|
||||
- Table-driven tests pair well with refactoring
|
||||
- Avoid deep nesting with early returns
|
||||
|
||||
### Rust
|
||||
- Use `Result` and `Option` instead of error codes and null
|
||||
- Pattern matching replaces if-else chains
|
||||
- `From` trait implementations clean up type conversions
|
||||
- Derive macros reduce boilerplate
|
||||
|
||||
---
|
||||
|
||||
## Common Refactoring Sequences
|
||||
|
||||
### Extract Method Sequence
|
||||
1. Create a new method named after intent
|
||||
2. Copy code fragment into new method
|
||||
3. Identify local variables → parameters / return values
|
||||
4. Call new method from original location
|
||||
5. Test
|
||||
|
||||
### Replace Conditional with Polymorphism Sequence
|
||||
1. Create subclasses for each variant
|
||||
2. Create a factory method that returns the right subclass
|
||||
3. Move the conditional body to the appropriate subclass method
|
||||
4. Delete the conditional
|
||||
|
||||
### Extract Class Sequence
|
||||
1. Identify a coherent subset of fields and methods
|
||||
2. Create a new class
|
||||
3. Create an instance from the old class
|
||||
4. Move fields and methods one at a time
|
||||
5. Update references in old class
|
||||
6. Test after each move
|
||||
|
||||
### Inline Class Sequence
|
||||
1. Identify all callers of the target class
|
||||
2. Move all methods/fields to the absorbing class
|
||||
3. Redirect all references to the absorbing class
|
||||
4. Delete the empty class
|
||||
5. Test
|
||||
|
||||
---
|
||||
|
||||
## Safety Protocol
|
||||
|
||||
### Before You Touch Anything
|
||||
```
|
||||
1. Characterization tests → capture what the code does now
|
||||
2. Git commit → save a known-good state
|
||||
3. Branch → isolate refactoring from other work
|
||||
```
|
||||
|
||||
### Every Single Step
|
||||
```
|
||||
1. One change → one refactoring technique
|
||||
2. Compile → must compile clean
|
||||
3. Tests → every test must pass
|
||||
4. Commit → message: "refactor: <technique> <what>"
|
||||
```
|
||||
|
||||
### If Tests Break
|
||||
```
|
||||
1. Undo the last change
|
||||
2. Understand what broke and why
|
||||
3. Try a smaller step
|
||||
4. If the test was wrong and behavior was correct, fix the test FIRST, then retry
|
||||
```
|
||||
|
||||
### On Completion
|
||||
```
|
||||
1. Full test suite → all tests pass
|
||||
2. Manual smoke test → quick sanity check
|
||||
3. Self-review diff → catch unintended changes
|
||||
4. Final commit → describe the overall transformation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Patterns in Refactoring
|
||||
|
||||
### Strategy Pattern
|
||||
Replace a conditional that chooses an algorithm. **Smell:** Switch on type code with different behavior per branch. **Technique:** Replace Conditional with Polymorphism + Extract Method.
|
||||
|
||||
### Template Method
|
||||
Extract common algorithm skeleton to superclass, letting subclasses fill in the variants. **Smell:** Duplicate code with slight variations. **Technique:** Form Template Method.
|
||||
|
||||
### State Pattern
|
||||
Replace a state-based conditional by extracting each state's behavior into a class. **Smell:** Switch on status field with behavior variation. **Technique:** Replace Type Code with State/Strategy.
|
||||
|
||||
### Composite Pattern
|
||||
Treat individual objects and groups uniformly. **Smell:** Client code has special handling for single vs. collection cases. **Technique:** Extract Interface + Create Composite.
|
||||
|
||||
### Decorator Pattern
|
||||
Add behavior dynamically by wrapping objects. **Smell:** Conditional logic for optional behaviors. **Technique:** Extract Class + use composition.
|
||||
|
||||
### Null Object Pattern
|
||||
Replace null checks with a default object. **Smell:** Repeated `if (x == null)` checks. **Technique:** Introduce Null Object.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Gotchas
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| No tests exist | Write characterization tests first. Run the code with various inputs, capture outputs. These are your safety net. |
|
||||
| Refactoring breaks a distant test | FIRST understand why. Maybe the test relied on implementation detail. If so, fix the test to test behavior, not implementation. Then resume. |
|
||||
| User wants behavior change + refactor together | REFUSE. Do them separately. Refactor first to make the behavior change easy, commit, then change behavior. |
|
||||
| Method is too complex to step through | Use Replace Method with Method Object. Turn the whole method into a class where each step can be extracted. |
|
||||
| Refactoring across a large codebase | Extract a micro-service or module boundary first. Then refactor within the boundary. "There is a refactoring for everything except too many refactorings." |
|
||||
| IDE automated refactoring available | Use it. Modern IDEs can safely rename, extract method, introduce variable, etc. Only do it manually when the IDE can't. |
|
||||
| Undo needed | `git stash` or `git reset --hard` back to last commit. Small commits make this painless. |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- Martin Fowler, *Refactoring: Improving the Design of Existing Code* (2nd Edition, 2018)
|
||||
- [refactoring.com](https://refactoring.com) — Fowler's online catalog
|
||||
- [refactoring.guru](https://refactoring.guru) — Illustrated refactoring patterns
|
||||
- [refactoring.guru/refactoring/catalog](https://refactoring.guru/refactoring/catalog) — full technique catalog (6 categories, 66 techniques) and code-smell taxonomy this skill mirrors
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: review-it
|
||||
description: "Code review closeout for Claude Code, Codex, OpenCode, DeepSeek TUI, and Antigravity CLI: local dirty changes, branch vs main, parallel tests."
|
||||
---
|
||||
|
||||
# CC Review
|
||||
|
||||
Run automated code review as a closeout check before committing or shipping. Works across multiple AI coding agents.
|
||||
|
||||
Use when:
|
||||
- user asks for code review / review-it / autoreview
|
||||
- after non-trivial code edits, before final/commit/ship
|
||||
- reviewing a local branch or PR branch after fixes
|
||||
|
||||
## Supported Agents
|
||||
|
||||
| Agent | Review Command | Notes |
|
||||
|-------|---------------|-------|
|
||||
| Claude Code | `/review` | Built-in, works on uncommitted changes or diff |
|
||||
| Codex | `codex review` | Pass diff file or let it auto-detect |
|
||||
| OpenCode | `/review` | Same as Claude Code |
|
||||
| DeepSeek TUI | `/review` or manual diff review | Pass diff content for analysis |
|
||||
| Antigravity CLI | `/code-review` | Built-in slash command, auto-detects diff |
|
||||
|
||||
## Contract
|
||||
|
||||
- Treat review output as advisory. Never blindly apply it.
|
||||
- Verify every finding by reading the real code path and adjacent files.
|
||||
- Read dependency docs/source/types when the finding depends on external behavior.
|
||||
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase.
|
||||
- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class.
|
||||
- Keep going until review returns no accepted/actionable findings.
|
||||
- If a review-triggered fix changes code, rerun focused tests and rerun review.
|
||||
- Stop as soon as the review comes back clean with no actionable findings.
|
||||
- If rejecting a finding as intentional/not worth fixing, add a brief inline code comment only when it explains a real invariant or ownership decision that future reviewers should know.
|
||||
- Do not push just to review. Push only when the user requested push/ship/PR update.
|
||||
|
||||
## Review Focus
|
||||
|
||||
请 review 当前 diff。不要只看语法和明显 bug,请重点检查以下维度,最后按严重程度排序:
|
||||
|
||||
1. **隐藏副作用 (Hidden Side Effects)** — 变更是否在非显而易见的地方产生级联影响?是否修改了共享状态、全局变量、或外部依赖的行为?
|
||||
2. **破坏兼容性 (Breaking Compatibility)** — 是否改变了 API 签名、数据结构、配置文件格式、或命令行接口?现有调用方是否会受影响?
|
||||
3. **边界情况 (Edge Cases)** — null/空值/空集合、极大/极小值、并发/竞态条件、异常路径是否被正确处理?
|
||||
4. **性能风险 (Performance Risks)** — 是否引入了不必要的循环嵌套、N+1 查询、大对象分配、阻塞 I/O、或锁竞争?
|
||||
5. **安全风险 (Security Risks)** — 是否存在注入、越权、敏感信息泄露、不安全的反序列化、或依赖版本漏洞?
|
||||
6. **命名误导 (Naming Misleading)** — 变量/函数/类型名称是否与实际行为不一致?是否存在名不副实或语义模糊的命名?
|
||||
7. **测试不足 (Insufficient Testing)** — 关键路径、边界条件、错误处理是否缺少测试覆盖?现有测试是否真正验证了期望行为?
|
||||
8. **未来维护成本 (Future Maintenance Cost)** — 是否引入了不必要的抽象、重复代码、隐式耦合、或难以追踪的控制流?后来者是否容易理解和修改?
|
||||
|
||||
## Pick Target
|
||||
|
||||
### Claude Code / OpenCode / DeepSeek TUI
|
||||
|
||||
Dirty local work (default — `/review` works on uncommitted changes):
|
||||
|
||||
```
|
||||
/review
|
||||
```
|
||||
|
||||
Branch/PR work — review all changes against base:
|
||||
|
||||
First generate a diff, then review it:
|
||||
|
||||
```bash
|
||||
git diff origin/main...HEAD > /tmp/review-it.diff
|
||||
```
|
||||
|
||||
Then review the diff file with a focused prompt:
|
||||
|
||||
```
|
||||
/review the changes in /tmp/review-it.diff against origin/main
|
||||
```
|
||||
|
||||
If an open PR exists, use its actual base:
|
||||
|
||||
```bash
|
||||
base=$(gh pr view --json baseRefName --jq .baseRefName)
|
||||
git diff "origin/$base"...HEAD > /tmp/review-it.diff
|
||||
```
|
||||
|
||||
### Antigravity CLI (`agy`)
|
||||
|
||||
Dirty local work:
|
||||
|
||||
```
|
||||
/code-review
|
||||
```
|
||||
|
||||
Branch/PR work — review all changes against base:
|
||||
|
||||
```bash
|
||||
git diff origin/main...HEAD > /tmp/review-it.diff
|
||||
```
|
||||
|
||||
Then pass the diff to the review command:
|
||||
|
||||
```
|
||||
/code-review the changes in /tmp/review-it.diff against origin/main
|
||||
```
|
||||
|
||||
If an open PR exists, use its actual base:
|
||||
|
||||
```bash
|
||||
base=$(gh pr view --json baseRefName --jq .baseRefName)
|
||||
git diff "origin/$base"...HEAD > /tmp/review-it.diff
|
||||
```
|
||||
|
||||
### Codex
|
||||
|
||||
```bash
|
||||
# Review uncommitted changes
|
||||
codex review
|
||||
|
||||
# Review branch diff
|
||||
git diff origin/main...HEAD > /tmp/review-it.diff
|
||||
codex review /tmp/review-it.diff
|
||||
```
|
||||
|
||||
## Parallel Closeout
|
||||
|
||||
Format first if formatting can change line locations. Then it's OK to run tests and review in parallel:
|
||||
|
||||
```bash
|
||||
scripts/review-it --parallel-tests "<focused test command>"
|
||||
```
|
||||
|
||||
Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain.
|
||||
|
||||
## Uncommitted vs Branch Review
|
||||
|
||||
Choose the right mode:
|
||||
|
||||
- **Uncommitted changes** (staged/unstaged): use `/review` directly (Antigravity: `/code-review`, Codex: `codex review`)
|
||||
- **Committed, not pushed**: use `git diff origin/main...HEAD` + review
|
||||
- **Pushed/PR**: same as committed, against the PR base
|
||||
- **Clean working tree**: skip review if there's truly nothing to review
|
||||
|
||||
## Helper
|
||||
|
||||
Bundled helper script for parallel test + review orchestration:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/review-it/scripts/review-it --help
|
||||
```
|
||||
|
||||
The helper:
|
||||
- Detects which agent is running (Claude Code, Antigravity CLI, Codex) via `--agent auto`
|
||||
- Detects whether to use uncommitted review or branch diff review
|
||||
- For branch mode: generates diff against `origin/main` (or PR base), then triggers review
|
||||
- Supports `--parallel-tests` for concurrent test + review execution
|
||||
- Supports `--dry-run` for checking what command would be used
|
||||
- Prints `review-it clean: no accepted/actionable findings reported` when review is clean
|
||||
|
||||
## Final Report
|
||||
|
||||
Include:
|
||||
- review target (uncommitted / branch / PR base)
|
||||
- tests/proof run
|
||||
- findings accepted/rejected, briefly why
|
||||
- the clean review result, or why a remaining finding was consciously rejected
|
||||
|
||||
Do not run another review solely to improve the final report wording. If review exited clean with no actionable findings, report that as clean.
|
||||
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env bash
|
||||
# review-it — Code review closeout helper
|
||||
# Orchestrates /review with optional parallel test execution.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
MODE="auto"
|
||||
BASE="origin/main"
|
||||
TESTS=""
|
||||
DRY_RUN=false
|
||||
AGENT="auto"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: review-it [options]
|
||||
|
||||
Options:
|
||||
--mode auto|local|branch Target selection. Default: auto.
|
||||
auto: uncommitted if dirty, branch diff otherwise
|
||||
local: force review on uncommitted changes
|
||||
branch: force diff review against base
|
||||
--agent auto|claude|antigravity|codex
|
||||
Agent to target. Default: auto (detects running agent).
|
||||
--base REF Base ref for branch review. Default: origin/main.
|
||||
--parallel-tests CMD Run tests in parallel with review.
|
||||
--dry-run Print what would be executed, don't run.
|
||||
--help Show this help.
|
||||
|
||||
Environment:
|
||||
CC_REVIEW_OUTPUT File to write review output to (default: stdout only).
|
||||
|
||||
For branch mode, generates a git diff against the base and instructs
|
||||
Claude Code's /review to analyze it.
|
||||
|
||||
Examples:
|
||||
review-it # auto-detect mode and agent
|
||||
review-it --agent antigravity # use /code-review for Antigravity CLI
|
||||
review-it --mode local # review uncommitted changes
|
||||
review-it --mode branch # review branch vs origin/main
|
||||
review-it --parallel-tests "go test ./..." # review + test in parallel
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Parse args ---
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode) MODE="$2"; shift 2 ;;
|
||||
--agent) AGENT="$2"; shift 2 ;;
|
||||
--base) BASE="$2"; shift 2 ;;
|
||||
--parallel-tests) TESTS="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Detect state ---
|
||||
STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ')
|
||||
UNSTAGED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
|
||||
UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ')
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")
|
||||
IS_MAIN=false
|
||||
[[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]] && IS_MAIN=true
|
||||
|
||||
HAS_DIRTY=false
|
||||
[[ "$STAGED" -gt 0 || "$UNSTAGED" -gt 0 || "$UNTRACKED" -gt 0 ]] && HAS_DIRTY=true
|
||||
|
||||
# --- Detect agent ---
|
||||
detect_agent() {
|
||||
# Check environment variables / parent process
|
||||
if [[ -n "${ANTIGRAVITY_CLI:-}" ]] || [[ -n "${GEMINI_CLI:-}" ]]; then
|
||||
echo "antigravity"
|
||||
elif [[ -n "${CLAUDE_CODE:-}" ]] || [[ -n "${CLAUDE_CLI:-}" ]]; then
|
||||
echo "claude"
|
||||
elif command -v codex &>/dev/null && [[ -n "${CODEX_CLI:-}" ]]; then
|
||||
echo "codex"
|
||||
else
|
||||
# Default to claude as most common
|
||||
echo "claude"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$AGENT" == "auto" ]]; then
|
||||
DETECTED_AGENT=$(detect_agent)
|
||||
else
|
||||
DETECTED_AGENT="$AGENT"
|
||||
fi
|
||||
|
||||
# --- Resolve review command by agent ---
|
||||
review_cmd_for() {
|
||||
local mode="$1"
|
||||
local diff_file="$2"
|
||||
case "$DETECTED_AGENT" in
|
||||
antigravity)
|
||||
if [[ "$mode" == "local" ]]; then
|
||||
echo "/code-review"
|
||||
else
|
||||
echo "/code-review the changes in $diff_file against $BASE"
|
||||
fi
|
||||
;;
|
||||
codex)
|
||||
if [[ "$mode" == "local" ]]; then
|
||||
echo "codex review"
|
||||
else
|
||||
echo "codex review $diff_file"
|
||||
fi
|
||||
;;
|
||||
claude|*)
|
||||
if [[ "$mode" == "local" ]]; then
|
||||
echo "/review"
|
||||
else
|
||||
echo "/review the changes in $diff_file against $BASE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- Resolve mode ---
|
||||
if [[ "$MODE" == "auto" ]]; then
|
||||
if $HAS_DIRTY; then
|
||||
REVIEW_MODE="local"
|
||||
elif $IS_MAIN; then
|
||||
echo "review-it: on main with clean tree, nothing to review."
|
||||
exit 0
|
||||
else
|
||||
REVIEW_MODE="branch"
|
||||
fi
|
||||
else
|
||||
REVIEW_MODE="$MODE"
|
||||
fi
|
||||
|
||||
# --- Check for PR base ---
|
||||
if [[ "$REVIEW_MODE" == "branch" ]]; then
|
||||
if command -v gh &>/dev/null; then
|
||||
PR_BASE=$(gh pr view --json baseRefName --jq .baseRefName 2>/dev/null || echo "")
|
||||
if [[ -n "$PR_BASE" ]]; then
|
||||
BASE="origin/$PR_BASE"
|
||||
fi
|
||||
fi
|
||||
# Fetch the base to ensure we have it
|
||||
if ! $DRY_RUN; then
|
||||
git fetch origin "$(echo "$BASE" | sed 's|origin/||')" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Build review command ---
|
||||
DIFF_FILE=""
|
||||
case "$REVIEW_MODE" in
|
||||
local)
|
||||
REVIEW_DESC="uncommitted changes"
|
||||
;;
|
||||
branch)
|
||||
DIFF_FILE="/tmp/review-it-$$.diff"
|
||||
REVIEW_DESC="branch $CURRENT_BRANCH vs $BASE"
|
||||
;;
|
||||
*)
|
||||
echo "review-it: unknown mode $REVIEW_MODE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
REVIEW_CMD=$(review_cmd_for "$REVIEW_MODE" "$DIFF_FILE")
|
||||
|
||||
# --- Dry run ---
|
||||
if $DRY_RUN; then
|
||||
echo "mode: $REVIEW_MODE"
|
||||
echo "agent: $DETECTED_AGENT"
|
||||
echo "target: $REVIEW_DESC"
|
||||
echo "review: $REVIEW_CMD"
|
||||
echo "dirty: staged=$STAGED unstaged=$UNSTAGED untracked=$UNTRACKED"
|
||||
if [[ "$REVIEW_MODE" == "branch" ]]; then
|
||||
echo "diff: $DIFF_FILE"
|
||||
echo "base: $BASE"
|
||||
fi
|
||||
if [[ -n "$TESTS" ]]; then
|
||||
echo "tests: $TESTS"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Run ---
|
||||
echo "review-it: mode=$REVIEW_MODE agent=$DETECTED_AGENT target=$REVIEW_DESC"
|
||||
echo ""
|
||||
|
||||
# Generate diff for branch mode
|
||||
if [[ "$REVIEW_MODE" == "branch" ]]; then
|
||||
if ! git diff "$BASE"...HEAD > "$DIFF_FILE" 2>/dev/null; then
|
||||
echo "review-it: failed to generate diff against $BASE"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -s "$DIFF_FILE" ]]; then
|
||||
echo "review-it: no changes between $BASE and HEAD"
|
||||
rm -f "$DIFF_FILE"
|
||||
exit 0
|
||||
fi
|
||||
echo "review-it: generated diff ($(wc -l < "$DIFF_FILE" | tr -d ' ') lines) at $DIFF_FILE"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Run tests in background if requested
|
||||
TEST_PID=""
|
||||
if [[ -n "$TESTS" ]]; then
|
||||
echo "review-it: running tests in background: $TESTS"
|
||||
eval "$TESTS" > /tmp/review-it-tests-$$.log 2>&1 &
|
||||
TEST_PID=$!
|
||||
echo "review-it: test PID=$TEST_PID"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Run review (this is a signal to the user — the actual /review
|
||||
# is executed by Claude Code when it reads this output)
|
||||
cat <<CCEOF
|
||||
review-it: ready for review ($REVIEW_DESC)
|
||||
|
||||
$REVIEW_CMD
|
||||
CCEOF
|
||||
|
||||
# Wait for tests if running
|
||||
if [[ -n "$TEST_PID" ]]; then
|
||||
echo ""
|
||||
echo "review-it: waiting for tests to complete (PID=$TEST_PID)..."
|
||||
wait "$TEST_PID" 2>/dev/null || true
|
||||
TEST_EXIT=$?
|
||||
echo ""
|
||||
echo "--- test output ---"
|
||||
cat /tmp/review-it-tests-$$.log
|
||||
echo "--- end test output ---"
|
||||
rm -f /tmp/review-it-tests-$$.log
|
||||
if [[ $TEST_EXIT -ne 0 ]]; then
|
||||
echo ""
|
||||
echo "review-it: tests FAILED (exit=$TEST_EXIT)"
|
||||
exit $TEST_EXIT
|
||||
else
|
||||
echo ""
|
||||
echo "review-it: tests passed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
if [[ "$REVIEW_MODE" == "branch" ]]; then
|
||||
rm -f "$DIFF_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "review-it: done"
|
||||
@@ -0,0 +1,181 @@
|
||||
---
|
||||
name: ship-it
|
||||
description: Code commit, PR creation, merge, and issue closure workflow via GitHub CLI (gh). Triggers after a goal (GitHub Issue) implementation is complete — commit code, push branch, create PR, merge, then close the issue. Use when the user says "提交代码", "commit and merge", "创建PR", "合入", "关闭issue", "ship-it", or when a goal implementation is done and code needs to be shipped.
|
||||
allowed-tools:
|
||||
- Bash(git:*)
|
||||
- Bash(gh:*)
|
||||
---
|
||||
|
||||
# After-Goal: 代码提交、PR 合入、Issue 关闭工作流(GitHub)
|
||||
|
||||
完成 GitHub Issue 实现后的标准收尾流程:提交代码 → 推送分支 → 创建 PR → 合入 → 关闭 Issue。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 当前 git 仓库有已实现的代码变更
|
||||
- 已知 Issue 编号(如 `#42`)
|
||||
- gh CLI 已登录(`gh auth status` 可验证)
|
||||
|
||||
## 工作流
|
||||
|
||||
### Step 1: 提交代码
|
||||
|
||||
```bash
|
||||
# 1a. 检查变更状态
|
||||
git status
|
||||
git diff --stat HEAD
|
||||
|
||||
# 1b. 暂存本次 Issue 相关的文件(不要 add 不相关的文件)
|
||||
git add <files related to this issue>
|
||||
|
||||
# 1c. 提交,commit message 关联 Issue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
{简要描述} (#issue-number)
|
||||
|
||||
{可选的详细说明}
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**关键规则:**
|
||||
- commit message 中包含 `#issue-number` 以关联 Issue
|
||||
- 只暂存当前 Issue 相关的文件,不要混入其他变更
|
||||
|
||||
### Step 2: 推送分支
|
||||
|
||||
```bash
|
||||
# 如果还在 main/master 上,先创建功能分支
|
||||
git checkout -b {branch-name} # 如已在功能分支则跳过
|
||||
|
||||
# 推送到远程
|
||||
git push -u origin {branch-name}
|
||||
```
|
||||
|
||||
分支命名建议:`feat/issue-42-short-desc` 或 `fix/issue-42-short-desc`
|
||||
|
||||
### Step 3: 创建 PR
|
||||
|
||||
```bash
|
||||
gh pr create \
|
||||
--title "{简要描述}" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
- 实现内容概述
|
||||
|
||||
Closes #{issue-number}
|
||||
|
||||
## Test plan
|
||||
- [ ] 测试项 1
|
||||
- [ ] 测试项 2
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**关键规则:**
|
||||
- PR body 中写 `Closes #N` 或 `Fixes #N`,合入后 GitHub 自动关闭 Issue
|
||||
- title 简洁,不超过 70 字符
|
||||
|
||||
### Step 4: 合入 PR
|
||||
|
||||
```bash
|
||||
# 4a. 查看 PR 状态(确认 checks 通过)
|
||||
gh pr checks
|
||||
|
||||
# 4b. 合入(默认 merge commit,可选 --squash 或 --rebase)
|
||||
gh pr merge --squash --delete-branch
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
- `--squash`: 压缩为单个 commit 合入(推荐)
|
||||
- `--rebase`: rebase 合入
|
||||
- `--merge`: 普通 merge commit
|
||||
- `--delete-branch`: 合入后删除远程分支
|
||||
|
||||
### Step 5: 添加实现总结评论
|
||||
|
||||
PR 合入后,始终在 Issue 上添加实现总结评论,方便后续直接从 Issue 回溯代码变更。
|
||||
|
||||
```bash
|
||||
gh issue comment {issue-number} --body "$(cat <<'EOF'
|
||||
## 实现总结
|
||||
- **核心变更**:{从 PR body 提取的实现摘要}
|
||||
- **PR**: #{pr-number}
|
||||
- **Commit**: {hash}
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**关键规则:**
|
||||
- 无论是 auto-close 还是手动 close,都必须添加此评论
|
||||
- 评论内容从 PR body 的 Summary 部分提取,保持简洁(3-5 条 bullet)
|
||||
- 附加 PR 编号和 commit hash,方便直接跳转
|
||||
|
||||
### Step 6: 手动关闭 Issue(仅当未自动关闭时)
|
||||
|
||||
如果 PR body 中已写 `Closes #N`,合入后 Issue 会自动关闭,跳过此步。否则手动关闭:
|
||||
|
||||
```bash
|
||||
gh issue close {issue-number} --reason completed
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| `gh pr checks` 有失败项 | 查看失败原因,修复后追加 commit 推送 |
|
||||
| PR 有 merge conflict | `git fetch origin main && git rebase origin/main`,解决冲突后 force push |
|
||||
| `gh pr merge` 被 branch protection 阻止 | 确认 required reviews 已满足,或请 reviewer approve |
|
||||
| Issue 合入后未自动关闭 | 确认 PR body 包含 `Closes #N`,或执行 Step 6 手动 `gh issue close` |
|
||||
|
||||
## 完整示例
|
||||
|
||||
```bash
|
||||
# 创建分支并提交
|
||||
git checkout -b feat/issue-42-case-model
|
||||
git add cases/case.go cases/case_test.go
|
||||
git commit -m "$(cat <<'EOF'
|
||||
Add Case data model and Markdown read/write (#42)
|
||||
|
||||
Define Case struct with YAML frontmatter + Markdown body
|
||||
serialization. Provide WriteCase/ReadCase/ListCases/UpdateCase.
|
||||
EOF
|
||||
)"
|
||||
|
||||
# 推送
|
||||
git push -u origin feat/issue-42-case-model
|
||||
|
||||
# 创建 PR
|
||||
gh pr create \
|
||||
--title "Add Case data model and Markdown read/write" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
- Define Case struct with YAML frontmatter + Markdown body
|
||||
- Implement WriteCase/ReadCase/ListCases/UpdateCase functions
|
||||
- Add comprehensive test coverage
|
||||
|
||||
Closes #42
|
||||
|
||||
## Test plan
|
||||
- [x] Unit tests pass
|
||||
- [x] go vet / lint clean
|
||||
EOF
|
||||
)"
|
||||
|
||||
# 确认 checks 通过后合入
|
||||
gh pr checks
|
||||
gh pr merge --squash --delete-branch
|
||||
|
||||
# 添加实现总结评论
|
||||
gh issue comment 42 --body "$(cat <<'EOF'
|
||||
## 实现总结
|
||||
- **核心变更**:Define Case struct with YAML frontmatter + Markdown body
|
||||
- **核心变更**:Implement WriteCase/ReadCase/ListCases/UpdateCase
|
||||
- **PR**: #43
|
||||
- **Commit**: abc1234
|
||||
EOF
|
||||
)"
|
||||
|
||||
# 切回主分支
|
||||
git checkout main
|
||||
git pull
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
# Smell — Architecture Bad Smell Detector
|
||||
|
||||
Analyze a codebase to find violations of software architecture principles, anti-patterns, and code "bad smells." Produces a comprehensive, actionable markdown report.
|
||||
|
||||
## Features
|
||||
|
||||
- Scans project structure for architectural anti-patterns (Big Ball of Mud, Distributed Monolith, etc.)
|
||||
- Detects coupling and cohesion issues (God Objects, circular dependencies, feature envy)
|
||||
- Identifies design principle violations (SOLID, DRY, KISS, YAGNI)
|
||||
- Finds code-level smells (Long Method, Primitive Obsession, Magic Numbers)
|
||||
- Assesses testing health (missing tests, test-implementation coupling)
|
||||
- Outputs a structured markdown report with severity levels and refactoring roadmap
|
||||
|
||||
## Knowledge Base
|
||||
|
||||
Built on architectural knowledge from:
|
||||
- [awesome-software-architecture](https://github.com/mehdihadeli/awesome-software-architecture)
|
||||
- Big Ball of Mud (Foote & Yoder, 1997)
|
||||
- Clean Architecture, Onion Architecture, Hexagonal Architecture
|
||||
- Domain-Driven Design, CQRS, Event-Driven Architecture
|
||||
- SOLID, DRY, KISS, YAGNI, GRASP principles
|
||||
|
||||
## Usage
|
||||
|
||||
Trigger with prompts like:
|
||||
|
||||
- "smell" or "/smell"
|
||||
- "find code smells"
|
||||
- "detect architecture anti-patterns"
|
||||
- "analyze architecture quality"
|
||||
- "找出坏味道"
|
||||
- "架构坏味道"
|
||||
- "代码坏味道检测"
|
||||
- "反模式分析"
|
||||
|
||||
## Files
|
||||
|
||||
- `SKILL.md` — Skill definition and comprehensive anti-pattern knowledge base
|
||||
- `test-prompts.json` — Test prompts for validation
|
||||
@@ -0,0 +1,690 @@
|
||||
---
|
||||
name: smell
|
||||
description: "Detect software architecture bad smells, algorithmic complexity hotspots, and anti-patterns in a codebase. Produces a detailed markdown report identifying violations of architectural principles, design patterns, code quality, and performance complexity. Triggers on: smell, code smell, architecture smell, find anti-patterns, detect bad smells, complexity analysis, 代码坏味道, 架构坏味道, 反模式, 找出坏味道, 复杂度分析."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Smell — Architecture Bad Smell Detector
|
||||
|
||||
Analyze a codebase to find violations of software architecture principles, anti-patterns, code "bad smells," and algorithmic complexity hotspots. Produce a comprehensive, actionable markdown report.
|
||||
|
||||
**Knowledge base:** This skill encodes architectural patterns, anti-patterns, code smells, and algorithmic complexity heuristics drawn from industry research and practice, including the classic code smells catalog by Martin Fowler / Kent Beck (as organized on refactoring.guru: Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables, Couplers).
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. Understand the scope — ask what part of the project to analyze (full project, specific module, or recent changes)
|
||||
2. Scan the codebase using `find`, `grep`, and `Agent` (Explore subagent) to gather evidence
|
||||
3. Identify architectural smells and anti-patterns
|
||||
4. Generate a detailed markdown report saved to `tasks/smell-report-[timestamp].md`
|
||||
5. Present a summary of findings to the user
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Scope Clarification
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
What scope should I analyze?
|
||||
A. Entire project (thorough, may take time)
|
||||
B. Specific module/directory: [please specify]
|
||||
C. Only recently changed files (git diff)
|
||||
D. Only architectural-level issues (skip low-level code smells)
|
||||
```
|
||||
|
||||
If the user doesn't specify, default to option A for small projects (< 100 files) or C for large projects.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Evidence Gathering
|
||||
|
||||
**Use the Explore subagent** (`Agent` with `subagent_type: "Explore"`) to scan the codebase for architectural patterns and anti-patterns. Run multiple parallel explorations:
|
||||
|
||||
### Exploration Commands
|
||||
|
||||
Run these in parallel to gather evidence efficiently:
|
||||
|
||||
1. **Project Structure Scan:** Map the directory tree, identify the architectural style (layered, modular monolith, microservices, etc.)
|
||||
2. **Dependency Analysis:** Find import/include patterns, check for circular dependencies, identify coupling hotspots
|
||||
3. **Module/Component Scan:** Identify God Objects (files > 500 lines), check cohesion, check single responsibility violations
|
||||
4. **Pattern Detection:** Look for known anti-pattern signatures (static cling, service locator abuse, leaky abstractions)
|
||||
5. **Testing Scan:** Check test coverage patterns, test file locations, test-to-code ratios
|
||||
6. **Naming & Clarity Scan:** Flag misleading names, overly generic names (Manager, Helper, Util), inconsistent naming conventions
|
||||
7. **Complexity Scan:** Detect algorithmic complexity hotspots — nested loops, N+1 queries, repeated scans, sort-in-loop, expensive recomputation in render paths
|
||||
|
||||
### Key Heuristics
|
||||
|
||||
| Category | Smell | Detection Heuristic |
|
||||
|----------|-------|-------------------|
|
||||
| **Architecture** | Big Ball of Mud | No clear directory structure; everything in root or one flat folder; no separation of concerns |
|
||||
| **Architecture** | Violated Layer Boundaries | Inner layers importing outer layers; infrastructure code in domain/core layer |
|
||||
| **Architecture** | Missing Architecture | No `src/`, `lib/`, `core/` separation; SQL inline with UI code; HTTP handlers mixed with business logic |
|
||||
| **Architecture** | Distributed Monolith | Microservices sharing a database; services that can't deploy independently |
|
||||
| **Architecture** | Anemic Domain Model | Model/entity classes with only getters/setters and no behavior; all logic in services |
|
||||
| **Architecture** | CQRS Without Need | Separate read/write models for simple CRUD; unnecessary complexity |
|
||||
| **Architecture** | Over-Layered Architecture | Excessive layers/tiers that add pass-through code with no real value |
|
||||
| **Architecture** | Over-Abstraction | So many indirections/interfaces/generics that you get lost following the code |
|
||||
| **Architecture** | Futuristic Architecture | Speculative flexibility for requirements that may never come (predicting the future) |
|
||||
| **Architecture** | Technology-Enthusiast Architecture | Shiny/unproven tech adopted in production because it's new, not because it fits |
|
||||
| **Architecture** | Overkill Architecture | Heavyweight architecture/tech thrown at a simple problem |
|
||||
| **Architecture** | Cloud/Visio Architecture | Diagrams disconnected from the actual code and runtime reality |
|
||||
| **Coupling** | Circular Dependencies | Module A imports B, B imports A; detected via import graph analysis |
|
||||
| **Coupling** | Content Coupling | One module directly accesses another's internal/private members |
|
||||
| **Coupling** | Common Coupling | Excessive global variables/shared mutable state; singleton abuse |
|
||||
| **Coupling** | Stamp Coupling | Passing large data structures when only a few fields are needed |
|
||||
| **Cohesion** | God Object | Single class/module > 500 lines; > 20 public methods; handles unrelated concerns |
|
||||
| **Cohesion** | Shotgun Surgery | A single change requires touching 5+ files across unrelated modules |
|
||||
| **Cohesion** | Feature Envy | Method calls foreign class methods more than its own class methods |
|
||||
| **Cohesion** | Data Clumps | Same group of 3+ parameters appearing together in multiple method signatures |
|
||||
| **Design** | Leaky Abstractions | Implementation details (DB queries, HTTP calls) exposed through interfaces |
|
||||
| **Design** | Static Cling | Excessive use of static methods; static state that prevents testability |
|
||||
| **Design** | Service Locator Abuse | DI container passed around instead of proper constructor injection |
|
||||
| **Design** | Violated SOLID | SRP violations, OCP violations (switch/if-else chains on types), ISP violations (fat interfaces) |
|
||||
| **Design** | Switch Statements | Same `switch`/if-else chain on a type code appearing in multiple places; should be polymorphism |
|
||||
| **Design** | Refused Bequest | Subclass inherits methods/fields it doesn't use or overrides them to throw/no-op |
|
||||
| **Design** | Alternative Classes w/ Different Interfaces | Two classes do the same thing but have differently-named methods |
|
||||
| **Design** | Parallel Inheritance Hierarchies | Creating a subclass in one hierarchy forces a matching subclass in another |
|
||||
| **Design** | Speculative Generality | Unused abstract classes, hooks, params, or generics "for future needs" (YAGNI) |
|
||||
| **Design** | Incomplete Library Class | Wrapping/patching a third-party class because it lacks needed methods |
|
||||
| **Cohesion** | Divergent Change | One module changed for many unrelated reasons (opposite of Shotgun Surgery) |
|
||||
| **Cohesion** | Data Class | Class with only fields + getters/setters, no behavior (anemic data bag) |
|
||||
| **Cohesion** | Lazy Class | Class/module that does too little to justify its existence |
|
||||
| **Coupling** | Inappropriate Intimacy | Two classes access each other's private/internal parts too much |
|
||||
| **Coupling** | Message Chains | Long call chains `a.getB().getC().getD()` (Law of Demeter violation) |
|
||||
| **Coupling** | Middle Man | Class that only delegates every call to another class |
|
||||
| **Code** | Temporary Field | Instance field set/used only in certain circumstances, empty otherwise |
|
||||
| **Code** | Duplicated Code | Identical/similar logic appearing in 3+ places; copy-paste patterns |
|
||||
| **Code** | Long Method | Methods > 50 lines; deep nesting (> 3 levels) |
|
||||
| **Code** | Long Parameter List | Methods with > 4 parameters |
|
||||
| **Code** | Primitive Obsession | Using strings/ints instead of domain types (e.g., `string email` instead of `Email` type) |
|
||||
| **Code** | Magic Numbers/Strings | Hardcoded literals without named constants |
|
||||
| **Code** | Comments as Deodorant | Excessive comments explaining bad code instead of refactoring |
|
||||
| **Code** | Dead Code | Unused imports, unreachable code, commented-out blocks |
|
||||
| **Testing** | No Tests | Modules with zero test coverage |
|
||||
| **Testing** | Test-Implementation Coupling | Tests that assert internal implementation details instead of behavior |
|
||||
| **Testing** | Slow Tests | Tests doing real I/O, database calls, network requests without mocking |
|
||||
| **Naming** | Vague Names | `Manager`, `Handler`, `Processor`, `Helper`, `Util`, `Service`, `Data`, `Info` used excessively without context |
|
||||
| **Naming** | Inconsistent Naming | Snake_case and camelCase mixed; different patterns for same concept |
|
||||
| **Readability** | Deep Nesting (Arrow Anti-Pattern) | Loops/conditionals nested > 3 levels deep; rightward-drifting "arrow" shape hard to trace |
|
||||
| **Complexity** | Nested Loops (O(n^2)+) | Loop inside loop; forEach inside for; map inside map; nested iteration suggesting polynomial complexity |
|
||||
| **Complexity** | Repeated Linear Scan | `includes()`/`indexOf()`/`.find()` inside a loop; O(n*m) membership check on list instead of Set/Map |
|
||||
| **Complexity** | Sort-in-Loop | `.sort()` or `sorted()` called inside iterative code; repeated O(n log n) when sort-once suffices |
|
||||
| **Complexity** | N+1 Query Pattern | Database/API/HTTP call inside a loop; `fetch`/`query`/`execute`/`findMany` per iteration instead of batch |
|
||||
| **Complexity** | Render-Path Recompute | `.filter().map().sort()` chains in component render body; expensive transforms without memoization |
|
||||
| **Complexity** | Pairwise Comparison | Nested iteration comparing every element with every other; O(n^2) when sort+two-pointer would be O(n log n) |
|
||||
| **Complexity** | Unnecessary Recompute | Same expensive computation repeated without caching; missing `useMemo`/`memo`/lazy eval |
|
||||
| **Complexity** | Wrong Data Structure | Array used where Set/Map would give O(1) lookup; List where Queue/Heap/Stack is natural fit |
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Report Generation
|
||||
|
||||
Generate the report in this structure:
|
||||
|
||||
```markdown
|
||||
# Architecture Smell Report
|
||||
|
||||
**Project:** [project-name]
|
||||
**Scope:** [scope description]
|
||||
**Date:** [date]
|
||||
**Analyzer:** smell skill (Ducc)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
[2-3 paragraph summary: architectural style detected, overall health assessment, and top 3-5 critical issues]
|
||||
|
||||
---
|
||||
|
||||
## Architectural Style Detected
|
||||
|
||||
[Identify the architectural style: Layered, Modular Monolith, Microservices, Hexagonal, Clean Architecture, or Big Ball of Mud]
|
||||
|
||||
### Style Expectations vs. Reality
|
||||
|
||||
| Expectation | Reality | Status |
|
||||
|-------------|---------|--------|
|
||||
| [e.g., Clear layer separation] | [what was found] | ✅/⚠️/🔴 |
|
||||
|
||||
---
|
||||
|
||||
## Findings by Category
|
||||
|
||||
### 🔴 Critical Issues (Must Fix)
|
||||
|
||||
[Issues that fundamentally undermine architecture]
|
||||
|
||||
### 🟡 Warnings (Should Fix)
|
||||
|
||||
[Issues that degrade maintainability but don't block function]
|
||||
|
||||
### 🔵 Suggestions (Nice to Fix)
|
||||
|
||||
[Minor improvements that would increase quality]
|
||||
|
||||
---
|
||||
|
||||
## Detailed Findings
|
||||
|
||||
### Finding #1: [Title]
|
||||
|
||||
- **Category:** [Architecture/Coupling/Cohesion/Design/Code/Testing/Naming/Complexity]
|
||||
- **Severity:** 🔴 Critical / 🟡 Warning / 🔵 Suggestion
|
||||
- **Anti-Pattern:** [Name of anti-pattern]
|
||||
- **Location:** [file:line references]
|
||||
- **Principle Violated:** [SOLID/DRY/KISS/etc.]
|
||||
- **Description:** [What was found and why it's a problem]
|
||||
- **Evidence:** [Code snippet or structure description]
|
||||
- **Recommendation:** [Specific fix, with refactoring approach]
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph Analysis
|
||||
|
||||
[Summary of module dependencies, circular dependencies found, coupling hotspots]
|
||||
|
||||
---
|
||||
|
||||
## Module Health Scorecard
|
||||
|
||||
| Module | Lines | God Object Risk | Coupling | Cohesion | Test Coverage | Health |
|
||||
|--------|-------|----------------|----------|----------|---------------|--------|
|
||||
| [name] | [N] | [Low/Med/High] | [Low/Med/High] | [Low/Med/High] | [% or N/A] | 🟢/🟡/🔴 |
|
||||
|
||||
---
|
||||
|
||||
## Smell Distribution
|
||||
|
||||
| Category | Count | Critical | Warning | Suggestion |
|
||||
|----------|-------|----------|---------|------------|
|
||||
| Architecture | [N] | [N] | [N] | [N] |
|
||||
| Coupling | [N] | [N] | [N] | [N] |
|
||||
| Cohesion | [N] | [N] | [N] | [N] |
|
||||
| Design | [N] | [N] | [N] | [N] |
|
||||
| Code | [N] | [N] | [N] | [N] |
|
||||
| Testing | [N] | [N] | [N] | [N] |
|
||||
| Naming | [N] | [N] | [N] | [N] |
|
||||
| Complexity | [N] | [N] | [N] | [N] |
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Roadmap
|
||||
|
||||
### Immediate Actions (This Sprint)
|
||||
1. [Actionable fix 1]
|
||||
2. [Actionable fix 2]
|
||||
|
||||
### Short-Term (1-3 Months)
|
||||
1. [Structural improvement 1]
|
||||
2. [Structural improvement 2]
|
||||
|
||||
### Long-Term (3-12 Months)
|
||||
1. [Architectural transformation 1]
|
||||
2. [Architectural transformation 2]
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Anti-Pattern Reference
|
||||
|
||||
[A condensed reference of anti-patterns checked, with brief descriptions]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Save and Present
|
||||
|
||||
Save the report to `tasks/smell-report-[YYYY-MM-DD-HHmm].md` and present a brief summary to the user.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Pattern Knowledge Base
|
||||
|
||||
This section documents the architectural anti-patterns and bad smells the skill knows about.
|
||||
|
||||
### Architectural Anti-Patterns
|
||||
|
||||
#### Big Ball of Mud
|
||||
The most common de-facto architecture. A haphazardly structured, sprawling system with no perceivable architecture. Characterized by:
|
||||
- Promiscuous sharing of information between distant elements
|
||||
- Global or duplicated important state
|
||||
- Structure eroded beyond recognition or never defined
|
||||
- Repeated expedient repair ("duct tape and bailing wire")
|
||||
- Forces: Time pressure, cost, inexperience, complexity, change, scale
|
||||
- **Remedy:** Define architecture boundaries, refactor incrementally, apply SHEARING LAYERS, KEEP IT WORKING
|
||||
|
||||
#### Distributed Monolith
|
||||
Microservices that must be deployed together. Symptoms:
|
||||
- Services share a database
|
||||
- Synchronous chains of service calls
|
||||
- Changes require coordinated deployments
|
||||
- **Remedy:** Decouple data stores, introduce async messaging, enforce bounded contexts
|
||||
|
||||
#### Anemic Domain Model
|
||||
Domain objects with only getters/setters (data bags), all logic in services. Violates:
|
||||
- "Tell, Don't Ask" principle
|
||||
- Rich Domain Model pattern from DDD
|
||||
- **Remedy:** Move behavior into domain objects, use domain services only for cross-aggregate operations
|
||||
|
||||
#### God Object
|
||||
A class that knows too much or does too much. Characteristics:
|
||||
- > 500 lines or > 20 public methods
|
||||
- Handles unrelated concerns
|
||||
- Difficult to test in isolation
|
||||
- Single Responsibility Principle violation
|
||||
- **Remedy:** Extract cohesive groups of methods into dedicated classes
|
||||
|
||||
#### Leaky Abstractions
|
||||
Abstractions that expose implementation details. Signs:
|
||||
- Interface methods named after implementation (e.g., `SaveToPostgres`, `FetchFromRedis`)
|
||||
- Consumers catching implementation-specific exceptions
|
||||
- Configuration details exposed through abstractions
|
||||
- **Remedy:** Design interfaces from the consumer's perspective, hide implementation details
|
||||
|
||||
#### Static Cling
|
||||
Excessive use of static methods/state. Problems:
|
||||
- Untestable (can't mock static calls)
|
||||
- Hidden dependencies
|
||||
- Thread-safety issues with static state
|
||||
- **Remedy:** Use dependency injection, convert stateless statics to instance methods
|
||||
|
||||
#### Service Locator Abuse
|
||||
Using a service locator instead of dependency injection. Issues:
|
||||
- Hidden dependencies (dependencies not visible in constructor)
|
||||
- Runtime errors instead of compile-time errors
|
||||
- Testing difficulty
|
||||
- **Remedy:** Use constructor injection, register dependencies at composition root
|
||||
|
||||
#### Violated Layer Boundaries (Clean/Onion/Hexagonal Architecture)
|
||||
In layered architectures:
|
||||
- **Clean Architecture:** Outer layers (frameworks) leaking into inner layers (use cases, entities)
|
||||
- **Onion Architecture:** Infrastructure concerns in domain core
|
||||
- **Hexagonal Architecture:** Business logic coupled to specific adapters instead of ports
|
||||
- **Remedy:** Apply dependency inversion, define clear port interfaces
|
||||
|
||||
#### CQRS Overuse
|
||||
Applying CQRS to simple CRUD. Signs:
|
||||
- Separate read/write models for trivial data access
|
||||
- Event sourcing when events don't add business value
|
||||
- Unnecessary complexity
|
||||
- **Remedy:** Use CQRS only when read/write models genuinely differ or have different scaling needs
|
||||
|
||||
#### Vertical Slice Contamination
|
||||
In Vertical Slice Architecture:
|
||||
- Cross-slice coupling (one feature directly calling another)
|
||||
- Shared service classes undermining slice independence
|
||||
- **Remedy:** Use events/messages for cross-slice communication, duplicate simple logic if needed
|
||||
|
||||
### Top Ten Software Architecture Mistakes
|
||||
|
||||
A set of architecture-level anti-patterns describing over- and under-engineering. The common thread: **architecture disconnected from real needs and reality.** The opposite extreme (too little architecture) is equally a smell.
|
||||
|
||||
#### Over-Layered / Multitier Architecture
|
||||
"Layers on layers on layers." Adding tiers beyond what the problem needs:
|
||||
- Each layer just forwards calls to the next with no transformation or value
|
||||
- Simple read requires touching 6+ classes across 4 layers
|
||||
- **Remedy:** Collapse pass-through layers; keep only layers that carry real responsibility
|
||||
|
||||
#### Over-Abstraction
|
||||
Abstraction piled on until the code is impossible to follow:
|
||||
- Excessive interfaces, generics, factories, and indirection for single implementations
|
||||
- You can't tell what actually runs without stepping through many hops
|
||||
- **Remedy:** Inline single-implementation abstractions; abstract only at real variation points (rule of three)
|
||||
|
||||
#### Futuristic Architecture
|
||||
Solution built for imagined future requirements that no one can actually predict:
|
||||
- Extensibility points, plugin systems, config knobs nothing uses
|
||||
- Most speculative flexibility is wasted effort — closely related to Speculative Generality and YAGNI
|
||||
- **Remedy:** Build for today's known requirements; add flexibility when a real second case arrives
|
||||
|
||||
#### Technology-Enthusiast Architecture
|
||||
New/shiny technology put into production because the architect liked it:
|
||||
- Unproven tech adopted without validating it fits the problem or scales
|
||||
- Chasing trends over stability
|
||||
- **Remedy:** Evaluate tech against actual requirements; prefer proven tools; prototype before committing
|
||||
|
||||
#### Overkill Architecture
|
||||
A simple problem solved with a disproportionate amount of architecture and technology:
|
||||
- Microservices, event sourcing, k8s for a CRUD app with a handful of users
|
||||
- **Remedy:** Match architecture weight to problem size (KISS); start simple, evolve when justified
|
||||
|
||||
#### Cloud / Visio Architecture
|
||||
"Architecture" that exists only in nice diagrams, disconnected from the code and runtime reality:
|
||||
- Diagrams don't match what's actually deployed; boxes and arrows with no code correspondence
|
||||
- **Remedy:** Keep architecture docs grounded in and verified against the real system
|
||||
|
||||
> **Note on the opposite extreme:** total *lack* of architecture (no boundaries, no structure) is equally a smell — see [Big Ball of Mud](#big-ball-of-mud) and Missing Architecture. Both under- and over-engineering are failures.
|
||||
|
||||
### Coupling & Cohesion Smells
|
||||
|
||||
#### Circular Dependencies
|
||||
Module A → Module B → Module A. Detected via:
|
||||
- Import graph analysis
|
||||
- "Cannot access before initialization" errors
|
||||
- **Remedy:** Extract shared interface/common module, apply dependency inversion
|
||||
|
||||
#### Content Coupling
|
||||
One module directly modifying another's internal state. Signs:
|
||||
- Direct field access across module boundaries
|
||||
- `friend`/package-private abuse
|
||||
- **Remedy:** Use public APIs, encapsulate internal state
|
||||
|
||||
#### Common Coupling (Global State)
|
||||
Multiple modules depending on shared global mutable state:
|
||||
- Global variables, singletons with mutable state
|
||||
- Ambient context (e.g., `CurrentUser` static property)
|
||||
- **Remedy:** Parameterize, use dependency injection, make state explicit
|
||||
|
||||
#### Stamp Coupling
|
||||
Passing entire data structures when only a few fields needed:
|
||||
- Functions receiving large DTOs but using one field
|
||||
- **Remedy:** Create focused parameters or smaller interfaces (ISP)
|
||||
|
||||
#### Shotgun Surgery
|
||||
A single change requires modifications across many files:
|
||||
- Adding a field touches 5+ files in different modules
|
||||
- **Remedy:** Consolidate related behavior, apply Single Responsibility
|
||||
|
||||
#### Feature Envy
|
||||
A method that uses another class's methods more than its own:
|
||||
- Method calls `other.foo()`, `other.bar()`, `other.baz()` with few self-calls
|
||||
- **Remedy:** Move the method to the class it envies
|
||||
|
||||
#### Data Clumps
|
||||
Same group of fields appearing together in multiple places:
|
||||
- `(street, city, zip)` appearing in 5 method signatures
|
||||
- **Remedy:** Extract into a value object
|
||||
|
||||
#### Divergent Change
|
||||
One module/class is repeatedly changed for many *unrelated* reasons (the opposite of Shotgun Surgery):
|
||||
- "I always change these three methods for DB changes, and those two for UI changes" in the same class
|
||||
- **Remedy:** Split the class along its axes of change (Single Responsibility)
|
||||
|
||||
#### Inappropriate Intimacy
|
||||
Two classes are too entangled with each other's internals:
|
||||
- Reaching into another class's private fields, tight bidirectional references
|
||||
- **Remedy:** Move methods/fields to the class they belong to, extract a shared class, or replace with delegation
|
||||
|
||||
#### Message Chains
|
||||
Long navigation chains like `a.getB().getC().getD().doThing()`:
|
||||
- Client coupled to the whole object graph; violates the Law of Demeter
|
||||
- **Remedy:** Hide delegation — add a method on the first object that returns what the client needs
|
||||
|
||||
#### Middle Man
|
||||
A class that delegates almost all of its work to another class:
|
||||
- Most methods just forward calls; adds indirection without value
|
||||
- **Remedy:** Remove the middle man and let clients talk to the real object (inline the delegation)
|
||||
|
||||
#### Parallel Inheritance Hierarchies
|
||||
Every time you add a subclass to one hierarchy, you must add one to another:
|
||||
- `Shape`/`ShapeRenderer`, `Employee`/`EmployeePermission` growing in lockstep
|
||||
- **Remedy:** Merge hierarchies or make one hierarchy reference the other instead of mirroring it
|
||||
|
||||
### Code-Level Smells
|
||||
|
||||
#### Long Method
|
||||
- Methods > 50 lines (or whatever suits the language)
|
||||
- Deep nesting > 3 levels
|
||||
- Multiple levels of abstraction mixed
|
||||
- **Remedy:** Extract methods at same abstraction level, compose
|
||||
|
||||
#### Long Parameter List
|
||||
- Methods with > 4 parameters
|
||||
- Boolean flags controlling behavior
|
||||
- **Remedy:** Introduce parameter object, split method, remove flag arguments
|
||||
|
||||
#### Duplicated Code
|
||||
- Identical or near-identical logic in 3+ places
|
||||
- Copy-paste with slight variations
|
||||
- **Remedy:** Extract shared method, apply Template Method or Strategy pattern
|
||||
|
||||
#### Primitive Obsession
|
||||
Using primitives instead of domain types:
|
||||
- `string` for Email, PhoneNumber, URL
|
||||
- `int` for Money, Age, Quantity
|
||||
- `decimal` without Currency context
|
||||
- **Remedy:** Create value objects with validation and behavior
|
||||
|
||||
#### Magic Numbers/Strings
|
||||
- Hardcoded literals without explanation
|
||||
- `if (status == 3)` instead of `if (status == Status.COMPLETED)`
|
||||
- **Remedy:** Extract named constants or enums
|
||||
|
||||
#### Comments as Deodorant
|
||||
- Comments that explain what code does (code should be self-documenting)
|
||||
- Commented-out code blocks
|
||||
- "TODO" comments accumulating without resolution
|
||||
- **Remedy:** Refactor to make code clear, delete dead code, track TODOs as issues
|
||||
|
||||
#### Deep Nesting (Arrow Anti-Pattern)
|
||||
Loops and conditionals nested so deeply the code drifts rightward into an "arrow" shape:
|
||||
- `if { if { for { if { ... } } } }` — hard to trace which conditions hold at any point
|
||||
- Usually > 3 levels of indentation in one function
|
||||
- **Remedy:** Guard clauses / early returns, extract nested blocks into methods, invert conditions, replace conditional with polymorphism
|
||||
|
||||
#### Dead Code
|
||||
- Unused imports, variables, functions
|
||||
- Unreachable branches
|
||||
- Commented-out code in version control
|
||||
- **Remedy:** Delete it (git history preserves it if needed)
|
||||
|
||||
#### Data Class
|
||||
A class that is only fields plus getters/setters, with no meaningful behavior:
|
||||
- A "data bag" other classes reach into and manipulate from outside
|
||||
- Closely related to Anemic Domain Model at the class level
|
||||
- **Remedy:** Move the behavior that operates on the data into the class ("Tell, Don't Ask")
|
||||
|
||||
#### Lazy Class
|
||||
A class/module that no longer does enough to justify its existence:
|
||||
- Left over after refactoring, or an abstraction that never grew
|
||||
- **Remedy:** Inline it into its caller or collapse the hierarchy
|
||||
|
||||
#### Speculative Generality
|
||||
Abstractions, hooks, parameters, or generics added for hypothetical future needs:
|
||||
- Unused abstract base classes, unused parameters, "just in case" configuration
|
||||
- Violates YAGNI
|
||||
- **Remedy:** Remove unused abstraction; add it when a real second use case appears
|
||||
|
||||
#### Temporary Field
|
||||
An instance field that is only set/used in certain circumstances and empty otherwise:
|
||||
- Fields populated only during one algorithm, confusing readers the rest of the time
|
||||
- **Remedy:** Extract the field + the methods that use it into their own class (Extract Class / introduce a Method Object)
|
||||
|
||||
### Testing Smells
|
||||
|
||||
#### No Tests
|
||||
- Modules with zero test coverage
|
||||
- Business logic without unit tests
|
||||
- **Remedy:** Write characterization tests first, then add behavior tests
|
||||
|
||||
#### Test-Implementation Coupling
|
||||
- Tests asserting internal method calls, private state, or implementation details
|
||||
- Tests breaking on refactoring without behavior changes
|
||||
- **Remedy:** Test through public APIs, assert behavior not implementation
|
||||
|
||||
#### Test Environment Dependency
|
||||
- Tests depending on file system, network, database, system clock without mocking
|
||||
- Non-deterministic tests (flaky tests)
|
||||
- **Remedy:** Use test doubles, control environment, use DI
|
||||
|
||||
### Complexity Smells (Algorithmic Anti-Patterns)
|
||||
|
||||
Complexity smells indicate code whose runtime grows inefficiently with input size. These are not mere "micro-optimizations" — they are algorithmic choices that cause real performance degradation at scale.
|
||||
|
||||
#### Nested Loops (O(n^2) and Worse)
|
||||
Two or more loops nested inside each other, producing polynomial complexity.
|
||||
- **Detection:** `for`/`while` inside another `for`/`while`; `forEach`/`map` inside `forEach`/`map`; loop containing another loop (any depth)
|
||||
- **Impact:** O(n^2) for double-nested, O(n^3) for triple; explodes with moderate data sizes
|
||||
- **Remedy:**
|
||||
- Build a Map/Set index for the inner collection → O(n+m)
|
||||
- Sort + two-pointer approach → O(n log n)
|
||||
- Group/bucket data before iterating
|
||||
- Sweep-line for interval/range problems
|
||||
- **Correctness checks:** Does order matter? Are there duplicate keys? Is the original picking first/last/all matches?
|
||||
|
||||
#### N+1 Query Pattern
|
||||
A database query, API call, or I/O operation inside a loop body.
|
||||
- **Detection:** `fetch()`/`axios()`/`query()`/`execute()`/`findMany()`/`findOne()`/`findUnique()`/`select()`/`where()` inside any loop construct
|
||||
- **Impact:** 1 + N round-trips instead of 1; network latency multiplied by item count
|
||||
- **Remedy:**
|
||||
- Batch fetch by IDs: `SELECT * FROM x WHERE id IN (...)` then join in memory
|
||||
- Use ORM eager-loading / `include` / `preload` / DataLoader
|
||||
- Bulk API endpoints accepting arrays
|
||||
- Preserve: auth filters, tenancy isolation, ordering, pagination, error semantics
|
||||
- **Correctness checks:** Don't fetch records the original per-item logic wouldn't authorize; preserve missing-record behavior
|
||||
|
||||
#### Repeated Linear Scan (Missing Index)
|
||||
Linear search (`includes`, `indexOf`, `.find`, `in_array`) inside a loop, where a Set/Map would give O(1) lookup.
|
||||
- **Detection:** `.includes()` / `.indexOf()` / `.find()` / `.findIndex()` / `in_array()` / `contains()` inside a loop body
|
||||
- **Impact:** O(n*m) instead of O(n+m) — each iteration scans the entire collection
|
||||
- **Remedy:** Build a `Set` (for membership) or `Map` (for key→value lookup) once before the loop
|
||||
- **Correctness checks:** Does equality semantics change after Set conversion? JavaScript object identity vs. value equality; Python hashability
|
||||
|
||||
#### Sort-in-Loop
|
||||
Sorting inside a loop body, repeating O(n log n) work unnecessarily.
|
||||
- **Detection:** `.sort()` / `sorted()` / `sort()` inside any iterative block
|
||||
- **Impact:** O(k * n log n) instead of O(n log n) — sort repeated k times
|
||||
- **Remedy:**
|
||||
- Sort once outside the loop
|
||||
- Maintain a heap (PriorityQueue) if incremental top-K is needed
|
||||
- Use binary search/insertion into sorted collection
|
||||
- **Correctness checks:** Is each intermediate sorted state externally observable? Does comparator depend on loop-local state?
|
||||
|
||||
#### Render-Path Recompute (UI Complexity)
|
||||
Expensive data transformation (filter→map→sort chains) inside UI component render bodies, recomputed on every render.
|
||||
- **Detection:** `.filter().map().sort().reduce()` chains inside React/Vue/Svelte component function bodies; inside `function Component()` or `const Component = () =>` in JSX/TSX
|
||||
- **Impact:** Re-derivation on every state change even if inputs unchanged; jank with large collections
|
||||
- **Remedy:**
|
||||
- `useMemo` / `computed` / `derived` with correct dependency arrays
|
||||
- Move derivation to selectors, loaders, or server-side
|
||||
- Virtualize long lists (windowing)
|
||||
- Stabilize callbacks and object props only when child renders are affected
|
||||
- **Correctness checks:** Dependency arrays must include every semantic input; memoization must not hide mutations of mutable inputs
|
||||
|
||||
#### Pairwise Comparison
|
||||
Comparing every element with every other element using double-nested iteration.
|
||||
- **Detection:** Two nested loops iterating the same or similar collections, comparing pairs
|
||||
- **Impact:** O(n^2) for pair matching, overlap detection, conflict checking, nearest-neighbor
|
||||
- **Remedy:**
|
||||
- Sort + two-pointer for pair/range matching
|
||||
- Sweep-line for interval overlaps
|
||||
- Spatial hashing or grid bucketing for proximity
|
||||
- Union-find for connectivity
|
||||
- **Correctness checks:** Order stability; tie-breaking in equality cases
|
||||
|
||||
#### Unnecessary Recompute (Missing Memoization)
|
||||
Same pure computation repeated with same inputs without caching.
|
||||
- **Detection:** Identical function calls with same arguments in hot paths; repeated expensive transforms; recursive calls without memoization
|
||||
- **Impact:** Linear/polynomial wasted work; especially bad with recursive Fibonacci-style patterns (O(2^n) → O(n) with memo)
|
||||
- **Remedy:** Add memoization/caching with proper invalidation; use `lru_cache`/`memoize`/`useMemo` as appropriate
|
||||
|
||||
#### Wrong Data Structure
|
||||
Using a suboptimal data structure for the access pattern.
|
||||
- **Detection:**
|
||||
- Array/List used for frequent membership tests → should be Set
|
||||
- Array/List used for key-value lookups → should be Map/Object
|
||||
- Array used as queue with `shift()`/`pop(0)` (O(n) per dequeue) → should use proper Queue
|
||||
- Sorted insertion into array (O(n) per insert) → should use Heap
|
||||
- **Remedy:** Replace with the data structure whose complexity matches the access pattern:
|
||||
- Set → O(1) has/add/delete
|
||||
- Map → O(1) get/set
|
||||
- Heap → O(log n) push/pop for priority
|
||||
- Queue/Deque → O(1) enqueue/dequeue
|
||||
|
||||
#### What NOT to Flag
|
||||
- **Cold paths:** Complexity that only runs on startup, config loading, or tiny N (< 100) is rarely worth fixing
|
||||
- **Intentional tradeoffs:** Clear, readable O(n) code where O(n log n) would add complexity with no measurable gain
|
||||
- **Already optimized:** Map/Set already in use; batch loading already implemented; memoization already present
|
||||
|
||||
### Design Principle Violations
|
||||
|
||||
#### SOLID Violations Checklist
|
||||
- **S (SRP):** Class/module has multiple reasons to change → God Object smell
|
||||
- **O (OCP):** switch/if-else chains on type codes → Strategy/Polymorphism needed
|
||||
- **L (LSP):** Subclass changes behavior of base class unexpectedly → Check pre/post conditions
|
||||
- **I (ISP):** Fat interfaces with methods clients don't use → Split interfaces
|
||||
- **D (DIP):** High-level modules depending on low-level details → Introduce abstractions
|
||||
|
||||
#### Other Principle Violations
|
||||
- **DRY Violation:** Same knowledge repeated in multiple places
|
||||
- **KISS Violation:** Over-engineered solutions; premature abstractions
|
||||
- **YAGNI Violation:** Code for hypothetical future requirements; unused abstractions
|
||||
|
||||
#### Object-Orientation Abusers (from Fowler / refactoring.guru)
|
||||
|
||||
##### Switch Statements (Type-Code Conditionals)
|
||||
Repeated `switch`/if-else chains that branch on a type code or enum:
|
||||
- The same conditional structure duplicated in several places
|
||||
- Adding a new type forces editing every switch (OCP violation)
|
||||
- **Remedy:** Replace conditional with polymorphism (Strategy/State), or Replace Type Code with Subclasses
|
||||
|
||||
##### Refused Bequest
|
||||
A subclass inherits methods/fields it doesn't need:
|
||||
- Overrides inherited methods to throw, no-op, or do something unrelated
|
||||
- Signals the inheritance relationship is wrong
|
||||
- **Remedy:** Push down unused members, or replace inheritance with delegation
|
||||
|
||||
##### Alternative Classes with Different Interfaces
|
||||
Two classes perform the same role but expose differently-named methods:
|
||||
- `sort()` vs `arrange()`, `getUser()` vs `fetchUser()` for interchangeable classes
|
||||
- **Remedy:** Unify the interface (rename methods, extract a common superclass/interface)
|
||||
|
||||
##### Incomplete Library Class
|
||||
A third-party/library class lacks methods you need and can't be modified:
|
||||
- Scattered helper functions or copy-paste wrappers around the library
|
||||
- **Remedy:** Introduce a Foreign Method or wrap it in an adapter/local extension class
|
||||
|
||||
> Other refactoring.guru smells are documented in their thematic sections above:
|
||||
> **Divergent Change**, **Data Class**, **Lazy Class**, **Speculative Generality**,
|
||||
> **Temporary Field**, **Parallel Inheritance Hierarchies**, **Inappropriate Intimacy**,
|
||||
> **Message Chains**, and **Middle Man**.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Fallback
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| User doesn't specify scope | Default to recent changes (`git diff`) for repos > 200 files, full analysis otherwise |
|
||||
| Project has no clear architecture | Report "Big Ball of Mud" with evidence, recommend incremental refactoring |
|
||||
| Empty/monorepo project | Report that architecture analysis requires code; ask user to specify module |
|
||||
| Language not supported | Report general structural observations; note language-specific checks are limited |
|
||||
| Report file path conflicts | Append `-2`, `-3`, etc. to filename |
|
||||
| User wants a quick check | Run only Critical-level scans, skip Code and Naming categories |
|
||||
| User wants only one category | Focus analysis on that category, skip others |
|
||||
|
||||
---
|
||||
|
||||
## Report Output Example
|
||||
|
||||
```
|
||||
🔍 Architecture Smell Analysis Complete
|
||||
|
||||
Project: goal-workflow
|
||||
Style: Modular Monolith (with some layering violations)
|
||||
Files Analyzed: 47
|
||||
Health: 🟡 Fair
|
||||
|
||||
Critical: 3 | Warnings: 6 | Suggestions: 9
|
||||
|
||||
🔴 Critical Issues:
|
||||
1. Anemic Domain Model — `models/` classes have only getters/setters,
|
||||
all logic in `services/`. Violates DDD Rich Domain Model principle.
|
||||
2. N+1 Query Pattern — `services/order.ts:142` fetches user per order in loop;
|
||||
should batch-load users by IDs (O(n*m) → O(n+m)).
|
||||
3. Static Cling — `util/ApiClient.ts` uses all static methods,
|
||||
making consumer code untestable.
|
||||
|
||||
🟡 Warnings:
|
||||
1. God Object — `services/workflow.ts` at 847 lines handles too many concerns
|
||||
2. Nested Loop O(n^2) — `analytics.ts:89` pairwise comparison of events;
|
||||
sort+two-pointer would be O(n log n)
|
||||
3. Leaky Abstraction — `repositories/user.ts` exposes MongoDB query syntax
|
||||
4. Duplicated Code — validation logic duplicated across 4 controllers
|
||||
5. Circular Dependency — `auth` ↔ `user` modules depend on each other
|
||||
6. Magic Numbers — ~23 hardcoded values without named constants
|
||||
|
||||
Full report: tasks/smell-report-2026-05-27-1530.md
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"prompt": "/smell",
|
||||
"description": "Basic invocation — should trigger full project analysis"
|
||||
},
|
||||
{
|
||||
"prompt": "find code smells in this project",
|
||||
"description": "Natural language trigger — full code smell analysis"
|
||||
},
|
||||
{
|
||||
"prompt": "detect architecture anti-patterns in the src/ directory",
|
||||
"description": "Scoped analysis to a specific directory"
|
||||
},
|
||||
{
|
||||
"prompt": "分析一下这个项目的架构坏味道",
|
||||
"description": "Chinese trigger — architecture smell analysis"
|
||||
},
|
||||
{
|
||||
"prompt": "找出反模式",
|
||||
"description": "Chinese trigger — anti-pattern detection"
|
||||
},
|
||||
{
|
||||
"prompt": "Does this codebase have any God Objects or Big Ball of Mud?",
|
||||
"description": "Specific anti-pattern query"
|
||||
},
|
||||
{
|
||||
"prompt": "run a quick architecture health check",
|
||||
"description": "Quick scan — should use critical-only mode"
|
||||
},
|
||||
{
|
||||
"prompt": "check the recent changes for code smells",
|
||||
"description": "Git diff scoped analysis"
|
||||
},
|
||||
{
|
||||
"prompt": "analyze code complexity and find algorithmic hotspots",
|
||||
"description": "Complexity-focused analysis — detect N+1, nested loops, etc."
|
||||
},
|
||||
{
|
||||
"prompt": "find N+1 queries and O(n^2) patterns",
|
||||
"description": "Specific complexity anti-pattern query"
|
||||
},
|
||||
{
|
||||
"prompt": "复杂度分析",
|
||||
"description": "Chinese trigger — complexity analysis"
|
||||
},
|
||||
{
|
||||
"prompt": "Are there any performance bottlenecks or inefficient algorithms?",
|
||||
"description": "Performance/complexity smell query"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,281 @@
|
||||
---
|
||||
name: to-design
|
||||
description: "Generate a design document (design proposal) from a PRD, in the style of Go's official design proposals — Abstract / Background / Design / Rationale / Compatibility / Implementation, heavy on the 'why' and tradeoffs. Triggers on: to-design, prd-to-design, prd转设计文档, 生成设计文档, 写设计文档, design doc, design proposal, 设计提案, 技术设计文档."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# to-design — PRD to Design Document
|
||||
|
||||
Turn a PRD (or a rough idea) into a **design document** written in the style of Go's official design proposals: plain language, concrete examples, and—above all—an honest account of *why this approach and not the alternatives*.
|
||||
|
||||
This is **not** the same as `prd-to-spec`. A SPEC is an implementation contract (tables, endpoints, schemas) for an engineer to build against. A design document is a **decision artifact**: it argues for an approach, surfaces the tradeoffs, and lets a team agree on the same facts before anyone writes code. When the question is "*how should we build this and why*", produce a design doc; when the question is "*give me the exact contract to implement*", produce a SPEC.
|
||||
|
||||
> 设计哲学源自对 5 篇 Go 官方 proposal(泛型 / 错误包装 / loopvar / slog / try)的分析。核心信念:**文档的价值不取决于方案是否通过,而取决于它是否让讨论建立在同一套事实和取舍之上。**
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
- A PRD exists and you need to decide *how* to build it before committing to implementation
|
||||
- The approach has real tradeoffs and you want them documented and debated
|
||||
- The change is risky, breaking, or hard to reverse (a design doc forces the compatibility conversation early)
|
||||
- Multiple people need to agree on a direction before work fans out
|
||||
- You want a durable record of "why we chose X and rejected Y" — even if the proposal is later rejected
|
||||
|
||||
If the team just needs the concrete contract to code against, use `/prd-to-spec` instead (or run `to-design` first, then `prd-to-spec`).
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. **Locate input** — find or receive the PRD (or idea)
|
||||
2. **Analyze context (optional)** — scan the codebase for existing patterns, constraints, and prior art
|
||||
3. **Surface the decisions** — identify the real design forks and ask clarifying questions (max 3-5)
|
||||
4. **Generate the design doc** — following the structure and writing style below
|
||||
5. **Review** — present for feedback, especially on the Rationale and Compatibility sections
|
||||
6. **Save** — write to the agreed location
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Locate Input
|
||||
|
||||
```
|
||||
Provide the PRD (or idea) to design from:
|
||||
|
||||
A. File path (e.g., tasks/prd-priority-system.md)
|
||||
B. GitHub Issue URL
|
||||
C. Paste content directly
|
||||
D. Just describe the idea — I'll design from the conversation
|
||||
```
|
||||
|
||||
A design doc can start from a half-formed idea, not only a polished PRD. If the input is thin, lean harder on Step 3.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Analyze Context (Optional)
|
||||
|
||||
Skip for greenfield. Otherwise scan to ground the design in reality:
|
||||
|
||||
- **Existing patterns** the design should match (naming, error handling, module boundaries)
|
||||
- **Prior art** — has something similar been tried or rejected here before?
|
||||
- **Constraints** — compatibility promises, public APIs, data the design can't break
|
||||
- **Real pain** — find the actual buggy/awkward code the design fixes, so Background can quote it
|
||||
|
||||
The most persuasive Background sections quote **real code from the user's own repo**, not hypotheticals.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Surface the Decisions
|
||||
|
||||
A design doc lives or dies on its Rationale. Before writing, find the **real forks in the road** — the points where a competent engineer could reasonably go two ways — and resolve them.
|
||||
|
||||
Ask only about genuine forks:
|
||||
|
||||
```
|
||||
Design decisions to settle before I write the doc:
|
||||
|
||||
1. Where does this logic live?
|
||||
A. Extend the existing X
|
||||
B. New standalone component Y
|
||||
C. Let me recommend based on the codebase
|
||||
|
||||
2. Is this a breaking change for existing callers?
|
||||
A. Yes — needs a migration path
|
||||
B. No — purely additive
|
||||
C. Unsure — I'll analyze and flag it
|
||||
|
||||
3. What's the one promise this design must keep? (e.g. backward compatibility,
|
||||
latency budget, no new dependencies)
|
||||
```
|
||||
|
||||
For every fork, also note the **option you are NOT choosing** — that becomes the Rationale.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Design Document Structure
|
||||
|
||||
This is the standard skeleton distilled from the 5 Go proposals. Keep section names; drop sections that genuinely don't apply (and say why if the omission is notable).
|
||||
|
||||
```markdown
|
||||
Title: <一句话说清"做什么" —— 标题就是结论,不是名词短语>
|
||||
Author(s): <作者>
|
||||
Last updated: <YYYY-MM-DD>
|
||||
Discussion at <issue / PR / 文档链接> # 让文档不孤立,永远附讨论入口
|
||||
Status: Draft | Under review | Accepted | Rejected
|
||||
|
||||
## Abstract / 摘要
|
||||
|
||||
一段话讲完全文:做什么、大致怎么做、以及**最重要的那个承诺**(如"向后兼容""不引入新依赖")。
|
||||
读者读完这一段就该知道全貌。把隐含的核心约束埋在这里。
|
||||
|
||||
## Background / 背景与动机
|
||||
|
||||
用**具体、可感的例子**说明"痛在哪",而不是抽象地说"现状不好"。
|
||||
- 能贴一段真实的 bug 代码 / 别扭的调用,就贴。先让读者"疼"起来。
|
||||
- 量化痛点(出现频率、踩坑次数、损失),不要用形容词堆砌。
|
||||
- 一句话给问题定性。
|
||||
|
||||
## Design / Proposal / 设计
|
||||
|
||||
文档主体。遵循三条:
|
||||
- **从简单到复杂,渐进式教学**:从最小例子起步,复杂场景留到读者有直觉之后。
|
||||
- **声明 + 示例 + 边界**三件套:每个 API/接口先给声明,再给用法片段,再划清适用边界。
|
||||
- **改造前 vs 改造后对照**:能并排展示收益的,就并排展示。
|
||||
能用一段可运行代码说清的,绝不用一段文字描述。
|
||||
|
||||
## Rationale / 理由与取舍
|
||||
|
||||
> Rationale = "为什么是这个方案,而不是别的"的论证。这是区分好文档和平庸文档的关键章节。
|
||||
|
||||
- 解释关键决策的动机。
|
||||
- **主动列出被放弃的备选方案 + 放弃原因**("我们没选 X,因为 Y")。这比单方面论证你选的方案更可信,也避免后人重复讨论。
|
||||
- 回应可预见的质疑。
|
||||
|
||||
## Compatibility / 兼容性
|
||||
|
||||
凡涉及破坏性变更,必须正面回应。
|
||||
- 是不是破坏性变更?**开门见山承认**。
|
||||
- 代价是什么(性能、行为变化、迁移成本)?**诚实列出**,不藏着。
|
||||
- 渐进迁移路径(按模块/按文件 opt-in、灰度、特性开关)。
|
||||
- 有先例佐证更好("某系统做过类似变更,结果平淡无奇")。
|
||||
|
||||
## Implementation / Transition / 实现与过渡
|
||||
|
||||
- 如何落地、分几步、配套什么工具。
|
||||
- **用数据和工具支撑"可落地"**:实测失败率、灰度结果、自动化迁移工具,比任何"我们认为风险可控"都管用。
|
||||
- 兼容老版本的过渡方案(如独立发布的兼容库)。
|
||||
|
||||
## Appendix / 附录(可选)
|
||||
|
||||
把会打断主线的细节后置:完整 API、端到端示例、FAQ。
|
||||
FAQ 专门回应高频质疑("为什么叫这个名字""为什么不用某语言的做法""和 X 有何不同")。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writing Style (照搬 Go 文档的文风)
|
||||
|
||||
Structure is the skeleton; style is the muscle. Enforce these — they're what make the doc readable.
|
||||
|
||||
### Voice / 主语
|
||||
- **决策用 "我们 / We"** — 把设计说成一群人可负责的选择,不是客观真理。("We propose…", "我们决定移除…")
|
||||
- **行为用代码本身当主语** — "this code has a bug" / "这段代码会…",让注意力落在程序上。
|
||||
- **说理对读者用 "你 / you"** — 像面对面解释。
|
||||
- **禁止无主语的被动腔** — 不写"据建议应当…""It is suggested that…"这类推卸责任的句式。
|
||||
|
||||
### Sentences / 句子
|
||||
- **判断用短句,论证用长句**。先用一个极短的句子拍板("这段代码有 bug。"),再用信息密集的长句铺开机制。
|
||||
- 长短交替制造节奏。不要通篇绕来绕去的长句。
|
||||
|
||||
### Paragraphs / 段落
|
||||
- **一段只讲一件事,观点放段首**(结论先行)。
|
||||
- **小标题写成一句完整的论点**,而不是名词短语。
|
||||
- 写 `老代码不受影响,编译结果与之前完全一致`,而不是 `兼容性`。
|
||||
- 读者光看标题就能读完整条论证链。
|
||||
|
||||
### Tone / 语气
|
||||
- **克制的诚实,甚至自嘲**。承认代价、承认自己也踩过坑,比形容词更有说服力。
|
||||
- **强调要省着用**。全文只在最关键处加粗/斜体一次,反而最醒目。
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Review & Iteration
|
||||
|
||||
Present the doc and steer feedback to the sections that matter most:
|
||||
|
||||
```
|
||||
设计文档已生成。重点请看这几处:
|
||||
|
||||
- Rationale:被放弃的方案和理由是否站得住?有没有遗漏的备选项?
|
||||
- Compatibility:破坏性和代价是否如实说清?迁移路径可行吗?
|
||||
- Background:痛点是否用具体例子讲清,而不是形容词?
|
||||
- 文风:标题是否是"结论"而非名词?有没有无主语的被动腔?
|
||||
|
||||
回复 OK 保存,或给出修改意见。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Save
|
||||
|
||||
```
|
||||
设计文档保存到哪里?
|
||||
|
||||
A. tasks/design-[feature-name].md(紧挨 PRD,推荐)
|
||||
B. docs/design/[feature-name].md
|
||||
C. 自定义路径:[指定]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapping: PRD → Design Doc
|
||||
|
||||
| PRD 部分 | Design Doc 部分 | 转化方式 |
|
||||
|----------|-----------------|----------|
|
||||
| Problem / 背景 | Background | 找到真实的痛点代码/场景,量化它 |
|
||||
| Goals / 目标 | Abstract + Background | 提炼成"最重要的承诺"埋进摘要 |
|
||||
| User Stories / 需求 | Design | 转成渐进式的设计示例 |
|
||||
| Technical Considerations | Design + Rationale | 约束 → 设计决策 + 取舍论证 |
|
||||
| Non-Goals | Rationale | 写成"我们没做 X,因为 Y" |
|
||||
| Risks / 风险 | Compatibility + Implementation | 风险 → 兼容性代价 + 迁移/灰度方案 |
|
||||
| 隐含的备选方案 | Rationale | 显式列出并解释为何不选 |
|
||||
|
||||
---
|
||||
|
||||
## Quality Criteria
|
||||
|
||||
A good design doc should pass these checks:
|
||||
|
||||
- [ ] 标题是一句"做什么"的结论,不是名词短语,且附了讨论链接
|
||||
- [ ] 摘要里埋了最重要的承诺/约束
|
||||
- [ ] Background 用了**具体例子或真实代码**讲痛点,而非形容词
|
||||
- [ ] Design 遵循"声明 + 示例 + 边界",并有渐进式教学
|
||||
- [ ] **Rationale 主动列出了至少一个被放弃的方案及原因**(最关键的检查项)
|
||||
- [ ] 凡破坏性变更,Compatibility 都正面承认并列出代价
|
||||
- [ ] Implementation 用数据/工具支撑"可落地",而非空喊"风险可控"
|
||||
- [ ] 文风:决策用"我们"、行为用代码、无无主语被动腔;长短句交替;小标题是论点句
|
||||
- [ ] 没有 "TBD / TODO"——要么解决,要么挪进 Open Questions
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Fallback
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| PRD 含糊不全 | 在 Step 3 多问,把缺失项写进 Open Questions / 假设 |
|
||||
| 没有真实痛点代码可引 | 用最小可信的示例代码代替,并注明是构造的 |
|
||||
| 没有备选方案可写 | 强迫思考"最朴素的做法是什么、为什么不够"——总有一个被否决的基线 |
|
||||
| 不是破坏性变更 | Compatibility 一句话说明"纯增量、无破坏",不必硬凑 |
|
||||
| 方案最终被否决 | 照样写好——记录"这条路为什么走不通"本身就是高价值产物,Status 标 Rejected |
|
||||
| 特性太大 | 拆成多篇 design doc(按边界),互相链接 |
|
||||
| 用户只要实现契约 | 提示改用 `/prd-to-spec`,或先 to-design 再 prd-to-spec |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
- **别只论证你选的方案。** 不写被放弃的备选项,文档就少了一半价值。
|
||||
- **别用形容词讲痛点。** "现状很糟"没有说服力;一段真实的 bug 代码才有。
|
||||
- **别藏代价。** 性能变慢、行为变化、迁移成本——都明说,再给迁移路径。
|
||||
- **别把标题写成名词。** "兼容性" → "老代码不受影响,编译结果完全一致"。
|
||||
- **别用无主语的被动腔。** 决策要有人负责,主语用"我们"。
|
||||
- **别写成 SPEC。** 设计文档讲"为什么这么选"和"取舍",不是字段级的实现契约。
|
||||
- **别因为方案可能被否就敷衍。** 文档质量与提案是否通过无关。
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Other Skills
|
||||
|
||||
```
|
||||
/prd → /to-design → /prd-to-spec → /goal → /review-it → /ship-it
|
||||
│ │ │ │
|
||||
│ 需求(what) │ 决策与取舍 │ 实现契约(how) │ 编码
|
||||
│ │ (why/which) │
|
||||
```
|
||||
|
||||
- **/prd** 产出 PRD(本 skill 的输入)
|
||||
- **/to-design** 产出设计文档:论证方案、暴露取舍、对齐认知(本 skill)
|
||||
- **/prd-to-spec** 产出实现级 SPEC:字段、接口、schema 契约
|
||||
- **/code-to-spec** 从既有代码逆向出 SPEC(互补:正向 vs 逆向)
|
||||
|
||||
> 写设计文档的终极目的不是"说服别人同意你",而是"让所有人在同一个事实和取舍基础上做决定"。
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: to-issues
|
||||
description: "Decompose a PRD and/or SPEC into implementable Issues and create them in your chosen platform (GitHub, Local, or Baidu iCafe). Use after /prd (and optionally /prd-to-spec) to turn requirements into actionable tickets. Triggers on: create issues, to-issues, 创建issue, 拆解issue, 生成卡片, 创建卡片, generate issues from PRD, issues from spec."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# to-issues — PRD/SPEC to Issues
|
||||
|
||||
Decompose a PRD and/or technical SPEC into small, independent, implementable Issues, then create them in your chosen platform. Works standalone — you don't need to have run `/prd` first.
|
||||
|
||||
---
|
||||
|
||||
## The Job
|
||||
|
||||
1. **Locate input** — find a PRD or SPEC file (auto-detect or user-specified)
|
||||
2. **Decompose into Issues** — break User Stories into implementable tickets
|
||||
3. **Review with user** — present Issue list for approval and adjustment
|
||||
4. **Choose platform** — GitHub / Local / Baidu iCafe
|
||||
5. **Create Issues** — create all tickets and print summary
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Locate Input
|
||||
|
||||
Find the input document:
|
||||
|
||||
```
|
||||
What should I base the Issues on?
|
||||
|
||||
A. Auto-detect: scan tasks/ for recent PRDs and SPECs
|
||||
B. Specific PRD file (e.g., tasks/prd-priority-system.md)
|
||||
C. Specific SPEC file (e.g., tasks/spec-priority-system.md)
|
||||
D. Both PRD and SPEC (best: PRD for requirements, SPEC for technical contracts)
|
||||
E. Paste requirements directly
|
||||
```
|
||||
|
||||
If auto-detecting, list available files and let the user choose.
|
||||
|
||||
If both PRD and SPEC are available, use the SPEC's Section 10.2 (Issue Mapping) as the primary guide, supplemented by PRD's User Stories. If only PRD is available, generate Issues directly from User Stories.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Decompose into Issues
|
||||
|
||||
Based on the input document(s), generate a list of Issues. Follow these rules:
|
||||
|
||||
- **One Issue per User Story** — each US-XXX becomes at least one Issue
|
||||
- **Split large stories** — if a US has 5+ acceptance criteria or spans frontend + backend, split into 2-3 smaller Issues with clear dependencies
|
||||
- **Merge tiny stories** — if a US has only 1-2 trivial criteria, merge it with a related US into a single Issue
|
||||
- **Each Issue must be independently implementable** — a single agent session should be able to complete it
|
||||
- **Number Issues sequentially** starting from 1
|
||||
- **If SPEC is available** — enrich Issues with SPEC references (API endpoints, data model sections, error handling contracts)
|
||||
|
||||
**Issue format:**
|
||||
|
||||
```
|
||||
Issue #N: [Title]
|
||||
---
|
||||
Description: [From US description, with context]
|
||||
Acceptance Criteria:
|
||||
- [ ] [From US acceptance criteria]
|
||||
- [ ] ...
|
||||
Dependencies: [None / Issue #X]
|
||||
Type: [backend / frontend / fullstack / ui / infra]
|
||||
Priority: [high / medium / low]
|
||||
SPEC Reference: [Section X.Y — only if SPEC available]
|
||||
```
|
||||
|
||||
**Present the Issue list for review:**
|
||||
|
||||
```
|
||||
📋 Generated N Issues from [PRD/SPEC]:
|
||||
|
||||
#1: Add priority field to database (backend, high)
|
||||
#2: Display priority indicator on task cards (frontend, high) — depends on #1
|
||||
#3: Add priority selector to task edit (frontend, medium) — depends on #1
|
||||
#4: Filter tasks by priority (frontend, medium) — depends on #1, #2
|
||||
|
||||
Please review. You can:
|
||||
- Remove issues: "remove #3"
|
||||
- Merge issues: "merge #2 and #3"
|
||||
- Add issues: "add an issue for sorting by priority"
|
||||
- Adjust: "change #2 priority to high"
|
||||
- Confirm: reply OK to proceed
|
||||
```
|
||||
|
||||
Wait for user confirmation before creating any Issues.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Choose Creation Mode
|
||||
|
||||
After user confirms the Issue list, ask:
|
||||
|
||||
```
|
||||
Choose where to create these Issues:
|
||||
|
||||
A. GitHub (via gh CLI)
|
||||
B. Local (save as .md files)
|
||||
C. Baidu iCafe (via icafe-cli)
|
||||
|
||||
Your choice:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Mode-Specific Creation
|
||||
|
||||
### Mode A: GitHub
|
||||
|
||||
**Prerequisites:** `gh` CLI installed and authenticated.
|
||||
|
||||
**Actions:**
|
||||
1. For each Issue, run:
|
||||
```bash
|
||||
gh issue create --title "[Title]" --body "[Description + Acceptance Criteria]" --label "[type]" --label "priority: [priority]"
|
||||
```
|
||||
2. If labels don't exist, create them first or skip the `--label` flag
|
||||
3. Report created Issue numbers and URLs
|
||||
|
||||
### Mode B: Local
|
||||
|
||||
**Ask user:**
|
||||
```
|
||||
Where should I save the Issue files? (default: .autoresearch/issues)
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
1. If the specified folder does not exist, create it with `mkdir -p`
|
||||
2. For each Issue #N, save a file named `issue-NNN-[slug].md` (zero-padded to 3 digits):
|
||||
```markdown
|
||||
# [Title]
|
||||
|
||||
## Description
|
||||
[Description from Issue]
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] [criterion 1]
|
||||
- [ ] [criterion 2]
|
||||
|
||||
## Dependencies
|
||||
[None / Issue #X]
|
||||
|
||||
## Type
|
||||
[backend / frontend / fullstack / ui / infra]
|
||||
|
||||
## Priority
|
||||
[high / medium / low]
|
||||
```
|
||||
3. Report created file paths
|
||||
|
||||
### Mode C: Baidu iCafe
|
||||
|
||||
**Ask user:**
|
||||
```
|
||||
Please provide the iCafe space prefix code (--space):
|
||||
```
|
||||
|
||||
Optionally ask:
|
||||
```
|
||||
Target branch for iCode CR? (default: master)
|
||||
```
|
||||
|
||||
**Prerequisites:** `icafe-cli` installed and logged in.
|
||||
|
||||
**Actions:**
|
||||
1. For each Issue, run:
|
||||
```bash
|
||||
icafe-cli card create --space [SPACE] --title "[Title]" --description "[Description + Acceptance Criteria]" --cardtype "[Task/Bug/Story]"
|
||||
```
|
||||
- Map Issue `type` to iCafe card type: `bug` → `Bug`, `ui`/`frontend` → `Story`, others → `Task`
|
||||
- Map `priority`: high → `高`, medium → `中`, low → `低`
|
||||
2. If iCafe card creation fails for an Issue, log the error and continue with remaining Issues
|
||||
3. Report created card sequence numbers
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Summary Report
|
||||
|
||||
After all Issues are created, print a summary:
|
||||
|
||||
```
|
||||
✅ Issue creation complete!
|
||||
|
||||
Source: [PRD/SEC path]
|
||||
Mode: [GitHub / Local / Baidu iCafe]
|
||||
Issues created: N
|
||||
|
||||
# | Title | Identifier
|
||||
---|------------------------------------------|------------
|
||||
1 | Add priority field to database | #42 (GitHub) / issue-001-*.md (Local) / #22210 (iCafe)
|
||||
2 | Display priority indicator | #43 / issue-002-*.md / #22211
|
||||
3 | Add priority selector | #44 / issue-003-*.md / #22212
|
||||
4 | Filter tasks by priority | #45 / issue-004-*.md / #22213
|
||||
|
||||
💡 Tip: Now implement each Issue with /goal:
|
||||
/goal 42 # GitHub mode
|
||||
/goal issue-001-*.md # Local mode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Fallback
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| No PRD/SPEC found in tasks/ | Ask user to provide file path or paste requirements |
|
||||
| PRD has no User Stories | Derive Issues from Functional Requirements instead |
|
||||
| SPEC has Issue Mapping (Section 10.2) | Use it as primary source, cross-reference with PRD |
|
||||
| `gh` CLI not authenticated for GitHub mode | Show error, suggest `gh auth login`, offer to switch to Local mode |
|
||||
| `icafe-cli` / `icode-cli` not installed for Baidu mode | Show error, suggest installation, offer to switch to Local mode |
|
||||
| Issue folder does not exist for Local mode | Auto-create the folder |
|
||||
| User declines Issue creation | Print the Issue list as a text summary, let user create manually later |
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Other Skills
|
||||
|
||||
```
|
||||
/prd → /prd-to-spec (optional) → /to-issues → /goal → /review-it → /ship-it
|
||||
│ │ │ │
|
||||
│ Requirements │ Technical design │ Tickets │ Implementation
|
||||
│ (what) │ (how) │ (units) │ (code)
|
||||
```
|
||||
|
||||
- **/prd** — produces the PRD (input to this skill)
|
||||
- **/prd-to-spec** — produces the SPEC (optional, enriches Issues with technical detail)
|
||||
- **/to-issues** — produces the Issues (this skill)
|
||||
- **/goal** — implements Issues one by one
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: weather
|
||||
description: Get current weather and forecasts (no API key required).
|
||||
homepage: https://wttr.in/:help
|
||||
metadata: {"clawdbot":{"emoji":"🌤️","requires":{"bins":["curl"]}}}
|
||||
---
|
||||
|
||||
# Weather
|
||||
|
||||
Two free services, no API keys needed.
|
||||
|
||||
## wttr.in (primary)
|
||||
|
||||
Quick one-liner:
|
||||
```bash
|
||||
curl -s "wttr.in/London?format=3"
|
||||
# Output: London: ⛅️ +8°C
|
||||
```
|
||||
|
||||
Compact format:
|
||||
```bash
|
||||
curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"
|
||||
# Output: London: ⛅️ +8°C 71% ↙5km/h
|
||||
```
|
||||
|
||||
Full forecast:
|
||||
```bash
|
||||
curl -s "wttr.in/London?T"
|
||||
```
|
||||
|
||||
Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon
|
||||
|
||||
Tips:
|
||||
- URL-encode spaces: `wttr.in/New+York`
|
||||
- Airport codes: `wttr.in/JFK`
|
||||
- Units: `?m` (metric) `?u` (USCS)
|
||||
- Today only: `?1` · Current only: `?0`
|
||||
- PNG: `curl -s "wttr.in/Berlin.png" -o /tmp/weather.png`
|
||||
|
||||
## Open-Meteo (fallback, JSON)
|
||||
|
||||
Free, no key, good for programmatic use:
|
||||
```bash
|
||||
curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12¤t_weather=true"
|
||||
```
|
||||
|
||||
Find coordinates for a city, then query. Returns JSON with temp, windspeed, weathercode.
|
||||
|
||||
Docs: https://open-meteo.com/en/docs
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn70pywhg0fyz996kpa8xj89s57yhv26",
|
||||
"slug": "weather",
|
||||
"version": "1.0.0",
|
||||
"publishedAt": 1767545394459
|
||||
}
|
||||
Reference in New Issue
Block a user