Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83722aeb7c | ||
|
|
2709d4c164 | ||
|
|
549553c05b | ||
|
|
ac3b69f864 | ||
|
|
a62ff5b064 |
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.claude/
|
||||
.superpowers/
|
||||
.worktrees/
|
||||
test-results/
|
||||
|
||||
101
AGENTS.md
Normal file
@@ -0,0 +1,101 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
## Always Do
|
||||
|
||||
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
|
||||
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
|
||||
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
|
||||
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
|
||||
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
|
||||
|
||||
## When Debugging
|
||||
|
||||
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
|
||||
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
|
||||
3. `READ gitnexus://repo/design-system/process/{processName}` — trace the full execution flow step by step
|
||||
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
|
||||
|
||||
## When Refactoring
|
||||
|
||||
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
|
||||
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
|
||||
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
|
||||
|
||||
## Never Do
|
||||
|
||||
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
|
||||
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
|
||||
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
|
||||
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
|
||||
|
||||
## Tools Quick Reference
|
||||
|
||||
| Tool | When to use | Command |
|
||||
|------|-------------|---------|
|
||||
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
|
||||
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
|
||||
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
|
||||
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
|
||||
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
|
||||
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
|
||||
|
||||
## Impact Risk Levels
|
||||
|
||||
| Depth | Meaning | Action |
|
||||
|-------|---------|--------|
|
||||
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
|
||||
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
|
||||
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
|
||||
|
||||
## Resources
|
||||
|
||||
| Resource | Use for |
|
||||
|----------|---------|
|
||||
| `gitnexus://repo/design-system/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/design-system/clusters` | All functional areas |
|
||||
| `gitnexus://repo/design-system/processes` | All execution flows |
|
||||
| `gitnexus://repo/design-system/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## Self-Check Before Finishing
|
||||
|
||||
Before completing any code modification task, verify:
|
||||
1. `gitnexus_impact` was run for all modified symbols
|
||||
2. No HIGH/CRITICAL risk warnings were ignored
|
||||
3. `gitnexus_detect_changes()` confirms changes match expected scope
|
||||
4. All d=1 (WILL BREAK) dependents were updated
|
||||
|
||||
## Keeping the Index Fresh
|
||||
|
||||
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
|
||||
|
||||
```bash
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
If the index previously included embeddings, preserve them by adding `--embeddings`:
|
||||
|
||||
```bash
|
||||
npx gitnexus analyze --embeddings
|
||||
```
|
||||
|
||||
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
|
||||
|
||||
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
|
||||
|
||||
## CLI
|
||||
|
||||
| Task | Read this skill file |
|
||||
|------|---------------------|
|
||||
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
|
||||
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
|
||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
|
||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
|
||||
|
||||
<!-- gitnexus:end -->
|
||||
@@ -132,7 +132,7 @@ import camelSvg from '@cameleer/design-system/assets/camel-logo.svg' // simp
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **design-system** (1461 symbols, 2336 relationships, 23 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 941 B After Width: | Height: | Size: 941 B |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cameleer/design-system",
|
||||
"version": "0.1.48",
|
||||
"version": "0.1.51",
|
||||
"type": "module",
|
||||
"main": "./dist/index.es.js",
|
||||
"module": "./dist/index.es.js",
|
||||
|
||||
@@ -8,9 +8,10 @@ interface BreadcrumbItem {
|
||||
interface BreadcrumbProps {
|
||||
items: BreadcrumbItem[]
|
||||
className?: string
|
||||
onNavigate?: (href: string) => void
|
||||
}
|
||||
|
||||
export function Breadcrumb({ items, className }: BreadcrumbProps) {
|
||||
export function Breadcrumb({ items, className, onNavigate }: BreadcrumbProps) {
|
||||
return (
|
||||
<nav aria-label="Breadcrumb" className={className}>
|
||||
<ol className={styles.list}>
|
||||
@@ -22,7 +23,11 @@ export function Breadcrumb({ items, className }: BreadcrumbProps) {
|
||||
{isLast ? (
|
||||
<span className={styles.active}>{item.label}</span>
|
||||
) : item.href ? (
|
||||
<a href={item.href} className={styles.link}>
|
||||
<a
|
||||
href={item.href}
|
||||
className={styles.link}
|
||||
onClick={onNavigate ? (e) => { e.preventDefault(); onNavigate(item.href!) } : undefined}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
) : (
|
||||
|
||||
@@ -61,6 +61,38 @@
|
||||
background: color-mix(in srgb, var(--text-faint) 8%, transparent);
|
||||
}
|
||||
|
||||
.sourceBadge {
|
||||
flex-shrink: 0;
|
||||
font-size: 9px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sourceContainer {
|
||||
color: var(--text-muted);
|
||||
background: color-mix(in srgb, var(--text-muted) 10%, transparent);
|
||||
}
|
||||
|
||||
.sourceApp {
|
||||
color: var(--running);
|
||||
background: color-mix(in srgb, var(--running) 10%, transparent);
|
||||
}
|
||||
|
||||
.sourceAgent {
|
||||
color: var(--warning);
|
||||
background: color-mix(in srgb, var(--warning) 10%, transparent);
|
||||
}
|
||||
|
||||
.sourceDefault {
|
||||
color: var(--text-muted);
|
||||
background: color-mix(in srgb, var(--text-muted) 8%, transparent);
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 12px;
|
||||
font-family: var(--font-mono);
|
||||
|
||||
@@ -3,9 +3,9 @@ import { render, screen } from '@testing-library/react'
|
||||
import { LogViewer, type LogEntry } from './LogViewer'
|
||||
|
||||
const entries: LogEntry[] = [
|
||||
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'Server started' },
|
||||
{ timestamp: '2024-01-15T10:30:05Z', level: 'warn', message: 'High memory usage' },
|
||||
{ timestamp: '2024-01-15T10:30:10Z', level: 'error', message: 'Connection failed' },
|
||||
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'Server started', source: 'app' },
|
||||
{ timestamp: '2024-01-15T10:30:05Z', level: 'warn', message: 'High memory usage', source: 'container' },
|
||||
{ timestamp: '2024-01-15T10:30:10Z', level: 'error', message: 'Connection failed', source: 'agent' },
|
||||
{ timestamp: '2024-01-15T10:30:15Z', level: 'debug', message: 'Query executed in 3ms' },
|
||||
{ timestamp: '2024-01-15T10:30:20Z', level: 'trace', message: 'Entering handleRequest()' },
|
||||
]
|
||||
@@ -52,6 +52,23 @@ describe('LogViewer', () => {
|
||||
expect(el.classList.contains('custom-class')).toBe(true)
|
||||
})
|
||||
|
||||
it('renders source badges when source is provided', () => {
|
||||
render(<LogViewer entries={entries} />)
|
||||
expect(screen.getByText('app')).toBeInTheDocument()
|
||||
expect(screen.getByText('container')).toBeInTheDocument()
|
||||
expect(screen.getByText('agent')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('omits source badge when source is not provided', () => {
|
||||
const noSourceEntries: LogEntry[] = [
|
||||
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'No source here' },
|
||||
]
|
||||
render(<LogViewer entries={noSourceEntries} />)
|
||||
expect(screen.getByText('No source here')).toBeInTheDocument()
|
||||
expect(screen.queryByText('app')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('container')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('has role="log" for accessibility', () => {
|
||||
render(<LogViewer entries={entries} />)
|
||||
expect(screen.getByRole('log')).toBeInTheDocument()
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface LogEntry {
|
||||
timestamp: string
|
||||
level: 'info' | 'warn' | 'error' | 'debug' | 'trace'
|
||||
message: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
export interface LogViewerProps {
|
||||
@@ -21,6 +22,12 @@ const LEVEL_CLASS: Record<LogEntry['level'], string> = {
|
||||
trace: styles.levelTrace,
|
||||
}
|
||||
|
||||
const SOURCE_CLASS: Record<string, string> = {
|
||||
container: styles.sourceContainer,
|
||||
app: styles.sourceApp,
|
||||
agent: styles.sourceAgent,
|
||||
}
|
||||
|
||||
function formatTime(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleTimeString('en-GB', {
|
||||
@@ -67,6 +74,11 @@ export function LogViewer({ entries, maxHeight = 400, className }: LogViewerProp
|
||||
<span className={[styles.levelBadge, LEVEL_CLASS[entry.level]].join(' ')}>
|
||||
{entry.level.toUpperCase()}
|
||||
</span>
|
||||
{entry.source && (
|
||||
<span className={[styles.sourceBadge, SOURCE_CLASS[entry.source] ?? styles.sourceDefault].join(' ')}>
|
||||
{entry.source}
|
||||
</span>
|
||||
)}
|
||||
<span className={styles.message}>{entry.message}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -14,6 +14,7 @@ interface TopBarProps {
|
||||
user?: { name: string }
|
||||
userMenuItems?: import('../../composites/Dropdown/Dropdown').DropdownItem[]
|
||||
onLogout?: () => void
|
||||
onNavigate?: (href: string) => void
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
@@ -24,6 +25,7 @@ export function TopBar({
|
||||
user,
|
||||
userMenuItems,
|
||||
onLogout,
|
||||
onNavigate,
|
||||
className,
|
||||
children,
|
||||
}: TopBarProps) {
|
||||
@@ -33,7 +35,7 @@ export function TopBar({
|
||||
return (
|
||||
<header className={`${styles.topbar} ${className ?? ''}`}>
|
||||
{/* Left: Breadcrumb */}
|
||||
<Breadcrumb items={breadcrumbOverride ?? breadcrumb} className={styles.breadcrumb} />
|
||||
<Breadcrumb items={breadcrumbOverride ?? breadcrumb} className={styles.breadcrumb} onNavigate={onNavigate} />
|
||||
|
||||
{/* Center: consumer-provided controls */}
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styles from './BrandAssetsSection.module.css'
|
||||
import camelLogoSvg from '../../../assets/camel-logo.svg'
|
||||
import cameleer3Logo from '../../../assets/cameleer3-logo.png'
|
||||
import cameleer3LogoSvg from '../../../assets/cameleer3-logo.svg'
|
||||
import cameleerLogo from '../../../assets/cameleer-logo.png'
|
||||
import cameleerLogoSvg from '../../../assets/cameleer-logo.svg'
|
||||
|
||||
const LOGO_SIZES = [16, 32, 48, 180, 192, 512] as const
|
||||
|
||||
@@ -11,7 +11,7 @@ export function BrandAssetsSection() {
|
||||
<h2 className={styles.sectionTitle}>Brand Assets</h2>
|
||||
|
||||
<div className={styles.componentCard}>
|
||||
<h3 className={styles.componentTitle}>Cameleer3 Logo (PNG)</h3>
|
||||
<h3 className={styles.componentTitle}>Cameleer Logo (PNG)</h3>
|
||||
<p className={styles.componentDesc}>
|
||||
Full-resolution logo and pre-generated size variants for favicons, PWA icons, and social images.
|
||||
Shipped as static assets via <code>@cameleer/design-system/assets/*</code> export.
|
||||
@@ -22,27 +22,27 @@ export function BrandAssetsSection() {
|
||||
<div key={size} className={styles.logoItem}>
|
||||
<div className={styles.logoPreview}>
|
||||
<img
|
||||
src={cameleer3Logo}
|
||||
src={cameleerLogo}
|
||||
alt={`Logo ${size}×${size}`}
|
||||
width={Math.min(size, 96)}
|
||||
height={Math.min(size, 96)}
|
||||
/>
|
||||
</div>
|
||||
<span className={styles.logoLabel}>{size}×{size}</span>
|
||||
<code className={styles.logoExport}>assets/cameleer3-{size}.png</code>
|
||||
<code className={styles.logoExport}>assets/cameleer-{size}.png</code>
|
||||
</div>
|
||||
))}
|
||||
<div className={styles.logoItem}>
|
||||
<div className={styles.logoPreview}>
|
||||
<img
|
||||
src={cameleer3Logo}
|
||||
src={cameleerLogo}
|
||||
alt="Full resolution logo"
|
||||
width={96}
|
||||
height={96}
|
||||
/>
|
||||
</div>
|
||||
<span className={styles.logoLabel}>Original</span>
|
||||
<code className={styles.logoExport}>assets/cameleer3-logo.png</code>
|
||||
<code className={styles.logoExport}>assets/cameleer-logo.png</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,10 +57,10 @@ export function BrandAssetsSection() {
|
||||
<div className={styles.logoGrid}>
|
||||
<div className={styles.logoItem}>
|
||||
<div className={styles.logoPreview}>
|
||||
<img src={cameleer3LogoSvg} alt="Cameleer3 SVG logo" width={96} height={96} />
|
||||
<img src={cameleerLogoSvg} alt="Cameleer SVG logo" width={96} height={96} />
|
||||
</div>
|
||||
<span className={styles.logoLabel}>Cameleer3 SVG</span>
|
||||
<code className={styles.logoExport}>assets/cameleer3-logo.svg</code>
|
||||
<span className={styles.logoLabel}>Cameleer SVG</span>
|
||||
<code className={styles.logoExport}>assets/cameleer-logo.svg</code>
|
||||
</div>
|
||||
<div className={styles.logoItem}>
|
||||
<div className={styles.logoPreview}>
|
||||
|
||||