From ae9a0b7a119763ec7423608c9c0fd0b327d9f2fb Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:44:41 +0530 Subject: [PATCH] chore: remove corpus/ and tests/ and drop test step from CI Deletes the corpus YAML layer entirely and the tests/ directory. Keeps the corpus-aware loaders in src/plugins/*/tools/*.ts intact - they silently fall through to in-file data when corpus files are absent, so removing corpus does not break any tool call path. Drops the 'Run tests' step from .github/workflows/publish.yml and the 'test' script from package.json because bun test exits non-zero with zero test files. Typecheck (bun run build) still exits 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 3 - corpus/backend/README.md | 4 - corpus/backend/echo/hello-world.yaml | 29 ---- corpus/backend/echo/index.yaml | 4 - .../backend/golang/context-first-param.yaml | 9 - corpus/backend/golang/error-wrapping.yaml | 13 -- .../backend/golang/goroutine-lifecycle.yaml | 19 --- corpus/backend/golang/index.yaml | 8 - corpus/backend/rust/borrow-over-clone.yaml | 8 - corpus/backend/rust/index.yaml | 8 - corpus/backend/rust/no-unwrap-in-prod.yaml | 10 -- corpus/backend/rust/result-not-panic.yaml | 14 -- corpus/frontend/README.md | 4 - corpus/frontend/design-tokens/index.yaml | 18 -- corpus/frontend/design-tokens/step-1.yaml | 51 ------ corpus/frontend/design-tokens/step-2.yaml | 66 -------- corpus/frontend/design-tokens/step-3.yaml | 41 ----- corpus/frontend/design-tokens/step-4.yaml | 31 ---- corpus/frontend/design-tokens/step-5.yaml | 34 ---- corpus/frontend/design-tokens/step-6.yaml | 33 ---- corpus/frontend/design-tokens/step-7.yaml | 32 ---- corpus/frontend/design-tokens/step-8.yaml | 27 --- corpus/frontend/designer/dashboard.yaml | 90 ---------- corpus/frontend/designer/index.yaml | 4 - corpus/frontend/lenis/accessibility.yaml | 32 ---- corpus/frontend/lenis/custom-container.yaml | 35 ---- .../lenis/framer-motion-integration.yaml | 44 ----- corpus/frontend/lenis/full-page.yaml | 25 --- corpus/frontend/lenis/gsap-integration.yaml | 52 ------ corpus/frontend/lenis/index.yaml | 16 -- corpus/frontend/lenis/next-js.yaml | 36 ---- corpus/frontend/lenis/scroll-to-nav.yaml | 41 ----- corpus/frontend/motion/animatepresence.yaml | 90 ---------- corpus/frontend/motion/examples/scroll.yaml | 54 ------ corpus/frontend/motion/index.yaml | 13 -- corpus/frontend/motion/motion.yaml | 66 -------- corpus/frontend/motion/usescroll.yaml | 106 ------------ corpus/frontend/motion/usespring.yaml | 79 --------- corpus/frontend/react/index.yaml | 10 -- corpus/frontend/react/rsc-default.yaml | 28 ---- corpus/frontend/react/state-hierarchy.yaml | 27 --- corpus/frontend/react/suspense-boundary.yaml | 20 --- corpus/frontend/react/zustand-store.yaml | 33 ---- corpus/frontend/reactflow/background.yaml | 41 ----- corpus/frontend/reactflow/controls.yaml | 46 ------ corpus/frontend/reactflow/handle.yaml | 71 -------- corpus/frontend/reactflow/index.yaml | 14 -- corpus/frontend/reactflow/minimap.yaml | 41 ----- corpus/frontend/reactflow/reactflow.yaml | 84 ---------- corpus/frontend/reactflow/usereactflow.yaml | 48 ------ corpus/frontend/shadcn/button.yaml | 35 ---- corpus/frontend/shadcn/dialog.yaml | 33 ---- corpus/frontend/shadcn/field.yaml | 37 ----- corpus/frontend/shadcn/index.yaml | 10 -- corpus/frontend/shadcn/select.yaml | 33 ---- .../frontend/ui-ux/dark-mode-principles.yaml | 19 --- corpus/frontend/ui-ux/index.yaml | 10 -- corpus/frontend/ui-ux/touch-targets.yaml | 17 -- corpus/frontend/ui-ux/type-scale.yaml | 17 -- corpus/frontend/ui-ux/wcag-contrast.yaml | 12 -- corpus/index.yaml | 23 --- corpus/shared/README.md | 3 - package.json | 1 - tests/artifact-contracts-behaviour.test.ts | 29 ---- tests/context-compiler-behaviour.test.ts | 86 ---------- ...dure-corpus-backed-tools-behaviour.test.ts | 86 ---------- ...late-corpus-backed-tools-behaviour.test.ts | 25 --- ...cipe-corpus-backed-tools-behaviour.test.ts | 22 --- tests/generator-behaviour.test.ts | 34 ---- ...tice-corpus-backed-tools-behaviour.test.ts | 40 ----- tests/helpers.ts | 44 ----- ...tern-corpus-backed-tools-behaviour.test.ts | 69 -------- tests/local-cli-behaviour.test.ts | 20 --- tests/local-cli-routing-behaviour.test.ts | 40 ----- ...tion-corpus-backed-tools-behaviour.test.ts | 53 ------ ...ples-corpus-backed-tools-behaviour.test.ts | 24 --- tests/plugin-registry-behaviour.test.ts | 60 ------- ...tern-corpus-backed-tools-behaviour.test.ts | 49 ------ ...flow-corpus-backed-tools-behaviour.test.ts | 67 -------- tests/role-harness-behaviour.test.ts | 136 --------------- tests/router-behaviour.test.ts | 90 ---------- tests/runtime-behaviour.test.ts | 156 ------------------ ...tice-corpus-backed-tools-behaviour.test.ts | 40 ----- ...nent-corpus-backed-tools-behaviour.test.ts | 41 ----- tests/skills-index-behaviour.test.ts | 69 -------- tests/tool-bridge-behaviour.test.ts | 21 --- tests/topology-artifacts-behaviour.test.ts | 24 --- tests/topology-manifest-behaviour.test.ts | 51 ------ ...iple-corpus-backed-tools-behaviour.test.ts | 49 ------ tests/workflow-behaviour.test.ts | 15 -- 90 files changed, 3402 deletions(-) delete mode 100644 corpus/backend/README.md delete mode 100644 corpus/backend/echo/hello-world.yaml delete mode 100644 corpus/backend/echo/index.yaml delete mode 100644 corpus/backend/golang/context-first-param.yaml delete mode 100644 corpus/backend/golang/error-wrapping.yaml delete mode 100644 corpus/backend/golang/goroutine-lifecycle.yaml delete mode 100644 corpus/backend/golang/index.yaml delete mode 100644 corpus/backend/rust/borrow-over-clone.yaml delete mode 100644 corpus/backend/rust/index.yaml delete mode 100644 corpus/backend/rust/no-unwrap-in-prod.yaml delete mode 100644 corpus/backend/rust/result-not-panic.yaml delete mode 100644 corpus/frontend/README.md delete mode 100644 corpus/frontend/design-tokens/index.yaml delete mode 100644 corpus/frontend/design-tokens/step-1.yaml delete mode 100644 corpus/frontend/design-tokens/step-2.yaml delete mode 100644 corpus/frontend/design-tokens/step-3.yaml delete mode 100644 corpus/frontend/design-tokens/step-4.yaml delete mode 100644 corpus/frontend/design-tokens/step-5.yaml delete mode 100644 corpus/frontend/design-tokens/step-6.yaml delete mode 100644 corpus/frontend/design-tokens/step-7.yaml delete mode 100644 corpus/frontend/design-tokens/step-8.yaml delete mode 100644 corpus/frontend/designer/dashboard.yaml delete mode 100644 corpus/frontend/designer/index.yaml delete mode 100644 corpus/frontend/lenis/accessibility.yaml delete mode 100644 corpus/frontend/lenis/custom-container.yaml delete mode 100644 corpus/frontend/lenis/framer-motion-integration.yaml delete mode 100644 corpus/frontend/lenis/full-page.yaml delete mode 100644 corpus/frontend/lenis/gsap-integration.yaml delete mode 100644 corpus/frontend/lenis/index.yaml delete mode 100644 corpus/frontend/lenis/next-js.yaml delete mode 100644 corpus/frontend/lenis/scroll-to-nav.yaml delete mode 100644 corpus/frontend/motion/animatepresence.yaml delete mode 100644 corpus/frontend/motion/examples/scroll.yaml delete mode 100644 corpus/frontend/motion/index.yaml delete mode 100644 corpus/frontend/motion/motion.yaml delete mode 100644 corpus/frontend/motion/usescroll.yaml delete mode 100644 corpus/frontend/motion/usespring.yaml delete mode 100644 corpus/frontend/react/index.yaml delete mode 100644 corpus/frontend/react/rsc-default.yaml delete mode 100644 corpus/frontend/react/state-hierarchy.yaml delete mode 100644 corpus/frontend/react/suspense-boundary.yaml delete mode 100644 corpus/frontend/react/zustand-store.yaml delete mode 100644 corpus/frontend/reactflow/background.yaml delete mode 100644 corpus/frontend/reactflow/controls.yaml delete mode 100644 corpus/frontend/reactflow/handle.yaml delete mode 100644 corpus/frontend/reactflow/index.yaml delete mode 100644 corpus/frontend/reactflow/minimap.yaml delete mode 100644 corpus/frontend/reactflow/reactflow.yaml delete mode 100644 corpus/frontend/reactflow/usereactflow.yaml delete mode 100644 corpus/frontend/shadcn/button.yaml delete mode 100644 corpus/frontend/shadcn/dialog.yaml delete mode 100644 corpus/frontend/shadcn/field.yaml delete mode 100644 corpus/frontend/shadcn/index.yaml delete mode 100644 corpus/frontend/shadcn/select.yaml delete mode 100644 corpus/frontend/ui-ux/dark-mode-principles.yaml delete mode 100644 corpus/frontend/ui-ux/index.yaml delete mode 100644 corpus/frontend/ui-ux/touch-targets.yaml delete mode 100644 corpus/frontend/ui-ux/type-scale.yaml delete mode 100644 corpus/frontend/ui-ux/wcag-contrast.yaml delete mode 100644 corpus/index.yaml delete mode 100644 corpus/shared/README.md delete mode 100644 tests/artifact-contracts-behaviour.test.ts delete mode 100644 tests/context-compiler-behaviour.test.ts delete mode 100644 tests/design-tokens-procedure-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/designer-page-template-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/echo-recipe-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/generator-behaviour.test.ts delete mode 100644 tests/golang-practice-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/helpers.ts delete mode 100644 tests/lenis-pattern-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/local-cli-behaviour.test.ts delete mode 100644 tests/local-cli-routing-behaviour.test.ts delete mode 100644 tests/motion-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/motion-examples-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/plugin-registry-behaviour.test.ts delete mode 100644 tests/react-pattern-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/reactflow-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/role-harness-behaviour.test.ts delete mode 100644 tests/router-behaviour.test.ts delete mode 100644 tests/runtime-behaviour.test.ts delete mode 100644 tests/rust-practice-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/shadcn-component-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/skills-index-behaviour.test.ts delete mode 100644 tests/tool-bridge-behaviour.test.ts delete mode 100644 tests/topology-artifacts-behaviour.test.ts delete mode 100644 tests/topology-manifest-behaviour.test.ts delete mode 100644 tests/ui-ux-principle-corpus-backed-tools-behaviour.test.ts delete mode 100644 tests/workflow-behaviour.test.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 45bda5e..e0cceb1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,9 +46,6 @@ jobs: - name: Regenerate skills index run: bun run skills:index - - name: Run tests - run: bun test - - name: Type-check run: bun run build diff --git a/corpus/backend/README.md b/corpus/backend/README.md deleted file mode 100644 index 1946fe2..0000000 --- a/corpus/backend/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Backend Corpus - -V1 placeholder root for backend research assets. -The migration keeps existing plugin-backed research and maps future corpus moves here. diff --git a/corpus/backend/echo/hello-world.yaml b/corpus/backend/echo/hello-world.yaml deleted file mode 100644 index cf9e6ca..0000000 --- a/corpus/backend/echo/hello-world.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: hello-world -category: routing -description: Minimal Echo server with a single GET route -when: Starting a new Echo project or verifying your setup -code: | - package main - - import ( - "net/http" - - "github.com/labstack/echo/v4" - ) - - func main() { - e := echo.New() - - e.GET("/", func(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") - }) - - e.Logger.Fatal(e.Start(":8080")) - } -gotchas: - - e.Logger.Fatal calls os.Exit on error - use it only in main - - "echo.New() returns a configured instance; always use this, not raw http.Server" -relatedRecipes: - - crud-api - - middleware-chain - - graceful-shutdown diff --git a/corpus/backend/echo/index.yaml b/corpus/backend/echo/index.yaml deleted file mode 100644 index 0601bb1..0000000 --- a/corpus/backend/echo/index.yaml +++ /dev/null @@ -1,4 +0,0 @@ -namespace: backend.echo -recipes: - hello-world: - file: hello-world.yaml diff --git a/corpus/backend/golang/context-first-param.yaml b/corpus/backend/golang/context-first-param.yaml deleted file mode 100644 index d96b656..0000000 --- a/corpus/backend/golang/context-first-param.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: context-first-param -topic: concurrency -priority: P0 -rule: Pass context.Context as the first parameter to every function that does I/O. -reason: Enables cancellation propagation and deadline enforcement across service boundaries. -good: | - func (s *Service) FetchUser(ctx context.Context, id string) (*User, error) { ... } -bad: | - func (s *Service) FetchUser(id string) (*User, error) { ... } // no cancellation possible diff --git a/corpus/backend/golang/error-wrapping.yaml b/corpus/backend/golang/error-wrapping.yaml deleted file mode 100644 index 4876609..0000000 --- a/corpus/backend/golang/error-wrapping.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: error-wrapping -topic: error-handling -priority: P0 -rule: 'Always wrap errors with context: fmt.Errorf("context: %w", err)' -reason: Provides call stack context without expensive stack traces. %w enables errors.Is/As. -good: | - if err := db.Query(ctx, q); err != nil { - return fmt.Errorf("userRepo.FindByID %s: %w", id, err) - } -bad: | - if err := db.Query(ctx, q); err != nil { - return err // context lost - which query failed? - } diff --git a/corpus/backend/golang/goroutine-lifecycle.yaml b/corpus/backend/golang/goroutine-lifecycle.yaml deleted file mode 100644 index 82f5467..0000000 --- a/corpus/backend/golang/goroutine-lifecycle.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: goroutine-lifecycle -topic: concurrency -priority: P0 -rule: Never start a goroutine without knowing how it stops. -reason: Goroutine leaks exhaust memory. Every goroutine needs a clear exit condition. -good: | - go func() { - defer wg.Done() - for { - select { - case <-ctx.Done(): return // clean exit - case job := <-jobCh: process(job) - } - } - }() -bad: | - go func() { - for job := range jobCh { process(job) } // what if jobCh never closes? - }() diff --git a/corpus/backend/golang/index.yaml b/corpus/backend/golang/index.yaml deleted file mode 100644 index 554773a..0000000 --- a/corpus/backend/golang/index.yaml +++ /dev/null @@ -1,8 +0,0 @@ -namespace: backend.golang -practices: - error-wrapping: - file: error-wrapping.yaml - goroutine-lifecycle: - file: goroutine-lifecycle.yaml - context-first-param: - file: context-first-param.yaml diff --git a/corpus/backend/rust/borrow-over-clone.yaml b/corpus/backend/rust/borrow-over-clone.yaml deleted file mode 100644 index a39f519..0000000 --- a/corpus/backend/rust/borrow-over-clone.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: borrow-over-clone -chapter: coding-styles -rule: Prefer &T over .clone() unless ownership transfer is required. -reason: Cloning allocates heap memory unnecessarily. References are zero-cost. -good: | - fn process(data: &[u8]) -> usize { data.len() } -bad: | - fn process(data: Vec) -> usize { data.len() } // forces caller to clone or give up ownership diff --git a/corpus/backend/rust/index.yaml b/corpus/backend/rust/index.yaml deleted file mode 100644 index ea73150..0000000 --- a/corpus/backend/rust/index.yaml +++ /dev/null @@ -1,8 +0,0 @@ -namespace: backend.rust -practices: - borrow-over-clone: - file: borrow-over-clone.yaml - result-not-panic: - file: result-not-panic.yaml - no-unwrap-in-prod: - file: no-unwrap-in-prod.yaml diff --git a/corpus/backend/rust/no-unwrap-in-prod.yaml b/corpus/backend/rust/no-unwrap-in-prod.yaml deleted file mode 100644 index feea5d2..0000000 --- a/corpus/backend/rust/no-unwrap-in-prod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: no-unwrap-in-prod -chapter: error-handling -rule: Never use unwrap() or expect() outside of tests. -reason: Both panic on None/Err. Use ? operator or proper error handling. -good: | - let value = map.get(&key)?; // returns None/Err to caller -bad: | - let value = map.get(&key).unwrap(); // panics in production -tips: - - expect() is slightly better than unwrap() (message on panic), but still banned in prod diff --git a/corpus/backend/rust/result-not-panic.yaml b/corpus/backend/rust/result-not-panic.yaml deleted file mode 100644 index 22f7966..0000000 --- a/corpus/backend/rust/result-not-panic.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: result-not-panic -chapter: error-handling -rule: Return Result for fallible operations. Never panic! in production code. -reason: panic! unwinds the stack and kills the thread. Use it only for programmer errors. -good: | - fn parse_config(path: &str) -> Result { - let text = fs::read_to_string(path)?; - toml::from_str(&text).map_err(ConfigError::Parse) - } -bad: | - fn parse_config(path: &str) -> Config { - let text = fs::read_to_string(path).unwrap(); // panics if file missing - toml::from_str(&text).unwrap() - } diff --git a/corpus/frontend/README.md b/corpus/frontend/README.md deleted file mode 100644 index 9a025d5..0000000 --- a/corpus/frontend/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Frontend Corpus - -V1 placeholder root for frontend research assets. -The migration keeps existing plugin-backed research and maps future corpus moves here. diff --git a/corpus/frontend/design-tokens/index.yaml b/corpus/frontend/design-tokens/index.yaml deleted file mode 100644 index 1f41659..0000000 --- a/corpus/frontend/design-tokens/index.yaml +++ /dev/null @@ -1,18 +0,0 @@ -namespace: frontend.design-tokens -procedures: - step-1: - file: step-1.yaml - step-2: - file: step-2.yaml - step-3: - file: step-3.yaml - step-4: - file: step-4.yaml - step-5: - file: step-5.yaml - step-6: - file: step-6.yaml - step-7: - file: step-7.yaml - step-8: - file: step-8.yaml diff --git a/corpus/frontend/design-tokens/step-1.yaml b/corpus/frontend/design-tokens/step-1.yaml deleted file mode 100644 index 02352d5..0000000 --- a/corpus/frontend/design-tokens/step-1.yaml +++ /dev/null @@ -1,51 +0,0 @@ -step: 1 -title: Define color ramp primitives in @theme -description: >- - Create 11-stop OKLCH ramps for brand, neutral, and pop colors. These are - static compile-time values. -code: | - @theme { - --color-brand-50: oklch(0.98 0.01 291); - --color-brand-100: oklch(0.96 0.02 291); - --color-brand-200: oklch(0.90 0.04 291); - --color-brand-300: oklch(0.82 0.07 291); - --color-brand-400: oklch(0.72 0.11 291); - --color-brand-500: oklch(0.62 0.14 291); - --color-brand-600: oklch(0.54 0.14 291); - --color-brand-700: oklch(0.46 0.13 291); - --color-brand-800: oklch(0.38 0.11 291); - --color-brand-900: oklch(0.28 0.08 291); - --color-brand-950: oklch(0.18 0.05 291); - - --color-neutral-50: oklch(0.98 0.008 60); - --color-neutral-100: oklch(0.96 0.008 60); - --color-neutral-200: oklch(0.92 0.008 60); - --color-neutral-300: oklch(0.84 0.008 60); - --color-neutral-400: oklch(0.72 0.008 60); - --color-neutral-500: oklch(0.58 0.010 60); - --color-neutral-600: oklch(0.46 0.010 60); - --color-neutral-700: oklch(0.35 0.010 60); - --color-neutral-800: oklch(0.26 0.010 60); - --color-neutral-900: oklch(0.18 0.008 60); - --color-neutral-950: oklch(0.12 0.005 60); - - --color-pop-50: oklch(0.97 0.025 50); - --color-pop-100: oklch(0.94 0.045 50); - --color-pop-200: oklch(0.88 0.075 50); - --color-pop-300: oklch(0.80 0.110 50); - --color-pop-400: oklch(0.72 0.145 50); - --color-pop-500: oklch(0.65 0.165 50); - --color-pop-600: oklch(0.57 0.155 50); - --color-pop-700: oklch(0.48 0.140 50); - --color-pop-800: oklch(0.38 0.110 50); - --color-pop-900: oklch(0.28 0.070 50); - --color-pop-950: oklch(0.18 0.040 50); - } -rules: - - Use @theme (not :root) for primitive ramps - they become Tailwind static utilities - - Hue angle (H) must be consistent across all stops in a ramp - - Chroma (C) peaks around 400-600, decreases toward 50 and 950 - - Lightness (L) must be monotonically decreasing from 50 to 950 -gotchas: - - "@theme values are static - they cannot reference CSS custom properties or be overridden at runtime by JavaScript." - - "If you change the H value after building components, all color utilities change across the app. Lock the hue angle early." diff --git a/corpus/frontend/design-tokens/step-2.yaml b/corpus/frontend/design-tokens/step-2.yaml deleted file mode 100644 index 048795e..0000000 --- a/corpus/frontend/design-tokens/step-2.yaml +++ /dev/null @@ -1,66 +0,0 @@ -step: 2 -title: Map semantic tokens in :root and .dark -description: >- - Create role-based semantic tokens that reference primitives. These are the - tokens your components actually use. -code: | - :root { - /* Backgrounds */ - --color-bg: var(--color-neutral-50); - --color-bg-subtle: var(--color-neutral-100); - --color-surface: #ffffff; - --color-surface-raised: var(--color-neutral-50); - - /* Borders */ - --color-border: var(--color-neutral-200); - --color-border-strong: var(--color-neutral-300); - --color-border-focus: var(--color-brand-500); - - /* Text */ - --color-text: var(--color-neutral-900); - --color-text-muted: var(--color-neutral-500); - --color-text-subtle: var(--color-neutral-400); - --color-text-inverted: #ffffff; - - /* Interactive */ - --color-primary: var(--color-brand-500); - --color-primary-hover: var(--color-brand-600); - --color-primary-fg: #ffffff; - --color-secondary: var(--color-neutral-200); - --color-secondary-hover: var(--color-neutral-300); - --color-secondary-fg: var(--color-neutral-900); - - /* Status */ - --color-success: oklch(0.55 0.14 145); - --color-success-bg: oklch(0.96 0.04 145); - --color-success-fg: #ffffff; - --color-error: oklch(0.55 0.20 25); - --color-error-bg: oklch(0.97 0.04 25); - --color-error-fg: #ffffff; - --color-warning: oklch(0.65 0.18 75); - --color-warning-bg: oklch(0.97 0.04 75); - --color-warning-fg: oklch(0.20 0.05 75); - } - - .dark { - --color-bg: oklch(0.15 0.008 265); - --color-bg-subtle: oklch(0.18 0.008 265); - --color-surface: oklch(0.21 0.008 265); - --color-surface-raised: oklch(0.24 0.008 265); - --color-border: oklch(0.28 0.008 265); - --color-border-strong: oklch(0.35 0.008 265); - --color-text: oklch(0.95 0.008 265); - --color-text-muted: oklch(0.65 0.008 265); - --color-text-subtle: oklch(0.50 0.008 265); - --color-primary: var(--color-brand-400); - --color-primary-hover: var(--color-brand-300); - --color-primary-fg: oklch(0.15 0.005 291); - } -rules: - - Components must only reference semantic tokens, never primitive ramp values directly - - Dark mode requires bg-color elevation (lighter bg per level) since shadows are invisible - - Status color L should be ~0.55 for 4.5:1 contrast with white foreground - - Dark mode primary shifts from brand-500 (light) to brand-400 (dark) for perceptual brightness parity -gotchas: - - "Status colors at L=0.63 (typical green/red) fail 4.5:1 AA with white. Always run contrast check after defining status colors." - - "Do not use hex values for semantic tokens - use oklch primitives so the color stays in the defined color space." diff --git a/corpus/frontend/design-tokens/step-3.yaml b/corpus/frontend/design-tokens/step-3.yaml deleted file mode 100644 index 8016fb5..0000000 --- a/corpus/frontend/design-tokens/step-3.yaml +++ /dev/null @@ -1,41 +0,0 @@ -step: 3 -title: Bridge to Tailwind v4 utilities with @theme inline -description: >- - Use @theme inline to expose runtime-swappable CSS custom properties as - Tailwind utility classes. -code: | - /* @theme inline READS from CSS vars at runtime */ - /* This means dark mode changes propagate to Tailwind utilities automatically */ - @theme inline { - /* Map semantic tokens to Tailwind utility names */ - --color-background: var(--color-bg); - --color-foreground: var(--color-text); - --color-muted: var(--color-text-muted); - --color-border: var(--color-border); - --color-surface: var(--color-surface); - - --color-primary: var(--color-primary); - --color-primary-foreground: var(--color-primary-fg); - --color-secondary: var(--color-secondary); - --color-secondary-foreground: var(--color-secondary-fg); - - --color-success: var(--color-success); - --color-success-foreground: var(--color-success-fg); - --color-error: var(--color-error); - --color-error-foreground: var(--color-error-fg); - --color-warning: var(--color-warning); - --color-warning-foreground: var(--color-warning-fg); - } - - /* Now these work as Tailwind utilities: */ - /* bg-background, text-foreground, bg-primary, text-primary-foreground */ - /* border-border, text-muted, bg-surface, bg-success, text-error... */ -rules: - - "@theme inline is read at runtime - it reflects CSS custom property values dynamically" - - Plain @theme is static - values are baked in at build time - - Use @theme inline ONLY for semantic tokens; use plain @theme for primitive ramps - - "Name mapping: --color-X in @theme inline → bg-X, text-X, border-X Tailwind classes" -gotchas: - - "If you put runtime-swappable vars in plain @theme (not inline), dark mode will NOT work - the values won't update when .dark class is toggled." - - "@theme inline does not accept hardcoded values - it should only map CSS var references." - - "@theme generates Tailwind utility classes but does NOT emit CSS custom properties on :root. If :root uses var(--color-brand-400) and --color-brand-400 is only defined in @theme, the var() resolves to undefined at runtime - making all text and backgrounds white. Correct architecture: raw OKLCH values on :root and .dark (runtime CSS vars), @theme inline bridges them to utilities, @theme separately generates primitive ramp utilities." diff --git a/corpus/frontend/design-tokens/step-4.yaml b/corpus/frontend/design-tokens/step-4.yaml deleted file mode 100644 index 0d7ec16..0000000 --- a/corpus/frontend/design-tokens/step-4.yaml +++ /dev/null @@ -1,31 +0,0 @@ -step: 4 -title: Define spacing system -description: Set the 4px base multiplier and create named semantic spacing tokens. -code: | - @theme { - /* 4px base - sets the multiplier for p-1, p-2, p-4, etc. */ - --spacing: 0.25rem; - } - - /* Named semantic tokens in :root */ - :root { - --spacing-section-y: 6rem; /* 96px */ - --spacing-section-x: 1.5rem; /* 24px mobile, override at md */ - --spacing-card: 1.75rem; /* 28px */ - --spacing-card-sm: 1.25rem; /* 20px */ - --spacing-grid-cards: 1.5rem; /* 24px */ - --spacing-inline: 0.5rem; /* 8px */ - --spacing-form-gap: 1.25rem; /* 20px */ - } - - @media (min-width: 768px) { - :root { - --spacing-section-x: 3rem; /* 48px desktop */ - } - } -rules: - - "--spacing is the global multiplier - changing it scales ALL numeric spacing utilities" - - "Named tokens auto-generate Tailwind utilities: p-card, py-section-y, gap-grid-cards" - - "Always follow 4px grid: 0.25rem, 0.5rem, 0.75rem, 1rem, 1.25rem, 1.5rem, 1.75rem..." -gotchas: - - "--spacing is NOT an individual token for a specific spacing value. It is the base MULTIPLIER that affects p-1, p-2, p-4, etc. Overriding it changes every numeric spacing utility in the project." diff --git a/corpus/frontend/design-tokens/step-5.yaml b/corpus/frontend/design-tokens/step-5.yaml deleted file mode 100644 index 7591d60..0000000 --- a/corpus/frontend/design-tokens/step-5.yaml +++ /dev/null @@ -1,34 +0,0 @@ -step: 5 -title: Set up typography scale -description: >- - Define fluid heading sizes with clamp(), fixed body sizes, and line-height - and tracking tokens. -code: | - :root { - --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif; - --font-mono: "JetBrains Mono Variable", ui-monospace, monospace; - - --text-display: clamp(3rem, 5vw + 1rem, 4.5rem); - --text-h1: clamp(2.25rem, 3.5vw + 0.5rem, 3rem); - --text-h2: clamp(1.75rem, 2.5vw + 0.25rem, 2.25rem); - --text-h3: clamp(1.375rem, 1.5vw + 0.25rem, 1.5rem); - --text-body: 1rem; - --text-body-sm: 0.875rem; - --text-caption: 0.75rem; - --text-overline: 0.6875rem; - - --leading-display: 1.05; - --leading-heading: 1.2; - --leading-body: 1.7; - - --tracking-tight: -0.03em; - --tracking-heading: -0.02em; - --tracking-normal: 0em; - --tracking-wide: 0.10em; - } -rules: - - Body text (16px) is NEVER fluid - use fixed rem values - - Headings MUST have tight line-height (1.05-1.2), not body line-height (1.6+) - - Negative tracking on body text is a readability error -gotchas: - - Applying clamp() to body text causes font-size to change on window resize, causing reflow and jarring user experience. diff --git a/corpus/frontend/design-tokens/step-6.yaml b/corpus/frontend/design-tokens/step-6.yaml deleted file mode 100644 index 159f382..0000000 --- a/corpus/frontend/design-tokens/step-6.yaml +++ /dev/null @@ -1,33 +0,0 @@ -step: 6 -title: Define shadows and elevation -description: >- - Build the 5-level elevation shadow system using warm oklch-tinted shadows. -code: | - :root { - --shadow-xs: 0 1px 2px 0 oklch(0.30 0.02 60 / 0.08); - --shadow-sm: - 0 1px 3px 0 oklch(0.30 0.02 60 / 0.10), - 0 1px 2px -1px oklch(0.30 0.02 60 / 0.06); - --shadow-md: - 0 4px 6px -1px oklch(0.30 0.02 60 / 0.10), - 0 2px 4px -2px oklch(0.30 0.02 60 / 0.06); - --shadow-lg: - 0 10px 15px -3px oklch(0.30 0.02 60 / 0.10), - 0 4px 6px -4px oklch(0.30 0.02 60 / 0.05); - --shadow-xl: - 0 20px 25px -5px oklch(0.30 0.02 60 / 0.10), - 0 8px 10px -6px oklch(0.30 0.02 60 / 0.04); - } - - .dark { - --shadow-xs: none; --shadow-sm: none; - --shadow-md: none; --shadow-lg: none; --shadow-xl: none; - --color-surface-1: oklch(0.22 0.008 265); - --color-surface-2: oklch(0.26 0.008 265); - --color-surface-3: oklch(0.30 0.008 265); - } -rules: - - Use oklch-tinted shadows, never rgba(0,0,0) - - Dark mode disables all shadows, uses bg-color elevation instead -gotchas: - - rgba(0,0,0) shadows look cold on warm backgrounds. The warm hue tint (H=60) makes shadows feel natural. diff --git a/corpus/frontend/design-tokens/step-7.yaml b/corpus/frontend/design-tokens/step-7.yaml deleted file mode 100644 index be8b7f3..0000000 --- a/corpus/frontend/design-tokens/step-7.yaml +++ /dev/null @@ -1,32 +0,0 @@ -step: 7 -title: Define motion and z-index tokens -description: Add duration, easing, and z-index scale with prefers-reduced-motion. -code: | - @theme { - --duration-fast: 100ms; - --duration-normal: 200ms; - --duration-slow: 300ms; - --ease-out: cubic-bezier(0, 0, 0.2, 1); - --ease-in: cubic-bezier(0.4, 0, 1, 1); - --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); - - --z-dropdown: 1000; - --z-sticky: 1010; - --z-modal: 1050; - --z-tooltip: 1070; - --z-toast: 1080; - } - - @layer base { - @media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - animation-duration: 0.01ms !important; - transition-duration: 0.01ms !important; - } - } - } -rules: - - prefers-reduced-motion MUST be in @layer base with !important - - Never use arbitrary z-index values -gotchas: - - Forgetting prefers-reduced-motion is a WCAG 2.3.3 violation. diff --git a/corpus/frontend/design-tokens/step-8.yaml b/corpus/frontend/design-tokens/step-8.yaml deleted file mode 100644 index 8ad66b2..0000000 --- a/corpus/frontend/design-tokens/step-8.yaml +++ /dev/null @@ -1,27 +0,0 @@ -step: 8 -title: Run contrast audit and verify token usage -description: >- - After building the full token system, run a contrast audit on all color - token pairs. -code: | - /* Run contrast checks on these pairs: */ - /* --color-text on --color-bg → target 7:1 (AAA) */ - /* --color-text-muted on --color-bg → target 4.5:1 (AA) */ - /* --color-primary-fg on --color-primary → target 4.5:1 (AA) */ - /* --color-success-fg on --color-success → target 4.5:1 (AA) */ - /* --color-error-fg on --color-error → target 4.5:1 (AA) */ - /* --color-border on --color-bg → target 1.1:1+ (perceptible) */ - - /* Tools: */ - /* - https://oklch.com - build and preview oklch colors */ - /* - https://www.myndex.com/APCA/ - APCA contrast checker */ - /* - Storybook a11y addon - automated component-level checks */ - - /* Check for stale hue references: */ - /* grep -r "violet-\|purple-\|blue-" src/ (old hardcoded Tailwind hue names) */ -rules: - - Run contrast audit AFTER finalizing the palette - before building components - - Status colors often need L adjusted to ~0.55 for AA compliance with white foreground - - Grep for stale hue-name references after renaming ramps -gotchas: - - "WCAG contrast is calculated on final rendered colors. If CSS vars are not resolving correctly in dark mode, contrast tools won't catch it - test with a browser extension on the live page." diff --git a/corpus/frontend/designer/dashboard.yaml b/corpus/frontend/designer/dashboard.yaml deleted file mode 100644 index b616096..0000000 --- a/corpus/frontend/designer/dashboard.yaml +++ /dev/null @@ -1,90 +0,0 @@ -type: dashboard -description: >- - Data-forward workspace with a fixed shell, clear hierarchy, and enough density - for power users without sacrificing readability. -layout: >- - App shell: fixed sidebar (240-280px) + header (56-64px) + scrollable content - area -sections: - - name: Sidebar Navigation - required: true - components: - - Logo/brand mark - - Primary nav items (5-7 max) - - Workspace switcher - - Collapsed state for mobile - - User avatar + settings at bottom - notes: >- - Persistent, keyboard navigable, and clearly indicates the active route. - - name: Header Bar - required: true - components: - - Page title / breadcrumbs - - Search (Cmd+K) - - Notifications - - User menu - notes: >- - Fixed or sticky. Keep power-user actions in the header so they are always - reachable. - - name: Metrics Row - required: false - components: - - 3-5 KPI cards - - Trend indicator (up/down arrow + %) - - Sparkline or mini chart - notes: >- - Group related metrics and keep visible count within working-memory limits. - - name: Primary Content - required: true - components: - - Data table or card grid - - Filters + sort controls - - Pagination or virtual scroll - - Bulk actions toolbar - notes: >- - Tables should right-align numbers, use monospace for values, and keep - header rows sticky. - - name: Detail Panel - required: false - components: - - Side drawer (not modal) - - Record details - - Edit-in-place or form - - Action buttons - notes: >- - Keep list context visible while the user inspects or edits a record. - - name: Empty States - required: true - components: - - Illustration (optional) - - Headline: what's missing - - Body: what to do next - - Single CTA - notes: >- - Every data container needs an empty state that helps the user recover or - create the first item. -cognitiveApply: - - miller - - hick - - gestalt - - fitts - - doherty -compositionApply: - - grid-theory - - visual-hierarchy -interactionApply: - - navigation - - skeleton-vs-spinner - - progressive-disclosure - - empty-states - - feedback -writingApply: - - headlines - - empty-states-copy - - loading-states -keyRules: - - Command palette (Cmd+K) for 50+ feature apps - - Keyboard shortcuts for all frequent actions - - Skeleton loaders for data panels, not spinners - - Right-align quantitative data in monospace font - - Compact density: 14px body, 48px section gaps, 20px card padding diff --git a/corpus/frontend/designer/index.yaml b/corpus/frontend/designer/index.yaml deleted file mode 100644 index de0c3c5..0000000 --- a/corpus/frontend/designer/index.yaml +++ /dev/null @@ -1,4 +0,0 @@ -namespace: frontend.designer -pageTemplates: - dashboard: - file: dashboard.yaml diff --git a/corpus/frontend/lenis/accessibility.yaml b/corpus/frontend/lenis/accessibility.yaml deleted file mode 100644 index 37510b3..0000000 --- a/corpus/frontend/lenis/accessibility.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: accessibility -description: >- - Respect prefers-reduced-motion by disabling smooth scrolling for users who - prefer it. -code: | - "use client"; - - import { ReactLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - - function useReducedMotion() { - if (typeof window === "undefined") return false; - return window.matchMedia("(prefers-reduced-motion: reduce)").matches; - } - - export function AccessibleSmoothScrollProvider({ children }: { children: React.ReactNode }) { - const prefersReducedMotion = useReducedMotion(); - - if (prefersReducedMotion) { - return <>{children}; - } - - return ( - - {children} - - ); - } -tips: - - Never force smooth scrolling on users who have opted out via prefers-reduced-motion. - - When skipping ReactLenis, native scroll is used - no polyfill needed. - - For a hook-based approach, use the useReducedMotion hook from Framer Motion or write your own with a useEffect + matchMedia listener. diff --git a/corpus/frontend/lenis/custom-container.yaml b/corpus/frontend/lenis/custom-container.yaml deleted file mode 100644 index 07e8955..0000000 --- a/corpus/frontend/lenis/custom-container.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: custom-container -description: >- - Scoped scroll container using wrapper and content refs for non-window smooth - scroll. -code: | - "use client"; - - import { useRef } from "react"; - import { ReactLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - - export function ScrollPanel({ children }: { children: React.ReactNode }) { - const wrapperRef = useRef(null); - const contentRef = useRef(null); - - return ( - -
-
- {children} -
-
-
- ); - } -tips: - - "The wrapper element needs overflow: hidden and a fixed height for container scroll to work." - - The content element is the scrollable inner div - it should grow naturally with its children. - - This pattern is useful for split-panel layouts, side drawers, or modal scroll areas. diff --git a/corpus/frontend/lenis/framer-motion-integration.yaml b/corpus/frontend/lenis/framer-motion-integration.yaml deleted file mode 100644 index 6ec978c..0000000 --- a/corpus/frontend/lenis/framer-motion-integration.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: framer-motion-integration -description: >- - Integrate Lenis with Framer Motion by disabling autoRaf and syncing via - frame.update. -code: | - "use client"; - - import { useEffect } from "react"; - import { ReactLenis, useLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - import { frame } from "motion"; - - function FramerSyncEffect() { - const lenis = useLenis(); - - useEffect(() => { - if (!lenis) return; - - function update({ timestamp }: { timestamp: number }) { - lenis.raf(timestamp); - } - - frame.update(update, true); - - return () => { - frame.cancel(update); - }; - }, [lenis]); - - return null; - } - - export function FramerLenisProvider({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - ); - } -tips: - - "Use frame from 'motion' (not 'framer-motion') - this is the Framer Motion v11+ low-level scheduler." - - "frame.update(fn, true) schedules the update to run on every frame. The second argument (true) enables loop mode." - - "autoRaf: false is mandatory - same reasoning as with GSAP, prevent double-ticking." diff --git a/corpus/frontend/lenis/full-page.yaml b/corpus/frontend/lenis/full-page.yaml deleted file mode 100644 index 64a6b65..0000000 --- a/corpus/frontend/lenis/full-page.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: full-page -description: >- - Standard root layout setup - ReactLenis wraps the entire app for full-page - smooth scrolling. -code: | - // app/layout.tsx (Next.js App Router) - "use client"; // Must be client component - - import { ReactLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - - export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - ); - } -tips: - - "root={true} is required for full-page scroll - without it, Lenis creates an overflow:hidden container." - - The CSS import is mandatory - skip it and the layout breaks. diff --git a/corpus/frontend/lenis/gsap-integration.yaml b/corpus/frontend/lenis/gsap-integration.yaml deleted file mode 100644 index 04cdac8..0000000 --- a/corpus/frontend/lenis/gsap-integration.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: gsap-integration -description: >- - Integrate Lenis with GSAP ScrollTrigger by disabling autoRaf and driving Lenis - from GSAP's ticker. -code: | - "use client"; - - import { useEffect } from "react"; - import { ReactLenis, useLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - import gsap from "gsap"; - import ScrollTrigger from "gsap/ScrollTrigger"; - - gsap.registerPlugin(ScrollTrigger); - - function GSAPSyncEffect() { - const lenis = useLenis(); - - useEffect(() => { - if (!lenis) return; - - // Sync Lenis with GSAP ticker - gsap.ticker.add((time) => { - lenis.raf(time * 1000); - }); - - // Disable GSAP's lagSmoothing to prevent conflicts - gsap.ticker.lagSmoothing(0); - - // Update ScrollTrigger on each scroll - lenis.on("scroll", ScrollTrigger.update); - - return () => { - lenis.off("scroll", ScrollTrigger.update); - }; - }, [lenis]); - - return null; - } - - export function GSAPLenisProvider({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - ); - } -tips: - - autoRaf: false is required - if GSAP and Lenis both run their own RAF loops, scroll updates fire twice per frame causing desync. - - gsap.ticker.lagSmoothing(0) prevents GSAP from adjusting delta time which would cause Lenis to stutter after tab switches. - - lenis.raf() expects milliseconds - multiply GSAP time (seconds) by 1000. diff --git a/corpus/frontend/lenis/index.yaml b/corpus/frontend/lenis/index.yaml deleted file mode 100644 index 1a865b8..0000000 --- a/corpus/frontend/lenis/index.yaml +++ /dev/null @@ -1,16 +0,0 @@ -namespace: frontend.lenis -patterns: - gsap-integration: - file: gsap-integration.yaml - full-page: - file: full-page.yaml - next-js: - file: next-js.yaml - framer-motion-integration: - file: framer-motion-integration.yaml - custom-container: - file: custom-container.yaml - scroll-to-nav: - file: scroll-to-nav.yaml - accessibility: - file: accessibility.yaml diff --git a/corpus/frontend/lenis/next-js.yaml b/corpus/frontend/lenis/next-js.yaml deleted file mode 100644 index 6f63ed1..0000000 --- a/corpus/frontend/lenis/next-js.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: next-js -description: >- - Next.js App Router pattern using a dedicated SmoothScrollProvider client - component to wrap the layout. -code: | - // components/smooth-scroll-provider.tsx - "use client"; - - import { ReactLenis } from "lenis/react"; - import "lenis/dist/lenis.css"; - - export function SmoothScrollProvider({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); - } - - // app/layout.tsx - import { SmoothScrollProvider } from "@/components/smooth-scroll-provider"; - - export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - ); - } -tips: - - "Keep app/layout.tsx as a Server Component - extract the 'use client' directive into SmoothScrollProvider." - - This preserves RSC boundaries and avoids unnecessarily client-rendering the entire layout. diff --git a/corpus/frontend/lenis/scroll-to-nav.yaml b/corpus/frontend/lenis/scroll-to-nav.yaml deleted file mode 100644 index 575d074..0000000 --- a/corpus/frontend/lenis/scroll-to-nav.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: scroll-to-nav -description: >- - Navigation link that uses lenis.scrollTo() for smooth in-page anchor - navigation. -code: | - "use client"; - - import { useLenis } from "lenis/react"; - - interface NavLinkProps { - href: string; - children: React.ReactNode; - offset?: number; - duration?: number; - } - - export function NavLink({ href, children, offset = -80, duration = 1.2 }: NavLinkProps) { - const lenis = useLenis(); - - function handleClick(e: React.MouseEvent) { - e.preventDefault(); - lenis?.scrollTo(href, { - offset, - duration, - easing: (t) => 1 - Math.pow(1 - t, 4), - }); - } - - return ( - - {children} - - ); - } - - // Usage: - // Features -tips: - - offset compensates for sticky headers - pass a negative value equal to the header height. - - "lenis.scrollTo() accepts a CSS selector ('#section'), HTMLElement, or pixel number." - - For Next.js App Router, use usePathname to detect route changes and reset scroll position. diff --git a/corpus/frontend/motion/animatepresence.yaml b/corpus/frontend/motion/animatepresence.yaml deleted file mode 100644 index fe02a36..0000000 --- a/corpus/frontend/motion/animatepresence.yaml +++ /dev/null @@ -1,90 +0,0 @@ -name: AnimatePresence -kind: component -description: >- - Enables exit animations when children are removed from the React tree. Direct - children must have unique key props. -importPath: 'import { AnimatePresence } from "motion/react"' -props: - - name: initial - type: boolean - description: Set false to disable initial animations on first render. - default: "true" - - name: mode - type: "'sync' | 'wait' | 'popLayout'" - description: "sync: simultaneous enter/exit. wait: exit completes before enter. popLayout: pop exiting elements out of layout flow." - default: "'sync'" - - name: custom - type: any - description: Data passed to exiting components via usePresenceData(). - - name: onExitComplete - type: () => void - description: Fires when all exit animations finish. - - name: propagate - type: boolean - description: If true, nested AnimatePresence children fire exit animations when parent exits. - - name: root - type: ShadowRoot | HTMLElement - description: Root element for popLayout styles. Defaults to document.head. Set to a ShadowRoot for shadow DOM usage. -usage: | - - {show && ( - - )} - -examples: - - title: Modal with exit animation - category: exit - code: | - function Modal({ isOpen, onClose }) { - return ( - - {isOpen && ( - - e.stopPropagation()} - > - Modal content - - - )} - - ); - } - - title: Page transitions with wait mode - category: exit - code: | - - - {children} - - -tips: - - "AnimatePresence must wrap the conditional - it goes OUTSIDE the {show && ...}." - - Each direct child needs a unique key prop. - - mode='wait' is useful for page transitions where old page exits before new enters. - - "mode='popLayout' pops exiting elements out of document flow immediately. Custom component children must use forwardRef (React 18) or accept ref prop (React 19)." -relatedApis: - - motion - - usePresence - - useIsPresent - - usePresenceData diff --git a/corpus/frontend/motion/examples/scroll.yaml b/corpus/frontend/motion/examples/scroll.yaml deleted file mode 100644 index a192acc..0000000 --- a/corpus/frontend/motion/examples/scroll.yaml +++ /dev/null @@ -1,54 +0,0 @@ -category: scroll -examples: - - title: Scroll progress rail - description: Fixed progress indicator driven by the page scroll position. - code: | - import { motion, useScroll, useSpring } from "motion/react" - - export function ScrollProgressRail() { - const { scrollYProgress } = useScroll() - const scaleX = useSpring(scrollYProgress, { stiffness: 120, damping: 30 }) - - return ( - - ) - } - - title: Section reveal on enter - description: Fade and lift content as it enters the viewport without wiring the effect to a global progress bar. - code: | - import { motion } from "motion/react" - - export function RevealSection({ children }) { - return ( - - {children} - - ) - } - - title: Horizontal story strip - description: Drive a horizontal motion lane from vertical scroll for long-form storytelling or product tours. - code: | - import { motion, useScroll, useTransform } from "motion/react" - import { useRef } from "react" - - export function HorizontalStoryStrip() { - const ref = useRef(null) - const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] }) - const x = useTransform(scrollYProgress, [0, 1], ["0%", "-75%"]) - - return ( -
-
- -
-
- ) - } diff --git a/corpus/frontend/motion/index.yaml b/corpus/frontend/motion/index.yaml deleted file mode 100644 index 0bc019a..0000000 --- a/corpus/frontend/motion/index.yaml +++ /dev/null @@ -1,13 +0,0 @@ -namespace: frontend.motion -apis: - motion: - file: motion.yaml - animatepresence: - file: animatepresence.yaml - usescroll: - file: usescroll.yaml - usespring: - file: usespring.yaml -examples: - scroll: - file: examples/scroll.yaml diff --git a/corpus/frontend/motion/motion.yaml b/corpus/frontend/motion/motion.yaml deleted file mode 100644 index d05f122..0000000 --- a/corpus/frontend/motion/motion.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: motion -kind: component -description: >- - The core building block. Every HTML and SVG element has a motion counterpart - (motion.div, motion.span, motion.circle, etc.). Accepts all standard props - plus animation-specific props. -importPath: import { motion } from "motion/react" -usage: | - import { motion } from "motion/react" - - export function HeroCard() { - return ( - - Motion content - - ) - } -props: - - name: initial - type: Target | VariantLabels | false - description: Initial visual state on mount. Set false to disable enter animation. - - name: animate - type: Target | VariantLabels - description: Target animation values on enter and update. - - name: exit - type: Target | VariantLabels - description: Target animation when removed (requires AnimatePresence parent). - - name: transition - type: Transition - description: Default transition config for this component. - - name: variants - type: Variants - description: "Named animation states. Object of { [name]: target }." - - name: style - type: MotionStyle - description: Extended style prop supporting motion values and independent transforms. -examples: - - title: Basic fade in - category: animation - code: | - import { motion } from "motion/react" - - export function FadeInCard() { - return ( - - Hello - - ) - } -tips: - - Use motion/react-client for React Server Components (Next.js app dir). - - motion.create(Component) wraps custom components. In React 18 the component must use forwardRef. In React 19 ref is passed via props automatically. - - Independent transforms: x, y, z, scale, scaleX, scaleY, rotate, rotateX, rotateY, rotateZ, skewX, skewY, transformPerspective. -relatedApis: - - AnimatePresence - - useAnimate - - useMotionValue - - MotionConfig diff --git a/corpus/frontend/motion/usescroll.yaml b/corpus/frontend/motion/usescroll.yaml deleted file mode 100644 index a90b612..0000000 --- a/corpus/frontend/motion/usescroll.yaml +++ /dev/null @@ -1,106 +0,0 @@ -name: useScroll -kind: hook -description: >- - Creates scroll-linked motion values. Tracks window or element scroll - position. Supports hardware-accelerated ScrollTimeline. -importPath: 'import { useScroll } from "motion/react"' -returns: "{ scrollX, scrollY, scrollXProgress, scrollYProgress }" -props: - - name: container - type: RefObject - description: "Scrollable element ref. Default: window." - - name: target - type: RefObject - description: Element to track progress through container. - - name: axis - type: "'x' | 'y'" - description: Scroll axis. - default: "'y'" - - name: offset - type: "[string, string]" - description: "Start/end intersection points. Format: 'targetPoint containerPoint'. Points: start, center, end, 0-1, px, %, vh, vw." - default: '["start start", "end end"]' - - name: trackContentSize - type: boolean - description: Track content size changes. - default: "false" -usage: | - // Window scroll - const { scrollYProgress } = useScroll(); - - // Element scroll - const ref = useRef(null); - const { scrollYProgress } = useScroll({ container: ref }); - - // Track element through viewport - const ref = useRef(null); - const { scrollYProgress } = useScroll({ - target: ref, - offset: ["start end", "end start"] - }); -examples: - - title: Scroll progress bar - category: scroll - code: | - function ProgressBar() { - const { scrollYProgress } = useScroll(); - - return ( - - ); - } - - title: Element reveal on scroll - category: scroll - code: | - function RevealSection() { - const ref = useRef(null); - const { scrollYProgress } = useScroll({ - target: ref, - offset: ["start end", "center center"] - }); - const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]); - const y = useTransform(scrollYProgress, [0, 1], [100, 0]); - - return ( - - Content revealed on scroll - - ); - } - - title: Horizontal scroll section - category: scroll - code: | - function HorizontalScroll() { - const containerRef = useRef(null); - const { scrollYProgress } = useScroll({ target: containerRef }); - const x = useTransform(scrollYProgress, [0, 1], ["0%", "-75%"]); - - return ( -
-
- - {panels.map(panel => )} - -
-
- ); - } -tips: - - "Offset format: ['start end', 'end start'] means animation starts when target's start hits viewport end, ends when target's end hits viewport start." - - Use useTransform to map scrollYProgress to transform/opacity/filter for GPU-accelerated animations. - - Combine with useSpring for smoothed scroll animations. -relatedApis: - - useTransform - - useSpring - - useMotionValue diff --git a/corpus/frontend/motion/usespring.yaml b/corpus/frontend/motion/usespring.yaml deleted file mode 100644 index eb8e2ad..0000000 --- a/corpus/frontend/motion/usespring.yaml +++ /dev/null @@ -1,79 +0,0 @@ -name: useSpring -kind: hook -description: >- - Creates a motion value that animates to targets with spring physics. Can - track another motion value. -importPath: 'import { useSpring } from "motion/react"' -returns: MotionValue -props: - - name: stiffness - type: number - description: Spring stiffness. - default: "1" - - name: damping - type: number - description: Spring damping. - default: "10" - - name: mass - type: number - description: Mass of the moving object. - default: "1" - - name: bounce - type: number - description: Bounciness (0-1). - default: "0.25" - - name: duration - type: number - description: Duration in seconds (spring will be configured to match). - - name: visualDuration - type: number - description: Perceived duration (spring settles to 1/10 of movement). - - name: skipInitialAnimation - type: boolean - description: Skip animation on initial render. - default: "false" -usage: | - // Direct control - const x = useSpring(0); - x.set(100); // springs to 100 - - // Track another value - const scrollY = useMotionValue(0); - const smoothY = useSpring(scrollY, { stiffness: 100, damping: 30 }); -examples: - - title: Smooth scroll tracking - category: scroll - code: | - function SmoothScroll() { - const { scrollYProgress } = useScroll(); - const smoothProgress = useSpring(scrollYProgress, { - stiffness: 100, - damping: 30, - restDelta: 0.001 - }); - - return ; - } - - title: Mouse follower - category: animation - code: | - function Cursor() { - const x = useMotionValue(0); - const y = useMotionValue(0); - const springX = useSpring(x, { stiffness: 300, damping: 25 }); - const springY = useSpring(y, { stiffness: 300, damping: 25 }); - - useEffect(() => { - const handler = (e: MouseEvent) => { - x.set(e.clientX); - y.set(e.clientY); - }; - window.addEventListener("mousemove", handler); - return () => window.removeEventListener("mousemove", handler); - }, []); - - return ; - } -relatedApis: - - useMotionValue - - useTransform diff --git a/corpus/frontend/react/index.yaml b/corpus/frontend/react/index.yaml deleted file mode 100644 index 96665ea..0000000 --- a/corpus/frontend/react/index.yaml +++ /dev/null @@ -1,10 +0,0 @@ -namespace: frontend.react -patterns: - rsc-default: - file: rsc-default.yaml - zustand-store: - file: zustand-store.yaml - state-hierarchy: - file: state-hierarchy.yaml - suspense-boundary: - file: suspense-boundary.yaml diff --git a/corpus/frontend/react/rsc-default.yaml b/corpus/frontend/react/rsc-default.yaml deleted file mode 100644 index cee7eea..0000000 --- a/corpus/frontend/react/rsc-default.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: rsc-default -category: rendering -description: Server Components are the default. Use 'use client' only when interactivity is required. -when: Every new component. Decide server vs client first. -code: | - // ✅ RSC by default - no directive needed - export default async function ProductList() { - const products = await db.query("SELECT * FROM products"); - return
    {products.map(p =>
  • {p.name}
  • )}
