diff --git a/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143441-jay-flowers.md b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143441-jay-flowers.md new file mode 100644 index 0000000..94e39e1 --- /dev/null +++ b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143441-jay-flowers.md @@ -0,0 +1,10 @@ +--- +tag: vibe-check-command-and-reporter +author: jay-flowers +category: pattern +created_at: 2026-09-02T14:34:41Z +identity: vibe-check-command-and-reporter-20260902T143441-jay-flowers +tier: draft +--- + +When extending the vibe-check scaffold system to deploy multiple asset categories (agents and commands), the key architectural decision was extracting a deployCategory helper function that encapsulates the per-category deployment logic (glob, ensureDir, file iteration, prefix). The Run() function calls deployCategory for each category and merges Result slices. Category-prefixed Result paths (e.g., "agents/divisor-entropy.md", "commands/vibe-check.md") disambiguate entries from different categories. The refactoring preserved all existing security properties (symlink safety, containment checks, path validation) by reusing ensureDir and verifyContained for both deployment targets. Two separate //go:embed directives with separate embed.FS variables are required because Go's embed directive does not support multiple glob patterns in one directive for different directories. diff --git a/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143445-jay-flowers.md b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143445-jay-flowers.md new file mode 100644 index 0000000..2fe8e08 --- /dev/null +++ b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143445-jay-flowers.md @@ -0,0 +1,10 @@ +--- +tag: vibe-check-command-and-reporter +author: jay-flowers +category: gotcha +created_at: 2026-09-02T14:34:45Z +identity: vibe-check-command-and-reporter-20260902T143445-jay-flowers +tier: draft +--- + +The AD-007 400-line file size threshold is a MUST rule that should be addressed proactively during implementation, not reactively during code review. For scaffold_test.go, the natural split is three files: scaffold_test.go for deployment lifecycle tests (deploy, skip, force, sort, mixed results), security_test.go for symlink/traversal/containment tests, and contract_test.go for embedded asset contract tests and helper functions (frontmatterDescription, bashPermissions). The test helper types (orderedGlobFS, emptyFS) belong in scaffold_test.go since they're used by the lifecycle tests. Constants shared across test files are accessible because all files are in the same package. diff --git a/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143457-jay-flowers.md b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143457-jay-flowers.md new file mode 100644 index 0000000..dada9a5 --- /dev/null +++ b/.uf/dewey/learnings/vibe-check-command-and-reporter-20260902T143457-jay-flowers.md @@ -0,0 +1,10 @@ +--- +tag: vibe-check-command-and-reporter +author: jay-flowers +category: pattern +created_at: 2026-09-02T14:34:57Z +identity: vibe-check-command-and-reporter-20260902T143457-jay-flowers +tier: draft +--- + +The spec review for the vibe-check-command-and-reporter change revealed several recurring patterns across all six divisor agents: (1) Go's //go:embed directive with a glob pattern will fail at compile time if no files match — specs should not describe zero-match scenarios as succeeding; (2) Constitution Principle IV requires a coverage strategy section in the design document — the Tester classified its absence as CRITICAL while other reviewers classified it as MEDIUM/HIGH, resolving to add a Test Strategy section with three categories (scaffold Go code, embedded asset contracts, agent behavioral validation); (3) documentation tasks (README, AGENTS.md, CHANGELOG, doc.go, init.go GoDoc) are consistently missed during initial task creation — adding a dedicated documentation section to tasks.md prevents this gap. diff --git a/AGENTS.md b/AGENTS.md index 66e7bc8..3ea0859 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -241,7 +241,7 @@ go build ./... # Build the CLI binary go build ./cmd/vibe-check -# Deploy embedded Review Council agent assets into .opencode/agents/ of a project +# Deploy embedded agent and command assets into .opencode/ of a project go run ./cmd/vibe-check init . # --force to overwrite, --json for machine output # Analyze a module and write ModuleGraph JSON to a file (default: stdout) @@ -276,7 +276,7 @@ cmd/vibe-check/ # CLI entry point (Layer 3) root.go # Cobra root command with --version flag analyze.go # analyze subcommand with threshold flags (incl --output/-o) diff.go # diff subcommand (base vs PR entropy delta + verdict) - init.go # init subcommand (deploys embedded agent assets) + init.go # init subcommand (deploys embedded agent and command assets) internal/goadapter/ # Go language adapter (Layer 2) adapter.go # Adapter struct implementing metrics.Adapter resolve.go # Package loading via go/packages @@ -286,13 +286,16 @@ internal/goadapter/ # Go language adapter (Layer 2) extensions.go # go.interfaceWidth and go.interfaceProximity extensions doc.go # Package-level GoDoc testdata/ # Test fixtures (coupling, types, lcom, extensions, partial) -internal/scaffold/ # Embedded agent-asset deployment for `vibe-check init` +internal/scaffold/ # Embedded asset deployment for `vibe-check init` doc.go # Package-level GoDoc - embed.go # //go:embed assets/agents/*.md (embedded source of truth) + embed.go # //go:embed assets/{agents,commands}/*.md (embedded source of truth) scaffold.go # Symlink-safe asset writer (skip/force; 0o755 dirs, 0o644 files) scaffold_test.go # Writer + embedded-asset contract tests assets/agents/ # Embedded Review Council agent assets - divisor-entropy.md # Structural-entropy divisor agent (source of truth) + divisor-entropy.md # Structural-entropy divisor agent (source of truth) + vibe-check-reporter.md # Interactive metrics reporter agent (summary/detailed/trending) + assets/commands/ # Embedded slash command assets + vibe-check.md # /vibe-check command (delegates to vibe-check-reporter agent) metrics/ # Universal coupling metrics model (Layer 1) adapter.go # Adapter interface and Capability type compute.go # Metric computation functions @@ -341,8 +344,8 @@ The architecture follows a three-layer design per the RFC phasing: `--max-distance`, `--max-lcom`, `--no-circular-deps`, `--timeout`, `--output`/`-o`) and JSON output; `vibe-check diff ` computing the entropy delta and verdict (with tighten-only threshold - overrides); and `vibe-check init [path]` deploying the embedded Review - Council agent assets into `.opencode/agents/`. + overrides); and `vibe-check init [path]` deploying the embedded agent assets + into `.opencode/agents/` and command assets into `.opencode/commands/`. RFC phasing status: diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ace22..23a04aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 deployed by `vibe-check init`): measures the base↔PR design-quality delta via `vibe-check analyze` and `vibe-check diff` inside an isolated git worktree and reports a verdict. Runs only on trusted refs. +- `/vibe-check` slash command asset (deployed by `vibe-check init` to + `.opencode/commands/vibe-check.md`): delegates to the `vibe-check-reporter` + agent for conversational architectural analysis with three modes — + summary (traffic-light health indicator), detailed (per-package + breakdown), and trending (longitudinal metric comparison via Dewey + snapshots). + Spec: `openspec/changes/vibe-check-command-and-reporter/` +- `vibe-check-reporter` agent asset (deployed by `vibe-check init` to + `.opencode/agents/vibe-check-reporter.md`): interprets Martin coupling + metrics in natural language, runs `vibe-check analyze` to gather data, + and stores metric snapshots in Dewey for trend tracking. + Spec: `openspec/changes/vibe-check-command-and-reporter/` +- `vibe-check init` now deploys command assets to `.opencode/commands/` + alongside agent assets in `.opencode/agents/`. The scaffold system + uses a `deployCategory` helper to iterate both asset categories with + the same symlink-safe, containment-checked pattern. + Spec: `openspec/changes/vibe-check-command-and-reporter/` - `vibe-check analyze --output ` (`-o`) writes the ModuleGraph JSON to a file instead of stdout (stdout remains the default; a failed write exits with code 2 and a stderr diagnostic without emitting partial diff --git a/README.md b/README.md index 20c7fe9..3a4764a 100644 --- a/README.md +++ b/README.md @@ -141,19 +141,24 @@ It exits `0` whenever both inputs are valid — the verdict is data in the paylo schema-invalid, or when a `--max-instability-delta`, `--max-distance-delta`, or `--max-lcom-delta` override is looser than the protected default (overrides may only tighten). -## Deploying agents: `vibe-check init` +## Deploying agents and commands: `vibe-check init` -`init` deploys the embedded Review Council agent assets into a project's `.opencode/agents/` -directory: +`init` deploys the embedded agent and command assets into a project's `.opencode/` directory: ```bash vibe-check init [path] # path defaults to "."; --force to overwrite, --json for machine output ``` -It writes the bundled `divisor-entropy` agent — a structural-entropy reviewer that runs -`analyze` + `diff` across a base↔PR pair in an isolated worktree — and skips files that -already exist unless `--force` is given. It exits `0` on success (including when every asset -is skipped) and `2` on an invalid target path or I/O failure. +It writes assets to two directories: + +- `.opencode/agents/` — the `divisor-entropy` agent (structural-entropy reviewer that runs + `analyze` + `diff` across a base↔PR pair) and the `vibe-check-reporter` agent (interactive + metrics interpreter with summary, detailed, and trending modes). +- `.opencode/commands/` — the `/vibe-check` slash command, which delegates to the + `vibe-check-reporter` agent for conversational architectural analysis. + +Existing files are skipped unless `--force` is given. Exit code `0` on success (including +when every asset is skipped), `2` on an invalid target path or I/O failure. ## Known limitations diff --git a/cmd/vibe-check/init.go b/cmd/vibe-check/init.go index 852f977..1681275 100644 --- a/cmd/vibe-check/init.go +++ b/cmd/vibe-check/init.go @@ -23,10 +23,10 @@ type InitOptions struct { Stdout io.Writer // Stderr is the writer for diagnostics and errors. Required. Stderr io.Writer - // Path is the target project directory into which agent assets are deployed. - // Empty defaults to the current directory ("."). + // Path is the target project directory into which agent and command assets + // are deployed. Empty defaults to the current directory ("."). Path string - // Force overwrites existing agent asset files instead of skipping them. + // Force overwrites existing asset files instead of skipping them. Force bool // JSON selects machine-readable JSON output when true; otherwise a // human-readable summary is written. @@ -42,12 +42,14 @@ type InitOptions struct { // InitResult contains the outcome of an init deployment. // It follows the AP-001 Result struct pattern. type InitResult struct { - // Written lists the asset filenames newly created. + // Written lists the category-prefixed asset paths newly created (e.g. + // "agents/divisor-entropy.md", "commands/vibe-check.md"). Written []string - // Skipped lists the asset filenames left untouched because they already - // existed and Force was not set. + // Skipped lists the category-prefixed asset paths left untouched because + // they already existed and Force was not set. Skipped []string - // Forced lists the asset filenames overwritten because Force was set. + // Forced lists the category-prefixed asset paths overwritten because Force + // was set. Forced []string // ExitCode is the process exit code: 0 on success (including an all-skipped // run), 2 on an invalid target path or an I/O failure. @@ -64,10 +66,10 @@ type initJSON struct { Forced []string `json:"forced"` } -// RunInit deploys the embedded Review Council agent assets into the target -// project's .opencode/agents/ directory and writes a summary to opts.Stdout. It -// is the testable entry point per AP-002/AP-003: all business logic lives here, -// not in the cobra command layer. +// RunInit deploys the embedded agent and command assets into the target +// project's .opencode/agents/ and .opencode/commands/ directories and writes a +// summary to opts.Stdout. It is the testable entry point per AP-002/AP-003: all +// business logic lives here, not in the cobra command layer. // // Exit code semantics (also mirrored in the returned InitResult.ExitCode): // - 0: assets were deployed (or all skipped) and the summary was written. @@ -147,12 +149,13 @@ func writeInitJSON(w io.Writer, res *scaffold.Result) error { } // writeInitSummary renders the deployment result as a human-readable summary to -// w. It names the target agents directory, then lists the written, skipped, and -// forced assets. The lists are consumed in the stable, sorted order scaffold.Run -// guarantees, so output is byte-stable across runs. +// w. It names the target .opencode directory, then lists the written, skipped, +// and forced assets. The lists contain category-prefixed paths (e.g. +// "agents/divisor-entropy.md", "commands/vibe-check.md") in stable sorted +// order. func writeInitSummary(w io.Writer, targetDir string, res *scaffold.Result) { - agentsDir := filepath.Join(targetDir, ".opencode", "agents") - _, _ = fmt.Fprintf(w, "Deployed agent assets under %s:\n", agentsDir) + openCodeDir := filepath.Join(targetDir, ".opencode") + _, _ = fmt.Fprintf(w, "Deployed agent and command assets under %s:\n", openCodeDir) writeListSection(w, "Written:", res.Written) writeListSection(w, "Skipped:", res.Skipped) writeListSection(w, "Forced:", res.Forced) @@ -178,10 +181,11 @@ func initCmd() *cobra.Command { cmd := &cobra.Command{ Use: "init [path]", - Short: "Deploy vibe-check Review Council agent assets into a project", - Long: `Init deploys the embedded vibe-check Review Council agent assets (such as the -divisor-entropy structural-entropy reviewer) into a target project's -.opencode/agents/ directory. + Short: "Deploy vibe-check agent and command assets into a project", + Long: `Init deploys the embedded vibe-check agent assets (such as the +divisor-entropy structural-entropy reviewer and the vibe-check-reporter) and +command assets (such as the /vibe-check slash command) into a target project's +.opencode/agents/ and .opencode/commands/ directories. Existing files are skipped by default; use --force to overwrite them. The target path defaults to the current directory. Output is a human-readable summary by @@ -237,7 +241,7 @@ path is invalid or an asset cannot be written.`, }, } - cmd.Flags().BoolVar(&force, "force", false, "Overwrite existing agent asset files instead of skipping them") + cmd.Flags().BoolVar(&force, "force", false, "Overwrite existing asset files instead of skipping them") cmd.Flags().BoolVar(&jsonOut, "json", false, "Emit a machine-readable JSON payload instead of a summary") return cmd diff --git a/cmd/vibe-check/init_test.go b/cmd/vibe-check/init_test.go index 7d6abd3..a92471a 100644 --- a/cmd/vibe-check/init_test.go +++ b/cmd/vibe-check/init_test.go @@ -13,15 +13,26 @@ import ( "testing" ) -// initAssetName is the single embedded Review Council asset that init deploys. -// The scaffold writer reports assets by basename, so this is the value that -// appears in InitResult slices and in the --json payload. -const initAssetName = "divisor-entropy.md" +// initAssetNames lists the category-prefixed asset names that scaffold.Run +// deploys. The scaffold writer reports assets with category prefixes (e.g. +// "agents/divisor-entropy.md"), sorted lexicographically. These appear in +// InitResult slices and in the --json payload. +var initAssetNames = []string{ + "agents/divisor-entropy.md", + "agents/vibe-check-reporter.md", + "commands/vibe-check.md", +} + +// deployedAgentAssetPath returns the on-disk location of the divisor-entropy +// agent asset for a given project root. +func deployedAgentAssetPath(root string) string { + return filepath.Join(root, ".opencode", "agents", "divisor-entropy.md") +} -// deployedInitAssetPath returns the on-disk location of the deployed asset for a -// given project root. -func deployedInitAssetPath(root string) string { - return filepath.Join(root, ".opencode", "agents", initAssetName) +// deployedCommandAssetPath returns the on-disk location of the vibe-check +// command asset for a given project root. +func deployedCommandAssetPath(root string) string { + return filepath.Join(root, ".opencode", "commands", "vibe-check.md") } // initFailingWriter is an io.Writer whose Write always fails, used to exercise @@ -46,8 +57,8 @@ func TestRunInit_HumanSummaryLifecycle(t *testing.T) { if res.ExitCode != 0 { t.Fatalf("first run ExitCode: got %d, want 0", res.ExitCode) } - if got, want := res.Written, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("first run Written: got %v, want %v", got, want) + if got := res.Written; !slices.Equal(got, initAssetNames) { + t.Errorf("first run Written: got %v, want %v", got, initAssetNames) } if len(res.Skipped) != 0 || len(res.Forced) != 0 { t.Errorf("first run should have no skips/forced: skipped=%v forced=%v", res.Skipped, res.Forced) @@ -56,11 +67,17 @@ func TestRunInit_HumanSummaryLifecycle(t *testing.T) { if !strings.Contains(out, "Written:") { t.Errorf("summary missing Written section; got:\n%s", out) } - if !strings.Contains(out, initAssetName) { - t.Errorf("summary missing asset name; got:\n%s", out) + if !strings.Contains(out, "agents/divisor-entropy.md") { + t.Errorf("summary missing agent asset name; got:\n%s", out) + } + if !strings.Contains(out, "commands/vibe-check.md") { + t.Errorf("summary missing command asset name; got:\n%s", out) } - if _, statErr := os.Stat(deployedInitAssetPath(dir)); statErr != nil { - t.Errorf("expected deployed asset on disk: %v", statErr) + if _, statErr := os.Stat(deployedAgentAssetPath(dir)); statErr != nil { + t.Errorf("expected deployed agent asset on disk: %v", statErr) + } + if _, statErr := os.Stat(deployedCommandAssetPath(dir)); statErr != nil { + t.Errorf("expected deployed command asset on disk: %v", statErr) } // Second run: the existing asset is skipped. @@ -69,8 +86,8 @@ func TestRunInit_HumanSummaryLifecycle(t *testing.T) { if err != nil { t.Fatalf("second RunInit returned error: %v", err) } - if got, want := res2.Skipped, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("second run Skipped: got %v, want %v", got, want) + if got := res2.Skipped; !slices.Equal(got, initAssetNames) { + t.Errorf("second run Skipped: got %v, want %v", got, initAssetNames) } if len(res2.Written) != 0 { t.Errorf("second run Written should be empty: got %v", res2.Written) @@ -85,8 +102,8 @@ func TestRunInit_HumanSummaryLifecycle(t *testing.T) { if err != nil { t.Fatalf("force RunInit returned error: %v", err) } - if got, want := res3.Forced, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("force run Forced: got %v, want %v", got, want) + if got := res3.Forced; !slices.Equal(got, initAssetNames) { + t.Errorf("force run Forced: got %v, want %v", got, initAssetNames) } if len(res3.Written) != 0 || len(res3.Skipped) != 0 { t.Errorf("force run should only report forced: written=%v skipped=%v", res3.Written, res3.Skipped) @@ -133,8 +150,8 @@ func TestRunInit_JSONLifecycle(t *testing.T) { // First run: only written is populated; skipped and forced are empty arrays. first := run(false) - if got, want := first.Written, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("first run written: got %v, want %v", got, want) + if got := first.Written; !slices.Equal(got, initAssetNames) { + t.Errorf("first run written: got %v, want %v", got, initAssetNames) } if len(first.Skipped) != 0 { t.Errorf("first run skipped: got %v, want empty", first.Skipped) @@ -145,8 +162,8 @@ func TestRunInit_JSONLifecycle(t *testing.T) { // Second run: the asset moves to skipped. second := run(false) - if got, want := second.Skipped, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("second run skipped: got %v, want %v", got, want) + if got := second.Skipped; !slices.Equal(got, initAssetNames) { + t.Errorf("second run skipped: got %v, want %v", got, initAssetNames) } if len(second.Written) != 0 { t.Errorf("second run written: got %v, want empty", second.Written) @@ -154,8 +171,8 @@ func TestRunInit_JSONLifecycle(t *testing.T) { // Force run: the asset moves to forced. forced := run(true) - if got, want := forced.Forced, []string{initAssetName}; !slices.Equal(got, want) { - t.Errorf("force run forced: got %v, want %v", got, want) + if got := forced.Forced; !slices.Equal(got, initAssetNames) { + t.Errorf("force run forced: got %v, want %v", got, initAssetNames) } if len(forced.Written) != 0 || len(forced.Skipped) != 0 { t.Errorf("force run should only populate forced: written=%v skipped=%v", forced.Written, forced.Skipped) @@ -317,11 +334,17 @@ func TestInitCmd_Execute(t *testing.T) { if err := cmd.Execute(); err != nil { t.Fatalf("initCmd Execute returned error: %v", err) } - if !strings.Contains(stdout.String(), initAssetName) { - t.Errorf("expected asset name in output; got:\n%s", stdout.String()) + if !strings.Contains(stdout.String(), "agents/divisor-entropy.md") { + t.Errorf("expected agent asset name in output; got:\n%s", stdout.String()) + } + if !strings.Contains(stdout.String(), "commands/vibe-check.md") { + t.Errorf("expected command asset name in output; got:\n%s", stdout.String()) } - if _, statErr := os.Stat(deployedInitAssetPath(dir)); statErr != nil { - t.Errorf("expected deployed asset on disk: %v", statErr) + if _, statErr := os.Stat(deployedAgentAssetPath(dir)); statErr != nil { + t.Errorf("expected deployed agent asset on disk: %v", statErr) + } + if _, statErr := os.Stat(deployedCommandAssetPath(dir)); statErr != nil { + t.Errorf("expected deployed command asset on disk: %v", statErr) } } @@ -344,8 +367,11 @@ func TestInitCmd_JSONFlag(t *testing.T) { if err := json.Unmarshal(stdout.Bytes(), &p); err != nil { t.Fatalf("json.Unmarshal(%q): %v", stdout.String(), err) } - if !slices.Contains(p.Written, initAssetName) { - t.Errorf("expected %s in written; got %v", initAssetName, p.Written) + if !slices.Contains(p.Written, "agents/divisor-entropy.md") { + t.Errorf("expected agents/divisor-entropy.md in written; got %v", p.Written) + } + if !slices.Contains(p.Written, "commands/vibe-check.md") { + t.Errorf("expected commands/vibe-check.md in written; got %v", p.Written) } } diff --git a/internal/scaffold/assets/agents/vibe-check-reporter.md b/internal/scaffold/assets/agents/vibe-check-reporter.md new file mode 100644 index 0000000..1f10787 --- /dev/null +++ b/internal/scaffold/assets/agents/vibe-check-reporter.md @@ -0,0 +1,341 @@ +--- +description: "Architectural health reporter -- analyzes a Go codebase's coupling metrics via vibe-check and presents results in summary, detailed, or trending mode with natural-language interpretation." +mode: subagent +temperature: 0.3 +permission: + edit: deny + webfetch: deny + bash: + "*": "deny" + "vibe-check analyze *": "allow" + "git rev-parse *": "allow" +--- + + +# Role: The Vibe-Check Reporter + +You are the architectural health reporter for this project. You run +`vibe-check analyze` on the current codebase and interpret the +Martin design-quality metrics -- afferent coupling (Ca), efferent +coupling (Ce), instability (I), abstractness (A), distance from the +main sequence (D), LCOM4 (cohesion), and circular dependencies -- +into actionable, natural-language guidance for the developer. + +You do NOT compute metrics in-prompt. The metrics are computed by the +tested Go `vibe-check analyze` command; you orchestrate the +measurement, then interpret and explain its output. This keeps +results deterministic and grounded in tested code. + +You operate in one of three modes: **summary** (default), **detailed**, +or **trending**. + +--- + +## Source Documents + +Before reporting, read: + +1. `AGENTS.md` -- Project overview, architecture, and coding conventions +2. `.specify/memory/constitution.md` -- Constitution principles (if present) + +--- + +## Mode Parsing + +Parse the user's input (`$ARGUMENTS`) to determine the mode: + +- No arguments, empty string, or `summary` --> **summary mode** +- `detailed` --> **detailed mode** +- `trending` --> **trending mode** +- Any other value --> report that the mode is unrecognized and list + the available modes: `summary`, `detailed`, `trending` + +The remaining arguments after the mode keyword are treated as the +**package pattern** (e.g., `./internal/...`). If no package pattern +is provided, default to `./...` (all packages). + +### Input Validation + +Before passing the package pattern to `vibe-check analyze`, validate +it against the safe character set: `^[A-Za-z0-9./_-]+$` + +If the pattern contains shell metacharacters, spaces, flags not +recognized by `vibe-check analyze`, or is empty after stripping the +mode keyword, reject it with a clear error message: + +> "The package pattern contains invalid characters. Package patterns +> must match `[A-Za-z0-9./_-]+` (e.g., `./...`, `./internal/...`)." + +--- + +## Summary Mode + +Summary mode provides a quick traffic-light health indicator. + +### Steps + +1. Run `vibe-check analyze --output ` where + `` is a file in the OS temporary directory with an + unpredictable name (e.g., `/tmp/vibe-check-.json`) and + `` is the validated package pattern. +2. Read the JSON output from the tempfile. +3. Clean up the tempfile (delete it). If cleanup fails, log a warning + but do not fail. +4. Interpret the results: + +**Exit code 0** (no threshold violations): +- Display a GREEN traffic-light indicator +- Show aggregate metrics: total packages analyzed, average instability, + average distance, total LCOM4, cycle count +- If any warnings exist in the output, mention them briefly + +**Exit code 1** (threshold violations detected): +- Display a RED traffic-light indicator +- Show which thresholds were violated and by which packages +- Provide remediation guidance for each violation + +**Exit code 2** (analysis error): +- Report the error clearly +- Suggest running `vibe-check analyze` manually to diagnose + +### Traffic-Light Format + +``` +## Architectural Health: [GREEN|RED] + +**Packages analyzed**: N +**Average instability**: X.XX +**Average distance**: X.XX +**Total LCOM4 (sum)**: N +**Circular dependencies**: N cycles + +[If RED: list threshold violations with remediation] +[If warnings: brief mention] +``` + +--- + +## Detailed Mode + +Detailed mode provides a per-package breakdown of all metrics. + +### Steps + +1. Run `vibe-check analyze --output ` (same + tempfile pattern as summary mode). +2. Read the JSON output. +3. Clean up the tempfile. +4. Present a per-package metric table: + +### Output Format + +``` +## Detailed Architectural Metrics + +| Package | Ca | Ce | I | A | D | LCOM4 | Zone | +|---------|----|----|---|---|---|-------|------| +| pkg/foo | 3 | 5 | 0.63 | 0.20 | 0.17 | 2 | Balanced | +| pkg/bar | 0 | 8 | 1.00 | 0.00 | 1.00 | 5 | Zone of Pain | +``` + +For each package: +- Classify the zone based on instability and abstractness: + - **Zone of Pain**: high abstractness, low instability (A > 0.5, I < 0.5) + - **Zone of Uselessness**: low abstractness, high instability (A < 0.5, I > 0.5, D > 0.5) + - **Main Sequence**: distance < 0.3 + - **Balanced**: all other cases +- If the package has warnings, note them +- If the package is in a circular dependency, flag it + +After the table, provide a **natural language summary** interpreting +the overall health (see Natural Language Interpretation section below). + +If `vibe-check analyze` exits with code 1 (threshold violations): +- Highlight the violating packages in the table +- Add remediation guidance for each + +If `vibe-check analyze` exits with code 2 (analysis error): +- Report the error and suggest running the CLI manually + +--- + +## Trending Mode + +Trending mode compares current metrics against the most recent +historical snapshot stored in Dewey. + +### Steps + +1. **Check Dewey availability**: Verify `dewey_semantic_search` and + `dewey_store_learning` tools are available. + + If Dewey is NOT available: + > "Trending mode requires Dewey MCP tools for historical comparison. + > Dewey is not available in this session. Use `summary` or `detailed` + > mode instead, or configure Dewey for trending support." + Stop here. + +2. **Run analysis**: Same as summary mode -- run `vibe-check analyze`, + read JSON, clean up tempfile. + +3. **Retrieve previous snapshot**: Call `dewey_semantic_search` with + query `vibe-check-snapshot ` (where `` is + from `go.mod`). Filter results to those whose content contains the + current module path. Parse ISO 8601 timestamps from each result's + content and select the most recent snapshot. + + If no previous snapshot exists: + > "No historical data found. This analysis will be stored as the + > baseline for future trending comparisons." + Store the current snapshot (step 5) and present current metrics + as a standalone report (use detailed mode output). + +4. **Compare metrics**: For each package present in both the current + and previous snapshots, compute the delta and classify: + + - **Improving**: instability/distance delta < -0.01, or LCOM4 + delta <= -1 + - **Degrading**: instability/distance delta > 0.01, or LCOM4 + delta >= 1 + - **Stable**: |instability/distance delta| <= 0.01, or + |LCOM4 delta| < 1 + + Note: Abstractness direction is zone-dependent. Show abstractness + deltas as raw values without improving/degrading classification. + + If a result from `dewey_semantic_search` contains a different + module path than the current project, skip it and search for the + next match. If a retrieved snapshot has missing or corrupted metric + fields, skip it with a warning and use the next available snapshot. + +5. **Store new snapshot**: Call `dewey_store_learning` with: + - `tag`: `vibe-check-snapshot` + - `information`: A compact summary containing: + - Module path (from `go.mod`) + - Commit SHA (from `git rev-parse HEAD`) + - ISO 8601 timestamp + - Per-package metrics (one line per package, ~50 bytes each): + `: I= A= D= LCOM4= Ca= Ce=` + - Total cycle count + + **Deduplication**: Before storing, check whether a snapshot for + the current commit SHA already exists (search Dewey for the SHA). + If one exists, skip storage. + +### Output Format + +``` +## Architectural Trends + +**Comparing**: vs () + +| Package | Instability | Distance | LCOM4 | Direction | +|---------|-------------|----------|-------|-----------| +| pkg/foo | 0.63 -> 0.55 (-0.08) | 0.17 -> 0.10 (-0.07) | 2 -> 2 | Improving | +| pkg/bar | 1.00 -> 1.00 (0.00) | 1.00 -> 1.00 (0.00) | 5 -> 5 | Stable | + +**Overall direction**: [Improving|Stable|Degrading] +[Summary interpretation] +``` + +--- + +## Natural Language Interpretation + +When presenting metrics, translate raw numbers into developer-friendly +explanations: + +### Instability (I) + +- **I = 0.0**: Maximally stable -- many packages depend on this one + (high Ca), so changes here ripple widely. Good for foundational + types and interfaces. +- **I = 1.0**: Maximally unstable -- this package depends on many + others (high Ce) but nothing depends on it. Changes are isolated. + Appropriate for application/CLI layers. +- **I > 0.7**: "Highly unstable -- this package has many outgoing + dependencies relative to incoming ones." +- **I < 0.3**: "Highly stable -- many other packages depend on this + one. Changes should be made carefully." + +### Abstractness (A) + +- **A = 0.0**: Entirely concrete -- no interfaces or abstract types. +- **A = 1.0**: Entirely abstract -- only interfaces and abstract types. +- **A > 0.7**: "Heavily abstract -- consider whether all interfaces + have concrete implementations." +- **A < 0.1**: "Entirely concrete -- consider defining interfaces for + testability and decoupling." + +### Distance from Main Sequence (D) + +- **D < 0.1**: "Balanced -- sits near the ideal line between + abstractness and instability." +- **D > 0.5**: "Far from the main sequence -- may be in the Zone of + Pain (too abstract and stable) or Zone of Uselessness (too concrete + and unstable)." + +### LCOM4 (Lack of Cohesion of Methods) + +- **LCOM4 = 1**: "Perfectly cohesive -- all methods and fields are + connected." +- **LCOM4 = 2-3**: "Slightly fragmented -- consider whether this + package has multiple responsibilities." +- **LCOM4 >= 4**: "Low cohesion -- this package likely contains + multiple unrelated responsibilities. Consider splitting." + +### Circular Dependencies + +- **0 cycles**: "No circular dependencies detected." +- **1+ cycles**: "Circular dependencies detected between: [packages]. + This creates tight coupling and makes independent testing difficult. + Consider introducing an interface to break the cycle." + +--- + +## Graceful Degradation + +### Dewey Unavailable + +When Dewey MCP tools are not available: +- Summary and detailed modes work normally (no Dewey dependency). +- Trending mode reports: "Dewey is not available -- trending mode + requires Dewey for historical snapshot storage and retrieval." +- Snapshot storage is silently skipped in summary/detailed modes. + +### Analysis Errors + +- **Binary not found**: "The `vibe-check` binary is not on PATH. Install + it with `go install github.com/zero-dot-force/vibe-check/cmd/vibe-check@latest` + or build it from source with `go build ./cmd/vibe-check`." +- **Timeout**: "Analysis timed out. Try analyzing fewer packages + (e.g., `./internal/...` instead of `./...`) or increasing the + timeout with `--timeout`." +- **Malformed JSON output**: "Analysis produced invalid output. Try + running `vibe-check analyze ./...` directly to see the raw output + and diagnose the issue." +- **Exit code 2**: Report the stderr output from `vibe-check analyze` + and suggest running it manually. + +### Unrecognized Mode + +Report: "Unrecognized mode: ``. Available modes are: `summary` +(default), `detailed`, `trending`." + +--- + +## Security / Operating Constraints + +The bash allowlist is intentionally minimal: only `vibe-check analyze *` +and `git rev-parse *` are permitted. All other commands are denied. + +- Do NOT attempt to run `vibe-check diff`, `git worktree`, `git fetch`, + or any other commands -- those are the divisor-entropy agent's domain. +- Do NOT compute metric arithmetic in-prompt. Report the values from + the JSON output as-is. +- Do NOT modify any files. The `edit` permission is denied. +- Do NOT fetch external URLs. The `webfetch` permission is denied. + +User-supplied package patterns MUST be validated against the safe +character set (`^[A-Za-z0-9./_-]+$`) before passing to bash. This +is a load-bearing security control. diff --git a/internal/scaffold/assets/commands/vibe-check.md b/internal/scaffold/assets/commands/vibe-check.md new file mode 100644 index 0000000..d198995 --- /dev/null +++ b/internal/scaffold/assets/commands/vibe-check.md @@ -0,0 +1,36 @@ +--- +description: "Analyze architectural health metrics for a Go codebase" +agent: vibe-check-reporter +--- + + +Analyze the architectural health of this Go codebase using coupling, +cohesion, and design quality metrics. + +## Usage + +``` +/vibe-check [mode] [package-pattern] +``` + +## Modes + +| Mode | Description | +|------|-------------| +| `summary` | Traffic-light health indicator with aggregate metrics (default) | +| `detailed` | Per-package metric table with zone classification | +| `trending` | Historical comparison against previous snapshots (requires Dewey) | + +## Examples + +``` +/vibe-check # Summary mode, all packages +/vibe-check summary # Same as above +/vibe-check detailed # Per-package breakdown +/vibe-check detailed ./internal/... # Per-package for internal only +/vibe-check trending # Compare against last snapshot +``` + +## Arguments + +Pass `$ARGUMENTS` to the agent for mode selection and package pattern. diff --git a/internal/scaffold/contract_test.go b/internal/scaffold/contract_test.go new file mode 100644 index 0000000..629ea0a --- /dev/null +++ b/internal/scaffold/contract_test.go @@ -0,0 +1,280 @@ +package scaffold + +import ( + "io/fs" + "path" + "slices" + "strings" + "testing" +) + +// TestEmbeddedAsset_DivisorEntropyContract covers the embedded +// divisor-entropy.md: required frontmatter, provenance marker, sections, and +// the bash allowlist. +func TestEmbeddedAsset_DivisorEntropyContract(t *testing.T) { + t.Parallel() + data, err := fs.ReadFile(agentAssetsFS, embeddedAgentAssetPath) + if err != nil { + t.Fatalf("read embedded asset: %v", err) + } + content := string(data) + + if ok, _ := path.Match("divisor-*.md", deployedAgentAssetName); !ok { + t.Errorf("asset name %q does not match divisor-*.md glob", deployedAgentAssetName) + } + matches, err := fs.Glob(agentAssetsFS, "assets/agents/divisor-*.md") + if err != nil { + t.Fatalf("glob embedded assets: %v", err) + } + if !slices.Contains(matches, embeddedAgentAssetPath) { + t.Errorf("embedded assets missing %q; got %v", embeddedAgentAssetPath, matches) + } + + for _, needle := range []string{ + "mode: subagent", + "temperature: 0.1", + "edit: deny", + "webfetch: deny", + `"*": "deny"`, + } { + if !strings.Contains(content, needle) { + t.Errorf("frontmatter missing %q", needle) + } + } + + if frontmatterDescription(content) == "" { + t.Errorf("frontmatter description is empty or missing") + } + + if !strings.Contains(content, "`), bash allowlist entries (exactly `vibe-check + analyze *` and `git rev-parse *` plus catch-all deny), and required + content sections. +- **Command (`vibe-check.md`)**: Valid YAML frontmatter with required + fields (`description`, `agent: vibe-check-reporter`), mode + documentation (summary/detailed/trending), and `$ARGUMENTS` + passthrough instruction. + +These tests follow the pattern of the existing +`TestEmbeddedAsset_Contract` for `divisor-entropy.md`. + +### 3. Agent behavioral validation + +Agent and command are markdown prompt assets — their behavioral +correctness is validated at integration time (manual invocation of +`/vibe-check` in an OpenCode session), not via Go unit tests. The +contract tests above ensure the structural prerequisites for correct +behavior (frontmatter, permissions, content sections) are in place. diff --git a/openspec/changes/vibe-check-command-and-reporter/proposal.md b/openspec/changes/vibe-check-command-and-reporter/proposal.md new file mode 100644 index 0000000..f5c0484 --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/proposal.md @@ -0,0 +1,86 @@ +# Proposal: /vibe-check Command and vibe-check-reporter Agent + +## Why + +Vibe-Check produces rich structural metrics (instability, abstractness, +distance from main sequence, LCOM4, circular dependencies) but +currently requires manual CLI invocation and JSON interpretation. There +is no way for developers to get architectural feedback within their +normal AI-assisted workflow, and no mechanism to track how metrics +evolve across commits. + +This change closes that gap by creating an OpenCode slash command and +companion agent that bring vibe-check analysis directly into the +developer conversation. It addresses GitHub issue #5 and is scoped to +Phase P2 of the RFC. + +## What Changes + +Two new embedded assets are added to the scaffold system and deployed +via `vibe-check init`: + +1. **`/vibe-check` slash command** (`internal/scaffold/assets/commands/vibe-check.md`) + — An OpenCode command definition that accepts a mode parameter and + delegates to the vibe-check-reporter agent. + +2. **`vibe-check-reporter` agent** (`internal/scaffold/assets/agents/vibe-check-reporter.md`) + — An OpenCode agent that invokes `vibe-check analyze`, interprets the + JSON output, and presents results in natural language with actionable + guidance. + +3. **Scaffold system extension** — The embed directives and deployment + logic in `internal/scaffold/` are extended to support a `commands/` + asset directory alongside the existing `agents/` directory. The + `vibe-check init` command deploys both asset types. + +## Capabilities + +### New Capabilities + +| Capability | Description | +|---|---| +| Summary mode | Traffic-light overview (green/yellow/red) of module health with top-level instability, distance, and LCOM4 summaries. Default mode. | +| Detailed mode | Per-package breakdown showing all Martin metrics, zone classification, and specific warnings with remediation guidance. | +| Trending mode | Compares current analysis against stored baselines using Dewey. Shows metric direction (improving/degrading/stable) per package. | +| Dewey snapshot storage | Agent stores ModuleGraph snapshots in Dewey with timestamp and commit metadata for longitudinal tracking. | +| Dewey snapshot retrieval | Agent retrieves previous snapshots from Dewey to compute trends and show historical context. | +| Natural language interpretation | Agent translates raw metric values into plain-English assessments (e.g., "Package X has high instability (0.89) — it depends on many packages but nothing depends on it, making it fragile to upstream changes"). | + +### Modified Capabilities + +| Capability | Change | +|---|---| +| `internal/scaffold/embed.go` | Add `//go:embed` directive for `assets/commands/*.md`. | +| `internal/scaffold/scaffold.go` | Extend `Run()` to deploy command assets to `.opencode/commands/` in addition to agent assets to `.opencode/agents/`. | +| `internal/scaffold/scaffold_test.go` | Add test coverage for command asset deployment. | + +## Impact + +- **Scaffold contract**: The `Result` struct gains command entries in + Written/Skipped/Forced slices. Existing agent-only consumers see no + behavioral change — new command assets are additive. +- **Dependencies**: No new Go module dependencies. The agent and command + are markdown assets embedded at compile time. The agent relies on + `vibe-check analyze` being available in PATH (already a prerequisite + for any project using vibe-check). +- **Dewey dependency**: Trending mode requires Dewey MCP tools + (`dewey_store_learning`, `dewey_semantic_search`). The agent degrades + gracefully when Dewey is unavailable — trending mode reports that + historical data is not available instead of failing. +- **Issue #2 dependency**: The agent invokes `vibe-check analyze` which + is already implemented (issue #2 is complete). +- **Documentation**: README.md, AGENTS.md, and CHANGELOG.md require + updates to reflect the new command, agent, and modified init behavior. + A website documentation sync issue is required per constitution. + +## Constitution Alignment + +| Principle | Assessment | +|---|---| +| I. Autonomous Collaboration | **Aligned.** The agent and command are self-describing markdown artifacts with metadata (frontmatter). The agent consumes `vibe-check analyze` output (a well-defined JSON schema) and Dewey learnings (tagged, timestamped artifacts) — no synchronous agent-to-agent coupling. | +| II. Composability First | **Aligned.** The agent degrades gracefully when Dewey is unavailable (trending mode reports limitation instead of failing). The command and agent are independently deployable via `vibe-check init`. The agent composes with the existing `vibe-check analyze` CLI without modification. | +| III. Observable Quality | **Partial.** The agent produces conversational markdown, which is human-readable but not machine-parseable. This is acceptable because the agent is a developer-facing interpretation layer, not a CI gate (that role belongs to `divisor-entropy`). The underlying metrics remain machine-parseable via `vibe-check analyze --output`. | +| IV. Testability | **Aligned.** The scaffold Go code (embed, deploy) is tested via unit tests following the existing pattern. Agent and command markdown assets are validated via embedded asset contract tests (frontmatter structure, required sections). Agent behavioral correctness is validated at integration time. Coverage strategy is defined in design.md. | +| V. Security by Default | **Aligned.** The agent's bash allowlist follows least-privilege (only `vibe-check analyze *` and `git rev-parse *`). The scaffold reuses the existing symlink-safe, containment-checked deployment pattern. User-supplied package patterns are validated before shell interpolation. | +| VI. Metric Fidelity | **Aligned.** The agent does not compute metrics — it delegates to `vibe-check analyze` which produces deterministic results. The agent interprets and presents metrics faithfully using the zone classification and threshold definitions from the `metrics/` package. Trending mode uses defined tolerance thresholds for stable/improving/degrading classification. | +| VII. Language Agnosticism | **Not applicable.** This change adds Go-specific agent and command assets. The underlying metrics model remains language-agnostic; the agent invokes the CLI which routes through the adapter registry. | diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/detailed-mode/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/detailed-mode/spec.md new file mode 100644 index 0000000..684f9c3 --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/detailed-mode/spec.md @@ -0,0 +1,74 @@ +# Spec: Detailed Mode + +## ADDED Requirements + +### Requirement: Detailed mode shows per-package metrics + +The agent SHALL present a per-package breakdown of all Martin metrics +when invoked in detailed mode. + +#### Scenario: Detailed mode invocation + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check detailed` +- **THEN** the agent runs `vibe-check analyze --output ./...` + and presents per-package results + +#### Scenario: Detailed mode with package pattern + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check detailed ./internal/...` +- **THEN** the agent validates the package pattern and analyzes only + the specified packages, presenting per-package results for those + packages + +### Requirement: Per-package metric table + +The detailed mode SHALL present each package's metrics in a structured +format including Ca, Ce, instability, abstractness, distance from main +sequence, LCOM4, and zone classification. + +#### Scenario: Package in Zone of Pain + +- **GIVEN** `vibe-check analyze` completes with exit code 0 or 1 +- **WHEN** a package has high stability (low instability) and low + abstractness (concrete + stable = Zone of Pain) +- **THEN** the detailed output identifies the package's zone as + "Zone of Pain" and explains that changes to this package ripple + widely because many packages depend on its concrete implementations + +#### Scenario: Package in Zone of Uselessness + +- **GIVEN** `vibe-check analyze` completes with exit code 0 or 1 +- **WHEN** a package has high instability and high abstractness + (Zone of Uselessness) +- **THEN** the detailed output identifies the zone and explains that + the package defines abstractions nothing concrete depends on + +### Requirement: Warnings and remediation guidance + +The detailed mode SHALL include any warnings from the analysis and +provide actionable remediation guidance for packages with concerning +metrics. + +#### Scenario: High LCOM4 package + +- **GIVEN** `vibe-check analyze` completes with exit code 0 or 1 +- **WHEN** a package has LCOM4 > 1 +- **THEN** the detailed output explains what LCOM4 measures and + suggests the package may benefit from being split into separate + packages along its disconnected responsibility clusters + +#### Scenario: Package with threshold violations + +- **GIVEN** `vibe-check analyze` was invoked with `--max-*` threshold flags +- **WHEN** a package exceeds a configured threshold +- **THEN** the detailed output highlights the violation and provides + specific guidance for reducing the metric value + +#### Scenario: Analysis error in detailed mode + +- **GIVEN** `vibe-check analyze` has been invoked +- **WHEN** the process exits with code 2 (error) +- **THEN** the agent displays the error and does not produce a + per-package table diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-retrieval/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-retrieval/spec.md new file mode 100644 index 0000000..3c96a56 --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-retrieval/spec.md @@ -0,0 +1,81 @@ +# Spec: Dewey Snapshot Retrieval + +## ADDED Requirements + +### Requirement: Agent retrieves previous snapshots from Dewey + +The agent SHALL retrieve the most recent snapshot for the current +project from Dewey when operating in trending mode. + +#### Scenario: Retrieving latest snapshot + +- **GIVEN** the agent operates in trending mode and Dewey is available +- **WHEN** the agent queries for previous snapshots +- **THEN** it calls `dewey_semantic_search` with a query containing + `vibe-check-snapshot` and the current Go module path (from `go.mod`), + filters results to match the current module path in the snapshot + content, parses the ISO 8601 timestamp from each matching result, + and selects the most recent result + +#### Scenario: Multiple snapshots available + +- **GIVEN** Dewey contains multiple snapshots for the project +- **WHEN** the agent retrieves snapshots +- **THEN** the agent selects the most recent snapshot (by parsed + timestamp) as the comparison baseline + +#### Scenario: Snapshot from wrong project returned + +- **GIVEN** Dewey returns a snapshot whose module path does not match + the current project +- **WHEN** the agent filters retrieval results +- **THEN** the agent skips the mismatched snapshot and selects the next + matching result, or falls through to the "no snapshots found" case + +#### Scenario: No snapshots found + +- **GIVEN** the agent operates in trending mode +- **WHEN** Dewey returns no matching snapshots for the current module path +- **THEN** the agent reports that no historical baseline exists and + stores the current analysis as the first snapshot + +### Requirement: Snapshot comparison produces deltas + +The agent SHALL compute the difference between the current analysis +and the retrieved snapshot to produce per-package metric deltas. + +#### Scenario: Package exists in both snapshots + +- **GIVEN** a valid baseline snapshot has been retrieved +- **WHEN** a package exists in both the current analysis and the + retrieved snapshot +- **THEN** the agent computes the delta for each metric (current − baseline) + and classifies the direction as improving (delta ≤ −0.01 for + instability/distance, delta ≤ −1 for LCOM4), degrading (delta ≥ + 0.01 for instability/distance, delta ≥ 1 for LCOM4), or stable + (delta within tolerance). For integer LCOM4, "decreased by 1 or + more" is improving, consistent with the trending mode spec + +#### Scenario: New package not in baseline + +- **GIVEN** a valid baseline snapshot has been retrieved +- **WHEN** a package exists in the current analysis but not in the + retrieved snapshot +- **THEN** the agent reports the package as "new" with its current + metric values and no delta + +#### Scenario: Removed package in baseline only + +- **GIVEN** a valid baseline snapshot has been retrieved +- **WHEN** a package exists in the retrieved snapshot but not in the + current analysis +- **THEN** the agent reports the package as "removed" + +#### Scenario: Corrupted or malformed snapshot + +- **GIVEN** a snapshot is retrieved from Dewey +- **WHEN** the snapshot content cannot be parsed (missing required + fields, non-numeric metric values, or truncated content) +- **THEN** the agent skips the corrupted snapshot, logs a warning, + and falls through to the next most recent snapshot or the "no + snapshots found" case diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-storage/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-storage/spec.md new file mode 100644 index 0000000..52a2b6a --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/dewey-snapshot-storage/spec.md @@ -0,0 +1,65 @@ +# Spec: Dewey Snapshot Storage + +## ADDED Requirements + +### Requirement: Agent stores metric snapshots in Dewey + +The agent SHALL store a summary snapshot of the current analysis in +Dewey after each analysis run when Dewey is available. Snapshots are +stored in all modes (not just trending) so that historical baselines +accumulate for future trending comparisons. + +#### Scenario: Successful analysis with Dewey available + +- **GIVEN** `vibe-check analyze` completes successfully +- **AND** Dewey MCP tools are available +- **WHEN** the agent prepares to store a snapshot +- **THEN** the agent stores a snapshot via `dewey_store_learning` with + the tag `vibe-check-snapshot`, the Go module path (from `go.mod`), + the current commit SHA, a timestamp, and per-package metric summaries + (instability, abstractness, distance, LCOM4, zone) + +#### Scenario: Analysis with Dewey unavailable + +- **GIVEN** `vibe-check analyze` completes successfully +- **WHEN** Dewey MCP tools are not available +- **THEN** the agent skips snapshot storage without error and proceeds + with result presentation + +#### Scenario: Duplicate snapshot for same commit + +- **GIVEN** Dewey is available and contains a snapshot for the current + commit SHA and module path +- **WHEN** the agent completes analysis on the same commit +- **THEN** the agent SHOULD skip storage to avoid redundant snapshots + and note that a snapshot for this commit already exists + +### Requirement: Snapshot content is compact + +The snapshot stored in Dewey SHALL contain only the per-package metric +summary values, not the full ModuleGraph JSON with type details. + +#### Scenario: Snapshot size for large codebase + +- **GIVEN** a codebase has 50+ packages +- **WHEN** the agent stores a snapshot +- **THEN** the stored snapshot contains one record per package with + the six core metric values (Ca, Ce, instability, abstractness, + distance, LCOM4), zone classification, and module-level metadata + (module path, commit SHA, timestamp, package count, cycle count). + Estimated size: ~50 bytes per package (e.g., 200 packages ≈ 10KB), + well within Dewey learning size limits. + +### Requirement: Snapshot includes commit metadata + +Each stored snapshot SHALL include the current commit SHA and timestamp +to enable chronological ordering and commit-level traceability. + +#### Scenario: Snapshot metadata + +- **GIVEN** a snapshot is being stored +- **WHEN** the agent constructs the learning content +- **THEN** the learning content includes the Go module path (from + `go.mod`), the output of `git rev-parse HEAD` as the commit SHA, + and the current ISO 8601 timestamp. The module path enables + disambiguation when multiple projects share the same Dewey instance. diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/natural-language-interpretation/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/natural-language-interpretation/spec.md new file mode 100644 index 0000000..86b3fea --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/natural-language-interpretation/spec.md @@ -0,0 +1,77 @@ +# Spec: Natural Language Interpretation + +## ADDED Requirements + +### Requirement: Agent translates metrics into plain-English assessments + +The agent SHALL translate raw numeric metric values into natural-language +explanations that a developer unfamiliar with Martin metrics can +understand. + +#### Scenario: High instability explanation + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package has instability > 0.7 +- **THEN** the agent explains in plain English that the package depends + on many other packages but few packages depend on it, making it + sensitive to upstream changes (e.g., "Package X has high instability + (0.89) — it depends on many packages but nothing depends on it") + +#### Scenario: Low distance explanation + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package has distance < 0.1 +- **THEN** the agent explains that the package sits near the ideal + balance between stability and abstractness on the main sequence + +#### Scenario: High LCOM4 explanation + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package has LCOM4 > 1 +- **THEN** the agent explains that the package has multiple disconnected + groups of methods/types that do not share state, suggesting it bundles + unrelated responsibilities + +### Requirement: Zone classification with guidance + +The agent SHALL explain each package's zone classification with +actionable guidance appropriate to that zone. + +#### Scenario: Zone of Pain guidance + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package is in the Zone of Pain (high stability, low + abstractness) +- **THEN** the agent explains that the package is concrete and heavily + depended upon, and suggests introducing interfaces to increase + abstractness and move toward the main sequence + +#### Scenario: Zone of Uselessness guidance + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package is in the Zone of Uselessness (high instability, + high abstractness) +- **THEN** the agent explains that the package defines abstractions + with few concrete dependents and suggests evaluating whether the + abstractions serve a real need + +#### Scenario: Main Sequence guidance + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph +- **WHEN** a package is near the main sequence (distance < 0.1) +- **THEN** the agent confirms the package has a healthy balance and + does not flag it for action + +### Requirement: Circular dependency explanation + +The agent SHALL explain circular dependencies in terms a developer +can act on. + +#### Scenario: Cycle with two packages + +- **GIVEN** `vibe-check analyze` has produced a ModuleGraph with cycles +- **WHEN** a circular dependency involves packages A and B +- **THEN** the agent explains which packages form the cycle, why + cycles are problematic (compilation order, testability, deployment + coupling), and suggests strategies to break the cycle (interface + extraction, dependency inversion, package merging) diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/scaffold-command-deployment/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/scaffold-command-deployment/spec.md new file mode 100644 index 0000000..ac2f15d --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/scaffold-command-deployment/spec.md @@ -0,0 +1,94 @@ +# Spec: Scaffold Command Deployment + +## ADDED Requirements + +### Requirement: Scaffold embeds command assets + +The scaffold package SHALL embed markdown files from +`assets/commands/*.md` via a new `//go:embed` directive in `embed.go`. + +#### Scenario: Command asset embedded at compile time + +- **GIVEN** the `assets/commands/` directory contains at least one `.md` file +- **WHEN** the vibe-check binary is built +- **THEN** the `internal/scaffold` package embeds all `.md` files from + `assets/commands/` in addition to the existing `assets/agents/` files + +Note: Go's `//go:embed` with a glob pattern requires at least one +matching file at compile time. The `assets/commands/vibe-check.md` +file serves this role, just as `divisor-entropy.md` does for the +agents directory. The `assets/commands/` directory MUST always contain +at least one `.md` file. + +### Requirement: Scaffold deploys commands to .opencode/commands/ + +The `scaffold.Run()` function SHALL deploy command assets to +`.opencode/commands/` in the target directory, in addition to deploying +agent assets to `.opencode/agents/`. + +#### Scenario: Fresh init with commands + +- **GIVEN** a project directory with no existing `.opencode/commands/` +- **WHEN** `vibe-check init .` is run +- **THEN** the scaffold creates `.opencode/commands/` with directory + permission 0o755, writes all embedded command assets with file + permission 0o644, the deployed content byte-matches the embedded + source, and `Result.Written` contains the command filenames with + category prefix (e.g., `commands/vibe-check.md`) + +#### Scenario: Existing commands directory + +- **GIVEN** a project that already has `.opencode/commands/vibe-check.md` +- **WHEN** `vibe-check init .` is run without `--force` +- **THEN** the scaffold skips the existing file and reports it in the + Skipped slice of the Result + +#### Scenario: Force overwrite + +- **GIVEN** a project that already has `.opencode/commands/vibe-check.md` +- **WHEN** `vibe-check init . --force` is run +- **THEN** existing command files are overwritten with file permission + 0o644 and reported in the Forced slice of the Result + +#### Scenario: Symlink safety for commands directory + +- **GIVEN** `.opencode/commands` is a symlink to an external directory +- **WHEN** `vibe-check init .` is run +- **THEN** the scaffold rejects the symlinked directory and returns an + error, consistent with the existing symlink-safety behavior for + `.opencode/agents/` + +Note: The scaffold MUST apply the same symlink-safety and containment +checks to `.opencode/commands/` as it does to `.opencode/agents/`. +The `ensureDir()` function already handles this — it is reused for +both deployment targets. + +### Requirement: Result struct includes command entries + +The `scaffold.Result` struct's Written, Skipped, and Forced slices +SHALL include command file paths alongside agent file paths. + +#### Scenario: Mixed result output + +- **GIVEN** a project with no existing `.opencode/agents/` or + `.opencode/commands/` directories +- **WHEN** `vibe-check init .` deploys agents and commands +- **THEN** the Result.Written slice contains entries from both + categories with category-prefixed paths (e.g., + `agents/divisor-entropy.md`, `commands/vibe-check.md`) and the + CLI output lists all deployed files grouped by category + +## MODIFIED Requirements + +### Requirement: Scaffold init output + +The `vibe-check init` command output SHALL list both agent and command +files in its summary. + +#### Scenario: Init with both asset types + +- **GIVEN** `vibe-check init .` is run on any project +- **WHEN** the deployment completes successfully +- **THEN** the text output lists all written/skipped/forced files + with their category prefix, regardless of whether they are agents + or commands diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/summary-mode/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/summary-mode/spec.md new file mode 100644 index 0000000..5aac6aa --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/summary-mode/spec.md @@ -0,0 +1,104 @@ +# Spec: Summary Mode + +## ADDED Requirements + +### Requirement: Default mode is summary + +The `/vibe-check` command SHALL default to summary mode when invoked +with no mode argument or with the explicit `summary` argument. + +#### Scenario: No arguments + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check` with no arguments +- **THEN** the agent runs `vibe-check analyze --output ./...` + and presents results in summary format + +#### Scenario: Explicit summary argument + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check summary` +- **THEN** the agent produces the same output as with no arguments + +#### Scenario: Custom package pattern + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check summary ./internal/...` +- **THEN** the agent validates the package pattern against the safe + character set (`^[A-Za-z0-9./_-]+$`) and runs analysis on the + specified package pattern instead of `./...` + +### Requirement: Traffic-light health indicator + +The summary mode SHALL present an overall health indicator using a +traffic-light metaphor (green/yellow/red) based on the analysis +verdict. + +#### Scenario: All metrics within thresholds + +- **GIVEN** `vibe-check analyze` has been invoked successfully +- **WHEN** the process exits with code 0 (no threshold violations) +- **THEN** the agent displays a GREEN health indicator with a message + indicating the module is architecturally healthy + +#### Scenario: Threshold violations detected + +- **GIVEN** `vibe-check analyze` has been invoked successfully +- **WHEN** the process exits with code 1 (threshold violations) +- **THEN** the agent displays a RED health indicator with a count of + packages exceeding thresholds + +#### Scenario: Analysis error + +- **GIVEN** `vibe-check analyze` has been invoked +- **WHEN** the process exits with code 2 (error) +- **THEN** the agent displays the error and does not produce a + traffic-light indicator + +### Requirement: Summary includes top-level metric aggregates + +The summary mode SHALL include aggregate instability, distance, and +LCOM4 statistics across all analyzed packages. + +#### Scenario: Module with multiple packages + +- **GIVEN** `vibe-check analyze` completes with exit code 0 or 1 +- **WHEN** the ModuleGraph contains multiple packages +- **THEN** the summary includes the count of packages analyzed, the + range (min–max) of instability and distance values, and the count of + packages with LCOM4 above 1 + +#### Scenario: Circular dependencies present + +- **GIVEN** `vibe-check analyze` completes with exit code 0 or 1 +- **WHEN** the ModuleGraph contains circular dependencies +- **THEN** the summary includes the number of cycles detected and + lists the packages involved + +### Requirement: Error handling for subprocess failures + +The agent SHALL handle subprocess failure modes gracefully and provide +actionable guidance to the user. + +#### Scenario: vibe-check binary not found + +- **GIVEN** the `vibe-check` binary is not installed or not in PATH +- **WHEN** the agent attempts to run `vibe-check analyze` +- **THEN** the agent reports that vibe-check needs to be installed and + provides installation guidance (e.g., `go install` command) + +#### Scenario: Malformed package pattern + +- **GIVEN** the user provides a package pattern containing shell + metacharacters (e.g., `; rm -rf /` or `$(whoami)`) +- **WHEN** the agent validates the input +- **THEN** the agent rejects the pattern with a clear error message + listing the valid character set and does NOT pass it to bash + +#### Scenario: Unrecognized mode argument + +- **GIVEN** the user invokes `/vibe-check` with an argument +- **WHEN** the argument does not match `summary`, `detailed`, or + `trending` and is not a valid package pattern +- **THEN** the agent reports the unrecognized mode and lists the + available modes (summary, detailed, trending) diff --git a/openspec/changes/vibe-check-command-and-reporter/specs/trending-mode/spec.md b/openspec/changes/vibe-check-command-and-reporter/specs/trending-mode/spec.md new file mode 100644 index 0000000..e754ecf --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/specs/trending-mode/spec.md @@ -0,0 +1,82 @@ +# Spec: Trending Mode + +## ADDED Requirements + +### Requirement: Trending mode compares current against historical snapshots + +The agent SHALL compare the current analysis results against previously +stored snapshots when invoked in trending mode. + +#### Scenario: Trending with available history + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check trending` +- **AND** Dewey contains previous snapshots for this project's module path +- **THEN** the agent retrieves the most recent snapshot and shows + per-package metric direction (improving/degrading/stable) with + delta values + +#### Scenario: Trending with no history + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check trending` +- **AND** Dewey contains no previous snapshots for this project's module path +- **THEN** the agent reports that no historical data is available, + stores the current analysis as the first baseline, and suggests + running trending mode again after future changes + +#### Scenario: Trending with package pattern + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check trending ./internal/...` +- **THEN** the agent validates the package pattern and compares only + the specified packages against their historical values + +### Requirement: Dewey unavailability degrades gracefully + +The trending mode SHALL report a clear limitation message when Dewey +MCP tools are not available, rather than failing. + +#### Scenario: Dewey unavailable + +- **GIVEN** the `vibe-check` binary is installed and in PATH +- **WHEN** user invokes `/vibe-check trending` +- **AND** Dewey MCP tools are not available +- **THEN** the agent reports that trending mode requires Dewey and + suggests using summary or detailed mode instead + +### Requirement: Trending output shows metric direction + +The trending mode SHALL classify each package's metric trajectory as +improving, degrading, or stable. + +#### Scenario: Improving metrics + +- **GIVEN** a previous snapshot exists for comparison +- **WHEN** a package's instability or distance has decreased by more + than 0.01, or LCOM4 has decreased by 1 or more since the last + snapshot +- **THEN** the trending output marks that metric as "improving" with + the delta value + +#### Scenario: Degrading metrics + +- **GIVEN** a previous snapshot exists for comparison +- **WHEN** a package's instability, distance has increased by more + than 0.01, or LCOM4 has increased by 1 or more since the last + snapshot +- **THEN** the trending output marks that metric as "degrading" with + the delta value and flags it for attention + +#### Scenario: Stable metrics + +- **GIVEN** a previous snapshot exists for comparison +- **WHEN** a package's metric deltas are within tolerance (|delta| ≤ + 0.01 for instability/abstractness/distance, |delta| < 1 for LCOM4) +- **THEN** the trending output marks that package as "stable" + +Note: Abstractness direction is zone-dependent — a package in the +Zone of Pain improves by increasing abstractness, while other packages +may not. Trending mode tracks instability, distance, and LCOM4 +directions only. Abstractness deltas are shown as raw values without +improving/degrading classification. diff --git a/openspec/changes/vibe-check-command-and-reporter/tasks.md b/openspec/changes/vibe-check-command-and-reporter/tasks.md new file mode 100644 index 0000000..96220a7 --- /dev/null +++ b/openspec/changes/vibe-check-command-and-reporter/tasks.md @@ -0,0 +1,47 @@ +# Tasks: /vibe-check Command and vibe-check-reporter Agent + +## 1. Scaffold System Extension + +- [x] 1.1 Add `assets/commands/` directory to `internal/scaffold/` with a `.gitkeep` or the command asset file +- [x] 1.2 Add `//go:embed assets/commands/*.md` directive in `internal/scaffold/embed.go` with a new `CommandAssets` variable +- [x] 1.3 Extend `scaffold.Run()` in `internal/scaffold/scaffold.go` to deploy command assets to `.opencode/commands/` in the target directory (same symlink-safe, containment-checked pattern as agent deployment) +- [x] 1.4 Add tests in `internal/scaffold/scaffold_test.go` for command asset deployment: fresh init (verify 0o755 dir, 0o644 files, byte-match content), skip existing, force overwrite, mixed result output with category-prefixed paths. Tests use real embedded assets for contract verification and synthetic `fstest.MapFS` for edge cases. Update existing `TestRun_DeploysEmbeddedAsset` to expect both agent and command files in `Result.Written` +- [x] 1.5 Verify `go build ./...` and `go test -race -count=1 ./internal/scaffold/...` pass + +## 2. Agent Asset + +- [x] 2.1 Create `internal/scaffold/assets/agents/vibe-check-reporter.md` with YAML frontmatter (`description`, `mode: subagent`, `temperature`, `permission` block with bash allowlist for `vibe-check analyze *` and `git rev-parse *`) and `` provenance marker after frontmatter +- [x] 2.2 Write agent body: role definition, source documents to read, mode parsing instructions +- [x] 2.3 Write summary mode instructions: run `vibe-check analyze --output`, parse ModuleGraph JSON, produce traffic-light indicator and aggregate metrics +- [x] 2.4 Write detailed mode instructions: per-package metric table, zone classification, warnings, remediation guidance +- [x] 2.5 Write trending mode instructions: retrieve snapshot from Dewey, compare current vs baseline, show per-package direction (improving/degrading/stable), store new snapshot +- [x] 2.6 Write natural language interpretation guidelines: metric explanations, zone guidance, cycle explanation +- [x] 2.7 Write Dewey snapshot storage instructions: `dewey_store_learning` with tag `vibe-check-snapshot:{module-path}`, commit SHA, module path, timestamp, compact per-package summary (~50 bytes/package). Include deduplication: check for existing snapshot at current commit SHA before storing +- [x] 2.8 Write graceful degradation section: Dewey unavailable handling, analysis error handling (binary not found, timeout, malformed JSON), unrecognized mode handling, input validation (package pattern must match `^[A-Za-z0-9./_-]+$`) +- [x] 2.9 Add contract test for `vibe-check-reporter.md` in `scaffold_test.go`: validate frontmatter (`description`, `mode: subagent`, `temperature`, `permission` block), provenance marker (``), required sections, bash allowlist entries (`vibe-check analyze *`, `git rev-parse *`, catch-all deny) + +## 3. Command Asset + +- [x] 3.1 Create `internal/scaffold/assets/commands/vibe-check.md` with YAML frontmatter (`description`, `agent: vibe-check-reporter`) and `` provenance marker after frontmatter +- [x] 3.2 Write command body: description, usage syntax, mode table (summary/detailed/trending), examples, instructions to pass `$ARGUMENTS` to agent +- [x] 3.3 Add contract test for `vibe-check.md` command asset in `scaffold_test.go`: validate frontmatter (`description`, `agent: vibe-check-reporter`), provenance marker, mode documentation (summary/detailed/trending), `$ARGUMENTS` passthrough instruction + +## 4. Integration Verification + +- [x] 4.1 Run `go build ./...` to verify embed directives compile +- [x] 4.2 Run `go test -race -count=1 ./...` to verify all tests pass +- [x] 4.3 Run `go vet ./...` to verify no vet issues +- [x] 4.4 Test `go run ./cmd/vibe-check init /tmp/test-project` end-to-end and verify both `.opencode/agents/vibe-check-reporter.md` and `.opencode/commands/vibe-check.md` are deployed +- [x] 4.5 Verify existing `divisor-entropy.md` agent deployment is unchanged + +## 5. Documentation Updates + +- [x] 5.1 Update `README.md` "Deploying agents: `vibe-check init`" section to document command deployment alongside agents, the new `/vibe-check` command, and the `vibe-check-reporter` agent +- [x] 5.2 Update `AGENTS.md` project structure to include `internal/scaffold/assets/commands/`, `internal/scaffold/assets/agents/vibe-check-reporter.md`, and updated `embed.go`/`scaffold.go` descriptions +- [x] 5.3 Update `internal/scaffold/doc.go` to describe both asset categories (agents and commands), the new embed directive, and the expanded deployment targets +- [x] 5.4 Update `cmd/vibe-check/init.go`: modify `writeInitSummary()` to reference both agents and commands directories, update cobra `Short`/`Long` descriptions, update GoDoc comments on `InitOptions`, `InitResult`, and `RunInit` +- [x] 5.5 Add `CHANGELOG.md` entry for the new command and agent +- [x] 5.6 File a documentation issue against `zero-dot-force/vibe-check` for README/AGENTS.md updates and a website documentation sync issue against `unbound-force/website` (constitution requirement) + + +