Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
16716bf
chore: coverage push + connection pluggability (#189-#192, #198)
alfredo1996 Mar 27, 2026
70f9823
feat: app features — CSV export, GFM tables, param badges, data trans…
alfredo1996 Mar 28, 2026
a1067b9
ci: trigger fresh SonarCloud scan for PR #222
alfredorubin96 Mar 28, 2026
86440c1
feat: enable jsdom component tests in app/ package
alfredorubin96 Mar 28, 2026
e67dc47
refactor: extract pure business logic from components for testability
alfredorubin96 Mar 28, 2026
823c657
refactor: wire components to use extracted lib functions
alfredorubin96 Mar 28, 2026
5d85c51
test: add transform pipeline tests + E2E spec
alfredorubin96 Mar 29, 2026
35a2271
test: comprehensive transform tests — pipeline ordering + E2E
alfredorubin96 Mar 29, 2026
31406e5
fix: query-editor test teardown leak — clear timers after each test
alfredorubin96 Mar 29, 2026
d7b42dd
fix: resolve lint errors in vitest setup and seed-query test
alfredorubin96 Mar 29, 2026
cc80fcc
chore: add husky pre-commit hook for lint-staged
alfredorubin96 Mar 29, 2026
dee380f
seed: add Transform Playground dashboard (dash-004)
alfredorubin96 Mar 29, 2026
2af574c
seed: add Transform Playground to seed-demo.mjs
alfredorubin96 Mar 29, 2026
cf63aa5
feat: multi-aggregation GroupBy UI + enable/disable transforms toggle
alfredorubin96 Mar 29, 2026
00e770f
feat: pipeline-aware column propagation + preview toggle fix
alfredorubin96 Mar 29, 2026
5c16ebb
fix: skip incomplete transforms in pipeline (no more empty-filter wipe)
alfredorubin96 Mar 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
settings.local.json
.e2e-needed
20 changes: 12 additions & 8 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: code-reviewer
description: Reviews code for quality, security, and NeoBoard conventions.
description: Reviews code for quality, security, and NeoBoard conventions. Use for pre-push reviews, PR reviews, or ad-hoc code audits.
model: sonnet
---
Senior reviewer for NeoBoard. Check staged/unstaged changes against these rules in priority order.
Expand All @@ -18,31 +18,35 @@ Senior reviewer for NeoBoard. Check staged/unstaged changes against these rules
- Credentials never logged or exposed in responses
- `tenant_id` filter present on all DB queries
- `can_write` enforced server-side in API routes, not just UI
- No command injection vectors in Bash/exec calls

### Query Safety (BLOCKING)
- Read-only transactions for non-Form widgets
- Read-only transactions for non-Form widgets (PostgreSQL: `BEGIN READ ONLY`, Neo4j: session access mode)
- Row limits use MAX_ROWS+1 pattern, never LIMIT on user queries
- Timeouts at driver level
- Timeouts at driver level (AbortSignal for pg, native for Neo4j)
- User queries never modified or wrapped

### Architecture (HIGH)
- `component/` has no imports from `app/` or business logic
- `connection/` has no UI/React imports
- `app/` orchestrates, doesn't duplicate component/connection logic
- Charts use `next/dynamic` with `ssr: false`
- ECharts imports from `echarts/core` + specific modules

### Code Quality (MEDIUM)
- TypeScript strict — no untyped `any` without justification
- TypeScript strict — no untyped `any` without justification comment
- New behavior has corresponding tests
- No over-engineering (single-use abstractions, premature generalization)
- Conventional Commits format

## Output Format

```
[CRITICAL] file:line — Issue → Required fix
[HIGH] file:line — Issue → Suggested fix
[MEDIUM] file:line — Issue → Suggested fix
[CRITICAL] file:line — Issue description → Required fix
[HIGH] file:line — Issue description → Suggested fix
[MEDIUM] file:line — Issue description → Suggested fix
[LOW] file:line — Issue description → Suggested fix

Verdict: APPROVE | REQUEST CHANGES (N critical, N high)
Summary: One-line summary.
Summary: One-line summary of the change quality.
```
50 changes: 0 additions & 50 deletions .claude/agents/pr-reviewer.md

This file was deleted.

38 changes: 38 additions & 0 deletions .claude/hooks/check-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
# Hook G: Coverage Threshold Warning
# After test runs, warn if coverage drops below 80%
# Event: PostToolUse (Bash) — non-blocking, async

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
[ -z "$COMMAND" ] && exit 0

# Only activate for test commands
echo "$COMMAND" | grep -qE '(vitest|npm test|npm run test|npx vitest)' || exit 0

STDOUT=$(echo "$INPUT" | jq -r '.tool_result.stdout // empty')
[ -z "$STDOUT" ] && exit 0

# Look for coverage summary lines like "All files | 44.12 | ..."
LOW_COVERAGE=false
WARNING_MSG=""

while IFS= read -r line; do
# Match vitest coverage table format: "All files | XX.XX |"
if echo "$line" | grep -qE '^\s*(All files|Statements|Branches|Functions|Lines)\s*\|?\s*[0-9]+(\.[0-9]+)?'; then
PCT=$(echo "$line" | grep -oE '[0-9]+(\.[0-9]+)?' | head -1)
if [ -n "$PCT" ]; then
INT_PCT=$(echo "$PCT" | cut -d. -f1)
if [ "$INT_PCT" -lt 80 ] 2>/dev/null; then
LOW_COVERAGE=true
WARNING_MSG="${WARNING_MSG} $(echo "$line" | xargs)\n"
fi
fi
fi
done <<< "$STDOUT"

if [ "$LOW_COVERAGE" = true ]; then
printf '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"WARNING: Coverage below 80%% target:\\n%s\\nConsider adding tests before committing."}}' "$WARNING_MSG"
fi

exit 0
31 changes: 31 additions & 0 deletions .claude/hooks/check-credential-logging.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
# Hook B: Credential Logging Guard
# Blocks console.log/warn/error of credential-related variables
# Rule: "NEVER log decrypted credentials."

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
[ -z "$FILE_PATH" ] && exit 0

# Only check TypeScript files in app/ and connection/ (handle both absolute and relative paths)
case "$FILE_PATH" in
*app/src/*|*connection/src/*) ;;
*) exit 0 ;;
esac
echo "$FILE_PATH" | grep -qE '\.(ts|tsx)$' || exit 0

# Get the content being written/edited
NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')
[ -z "$NEW_CONTENT" ] && exit 0

# Credential-related identifiers (case-insensitive)
CRED_PATTERN='(password|passwd|secret|credential|apiKey|api_key|encryptionKey|encryption_key|decrypted|privateKey|private_key|accessToken|access_token|refreshToken|refresh_token)'

# Detect console.log/warn/error/debug containing credential identifiers
if echo "$NEW_CONTENT" | grep -iE "console\.(log|warn|error|debug|info)" | grep -qiE "${CRED_PATTERN}"; then
echo "BLOCKED: Detected logging of credential-related variable." >&2
echo "Rule: NEVER log decrypted credentials. Remove the log statement or redact sensitive data." >&2
exit 2
fi

exit 0
45 changes: 45 additions & 0 deletions .claude/hooks/check-query-safety.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# Hook A: Query Interpolation Guard
# Blocks string interpolation in SQL/Cypher query strings
# Rule: "ALWAYS use parameterized queries. NEVER interpolate user input into query strings."

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
[ -z "$FILE_PATH" ] && exit 0

# Only check files in connection/ and API routes (handle both absolute and relative paths)
case "$FILE_PATH" in
*connection/src/*|*app/src/app/api/*) ;;
*) exit 0 ;;
esac

# Only check TypeScript files
echo "$FILE_PATH" | grep -qE '\.(ts|tsx)$' || exit 0

# Get the content being written/edited
NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')
[ -z "$NEW_CONTENT" ] && exit 0

# Detect template literals with interpolation that look like queries
# Check for SQL/Cypher keywords near ${...} interpolation
QUERY_KEYWORDS='(SELECT|INSERT|UPDATE|DELETE|MERGE|MATCH|CREATE|DROP|ALTER|CALL|RETURN|WITH|UNWIND)'
if echo "$NEW_CONTENT" | grep -qiE "${QUERY_KEYWORDS}" && echo "$NEW_CONTENT" | grep -qF '${'; then
# Confirm it's interpolation inside a template literal (backtick string), not just a standalone ${
# Look for lines that have both a query keyword and ${...} pattern
if echo "$NEW_CONTENT" | grep -iE "${QUERY_KEYWORDS}" | grep -qF '${'; then
echo "BLOCKED: Detected string interpolation (\${...}) near a query keyword." >&2
echo "Rule: ALWAYS use parameterized queries. NEVER interpolate user input into query strings." >&2
echo "Use query parameters (\$1, \$2 for PostgreSQL or \$paramName for Neo4j) instead." >&2
exit 2
fi
fi

# Detect string concatenation with query keywords
# Pattern: a quoted string containing a query keyword, followed by + (concat operator)
if echo "$NEW_CONTENT" | grep -iE "${QUERY_KEYWORDS}" | grep -qE '["\"][[:space:]]*\+[[:space:]]'; then
echo "BLOCKED: Detected string concatenation in what appears to be a query." >&2
echo "Rule: ALWAYS use parameterized queries. NEVER interpolate user input." >&2
exit 2
fi

exit 0
56 changes: 56 additions & 0 deletions .claude/hooks/enforce-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
# Hook: Enforce E2E testing when UI files are edited
# Three modes:
# mark — PostToolUse Edit|Write: flag when UI files change
# check-commit — PreToolUse Bash: block git commit if E2E not run
# clear-on-test — PostToolUse Bash: clear flag after playwright runs

PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
[ -z "$PROJECT_DIR" ] && exit 0
MARKER="$PROJECT_DIR/.claude/.e2e-needed"

case "$1" in
mark)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
[ -z "$FILE_PATH" ] && exit 0
case "$FILE_PATH" in
*/app/src/components/*|*/app/src/app/*)
touch "$MARKER"
if ! grep -qxF "$FILE_PATH" "$MARKER" 2>/dev/null; then
echo "$FILE_PATH" >> "$MARKER"
fi
;;
esac
;;

check-commit)
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Only trigger on git commit commands
echo "$CMD" | grep -qE '^\s*git commit' || exit 0
[ ! -f "$MARKER" ] && exit 0
COUNT=$(sort -u "$MARKER" | wc -l | tr -d ' ')
echo "BLOCKED: $COUNT UI file(s) were edited but Playwright E2E tests have not been run this session." >&2
echo "Run first: cd app && npx playwright test" >&2
echo "" >&2
echo "Edited UI files:" >&2
sort -u "$MARKER" | while read -r f; do echo " - $f" >&2; done
exit 2
;;

clear-on-test)
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Clear marker when playwright tests are run
echo "$CMD" | grep -qE 'playwright test' || exit 0
[ -f "$MARKER" ] && rm -f "$MARKER"
;;

*)
echo "Usage: enforce-e2e.sh <mark|check-commit|clear-on-test>" >&2
exit 1
;;
esac

exit 0
8 changes: 5 additions & 3 deletions .claude/hooks/format-and-lint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
# Auto-format and lint TypeScript files after edits
FILE_PATH=$(echo "$CLAUDE_FILE_PATHS" | head -1)
# Reads file path from stdin JSON (PostToolUse provides tool_input)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
[ -z "$FILE_PATH" ] && exit 0

# Only process TypeScript files
Expand All @@ -10,7 +12,7 @@ echo "$FILE_PATH" | grep -qE '\.(ts|tsx)$' || exit 0
npx prettier --write "$FILE_PATH" 2>/dev/null || true

# Determine package and run appropriate linter
PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
[ -z "$PROJECT_DIR" ] && exit 0

REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
Expand All @@ -23,4 +25,4 @@ elif [[ "$REL_PATH" == connection/* ]]; then
cd "$PROJECT_DIR/connection" && npx eslint --fix "$FILE_PATH" 2>/dev/null || true
fi

exit 0
exit 0
63 changes: 63 additions & 0 deletions .claude/hooks/session-context.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/bash
# Hook E: Inject useful context at session start
# Event: SessionStart (startup)

PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
[ -z "$PROJECT_DIR" ] && exit 0
cd "$PROJECT_DIR"

echo "=== Session Context ==="

# Current branch & tracking
BRANCH=$(git branch --show-current 2>/dev/null)
echo "Branch: $BRANCH"

TRACKING=$(git rev-parse --abbrev-ref "@{upstream}" 2>/dev/null)
if [ -n "$TRACKING" ]; then
AHEAD=$(git rev-list --count "$TRACKING..HEAD" 2>/dev/null)
BEHIND=$(git rev-list --count "HEAD..$TRACKING" 2>/dev/null)
echo "Tracking: $TRACKING (ahead $AHEAD, behind $BEHIND)"
else
echo "Tracking: no upstream set"
fi

# Working tree status
if git diff --quiet && git diff --cached --quiet; then
UNTRACKED=$(git ls-files --others --exclude-standard | wc -l | tr -d ' ')
if [ "$UNTRACKED" = "0" ]; then
echo "Working tree: clean"
else
echo "Working tree: clean ($UNTRACKED untracked files)"
fi
else
MODIFIED=$(git diff --name-only | wc -l | tr -d ' ')
STAGED=$(git diff --cached --name-only | wc -l | tr -d ' ')
echo "Working tree: $MODIFIED modified, $STAGED staged"
fi

# Recent commits
echo ""
echo "Recent commits:"
git log --oneline -5 2>/dev/null

# Open PR on this branch
echo ""
PR_INFO=$(gh pr view --json number,title,state,url 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$PR_INFO" ]; then
PR_NUM=$(echo "$PR_INFO" | jq -r '.number')
PR_TITLE=$(echo "$PR_INFO" | jq -r '.title')
PR_STATE=$(echo "$PR_INFO" | jq -r '.state')
PR_URL=$(echo "$PR_INFO" | jq -r '.url')
echo "Open PR: #$PR_NUM — $PR_TITLE ($PR_STATE)"
echo "URL: $PR_URL"
else
echo "No open PR on this branch."
fi

# Persist project dir as env var for other hooks via CLAUDE_ENV_FILE
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo "NEOBOARD_PROJECT_DIR=$PROJECT_DIR" >> "$CLAUDE_ENV_FILE"
echo "NEOBOARD_BRANCH=$BRANCH" >> "$CLAUDE_ENV_FILE"
fi

exit 0
Loading
Loading