; - } - - // ✅ Client only when needed - "use client"; - import { useState } from "react"; - export function Counter() { - const [count, setCount] = useState(0); - return ; - } -antiPattern: | - // ❌ 'use client' on a component that just renders data - "use client"; - export default function ProductCard({ product }) { - return
{product.name}
; // no interactivity - RSC is fine - } -tips: - - SEO-critical content → RSC/SSR/SSG/ISR - - Non-SEO + interactive → Client Component - - Fetching data → always RSC unless client-side only diff --git a/corpus/frontend/react/state-hierarchy.yaml b/corpus/frontend/react/state-hierarchy.yaml deleted file mode 100644 index 089e079..0000000 --- a/corpus/frontend/react/state-hierarchy.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: state-hierarchy -category: state -description: "Strict state placement order: URL → server → local → Zustand → Context (injection only)." -when: Deciding where to put any new piece of state. -code: | - // 1. URL state (searchParams) - shareable, bookmarkable - const searchParams = useSearchParams(); - const sort = searchParams.get("sort") ?? "asc"; - - // 2. Server state - React Query / SWR / RSC fetch - const { data } = useQuery({ queryKey: ["users"], queryFn: fetchUsers }); - - // 3. Local component state - const [open, setOpen] = useState(false); - - // 4. Shared client state - Zustand - const user = useAuthStore((s) => s.user); - - // 5. Context - injection only (theme, auth, i18n, feature flags) - const theme = useContext(ThemeContext); -antiPattern: | - // ❌ Context for frequently changing state - const CountContext = createContext(0); // re-renders all consumers on every change -tips: - - "Ask: can this live in the URL? If yes → URL state" - - Context is for injection (stable values), not reactive state - - Redux is banned - Zustand only for shared client state diff --git a/corpus/frontend/react/suspense-boundary.yaml b/corpus/frontend/react/suspense-boundary.yaml deleted file mode 100644 index 56e2105..0000000 --- a/corpus/frontend/react/suspense-boundary.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: suspense-boundary -category: rendering -description: Suspense for async RSC children. Error boundaries for error states. -when: Any page with async data in RSC children. -code: | - import { Suspense } from "react"; - import { ErrorBoundary } from "react-error-boundary"; - - export default function Page() { - return ( - }> - }> - - - - ); - } -tips: - - Skeleton over spinner for layout-stable loading - - ErrorBoundary wraps Suspense diff --git a/corpus/frontend/react/zustand-store.yaml b/corpus/frontend/react/zustand-store.yaml deleted file mode 100644 index 7fd8507..0000000 --- a/corpus/frontend/react/zustand-store.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: zustand-store -category: state -description: Zustand for shared client state. Slice pattern for large stores. -when: State shared across multiple client components that isn't URL or server state. -code: | - import { create } from "zustand"; - import { persist } from "zustand/middleware"; - - interface AuthStore { - user: User | null; - token: string | null; - setUser: (user: User, token: string) => void; - logout: () => void; - } - - export const useAuthStore = create()( - persist( - (set) => ({ - user: null, - token: null, - setUser: (user, token) => set({ user, token }), - logout: () => set({ user: null, token: null }), - }), - { name: "auth-storage" } - ) - ); - - // Usage - subscribe only to needed slice (perf) - const user = useAuthStore((s) => s.user); -tips: - - Never use Redux - - Slice selectors prevent unnecessary re-renders - - persist middleware for auth/settings diff --git a/corpus/frontend/reactflow/background.yaml b/corpus/frontend/reactflow/background.yaml deleted file mode 100644 index 9bd785a..0000000 --- a/corpus/frontend/reactflow/background.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Background -kind: component -description: >- - Renders different background types common in node-based UIs. Comes with three - variants: lines, dots, and cross. -importPath: "import { Background, BackgroundVariant } from '@xyflow/react'" -props: - - name: variant - type: BackgroundVariant - description: Background pattern type. - default: BackgroundVariant.Dots - - name: gap - type: "number | [number, number]" - description: Gap between pattern elements. - default: "20" - - name: size - type: number - description: Size of pattern dots/lines. - default: "1" - - name: color - type: string - description: Pattern color. - - name: lineWidth - type: number - description: Stroke width for lines/cross variant. - default: "1" -usage: | - - - -examples: - - title: Cross pattern background - category: styling - code: | - import { Background, BackgroundVariant } from '@xyflow/react'; - - -relatedApis: - - ReactFlow - - MiniMap - - Controls diff --git a/corpus/frontend/reactflow/controls.yaml b/corpus/frontend/reactflow/controls.yaml deleted file mode 100644 index aed07c1..0000000 --- a/corpus/frontend/reactflow/controls.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: Controls -kind: component -description: >- - Renders a small panel with zoom in, zoom out, fit view, and lock viewport - buttons. -importPath: "import { Controls, ControlButton } from '@xyflow/react'" -props: - - name: showZoom - type: boolean - description: Show zoom buttons. - default: "true" - - name: showFitView - type: boolean - description: Show fit view button. - default: "true" - - name: showInteractive - type: boolean - description: Show lock button. - default: "true" - - name: position - type: PanelPosition - description: Corner position. - default: "'bottom-left'" - - name: orientation - type: "'horizontal' | 'vertical'" - description: Layout direction. - default: "'vertical'" -usage: | - - - -examples: - - title: Custom control button - category: interaction - code: | - import { Controls, ControlButton } from '@xyflow/react'; - - - console.log('custom action')}> - - - -relatedApis: - - ReactFlow - - ControlButton - - Panel diff --git a/corpus/frontend/reactflow/handle.yaml b/corpus/frontend/reactflow/handle.yaml deleted file mode 100644 index b2b57f9..0000000 --- a/corpus/frontend/reactflow/handle.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: Handle -kind: component -description: >- - Used in custom nodes to define connection points. Handles can be sources - (outgoing) or targets (incoming). -importPath: "import { Handle, Position } from '@xyflow/react'" -props: - - name: type - type: "'source' | 'target'" - description: Whether this is an input or output handle. - default: "'source'" - - name: position - type: Position - description: "Side of the node: Position.Top, Bottom, Left, Right." - default: Position.Top - - name: id - type: string - description: Handle ID, needed when a node has multiple handles of the same type. - - name: isConnectable - type: boolean - description: Whether connections can be made to/from this handle. - default: "true" - - name: isConnectableStart - type: boolean - description: Whether a connection can start from this handle. - default: "true" - - name: isConnectableEnd - type: boolean - description: Whether a connection can end on this handle. - default: "true" - - name: isValidConnection - type: IsValidConnection - description: Custom validation logic for connections to this handle. - - name: onConnect - type: OnConnect - description: Callback when connection is made to this handle. -usage: | - import { Handle, Position } from '@xyflow/react'; - - function CustomNode({ data }) { - return ( - <> - -
{data.label}
- - - ); - } -examples: - - title: Multiple handles - category: custom-nodes - code: | - function MultiHandleNode({ data }) { - return ( -
- - -
{data.label}
- - -
- ); - } -tips: - - Use the id prop when a node has multiple source or target handles. - - Prefer isValidConnection on over per-handle validation for performance. - - Add 'nodrag' class to interactive elements inside a node to prevent drag when clicking them. -relatedApis: - - ReactFlow - - NodeResizer - - NodeToolbar diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml deleted file mode 100644 index d3bb54e..0000000 --- a/corpus/frontend/reactflow/index.yaml +++ /dev/null @@ -1,14 +0,0 @@ -namespace: frontend.reactflow -apis: - reactflow: - file: reactflow.yaml - handle: - file: handle.yaml - background: - file: background.yaml - controls: - file: controls.yaml - minimap: - file: minimap.yaml - usereactflow: - file: usereactflow.yaml diff --git a/corpus/frontend/reactflow/minimap.yaml b/corpus/frontend/reactflow/minimap.yaml deleted file mode 100644 index 9c9d435..0000000 --- a/corpus/frontend/reactflow/minimap.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: MiniMap -kind: component -description: >- - Renders an overview of your flow. Each node appears as an SVG element showing - the current viewport position relative to the full flow. -importPath: "import { MiniMap } from '@xyflow/react'" -props: - - name: nodeColor - type: "string | ((node: Node) => string)" - description: Color of minimap nodes. - - name: nodeStrokeColor - type: "string | ((node: Node) => string)" - description: Stroke color of minimap nodes. - - name: nodeStrokeWidth - type: number - description: Stroke width. - default: "2" - - name: maskColor - type: string - description: Color of the area outside the viewport. - - name: position - type: PanelPosition - description: Corner position. - default: "'bottom-right'" - - name: pannable - type: boolean - description: Allow panning via minimap. - default: "false" - - name: zoomable - type: boolean - description: Allow zooming via minimap. - default: "false" -usage: | - - n.type === 'input' ? '#6366f1' : '#94a3b8'} pannable zoomable /> - -examples: [] -relatedApis: - - ReactFlow - - Background - - Controls diff --git a/corpus/frontend/reactflow/reactflow.yaml b/corpus/frontend/reactflow/reactflow.yaml deleted file mode 100644 index d2e1fb3..0000000 --- a/corpus/frontend/reactflow/reactflow.yaml +++ /dev/null @@ -1,84 +0,0 @@ -name: ReactFlow -kind: component -description: >- - Core canvas component for rendering nodes and edges, wiring connection - interactions, and managing controlled or uncontrolled flow state. -importPath: "import { ReactFlow } from '@xyflow/react'" -props: - - name: nodes - type: Node[] - description: Array of nodes for controlled flows. - default: "[]" - - name: edges - type: Edge[] - description: Array of edges for controlled flows. - default: "[]" - - name: defaultNodes - type: Node[] - description: Initial nodes for uncontrolled flows. - - name: defaultEdges - type: Edge[] - description: Initial edges for uncontrolled flows. - - name: onConnect - type: OnConnect - description: Fires when a new connection is completed. - - name: onNodesChange - type: OnNodesChange - description: Called for drag, select, and position changes in controlled flows. - - name: onEdgesChange - type: OnEdgesChange - description: Called when edges change in controlled flows. - - name: nodeTypes - type: NodeTypes - description: Custom node components keyed by node type. - - name: edgeTypes - type: EdgeTypes - description: Custom edge components keyed by edge type. - - name: isValidConnection - type: IsValidConnection - description: Validate candidate connections before an edge is created. - - name: fitView - type: boolean - description: Fit the viewport to all nodes on initial render. - default: "false" - - name: defaultViewport - type: Viewport - description: Initial viewport position and zoom. - default: "{ x: 0, y: 0, zoom: 1 }" -usage: | - import { ReactFlow } from '@xyflow/react'; - - export function FlowCanvas() { - return ( -
- -
- ); - } -examples: - - title: Controlled flow - category: state-management - code: | - import { ReactFlow } from '@xyflow/react'; - - export function ControlledFlow({ nodes, edges, onNodesChange, onEdgesChange, onConnect }) { - return ( - - ); - } -tips: - - Import '@xyflow/react/dist/style.css' once at the app root. - - The parent container must have an explicit width and height. - - Define nodeTypes and edgeTypes outside the component or memoize them. -relatedApis: - - Background - - Controls - - MiniMap - - useReactFlow diff --git a/corpus/frontend/reactflow/usereactflow.yaml b/corpus/frontend/reactflow/usereactflow.yaml deleted file mode 100644 index f1f7a29..0000000 --- a/corpus/frontend/reactflow/usereactflow.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: useReactFlow -kind: hook -description: >- - Returns a ReactFlowInstance to update nodes/edges, manipulate the viewport, - or query flow state. Does NOT cause re-renders on state changes. -importPath: "import { useReactFlow } from '@xyflow/react'" -returns: ReactFlowInstance -usage: | - const { getNodes, setNodes, addNodes, getEdges, setEdges, addEdges, - fitView, zoomIn, zoomOut, getViewport, setViewport, - screenToFlowPosition, deleteElements, updateNode, updateNodeData, - getIntersectingNodes, toObject } = useReactFlow(); -examples: - - title: Add node on button click - category: interaction - code: | - function AddNodeButton() { - const { addNodes, screenToFlowPosition } = useReactFlow(); - const onClick = () => { - addNodes({ - id: crypto.randomUUID(), - position: screenToFlowPosition({ x: 200, y: 200 }), - data: { label: 'New Node' }, - }); - }; - return ; - } - - title: Delete selected elements - category: interaction - code: | - function DeleteButton() { - const { deleteElements, getNodes, getEdges } = useReactFlow(); - const onClick = async () => { - const selectedNodes = getNodes().filter((n) => n.selected); - const selectedEdges = getEdges().filter((e) => e.selected); - await deleteElements({ nodes: selectedNodes, edges: selectedEdges }); - }; - return ; - } -tips: - - Must be used inside or . - - Unlike useNodes/useEdges, this hook won't cause re-renders on state changes. Query state on demand. - - "Pass useReactFlow() as dependency to useCallback/useEffect - it's not initialized on first render." -relatedApis: - - ReactFlowProvider - - ReactFlowInstance - - useNodes - - useEdges diff --git a/corpus/frontend/shadcn/button.yaml b/corpus/frontend/shadcn/button.yaml deleted file mode 100644 index ef71039..0000000 --- a/corpus/frontend/shadcn/button.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Button -category: button -description: Interactive button with variant + size support. Uses CVA for variant composition. -basePrimitive: native - - // With variants and sizes - - - - // Icon usage (data-slot automatic in component) - -pairsWith: - - Dialog - - Form - - DropdownMenu diff --git a/corpus/frontend/shadcn/dialog.yaml b/corpus/frontend/shadcn/dialog.yaml deleted file mode 100644 index d952c95..0000000 --- a/corpus/frontend/shadcn/dialog.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Dialog -category: dialog -description: Modal dialog with backdrop and portal. Uses @base-ui/react Dialog primitive. -basePrimitive: "@base-ui/react/dialog" -dataSlots: - - dialog-overlay - - dialog-content - - dialog-header - - dialog-title - - dialog-description - - dialog-footer -variants: [] -sizes: [] -requiresUseClient: true -usageSnippet: | - - }>Open Dialog - - - Are you sure? - - This action cannot be undone. - - - - - - - -pairsWith: - - Button - - Form - - Select diff --git a/corpus/frontend/shadcn/field.yaml b/corpus/frontend/shadcn/field.yaml deleted file mode 100644 index e212045..0000000 --- a/corpus/frontend/shadcn/field.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Field -category: form -description: Form field wrapper with label, description, error. Uses @container for responsive layout. -basePrimitive: "@base-ui/react/field" -dataSlots: - - field - - field-label - - field-control - - field-description - - field-error -variants: - - vertical - - horizontal -sizes: [] -requiresUseClient: false -usageSnippet: | - - Username - - - This is your public display name. - - - - - // Horizontal orientation with Container Queries - - - Email - - - -pairsWith: - - Input - - Select - - Textarea - - Checkbox diff --git a/corpus/frontend/shadcn/index.yaml b/corpus/frontend/shadcn/index.yaml deleted file mode 100644 index ba4ba60..0000000 --- a/corpus/frontend/shadcn/index.yaml +++ /dev/null @@ -1,10 +0,0 @@ -namespace: frontend.shadcn -components: - button: - file: button.yaml - dialog: - file: dialog.yaml - field: - file: field.yaml - select: - file: select.yaml diff --git a/corpus/frontend/shadcn/select.yaml b/corpus/frontend/shadcn/select.yaml deleted file mode 100644 index aecb292..0000000 --- a/corpus/frontend/shadcn/select.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Select -category: dropdown -description: Accessible select with search and keyboard nav. Uses @base-ui/react Select. -basePrimitive: "@base-ui/react/select" -dataSlots: - - select-trigger - - select-value - - select-content - - select-item - - select-icon -variants: [] -sizes: - - sm - - md - - lg -requiresUseClient: true -usageSnippet: | - -pairsWith: - - Field - - Form diff --git a/corpus/frontend/ui-ux/dark-mode-principles.yaml b/corpus/frontend/ui-ux/dark-mode-principles.yaml deleted file mode 100644 index e45e6c1..0000000 --- a/corpus/frontend/ui-ux/dark-mode-principles.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: dark-mode-principles -domain: color -rule: "Dark mode: redesign, don't invert. Warm charcoal, not black." -detail: >- - Background: oklch(0.13 0.008 265) - warm charcoal, not #000. Text: - oklch(0.94 0.008 265) - off-white, not #fff. Higher elevation = lighter bg - (shadows invisible on dark). Reduce saturation slightly (vivid on dark = - neon). Primary accents brighten: brand-600 → brand-400. -cssExample: | - .dark { - --color-bg: oklch(0.13 0.008 265); /* warm charcoal */ - --color-text: oklch(0.94 0.008 265); /* off-white */ - /* surface-1 */ --color-surface: oklch(0.16 0.008 265); - /* surface-2 */ --color-card: oklch(0.19 0.008 265); - } -antiPatterns: - - Pure black background (#000) - - Pure white text (#fff) on dark - - Inverting light mode colors directly diff --git a/corpus/frontend/ui-ux/index.yaml b/corpus/frontend/ui-ux/index.yaml deleted file mode 100644 index e2291f7..0000000 --- a/corpus/frontend/ui-ux/index.yaml +++ /dev/null @@ -1,10 +0,0 @@ -namespace: frontend.ui-ux -principles: - type-scale: - file: type-scale.yaml - wcag-contrast: - file: wcag-contrast.yaml - dark-mode-principles: - file: dark-mode-principles.yaml - touch-targets: - file: touch-targets.yaml diff --git a/corpus/frontend/ui-ux/touch-targets.yaml b/corpus/frontend/ui-ux/touch-targets.yaml deleted file mode 100644 index e4937cd..0000000 --- a/corpus/frontend/ui-ux/touch-targets.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: touch-targets -domain: accessibility -rule: "Touch targets: min 44×44px (WCAG), recommended 48×48px. Gap ≥ 8px between targets." -detail: >- - Visual size ≠ touch target. A 34px button can have a 44px hit area via - padding or ::after pseudo-element. Gap prevents accidental taps on adjacent - targets. -cssExample: | - /* Hit area expansion */ - .btn-small::after { - content: ''; - position: absolute; - inset: -8px; - } -antiPatterns: - - "< 44px touch targets on mobile" - - Adjacent buttons with no gap diff --git a/corpus/frontend/ui-ux/type-scale.yaml b/corpus/frontend/ui-ux/type-scale.yaml deleted file mode 100644 index 77e81ac..0000000 --- a/corpus/frontend/ui-ux/type-scale.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: type-scale -domain: typography -rule: Use mathematical ratios for type scale, not arbitrary sizes. -detail: >- - Common ratios: 1.25 (Major Third), 1.333 (Perfect Fourth), 1.414 (Augmented - Fourth). Recommended web scale: Display 48-72px, H1 40-56px, H2 28-40px, - H3 20-24px, Subtitle 16-20px, Body 16px, Body-sm 14px, Caption 13px, - Overline 12px. -cssExample: | - /* fluid headings, fixed body */ - font-size: clamp(2.5rem, 4vw, 3.5rem); /* h1 */ - font-size: clamp(1.75rem, 3vw, 2.5rem); /* h2 */ - font-size: 1rem; /* body - fixed */ -antiPatterns: - - Random px values with no ratio - - Fluid body text (causes reflow) - - More than 2 type families diff --git a/corpus/frontend/ui-ux/wcag-contrast.yaml b/corpus/frontend/ui-ux/wcag-contrast.yaml deleted file mode 100644 index c557c7e..0000000 --- a/corpus/frontend/ui-ux/wcag-contrast.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: wcag-contrast -domain: color -rule: "Body text: 4.5:1 (AA). Large text ≥18px bold or ≥24px: 3:1 (AA). UI components: 3:1." -detail: >- - AAA (enhanced): 7:1 for body, 4.5:1 for large. Fix: reduce OKLCH Lightness - (L) of status colors from ~0.63 to ~0.55. Keep C and H unchanged. -examples: - - "Run: npm install wcag-contrast" - - "Online: https://webaim.org/resources/contrastchecker/" -antiPatterns: - - Testing only in light mode - - Assuming brand colors pass without checking diff --git a/corpus/index.yaml b/corpus/index.yaml deleted file mode 100644 index af30c6d..0000000 --- a/corpus/index.yaml +++ /dev/null @@ -1,23 +0,0 @@ -namespaces: - frontend.motion: - index: frontend/motion/index.yaml - frontend.reactflow: - index: frontend/reactflow/index.yaml - frontend.lenis: - index: frontend/lenis/index.yaml - frontend.designer: - index: frontend/designer/index.yaml - frontend.design-tokens: - index: frontend/design-tokens/index.yaml - frontend.shadcn: - index: frontend/shadcn/index.yaml - backend.golang: - index: backend/golang/index.yaml - backend.echo: - index: backend/echo/index.yaml - frontend.ui-ux: - index: frontend/ui-ux/index.yaml - frontend.react: - index: frontend/react/index.yaml - backend.rust: - index: backend/rust/index.yaml diff --git a/corpus/shared/README.md b/corpus/shared/README.md deleted file mode 100644 index c18a551..0000000 --- a/corpus/shared/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Shared Corpus - -V1 placeholder root for shared system and topology research assets. diff --git a/package.json b/package.json index 1553f47..20539da 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "compile:context": "tsx src/internal/compile-runtime-context.ts", "generate:local-tools": "tsx scripts/generate-local-tool-registry.ts", "generate:topology": "tsx scripts/generate-topology-artifacts.ts", - "test": "bun test", "start": "bun src/index.ts", "dev": "bun --watch src/index.ts", "docker:run": "bun scripts/ensure-singleton.ts", diff --git a/tests/artifact-contracts-behaviour.test.ts b/tests/artifact-contracts-behaviour.test.ts deleted file mode 100644 index 8e82acf..0000000 --- a/tests/artifact-contracts-behaviour.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect, test } from "bun:test"; -import { loadTopology } from "../src/engine/topology-loader.ts"; -import { validateArtifactPayload } from "../src/engine/artifact-validator.ts"; - -test("validateArtifactPayload accepts a complete workspace_inventory", () => { - const topology = loadTopology(process.cwd()); - const result = validateArtifactPayload(topology, "workspace_inventory", { - repo_type: "web-app", - stack: ["react", "tailwind"], - touched_surfaces: ["settings page"], - existing_patterns: ["shadcn form"], - verification_commands: ["bun test", "bun run build"], - project_mode: "existing", - }); - - expect(result.ok).toBe(true); -}); - -test("validateArtifactPayload rejects missing design_contract fields", () => { - const topology = loadTopology(process.cwd()); - const result = validateArtifactPayload(topology, "design_contract", { - visual_theme: "dark", - color_system: "brand + neutral", - }); - - expect(result.ok).toBe(false); - expect(result.missingFields).toContain("typography"); - expect(result.missingFields).toContain("component_states"); -}); diff --git a/tests/context-compiler-behaviour.test.ts b/tests/context-compiler-behaviour.test.ts deleted file mode 100644 index ad4ca29..0000000 --- a/tests/context-compiler-behaviour.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { test, expect } from "bun:test"; -import { readFileSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { - compileUsingHyperstackBootstrap, - generateHyperstackBootstrap, -} from "../src/internal/context-compiler.ts"; - -function normalize(str: string): string { - return str.replace(/\r\n/g, "\n"); -} - -test("compileUsingHyperstackBootstrap keeps required invariants while shrinking the source", () => { - const source = ` - -Not for subagents. - - - -This is extremely important. - - -# Skill Name -This is a skill. -{INV: invariant-1} -{INV: invariant-2} - -## The Iron Laws -\`\`\` -- Law 1 -{INV: invariant-1} -{INV: invariant-2} -\`\`\` - -## Instruction Priority -- P1 - -## Red Flags - STOP -- Red 1 -${"x".repeat(2000)} - -## Layer 1: MCP Tools (Ground-Truth Data) -- Tool 1 - -## Layer 2: Skills (Engineering Process) -- Skill 1 - -## Role Registry -- Role 1 - -## Routing Summary -- Route 1 - -## Allowed Transitions -- Transition 1 - -## Disallowed Transitions -- Transition 2 - -## The Rationalization Catalog (Read Before Every Session) -- Rational 1 - -## The One Rule That Governs All Rules -- Rule 1 - -## Final Check Before Any Response -- Check 1 - -### Steps -1. Step 1 - `; - const { content } = compileUsingHyperstackBootstrap(source); - - expect(content).toMatch(/invariant-1/); - expect(content).toMatch(/invariant-2/); - expect(content.length).toBeLessThan(source.length); -}); - -test("generated bootstrap artifact stays in sync with the compiler output", () => { - const skillSource = normalize(readFileSync(resolve("skills/hyperstack/SKILL.md"), "utf8")); - const currentBootstrap = normalize(readFileSync(resolve("generated/runtime-context/hyperstack.bootstrap.md"), "utf8")); - - const nextBootstrap = generateHyperstackBootstrap(skillSource); - - expect(normalize(nextBootstrap)).toBe(currentBootstrap); -}); diff --git a/tests/design-tokens-procedure-corpus-backed-tools-behaviour.test.ts b/tests/design-tokens-procedure-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 82ff33e..0000000 --- a/tests/design-tokens-procedure-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerDesignTokensGetProcedure } from "../src/plugins/design-tokens/tools/get-procedure.ts"; - -const designTokensGetProcedure = captureTool(registerDesignTokensGetProcedure); - -test("design_tokens_get_procedure prefers corpus metadata for step 1", async () => { - const result = await designTokensGetProcedure.invoke({ step: 1 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 1: Define color ramp primitives in @theme"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--color-brand-50: oklch(0.98 0.01 291);"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 2", async () => { - const result = await designTokensGetProcedure.invoke({ step: 2 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 2: Map semantic tokens in :root and .dark"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--color-bg: var(--color-neutral-50);"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 3", async () => { - const result = await designTokensGetProcedure.invoke({ step: 3 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 3: Bridge to Tailwind v4 utilities with @theme inline"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--color-background: var(--color-bg);"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 4", async () => { - const result = await designTokensGetProcedure.invoke({ step: 4 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 4: Define spacing system"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--spacing: 0.25rem;"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 5", async () => { - const result = await designTokensGetProcedure.invoke({ step: 5 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 5: Set up typography scale"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--font-sans:"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 6", async () => { - const result = await designTokensGetProcedure.invoke({ step: 6 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 6: Define shadows and elevation"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--shadow-xs: 0 1px 2px 0 oklch(0.30 0.02 60 / 0.08);"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 7", async () => { - const result = await designTokensGetProcedure.invoke({ step: 7 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 7: Define motion and z-index tokens"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--duration-fast: 100ms;"); -}); - -test("design_tokens_get_procedure prefers corpus metadata for step 8", async () => { - const result = await designTokensGetProcedure.invoke({ step: 8 }); - const text = extractTextContent(result); - - expect(text).toContain("# Step 8: Run contrast audit and verify token usage"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("--color-text on --color-bg"); -}); - -test("design_tokens_get_procedure summary includes corpus source when corpus metadata is present", async () => { - const result = await designTokensGetProcedure.invoke({}); - const text = extractTextContent(result); - - expect(text).toContain("# Design Token Build Procedure"); - expect(text).toContain("**Corpus Source:** frontend.design-tokens"); - expect(text).toContain("## Step 1: Define color ramp primitives in @theme"); -}); diff --git a/tests/designer-page-template-corpus-backed-tools-behaviour.test.ts b/tests/designer-page-template-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 3524c32..0000000 --- a/tests/designer-page-template-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerDesignerPageTemplate } from "../src/plugins/designer/tools/get-page-template.ts"; - -const designerGetPageTemplate = captureTool(registerDesignerPageTemplate); - -test("designer_get_page_template prefers corpus metadata for dashboard", async () => { - const result = await designerGetPageTemplate.invoke({ type: "dashboard" }); - const text = extractTextContent(result); - - expect(text).toContain("# Page Template: dashboard"); - expect(text).toContain("**Corpus Source:** frontend.designer"); - expect(text).toContain("## Sections"); - expect(text).toContain("Sidebar Navigation"); -}); - -test("designer_get_page_template falls back to in-file data for non-corpus templates", async () => { - const result = await designerGetPageTemplate.invoke({ type: "auth" }); - const text = extractTextContent(result); - - expect(text).toContain("# Page Template: auth"); - expect(text).toContain("## Sections"); - expect(text).toContain("Login"); - expect(text).not.toContain("**Corpus Source:** frontend.designer"); -}); diff --git a/tests/echo-recipe-corpus-backed-tools-behaviour.test.ts b/tests/echo-recipe-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 78bd3ae..0000000 --- a/tests/echo-recipe-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerEchoGetRecipe } from "../src/plugins/echo/tools/get-recipe.ts"; - -const echoGetRecipe = captureTool(registerEchoGetRecipe); - -test("echo_get_recipe prefers corpus metadata for hello-world", async () => { - const result = await echoGetRecipe.invoke({ name: "hello-world" }); - const text = extractTextContent(result); - - expect(text).toContain("# hello-world"); - expect(text).toContain("**Corpus Source:** backend.echo"); - expect(text).toContain('e.Start(":8080")'); -}); - -test("echo_get_recipe falls back to in-file data for non-corpus recipes", async () => { - const result = await echoGetRecipe.invoke({ name: "crud-api" }); - const text = extractTextContent(result); - - expect(text).toContain("# crud-api"); - expect(text).not.toContain("**Corpus Source:** backend.echo"); -}); diff --git a/tests/generator-behaviour.test.ts b/tests/generator-behaviour.test.ts deleted file mode 100644 index db76d98..0000000 --- a/tests/generator-behaviour.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { test, expect } from "bun:test"; -import { generateFlowCode } from "../src/plugins/reactflow/tools/generate-flow.ts"; -import { generateAnimationCode } from "../src/plugins/motion/tools/generate-animation.ts"; -import { generateSetupCode } from "../src/plugins/lenis/tools/generate-setup.ts"; -import { buildTasks, parseDesignMd } from "../src/plugins/designer/tools/generate-implementation-plan.ts"; - -test("reactflow_generate_flow returns self-contained controlled-flow code", () => { - const result = generateFlowCode("simple flow", false); - expect(result).toMatch(/import { .*ReactFlow.* } from '@xyflow\/react'/); - expect(result).toMatch(/useNodesState/); - expect(result).toMatch(/useEdgesState/); -}); - -test("motion_generate_animation does not emit placeholder exit keys", () => { - const result = generateAnimationCode("fade in"); - expect(result).not.toMatch(/exit=\{\}/); -}); - -test("motion_generate_animation declares list inputs for staggered list output", () => { - const result = generateAnimationCode("staggered list"); - expect(result).toMatch(/items\.map/); -}); - -test("lenis_generate_setup uses a reactive reduced-motion check", () => { - const { code } = generateSetupCode("next.js setup with basic accessibility"); - expect(code).toMatch(/useReducedMotion|matchMedia/); -}); - -test("buildTasks preserves multi-word component names from DESIGN.md", () => { - const designMd = `## 5. Components\n\n### Header Navigation\n- Build a header with navigation.`; - const sections = parseDesignMd(designMd); - const tasks = buildTasks(sections, "react"); - expect(tasks.some((t) => t.name.includes("Header Navigation"))).toBe(true); -}); diff --git a/tests/golang-practice-corpus-backed-tools-behaviour.test.ts b/tests/golang-practice-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 6eaf8ca..0000000 --- a/tests/golang-practice-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerGolangGetPractice } from "../src/plugins/golang/tools/get-practice.ts"; - -const golangGetPractice = captureTool(registerGolangGetPractice); - -test("golang_get_practice prefers corpus metadata for error-wrapping", async () => { - const result = await golangGetPractice.invoke({ name: "error-wrapping" }); - const text = extractTextContent(result); - - expect(text).toContain("# error-wrapping [error-handling] - P0"); - expect(text).toContain("**Corpus Source:** backend.golang"); - expect(text).toContain('fmt.Errorf("userRepo.FindByID'); -}); - -test("golang_get_practice prefers corpus metadata for goroutine-lifecycle", async () => { - const result = await golangGetPractice.invoke({ name: "goroutine-lifecycle" }); - const text = extractTextContent(result); - - expect(text).toContain("# goroutine-lifecycle [concurrency] - P0"); - expect(text).toContain("**Corpus Source:** backend.golang"); - expect(text).toContain("case <-ctx.Done(): return"); -}); - -test("golang_get_practice prefers corpus metadata for context-first-param", async () => { - const result = await golangGetPractice.invoke({ name: "context-first-param" }); - const text = extractTextContent(result); - - expect(text).toContain("# context-first-param [concurrency] - P0"); - expect(text).toContain("**Corpus Source:** backend.golang"); - expect(text).toContain("ctx context.Context, id string"); -}); - -test("golang_get_practice falls back to in-file data for non-corpus practices", async () => { - const result = await golangGetPractice.invoke({ name: "handle-once" }); - const text = extractTextContent(result); - - expect(text).toContain("# handle-once [error-handling] - P0"); - expect(text).not.toContain("**Corpus Source:** backend.golang"); -}); diff --git a/tests/helpers.ts b/tests/helpers.ts deleted file mode 100644 index d6d4677..0000000 --- a/tests/helpers.ts +++ /dev/null @@ -1,44 +0,0 @@ -import assert from "node:assert/strict"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -type ToolHandler = (args: Record) => Promise | unknown; - -export function captureTool(register: (server: McpServer) => void) { - let capturedName = ""; - let capturedHandler: ToolHandler | undefined; - - const server = { - tool(name: string, _description: string, _schema: unknown, handler: ToolHandler) { - capturedName = name; - capturedHandler = handler; - }, - } as unknown as McpServer; - - register(server); - - assert.ok(capturedName, "tool registration did not provide a name"); - assert.ok(capturedHandler, "tool registration did not provide a handler"); - - return { - name: capturedName, - async invoke(args: Record) { - return capturedHandler!(args); - }, - }; -} - -export function extractTextContent(result: unknown): string { - const payload = result as { - content?: Array<{ type?: string; text?: string }>; - }; - - const textBlock = payload.content?.find((item) => item.type === "text" && typeof item.text === "string"); - assert.ok(textBlock?.text, "tool response did not include a text content block"); - return textBlock.text; -} - -export function extractTsxFence(markdown: string): string { - const match = markdown.match(/```tsx\n([\s\S]*?)\n```/); - assert.ok(match?.[1], "tool response did not include a tsx code fence"); - return match[1]; -} diff --git a/tests/lenis-pattern-corpus-backed-tools-behaviour.test.ts b/tests/lenis-pattern-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index f704e3f..0000000 --- a/tests/lenis-pattern-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerLenisGetPattern } from "../src/plugins/lenis/tools/get-pattern.ts"; - -const lenisGetPattern = captureTool(registerLenisGetPattern); - -test("lenis_get_pattern prefers corpus metadata for gsap-integration", async () => { - const result = await lenisGetPattern.invoke({ name: "gsap-integration" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: gsap-integration"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain('import { ReactLenis, useLenis } from "lenis/react"'); -}); - -test("lenis_get_pattern prefers corpus metadata for full-page", async () => { - const result = await lenisGetPattern.invoke({ name: "full-page" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: full-page"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain(''); -}); - -test("lenis_get_pattern prefers corpus metadata for next-js", async () => { - const result = await lenisGetPattern.invoke({ name: "next-js" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: next-js"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain("SmoothScrollProvider"); -}); - -test("lenis_get_pattern prefers corpus metadata for framer-motion-integration", async () => { - const result = await lenisGetPattern.invoke({ name: "framer-motion-integration" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: framer-motion-integration"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain('import { frame } from "motion";'); -}); - -test("lenis_get_pattern prefers corpus metadata for custom-container", async () => { - const result = await lenisGetPattern.invoke({ name: "custom-container" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: custom-container"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain("ScrollPanel"); -}); - -test("lenis_get_pattern prefers corpus metadata for scroll-to-nav", async () => { - const result = await lenisGetPattern.invoke({ name: "scroll-to-nav" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: scroll-to-nav"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain("lenis?.scrollTo(href"); -}); - -test("lenis_get_pattern prefers corpus metadata for accessibility", async () => { - const result = await lenisGetPattern.invoke({ name: "accessibility" }); - const text = extractTextContent(result); - - expect(text).toContain("# Lenis Pattern: accessibility"); - expect(text).toContain("**Corpus Source:** frontend.lenis"); - expect(text).toContain("prefers-reduced-motion"); - expect(text).toContain("AccessibleSmoothScrollProvider"); -}); diff --git a/tests/local-cli-behaviour.test.ts b/tests/local-cli-behaviour.test.ts deleted file mode 100644 index 35ccdac..0000000 --- a/tests/local-cli-behaviour.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect, test } from "bun:test"; -import { spawnSync } from "node:child_process"; -import { resolve } from "node:path"; - -test("local CLI invokes stable tool names with JSON payload", () => { - const result = spawnSync( - process.execPath, - [ - resolve("bin/hyperstack.mjs"), - "tool", - "designer_resolve_intent", - "--json", - '{"product":"developer analytics dashboard"}', - ], - { cwd: process.cwd(), encoding: "utf8" }, - ); - - expect(result.status).toBe(0); - expect(result.stdout).toMatch(/Resolved Design Intent/); -}); diff --git a/tests/local-cli-routing-behaviour.test.ts b/tests/local-cli-routing-behaviour.test.ts deleted file mode 100644 index 4333424..0000000 --- a/tests/local-cli-routing-behaviour.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect, test } from "bun:test"; -import { spawnSync } from "node:child_process"; -import { resolve } from "node:path"; - -test("CLI route command returns routed agent and required artifacts", () => { - const result = spawnSync( - process.execPath, - [ - resolve("bin/hyperstack.mjs"), - "route", - "--json", - '{"requestId":"req-1","domainTargets":["frontend"],"capabilityTargets":["frontend.patterns"],"workspaceInventory":{"projectMode":"existing","existingPatterns":["existing form shell"]},"changeClassification":"frontend_logic"}', - ], - { cwd: process.cwd(), encoding: "utf8" }, - ); - - expect(result.status).toBe(0); - expect(result.stdout).toMatch(/frontend-builder/); - expect(result.stdout).toMatch(/workspace_inventory/); - expect(result.stdout).not.toMatch(/design_contract/); -}); - -test("CLI artifact validate command reports missing fields", () => { - const result = spawnSync( - process.execPath, - [ - resolve("bin/hyperstack.mjs"), - "artifact", - "validate", - "workspace_inventory", - "--json", - '{"repo_type":"web-app"}', - ], - { cwd: process.cwd(), encoding: "utf8" }, - ); - - expect(result.status).toBe(0); - expect(result.stdout).toMatch(/missingFields/); - expect(result.stdout).toMatch(/stack/); -}); diff --git a/tests/motion-corpus-backed-tools-behaviour.test.ts b/tests/motion-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index fb9ab04..0000000 --- a/tests/motion-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { test, expect } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerMotionApi } from "../src/plugins/motion/tools/get-api.ts"; - -const motionGetApi = captureTool(registerMotionApi); - -test("motion_get_api prefers corpus metadata for motion", async () => { - const result = await motionGetApi.invoke({ name: "motion" }); - const text = extractTextContent(result); - - expect(text).toContain("# motion"); - expect(text).toContain("**Corpus Source:** frontend.motion"); - expect(text).toContain('import { motion } from "motion/react"'); -}); - -test("motion_get_api prefers corpus metadata for AnimatePresence", async () => { - const result = await motionGetApi.invoke({ name: "AnimatePresence" }); - const text = extractTextContent(result); - - expect(text).toContain("# AnimatePresence"); - expect(text).toContain("**Kind:** component"); - expect(text).toContain("**Corpus Source:** frontend.motion"); - expect(text).toContain('import { AnimatePresence } from "motion/react"'); -}); - -test("motion_get_api prefers corpus metadata for useScroll", async () => { - const result = await motionGetApi.invoke({ name: "useScroll" }); - const text = extractTextContent(result); - - expect(text).toContain("# useScroll"); - expect(text).toContain("**Kind:** hook"); - expect(text).toContain("**Corpus Source:** frontend.motion"); - expect(text).toContain('import { useScroll } from "motion/react"'); -}); - -test("motion_get_api prefers corpus metadata for useSpring", async () => { - const result = await motionGetApi.invoke({ name: "useSpring" }); - const text = extractTextContent(result); - - expect(text).toContain("# useSpring"); - expect(text).toContain("**Kind:** hook"); - expect(text).toContain("**Corpus Source:** frontend.motion"); - expect(text).toContain('import { useSpring } from "motion/react"'); -}); - -test("motion_get_api falls back to in-file data for non-corpus motion APIs", async () => { - const result = await motionGetApi.invoke({ name: "useAnimate" }); - const text = extractTextContent(result); - - expect(text).toContain("# useAnimate"); - expect(text).toContain("**Kind:** hook"); - expect(text).not.toContain("**Corpus Source:** frontend.motion"); -}); diff --git a/tests/motion-examples-corpus-backed-tools-behaviour.test.ts b/tests/motion-examples-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 5f0aa75..0000000 --- a/tests/motion-examples-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerMotionExamples } from "../src/plugins/motion/tools/get-examples.ts"; - -const motionGetExamples = captureTool(registerMotionExamples); - -test("motion_get_examples prefers corpus metadata for scroll", async () => { - const result = await motionGetExamples.invoke({ category: "scroll" }); - const text = extractTextContent(result); - - expect(text).toContain("# Scroll Examples"); - expect(text).toContain("Scroll progress rail"); - expect(text).toContain("**Corpus Source:** frontend.motion"); - expect(text).toContain('import { motion, useScroll, useSpring } from "motion/react"'); -}); - -test("motion_get_examples falls back to in-file data for layout", async () => { - const result = await motionGetExamples.invoke({ category: "layout" }); - const text = extractTextContent(result); - - expect(text).toContain("# Layout Examples"); - expect(text).toContain("Layout animation"); - expect(text).not.toContain("**Corpus Source:** frontend.motion"); -}); diff --git a/tests/plugin-registry-behaviour.test.ts b/tests/plugin-registry-behaviour.test.ts deleted file mode 100644 index da7f6ef..0000000 --- a/tests/plugin-registry-behaviour.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { test, expect } from "bun:test"; -import { allPlugins } from "../src/index.ts"; - -function getRegisteredTools() { - const tools: Array<{ name: string; description: string }> = []; - const mockServer = { - tool: (name: string, description: string) => { - tools.push({ name, description }); - }, - resource: () => {}, - prompt: () => {}, - } as any; - - for (const plugin of allPlugins) { - plugin.register(mockServer); - } - return tools; -} - -test("all 12 plugins register at least one tool", () => { - const tools = getRegisteredTools(); - const pluginPrefixes = new Set(tools.map((t) => t.name.split("_")[0])); - expect(pluginPrefixes.size).toBe(12); -}); - -test("every registered tool has a non-empty name and description", () => { - const tools = getRegisteredTools(); - for (const tool of tools) { - expect(tool.name).not.toBe(""); - expect(tool.description).not.toBe(""); - } -}); - -test("tool names follow namespace convention (plugin_name_action)", () => { - const tools = getRegisteredTools(); - for (const tool of tools) { - expect(tool.name).toMatch(/^[a-z]+_[a-z0-9_]+$/); - } -}); - -test("designer plugin registers the required MCP tools referenced in skills", () => { - const tools = getRegisteredTools(); - const toolNames = new Set(tools.map((t) => t.name)); - expect(toolNames.has("designer_resolve_intent")).toBe(true); - expect(toolNames.has("designer_generate_design_brief")).toBe(true); -}); - -test("shadcn plugin registers the required MCP tools referenced in skills", () => { - const tools = getRegisteredTools(); - const toolNames = new Set(tools.map((t) => t.name)); - expect(toolNames.has("shadcn_get_component")).toBe(true); - expect(toolNames.has("shadcn_get_composition")).toBe(true); -}); - -test("no two plugins register a tool with the same name", () => { - const tools = getRegisteredTools(); - const names = tools.map((t) => t.name); - const uniqueNames = new Set(names); - expect(names.length).toBe(uniqueNames.size); -}); diff --git a/tests/react-pattern-corpus-backed-tools-behaviour.test.ts b/tests/react-pattern-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index a5d8554..0000000 --- a/tests/react-pattern-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerReactGetPattern } from "../src/plugins/react/tools/get-pattern.ts"; - -const reactGetPattern = captureTool(registerReactGetPattern); - -test("react_get_pattern prefers corpus metadata for rsc-default", async () => { - const result = await reactGetPattern.invoke({ name: "rsc-default" }); - const text = extractTextContent(result); - - expect(text).toContain("# rsc-default [rendering]"); - expect(text).toContain("**Corpus Source:** frontend.react"); - expect(text).toContain("export default async function ProductList()"); -}); - -test("react_get_pattern prefers corpus metadata for zustand-store", async () => { - const result = await reactGetPattern.invoke({ name: "zustand-store" }); - const text = extractTextContent(result); - - expect(text).toContain("# zustand-store [state]"); - expect(text).toContain("**Corpus Source:** frontend.react"); - expect(text).toContain("create<"); -}); - -test("react_get_pattern prefers corpus metadata for state-hierarchy", async () => { - const result = await reactGetPattern.invoke({ name: "state-hierarchy" }); - const text = extractTextContent(result); - - expect(text).toContain("# state-hierarchy [state]"); - expect(text).toContain("**Corpus Source:** frontend.react"); - expect(text).toContain("useSearchParams()"); -}); - -test("react_get_pattern prefers corpus metadata for suspense-boundary", async () => { - const result = await reactGetPattern.invoke({ name: "suspense-boundary" }); - const text = extractTextContent(result); - - expect(text).toContain("# suspense-boundary [rendering]"); - expect(text).toContain("**Corpus Source:** frontend.react"); - expect(text).toContain("}>"); -}); - -test("react_get_pattern falls back to in-file data for non-corpus patterns", async () => { - const result = await reactGetPattern.invoke({ name: "composition-pattern" }); - const text = extractTextContent(result); - - expect(text).toContain("# composition-pattern [architecture]"); - expect(text).not.toContain("**Corpus Source:** frontend.react"); -}); diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 4e104ad..0000000 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerReactFlowApi } from "../src/plugins/reactflow/tools/get-api.ts"; - -const reactFlowGetApi = captureTool(registerReactFlowApi); - -test("reactflow_get_api prefers corpus metadata for ReactFlow", async () => { - const result = await reactFlowGetApi.invoke({ name: "ReactFlow" }); - const text = extractTextContent(result); - - expect(text).toContain("# ReactFlow (component)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("import { ReactFlow } from '@xyflow/react'"); -}); - -test("reactflow_get_api prefers corpus metadata for Handle", async () => { - const result = await reactFlowGetApi.invoke({ name: "Handle" }); - const text = extractTextContent(result); - - expect(text).toContain("# Handle (component)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("import { Handle, Position } from '@xyflow/react'"); -}); - -test("reactflow_get_api prefers corpus metadata for Background", async () => { - const result = await reactFlowGetApi.invoke({ name: "Background" }); - const text = extractTextContent(result); - - expect(text).toContain("# Background (component)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("import { Background, BackgroundVariant } from '@xyflow/react'"); -}); - -test("reactflow_get_api prefers corpus metadata for Controls", async () => { - const result = await reactFlowGetApi.invoke({ name: "Controls" }); - const text = extractTextContent(result); - - expect(text).toContain("# Controls (component)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("import { Controls, ControlButton } from '@xyflow/react'"); -}); - -test("reactflow_get_api prefers corpus metadata for MiniMap", async () => { - const result = await reactFlowGetApi.invoke({ name: "MiniMap" }); - const text = extractTextContent(result); - - expect(text).toContain("# MiniMap (component)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("import { MiniMap } from '@xyflow/react'"); -}); - -test("reactflow_get_api prefers corpus metadata for useReactFlow", async () => { - const result = await reactFlowGetApi.invoke({ name: "useReactFlow" }); - const text = extractTextContent(result); - - expect(text).toContain("# useReactFlow (hook)"); - expect(text).toContain("**Corpus Source:** frontend.reactflow"); - expect(text).toContain("fitView, zoomIn, zoomOut"); -}); - -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { - const result = await reactFlowGetApi.invoke({ name: "NodeResizer" }); - const text = extractTextContent(result); - - expect(text).toContain("# NodeResizer (component)"); - expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); -}); diff --git a/tests/role-harness-behaviour.test.ts b/tests/role-harness-behaviour.test.ts deleted file mode 100644 index a08f2aa..0000000 --- a/tests/role-harness-behaviour.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { test, expect } from "bun:test"; -import { existsSync, readFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { - compileUsingHyperstackBootstrap, - validateUsingHyperstackBootstrap, -} from "../src/internal/context-compiler.ts"; - -const REQUIRED_ROLE_FILES = [ - "agents/hyper/PROFILE.md", - "agents/hyper/LIFECYCLE.md", - "agents/hyper/CONTEXT.md", - "agents/hyper/CHECKS.md", - "agents/frontend-builder/PROFILE.md", - "agents/frontend-builder/LIFECYCLE.md", - "agents/frontend-builder/CONTEXT.md", - "agents/frontend-builder/CHECKS.md", - "harness/router.md", - "harness/transitions.md", - "harness/context-policy.md", - "harness/observability.md", -]; - -const REQUIRED_PROFILE_KEYS = [ - "name", - "kind", - "auto_invoke_when", - "owns", - "must_not_do", - "delegates_to", - "requires", -]; - -function normalize(str: string): string { - return str.replace(/\r\n/g, "\n"); -} - -test("role harness files exist for hyper and frontend-builder", () => { - for (const relativePath of REQUIRED_ROLE_FILES) { - expect(existsSync(resolve(relativePath))).toBe(true); - } -}); - -test("role profile frontmatter includes the required contract keys", () => { - for (const relativePath of [ - "agents/hyper/PROFILE.md", - "agents/frontend-builder/PROFILE.md", - ]) { - const content = normalize(readFileSync(resolve(relativePath), "utf8")); - const frontmatter = content.match(/^---\n([\s\S]*?)\n---\n/); - expect(frontmatter?.[1]).toBeDefined(); - - if (frontmatter) { - for (const key of REQUIRED_PROFILE_KEYS) { - expect(frontmatter[1]).toMatch(new RegExp(`^${key}:`, "m")); - } - } - } -}); - -test("role lifecycle and checks documents expose required headings", () => { - const lifecycleContent = normalize(readFileSync(resolve("agents/hyper/LIFECYCLE.md"), "utf8")); - expect(lifecycleContent).toMatch(/^## Entry Criteria$/m); - expect(lifecycleContent).toMatch(/^## Steps$/m); - expect(lifecycleContent).toMatch(/^## Handoffs$/m); - expect(lifecycleContent).toMatch(/^## Exit Criteria$/m); - expect(lifecycleContent).toMatch(/^## Failure Escalation$/m); - - const checksContent = normalize(readFileSync(resolve("agents/frontend-builder/CHECKS.md"), "utf8")); - expect(checksContent).toMatch(/^## Preconditions$/m); - expect(checksContent).toMatch(/^## Required Evidence$/m); - expect(checksContent).toMatch(/^## Done Criteria$/m); - expect(checksContent).toMatch(/^## Red Flags$/m); -}); - -test("hyperstack bootstrap compiler preserves role-routing markers", () => { - const source = normalize(readFileSync(resolve("skills/hyperstack/SKILL.md"), "utf8")); - const { content } = compileUsingHyperstackBootstrap(source); - const missing = validateUsingHyperstackBootstrap(content); - - expect(missing.length).toBe(0); - expect(content).toMatch(/hyper/); - expect(content).toMatch(/frontend-builder/); - expect(content).toMatch(/auto-called/); - expect(content).toMatch(/hyper -> frontend-builder/); -}); - -test("frontend-builder lifecycle requires workspace discovery before frontend decisions", () => { - const lifecycleContent = normalize(readFileSync(resolve("agents/frontend-builder/LIFECYCLE.md"), "utf8")); - const contextContent = normalize(readFileSync(resolve("agents/frontend-builder/CONTEXT.md"), "utf8")); - - expect(lifecycleContent).toMatch(/workspace/i); - expect(lifecycleContent).toMatch(/package\.json|manifests?|dependencies|packages/i); - expect(lifecycleContent).toMatch(/frontend core files|core frontend files|routes|components|tokens|styles/i); - expect(contextContent).toMatch(/package\.json|manifests?|dependencies|packages/i); -}); - -test("designer skill gives user preferences precedence over auto-resolved defaults", () => { - const designerContent = normalize(readFileSync(resolve("skills/designer/SKILL.md"), "utf8")); - - expect(designerContent).toMatch(/user preferences?/i); - expect(designerContent).toMatch(/preferences?.*override|override.*preferences?/i); - expect(designerContent).toMatch(/auto-resolved defaults?|defaults?.*suggestions?/i); -}); - -test("workspace-first planning makes design contracts conditional rather than universal", () => { - const blueprintContent = normalize(readFileSync(resolve("skills/blueprint/SKILL.md"), "utf8")); - const designerContent = normalize(readFileSync(resolve("skills/designer/SKILL.md"), "utf8")); - const hyperstackContent = normalize(readFileSync(resolve("skills/hyperstack/SKILL.md"), "utf8")); - const shipGateContent = normalize(readFileSync(resolve("skills/ship-gate/SKILL.md"), "utf8")); - const shadcnExpertContent = normalize(readFileSync(resolve("skills/shadcn-expert/SKILL.md"), "utf8")); - - expect(blueprintContent).toMatch(/workspace_inventory/i); - expect(blueprintContent).toMatch(/design_contract/i); - expect(blueprintContent).toMatch(/conditional|required only/i); - - expect(designerContent).toMatch(/conditional|required only/i); - expect(designerContent).toMatch(/new surface|visual-semantic|existing pattern/i); - - expect(hyperstackContent).toMatch(/workspace_inventory/i); - expect(hyperstackContent).toMatch(/design_contract/i); - expect(hyperstackContent).toMatch(/conditional|required only/i); - - expect(shipGateContent).toMatch(/design contract/i); - expect(shipGateContent).toMatch(/when.*required|required.*design contract/i); - - expect(shadcnExpertContent).toMatch(/workspace inventory|workspace-first/i); - expect(shadcnExpertContent).toMatch(/designer only if required|conditional/i); -}); - -test("legacy website-builder role files are removed", () => { - expect(existsSync(resolve("agents/website-builder/PROFILE.md"))).toBe(false); - expect(existsSync(resolve("agents/website-builder/LIFECYCLE.md"))).toBe(false); - expect(existsSync(resolve("agents/website-builder/CONTEXT.md"))).toBe(false); - expect(existsSync(resolve("agents/website-builder/CHECKS.md"))).toBe(false); -}); diff --git a/tests/router-behaviour.test.ts b/tests/router-behaviour.test.ts deleted file mode 100644 index cdccfe8..0000000 --- a/tests/router-behaviour.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { expect, test } from "bun:test"; -import { loadTopology } from "../src/engine/topology-loader.ts"; -import { routeRequest } from "../src/engine/router.ts"; -import { assertSkillAllowedForAgent } from "../src/engine/skill-enforcer.ts"; - -test("routeRequest sends existing-project frontend logic work to frontend-builder without design contract", () => { - const topology = loadTopology(process.cwd()); - const route = routeRequest(topology, { - requestId: "req-1", - domainTargets: ["frontend"], - capabilityTargets: ["frontend.patterns"], - workspaceInventory: { - projectMode: "existing", - existingPatterns: ["existing form shell"], - }, - changeClassification: "frontend_logic", - }); - - expect(route.agent.id).toBe("frontend-builder"); - expect(route.requiredArtifacts).toContain("workspace_inventory"); - expect(route.requiredArtifacts).not.toContain("design_contract"); -}); - -test("routeRequest requires design contract for a new visual surface", () => { - const topology = loadTopology(process.cwd()); - const route = routeRequest(topology, { - requestId: "req-2", - domainTargets: ["frontend"], - capabilityTargets: ["design.intent"], - workspaceInventory: { - projectMode: "greenfield", - existingPatterns: [], - }, - changeClassification: "frontend_visual", - }); - - expect(route.agent.id).toBe("frontend-builder"); - expect(route.requiredArtifacts).toContain("workspace_inventory"); - expect(route.requiredArtifacts).toContain("design_contract"); - expect(route.proofMode).toBe("visual_and_behavioral"); -}); - -test("routeRequest sends mixed frontend+backend work to fullstack-builder", () => { - const topology = loadTopology(process.cwd()); - const route = routeRequest(topology, { - requestId: "req-3", - domainTargets: ["frontend", "backend"], - capabilityTargets: ["frontend.patterns", "backend.http.patterns"], - workspaceInventory: { - projectMode: "existing", - existingPatterns: [], - }, - changeClassification: "fullstack_slice", - }); - - expect(route.agent.id).toBe("fullstack-builder"); - expect(route.proofMode).toBe("visual_and_behavioral"); -}); - -test("assertSkillAllowedForAgent rejects backend-only review skill on frontend-builder", () => { - const topology = loadTopology(process.cwd()); - const route = routeRequest(topology, { - requestId: "req-4", - domainTargets: ["frontend"], - capabilityTargets: ["frontend.patterns"], - workspaceInventory: { - projectMode: "existing", - existingPatterns: ["existing form shell"], - }, - changeClassification: "frontend_logic", - }); - - expect(() => assertSkillAllowedForAgent(route.agent, "security-review")).toThrow(/not allowed/i); -}); - -test("assertSkillAllowedForAgent accepts designer for frontend-builder", () => { - const topology = loadTopology(process.cwd()); - const route = routeRequest(topology, { - requestId: "req-5", - domainTargets: ["frontend"], - capabilityTargets: ["design.intent"], - workspaceInventory: { - projectMode: "greenfield", - existingPatterns: [], - }, - changeClassification: "frontend_visual", - }); - - expect(() => assertSkillAllowedForAgent(route.agent, "designer")).not.toThrow(); -}); diff --git a/tests/runtime-behaviour.test.ts b/tests/runtime-behaviour.test.ts deleted file mode 100644 index d0a74cd..0000000 --- a/tests/runtime-behaviour.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { test, expect } from "bun:test"; -import { readFile } from "node:fs/promises"; -import { resolve } from "node:path"; -import { once } from "node:events"; -import { spawn } from "node:child_process"; -import { generateMcpPatch } from "../src/internal/setup-hyperstack.ts"; - -function normalize(str: string): string { - return str.replace(/\r\n/g, "\n"); -} - -async function runSessionStartHook(envOverrides: Record) { - const child = spawn(process.execPath, [resolve("hooks/session-start.mjs")], { - cwd: process.cwd(), - env: { ...process.env, ...envOverrides }, - stdio: ["ignore", "pipe", "pipe"], - }); - - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk: Buffer | string) => { - stdout += chunk.toString(); - }); - child.stderr.on("data", (chunk: Buffer | string) => { - stderr += chunk.toString(); - }); - - const [exitCode] = (await once(child, "close")) as [number | null]; - if (exitCode !== 0) { - throw new Error(`SessionStart hook failed.\nstdout:\n${stdout}\nstderr:\n${stderr}`); - } - - return JSON.parse(stdout) as { - additional_context?: string; - additionalContext?: string; - hookSpecificOutput?: { additionalContext?: string }; - }; -} - -test("Claude SessionStart hook command executes successfully on this platform", async () => { - const raw = await readFile(resolve("hooks/hooks.json"), "utf8"); - const config = JSON.parse(raw) as { - hooks: { - SessionStart: Array<{ - hooks: Array<{ command: string }>; - }>; - }; - }; - - const command = config.hooks.SessionStart[0]?.hooks[0]?.command; - expect(command).toBeDefined(); - - const scriptPath = resolve("hooks/session-start.mjs"); - const child = spawn(process.execPath, [scriptPath], { - cwd: process.cwd(), - env: { ...process.env, CLAUDE_PLUGIN_ROOT: process.cwd() }, - stdio: ["ignore", "pipe", "pipe"], - }); - - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk: Buffer | string) => { - stdout += chunk.toString(); - }); - child.stderr.on("data", (chunk: Buffer | string) => { - stderr += chunk.toString(); - }); - - const [exitCode] = (await once(child, "close")) as [number | null]; - if (exitCode !== 0) { - throw new Error(`SessionStart hook failed.\nstdout:\n${stdout}\nstderr:\n${stderr}`); - } - - const payload = JSON.parse(stdout) as { - additionalContext?: string; - hookSpecificOutput?: { additionalContext?: string }; - }; - - expect(payload.additionalContext || payload.hookSpecificOutput?.additionalContext).toBeDefined(); - expect(normalize(payload.additionalContext || payload.hookSpecificOutput?.additionalContext || "")).toMatch( - /generated topology bootstrap|compiled runtime bootstrap/, - ); -}); - -test("SessionStart hook emits Cursor-compatible output shape when CURSOR_PLUGIN_ROOT is set", async () => { - const payload = await runSessionStartHook({ - CURSOR_PLUGIN_ROOT: process.cwd(), - CLAUDE_PLUGIN_ROOT: undefined, - COPILOT_CLI: undefined, - }); - - expect(payload.additional_context).toBeDefined(); - expect(payload.additionalContext).toBeUndefined(); - expect(payload.hookSpecificOutput).toBeUndefined(); -}); - -test("SessionStart hook emits SDK-standard output shape when no platform-specific env is set", async () => { - const payload = await runSessionStartHook({ - CURSOR_PLUGIN_ROOT: undefined, - CLAUDE_PLUGIN_ROOT: undefined, - COPILOT_CLI: undefined, - }); - - expect(payload.additionalContext).toBeDefined(); - expect(payload.additional_context).toBeUndefined(); - expect(payload.hookSpecificOutput).toBeUndefined(); -}); - -test("SessionStart hook prefers Cursor output when both CURSOR_PLUGIN_ROOT and CLAUDE_PLUGIN_ROOT are set", async () => { - const payload = await runSessionStartHook({ - CURSOR_PLUGIN_ROOT: process.cwd(), - CLAUDE_PLUGIN_ROOT: process.cwd(), - COPILOT_CLI: undefined, - }); - - expect(payload.additional_context).toBeDefined(); - expect(payload.additionalContext).toBeUndefined(); - expect(payload.hookSpecificOutput).toBeUndefined(); -}); - -test("package bin entry prints usage when invoked without command arguments", async () => { - const raw = await readFile(resolve("package.json"), "utf8"); - const pkg = JSON.parse(raw) as { - bin?: { hyperstack?: string }; - }; - - const binEntry = pkg.bin?.hyperstack; - expect(binEntry).toBeDefined(); - - const child = spawn(process.execPath, [resolve(binEntry!)], { - cwd: process.cwd(), - stdio: ["pipe", "pipe", "pipe"], - }); - - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk: Buffer | string) => { - stdout += chunk.toString(); - }); - child.stderr.on("data", (chunk: Buffer | string) => { - stderr += chunk.toString(); - }); - - const [exitCode] = (await once(child, "close")) as [number | null]; - expect(exitCode).toBe(1); - expect(`${stdout}${stderr}`).toMatch(/Usage: hyperstack tool/); -}); - -test("generateMcpPatch defaults to local runtime instead of docker", () => { - const patch = generateMcpPatch("/tmp/config.json", "/repo", "cursor"); - const serialized = JSON.stringify(patch.content); - - expect(serialized).toMatch(/hyperstack/); - expect(serialized).toMatch(/bin\/hyperstack\.mjs/); - expect(serialized).not.toMatch(/docker/); -}); diff --git a/tests/rust-practice-corpus-backed-tools-behaviour.test.ts b/tests/rust-practice-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index a5a734b..0000000 --- a/tests/rust-practice-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerRustGetPractice } from "../src/plugins/rust/tools/get-practice.ts"; - -const rustGetPractice = captureTool(registerRustGetPractice); - -test("rust_get_practice prefers corpus metadata for borrow-over-clone", async () => { - const result = await rustGetPractice.invoke({ name: "borrow-over-clone" }); - const text = extractTextContent(result); - - expect(text).toContain("# borrow-over-clone [coding-styles]"); - expect(text).toContain("**Corpus Source:** backend.rust"); - expect(text).toContain("fn process(data: &[u8])"); -}); - -test("rust_get_practice prefers corpus metadata for result-not-panic", async () => { - const result = await rustGetPractice.invoke({ name: "result-not-panic" }); - const text = extractTextContent(result); - - expect(text).toContain("# result-not-panic [error-handling]"); - expect(text).toContain("**Corpus Source:** backend.rust"); - expect(text).toContain("fs::read_to_string(path)?"); -}); - -test("rust_get_practice prefers corpus metadata for no-unwrap-in-prod", async () => { - const result = await rustGetPractice.invoke({ name: "no-unwrap-in-prod" }); - const text = extractTextContent(result); - - expect(text).toContain("# no-unwrap-in-prod [error-handling]"); - expect(text).toContain("**Corpus Source:** backend.rust"); - expect(text).toContain("map.get(&key)?"); -}); - -test("rust_get_practice falls back to in-file data for non-corpus practices", async () => { - const result = await rustGetPractice.invoke({ name: "prefer-iterators" }); - const text = extractTextContent(result); - - expect(text).toContain("# prefer-iterators [performance]"); - expect(text).not.toContain("**Corpus Source:** backend.rust"); -}); diff --git a/tests/shadcn-component-corpus-backed-tools-behaviour.test.ts b/tests/shadcn-component-corpus-backed-tools-behaviour.test.ts deleted file mode 100644 index 42815ff..0000000 --- a/tests/shadcn-component-corpus-backed-tools-behaviour.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { expect, test } from "bun:test"; -import { captureTool, extractTextContent } from "./helpers"; -import { register as registerShadcnGetComponent } from "../src/plugins/shadcn/tools/get-component.ts"; - -const shadcnGetComponent = captureTool(registerShadcnGetComponent); - -test("shadcn_get_component prefers corpus metadata for Button", async () => { - const result = await shadcnGetComponent.invoke({ name: "Button" }); - const text = extractTextContent(result); - - expect(text).toContain("# Button"); - expect(text).toContain("**Corpus Source:** frontend.shadcn"); - expect(text).toContain('