Skip to content

Commit 6b6bc9d

Browse files
committed
feat: add what's-new audit system, ast-grep structural rules, and GH Actions workflow
- audit-whatsnew.sh: cross-repo scan of SvelteKit patterns feed + CF changelog - whatsnew-check.md: Claude rule for platform update awareness - whatsnew-report.md: slash command for deep what's-new review with feed gap detection - ast-grep-rules/: 8 YAML rules for D1/SQL antipatterns (injection, N+1, unbounded, unchecked, empty catch, god functions) - ast-grep-scan.yml: reusable GH Actions workflow with SARIF support - sync.ts: register all new files for distribution to consumer repos InfoSec: ast-grep rules enforce parameterized queries and catch SQL injection patterns
1 parent 234010b commit 6b6bc9d

13 files changed

Lines changed: 935 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
allowed-tools: Read, Glob, Grep, Bash, WebFetch, Write
3+
description: Check for new SvelteKit patterns and Cloudflare updates relevant to this project
4+
---
5+
6+
## Context
7+
- Current directory: !`pwd`
8+
- Svelte version: !`cat package.json 2>/dev/null | grep -E '"svelte"' | head -1 || echo "not found"`
9+
- SvelteKit version: !`cat package.json 2>/dev/null | grep -E '"@sveltejs/kit"' | head -1 || echo "not found"`
10+
- Wrangler config: !`ls wrangler.jsonc wrangler.json wrangler.toml 2>/dev/null | head -1 || echo "none"`
11+
12+
## Your task
13+
14+
Audit this project against upstream SvelteKit and Cloudflare updates, then produce a structured "What's New" report showing what the project could adopt.
15+
16+
### Step 1: Gather project context
17+
18+
1. Read `package.json` to get current Svelte, SvelteKit, and wrangler versions
19+
2. Read `wrangler.jsonc` (or `.json`/`.toml`) to identify which Cloudflare bindings are in use (D1, R2, KV, Queues, Durable Objects, etc.)
20+
3. Use Glob to get a sense of the project structure (`src/routes/**`, `src/lib/**`)
21+
22+
### Step 2: Fetch SvelteKit patterns feed
23+
24+
Use WebFetch to retrieve `https://svelte.cogley.jp/feeds/patterns.json`.
25+
26+
For each pattern in the feed:
27+
- Check if the project's Svelte/SvelteKit version meets the pattern's `since` requirement
28+
- If yes, use Grep to search `src/` for the pattern's `search_signatures`
29+
- Classify each pattern as:
30+
- **Actionable** — project version qualifies AND legacy signatures found in code
31+
- **Available** — project version qualifies but no legacy code found (already adopted or not applicable)
32+
- **Blocked** — project version is too old for this pattern
33+
34+
Focus on **Actionable** patterns — these represent concrete modernization opportunities.
35+
36+
### Step 3: Fetch Cloudflare changelog
37+
38+
Use WebFetch to retrieve `https://developers.cloudflare.com/changelog/rss/index.xml`.
39+
40+
Filter entries from the last 90 days to only those matching the project's Cloudflare bindings. For each relevant entry:
41+
- Summarize what changed
42+
- Assess impact: **Breaking** (must act), **Opportunity** (should consider), or **Informational** (nice to know)
43+
- Check if the project's `wrangler.jsonc` or code already reflects the change
44+
45+
### Step 4: Cross-check the patterns feed
46+
47+
The SvelteKit patterns feed at `https://svelte.cogley.jp/feeds/patterns.json` is maintained in the `pub-cogley/apps/migrate-to-svelte` app. When you discover new Svelte/SvelteKit features from the changelog or releases that are NOT yet in the patterns feed, flag them:
48+
49+
- Compare your findings from Steps 2-3 against the feed's pattern IDs and `since` versions
50+
- For each new feature you found upstream that the feed doesn't cover yet, add it to a "Feed Gaps" section in the report
51+
- This helps keep the reference feed current — the user can then update it via `/local:update-svelte-reference` in the pub-cogley repo
52+
53+
### Step 5: Check for deprecations
54+
55+
56+
Search the codebase for known deprecation patterns:
57+
- `compatibility_date` in wrangler config — is it older than 6 months?
58+
- Deprecated Cloudflare APIs (e.g., `HTMLRewriter` constructor changes, old D1 session API)
59+
- Deprecated SvelteKit APIs (e.g., old `load` function signatures, `goto` options)
60+
61+
### Step 6: Write the report
62+
63+
Create the report at `docs/plans/whatsnew-report-YYYY-MM-DD.md` (use today's date).
64+
65+
```markdown
66+
# What's New Report — YYYY-MM-DD
67+
68+
**Project:** (name from package.json)
69+
**Svelte:** X.Y.Z | **SvelteKit:** X.Y.Z | **Wrangler:** X.Y.Z
70+
**Cloudflare bindings:** D1, R2, KV, ... (from wrangler config)
71+
72+
## Executive Summary
73+
74+
(2-3 sentences: how current is this project? any urgent items?)
75+
76+
## Urgency Tiers
77+
78+
### Must Act (Breaking changes / Deprecations)
79+
80+
| Source | Item | Impact | Action Required |
81+
|--------|------|--------|-----------------|
82+
| CF/SK | ... | ... | ... |
83+
84+
### Should Consider (Opportunities)
85+
86+
| Source | Item | Benefit | Effort |
87+
|--------|------|---------|--------|
88+
| CF/SK | ... | ... | ... |
89+
90+
### Informational (Nice to know)
91+
92+
- Bullet list of FYI items
93+
94+
## SvelteKit Patterns Detail
95+
96+
### Actionable Patterns
97+
98+
#### Pattern Title (`since`)
99+
- **Category:** architecture/syntax/tooling
100+
- **Legacy found in:** `src/path/file.svelte:42`, ...
101+
- **Modern approach:** (from pattern's replacement field)
102+
- **Docs:** [link](url)
103+
104+
(repeat for each actionable pattern)
105+
106+
### Already Adopted
107+
- pattern-id-1: Title
108+
- pattern-id-2: Title
109+
110+
## Cloudflare Updates Detail
111+
112+
### Relevant Changelog Entries (last 90 days)
113+
114+
#### [Date] Product: Title
115+
- **Impact:** Breaking / Opportunity / Informational
116+
- **Summary:** what changed
117+
- **Project status:** Already adopted / Needs update / Not applicable
118+
- **Link:** changelog URL
119+
120+
(repeat for each relevant entry)
121+
122+
## Patterns Feed Gaps
123+
124+
Features found upstream that are not yet in the `svelte.cogley.jp/feeds/patterns.json` feed:
125+
126+
| Feature | Version | Why it should be a pattern |
127+
|---------|---------|---------------------------|
128+
| ... | ... | ... |
129+
130+
To update the feed, work in `pub-cogley` and run `/local:update-svelte-reference`.
131+
132+
## Staleness Indicators
133+
134+
| Check | Status | Detail |
135+
|-------|--------|--------|
136+
| compatibility_date | current/stale | value and age |
137+
| Svelte version | current/behind | latest vs installed |
138+
| SvelteKit version | current/behind | latest vs installed |
139+
| Wrangler version | current/behind | latest vs installed |
140+
```
141+
142+
Create the `docs/plans/` directory if it does not exist.
143+
144+
### Step 7: Summarize for the user
145+
146+
After writing the report:
147+
- Show the path to the report file
148+
- Count: N actionable SvelteKit patterns, M relevant CF updates, K deprecation warnings
149+
- Highlight the top 3 most impactful items to act on
150+
- Note: for a quick cross-repo scan, run `./scripts/audit-whatsnew.sh`
151+
152+
Handle any arguments: $ARGUMENTS
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# What's New Check Rule
2+
3+
When updating dependencies (package.json, wrangler.jsonc) or working on SvelteKit/Cloudflare Workers code, check if there are newer platform features the project could adopt.
4+
5+
## When this applies
6+
7+
- After running `npm update`, `pnpm update`, or modifying dependency versions
8+
- When modifying `wrangler.jsonc` or Cloudflare bindings
9+
- When adding new SvelteKit routes, load functions, or server endpoints
10+
11+
## What to check
12+
13+
1. **SvelteKit patterns feed:** Fetch `https://svelte.cogley.jp/feeds/patterns.json` and compare the project's Svelte/SvelteKit version against each pattern's `since` field. Flag patterns where the project's version is high enough to adopt the modern approach but the codebase still uses legacy signatures.
14+
15+
2. **Cloudflare changelog:** Check `https://developers.cloudflare.com/changelog/rss/index.xml` for entries in the last 90 days matching the project's Cloudflare bindings (D1, R2, KV, Workers, etc. — read from `wrangler.jsonc`). Flag entries that introduce new APIs, deprecations, or breaking changes relevant to the project.
16+
17+
## How to report
18+
19+
If you find relevant updates, mention them briefly after completing the primary task:
20+
21+
> **Platform updates available:** Found N SvelteKit pattern(s) and M Cloudflare update(s) relevant to this project. Run `/dev:whatsnew-report` for details.
22+
23+
If you discover upstream features that are NOT in the patterns feed, note them:
24+
25+
> **Patterns feed gap:** Found N feature(s) from recent Svelte/SvelteKit releases not yet in the svelte.cogley.jp patterns feed. Update the feed in `pub-cogley` with `/local:update-svelte-reference`.
26+
27+
Do not block the user's primary task for these checks. This is informational only.
28+
29+
## Deterministic companion
30+
31+
For a full cross-repo audit, the developer can run `./scripts/audit-whatsnew.sh` which performs the same checks deterministically across all eSolia repos.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# ============================================================================
2+
# Reusable ast-grep Structural Scan Workflow
3+
# ============================================================================
4+
#
5+
# Runs ast-grep rules against the codebase to catch structural antipatterns
6+
# that oxlint/ESLint cannot express: N+1 queries, SQL injection via template
7+
# literals, unchecked D1 mutations, unbounded queries, empty catch blocks.
8+
#
9+
# Usage in calling repo:
10+
#
11+
# jobs:
12+
# ast-grep:
13+
# uses: eSolia/.github/.github/workflows/ast-grep-scan.yml@main
14+
# with:
15+
# source-paths: 'src/'
16+
#
17+
# Rules are synced from esolia.github into scripts/shared/ast-grep-rules/
18+
# via sync.ts. If the rules directory is missing, the workflow will sync it.
19+
#
20+
# InfoSec: All inputs are passed via env: to prevent command injection (CWE-78)
21+
# ============================================================================
22+
23+
name: ast-grep Structural Scan
24+
25+
on:
26+
workflow_call:
27+
inputs:
28+
node-version:
29+
description: 'Node.js version'
30+
required: false
31+
type: string
32+
default: '22'
33+
source-paths:
34+
description: 'Space-separated paths to scan'
35+
required: false
36+
type: string
37+
default: 'src/'
38+
fail-on-error:
39+
description: 'Fail the workflow if error-severity findings exist'
40+
required: false
41+
type: boolean
42+
default: true
43+
fail-on-warning:
44+
description: 'Fail the workflow if warning-severity findings exist'
45+
required: false
46+
type: boolean
47+
default: false
48+
upload-sarif:
49+
description: 'Upload SARIF to GitHub Code Scanning (requires code-scanning write permission)'
50+
required: false
51+
type: boolean
52+
default: false
53+
54+
permissions:
55+
contents: read
56+
security-events: write
57+
58+
jobs:
59+
scan:
60+
name: ast-grep Scan
61+
runs-on: ubuntu-latest
62+
63+
steps:
64+
- name: Checkout
65+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
66+
67+
- name: Setup Node.js
68+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
69+
with:
70+
node-version: ${{ inputs.node-version }}
71+
72+
- name: Install ast-grep
73+
run: npm install -g @ast-grep/cli
74+
75+
- name: Locate rules
76+
id: rules
77+
env:
78+
INPUT_PATHS: ${{ inputs.source-paths }}
79+
run: |
80+
# Check for synced rules first, then fallback locations
81+
if [ -d "scripts/shared/ast-grep-rules" ] && [ -f "scripts/shared/ast-grep-rules/sgconfig.yml" ]; then
82+
echo "config=scripts/shared/ast-grep-rules/sgconfig.yml" >> "$GITHUB_OUTPUT"
83+
echo "found=true" >> "$GITHUB_OUTPUT"
84+
elif [ -d "ast-grep-rules" ] && [ -f "ast-grep-rules/sgconfig.yml" ]; then
85+
echo "config=ast-grep-rules/sgconfig.yml" >> "$GITHUB_OUTPUT"
86+
echo "found=true" >> "$GITHUB_OUTPUT"
87+
else
88+
echo "::warning::No ast-grep rules found. Run sync.ts to fetch shared rules."
89+
echo "found=false" >> "$GITHUB_OUTPUT"
90+
fi
91+
92+
- name: Run ast-grep scan
93+
if: steps.rules.outputs.found == 'true'
94+
id: scan
95+
env:
96+
CONFIG_PATH: ${{ steps.rules.outputs.config }}
97+
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
98+
FAIL_ON_WARNING: ${{ inputs.fail-on-warning }}
99+
run: |
100+
echo "## ast-grep Structural Scan" >> "$GITHUB_STEP_SUMMARY"
101+
echo "" >> "$GITHUB_STEP_SUMMARY"
102+
103+
# Run scan and capture output
104+
set +e
105+
sg scan --config "$CONFIG_PATH" --format json > /tmp/sg-results.json 2>&1
106+
SCAN_EXIT=$?
107+
set -e
108+
109+
# Parse results
110+
ERROR_COUNT=$(jq '[.[] | select(.severity == "error")] | length' /tmp/sg-results.json 2>/dev/null || echo "0")
111+
WARN_COUNT=$(jq '[.[] | select(.severity == "warning")] | length' /tmp/sg-results.json 2>/dev/null || echo "0")
112+
INFO_COUNT=$(jq '[.[] | select(.severity == "hint" or .severity == "info")] | length' /tmp/sg-results.json 2>/dev/null || echo "0")
113+
TOTAL=$((ERROR_COUNT + WARN_COUNT + INFO_COUNT))
114+
115+
echo "| Severity | Count |" >> "$GITHUB_STEP_SUMMARY"
116+
echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY"
117+
echo "| Error | $ERROR_COUNT |" >> "$GITHUB_STEP_SUMMARY"
118+
echo "| Warning | $WARN_COUNT |" >> "$GITHUB_STEP_SUMMARY"
119+
echo "| Info | $INFO_COUNT |" >> "$GITHUB_STEP_SUMMARY"
120+
echo "" >> "$GITHUB_STEP_SUMMARY"
121+
122+
if [ "$TOTAL" -eq 0 ]; then
123+
echo "✅ No structural issues found." >> "$GITHUB_STEP_SUMMARY"
124+
else
125+
# Show findings in summary
126+
echo "### Findings" >> "$GITHUB_STEP_SUMMARY"
127+
echo '```' >> "$GITHUB_STEP_SUMMARY"
128+
sg scan --config "$CONFIG_PATH" 2>&1 | head -100 >> "$GITHUB_STEP_SUMMARY"
129+
echo '```' >> "$GITHUB_STEP_SUMMARY"
130+
fi
131+
132+
# Determine exit code
133+
if [ "$FAIL_ON_ERROR" = "true" ] && [ "$ERROR_COUNT" -gt 0 ]; then
134+
echo "::error::Found $ERROR_COUNT error-severity structural issues"
135+
echo "should_fail=true" >> "$GITHUB_OUTPUT"
136+
elif [ "$FAIL_ON_WARNING" = "true" ] && [ "$WARN_COUNT" -gt 0 ]; then
137+
echo "::error::Found $WARN_COUNT warning-severity structural issues"
138+
echo "should_fail=true" >> "$GITHUB_OUTPUT"
139+
else
140+
echo "should_fail=false" >> "$GITHUB_OUTPUT"
141+
fi
142+
143+
echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
144+
echo "errors=$ERROR_COUNT" >> "$GITHUB_OUTPUT"
145+
echo "warnings=$WARN_COUNT" >> "$GITHUB_OUTPUT"
146+
147+
- name: Generate SARIF
148+
if: steps.rules.outputs.found == 'true' && inputs.upload-sarif
149+
env:
150+
CONFIG_PATH: ${{ steps.rules.outputs.config }}
151+
run: |
152+
sg scan --config "$CONFIG_PATH" --format sarif > /tmp/sg-sarif.json 2>/dev/null || true
153+
154+
- name: Upload SARIF
155+
if: steps.rules.outputs.found == 'true' && inputs.upload-sarif
156+
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
157+
with:
158+
sarif_file: /tmp/sg-sarif.json
159+
category: ast-grep
160+
continue-on-error: true
161+
162+
- name: Upload results artifact
163+
if: steps.rules.outputs.found == 'true' && steps.scan.outputs.total != '0'
164+
uses: actions/upload-artifact@ea165f8d65b6db9b8a1f7e126e6c37a8cdc148fe # v4.6.2
165+
with:
166+
name: ast-grep-results
167+
path: /tmp/sg-results.json
168+
retention-days: 30
169+
170+
- name: Fail on findings
171+
if: steps.scan.outputs.should_fail == 'true'
172+
run: exit 1
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Detect empty catch blocks that silently swallow errors
2+
# In Workers there is no long-lived process to crash — silent failures
3+
# mean data loss or broken state with no signal.
4+
id: empty-catch-block
5+
language: TypeScript
6+
severity: warning
7+
message: >-
8+
Empty catch block silently swallows errors. At minimum, log the error
9+
or re-throw. In Workers, silent failures are especially dangerous
10+
because there is no process crash to surface the problem.
11+
rule:
12+
kind: catch_clause
13+
has:
14+
kind: statement_block
15+
not:
16+
has:
17+
kind: expression_statement

0 commit comments

Comments
 (0)