feat(website): derive version + counts from source at build#167
feat(website): derive version + counts from source at build#167
Conversation
…uild Website chip + stats + terminal + features were hardcoded to 0.8.13, 44, 12, 49 — they drifted every release. Now every one of them is read at build time from the real source of truth: - lib/meta.ts (server-only) reads package.json, src/triggers/api.ts (api_path count), src/mcp/tools-registry.ts (memory_* name count), and the HookType union in src/types.ts. - Hero chip, Stats, LiveTerminal, and Features all consume the computed counts via props; no more literal strings in component bodies. - Fallback defaults (44 / 12 / 49) kick in only if the source files cannot be read, so a monorepo re-root still boots. Result: cutting a release bumps the website hero to v<new> automatically, and any new MCP tool or REST endpoint raises the displayed count without an edit. next build stays clean — single static page prerendered.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughPage now calls a new server-side Changes
Sequence DiagramsequenceDiagram
participant Page as Page.tsx
participant Meta as getProjectMeta()
participant FS as Filesystem
participant Stats as Stats
participant Features as Features
participant LiveTerminal as LiveTerminal
participant Hero as Hero
Page->>Meta: invoke getProjectMeta()
Meta->>FS: read package.json, src/triggers/api.ts, src/mcp/tools-registry.ts, src/types.ts, test/
FS-->>Meta: return file contents / counts
Meta-->>Page: {version, mcpTools, hooks, restEndpoints, testsPassing}
Page->>Stats: pass mcpTools, hooks, testsPassing
Page->>Features: pass hooks, mcpTools, restEndpoints
Page->>LiveTerminal: pass mcpTools, hooks
Page->>Hero: pass version
Stats->>Stats: build STATS with props
Features->>Features: compute FEATURES from props
LiveTerminal->>LiveTerminal: generate script with injected values
Hero->>Hero: render v{version}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
website/components/Stats.tsx (1)
33-79: Effect re-runs on prop change, but animation won't replay.Dependency array now includes
mcpTools/hooks/testsPassing, so on prop change the effect tears down the observer and builds a new one — but the target elements still carrydata-done="1"from the first pass, so the new observer willunobservewithout counting. In practice these props are build-time constants so this never triggers, but if the component is ever reused with mutable props the animation will silently stop working. Either resetdata-doneat the start of the effect, or keep the old[]deps and read the latest values from refs.♻️ Optional hardening
useEffect(() => { if (!rootRef.current) return; + rootRef.current + .querySelectorAll<HTMLDivElement>("[data-num]") + .forEach((n) => { delete n.dataset.done; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/components/Stats.tsx` around lines 33 - 79, The effect attaching the IntersectionObserver (inside useEffect) re-runs when mcpTools/hooks/testsPassing change but doesn’t reset per-element state so elements keep data-done="1" and the animation never replays; fix by clearing that flag at the start of the effect (iterate rootRef.current.querySelectorAll("[data-num]") and delete/clear dataset.done) or alternatively revert the dependency array to [] and read latest values via refs; update the code paths referencing rootRef, the count function, and the IntersectionObserver setup to ensure data-done is reset before observing.website/components/Features.tsx (1)
9-13: Default magic numbers duplicated across components.
12 / 44 / 49are repeated here, inStats,LiveTerminal, and insidegetProjectMeta. If the fallback ever needs to change, four files must stay in sync. Consider exporting aDEFAULT_METAconstant from@/lib/metaand reusing it (either as the literal default, or by making props non-optional and always threadingmeta).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/components/Features.tsx` around lines 9 - 13, Default numeric defaults (12, 44, 49) are duplicated across components; export a single DEFAULT_META constant from "@/lib/meta" (e.g., DEFAULT_META = { hooks: 12, mcpTools: 44, restEndpoints: 49 }) and update Features (the Features function signature and its Props), Stats, LiveTerminal and getProjectMeta to use that constant (either by using DEFAULT_META as the inline default for the props or by making props required and supplying DEFAULT_META where components are instantiated) so all fallback values come from one source of truth.website/lib/meta.ts (2)
47-51: Redundant double fallback.
mcpToolsalready falls back to44on line 51, somcpTools || 44on line 62 is dead code. Same shape forhooks/restEndpoints— fallback is applied inconsistently (once at computation, again in the return). Pick one location so the defaults live in exactly one place.♻️ Proposed cleanup
- return { - version: pkg?.version ?? "0.0.0", - mcpTools: mcpTools || 44, - hooks: hooks || 12, - restEndpoints: restEndpoints || 49, - testsPassing: 777, - }; + return { + version: pkg?.version ?? "0.0.0", + mcpTools, + hooks, + restEndpoints: restEndpoints || 49, + testsPassing: 777, + };Also applies to: 60-64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/lib/meta.ts` around lines 47 - 51, The computed counts (mcpTools, hooks, restEndpoints) currently apply the default value twice (once inside the safeCountMatches expression and again at the return), creating dead code; choose a single place for defaults and make them consistent — either remove the trailing "|| 44" / "|| 30" at the return site or remove the defaults inside the safeCountMatches assignments so each variable (mcpTools, hooks, restEndpoints) has its fallback applied exactly once; update the code paths that reference mcpTools, hooks, and restEndpoints to rely on that single fallback location (functions: safeCountMatches, and the variables mcpTools, hooks, restEndpoints) so there are no redundant fallbacks.
53-58: Hook counting via quote-division is fragile.
(matches / 2)only works because every union member is a double-quoted string and nothing else in the captured span uses". A future doc-comment like// e.g. "session_start"inside the union, or aHookTypealias written with backticks, silently halves or doubles the count. Prefer matching the union variants directly:♻️ Proposed fix
- const hookUnionFile = readFileSafe(join(repoRoot, "src", "types.ts")); - const hookUnion = hookUnionFile.match( - /export type HookType[\s\S]*?;(?=\s*\n)/, - ); - const hooks = hookUnion ? (hookUnion[0].match(/"/g)?.length ?? 0) / 2 : 12; + const hookUnionFile = readFileSafe(join(repoRoot, "src", "types.ts")); + const hookUnionMatch = hookUnionFile.match( + /export type HookType\s*=\s*([\s\S]*?);/, + ); + const hooks = + hookUnionMatch?.[1].match(/"[a-z_]+"/g)?.length ?? 12;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/lib/meta.ts` around lines 53 - 58, The current hook-counting logic (using hookUnion and hooks derived from readFileSafe(... "src/types.ts")) is fragile because it counts quotes rather than actual union members; update the extraction to first capture the HookType union body (hookUnion), then find and count actual string-literal variants inside that body (e.g., match double-quoted, single-quoted, or backtick-quoted literals) instead of dividing quote occurrences by 2—alternatively split the union body on '|' and trim/filter empty entries to compute the correct count; change the logic around hookUnion and hooks accordingly so it robustly counts members regardless of inline comments or other quotes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/lib/meta.ts`:
- Line 65: The hardcoded testsPassing: 777 in the ProjectMeta object will rot;
replace it with a real source or remove it: either compute testsPassing
dynamically (e.g., add a function that parses CI JUnit/JSON reports or walks the
test/ directory and counts occurrences of "it(" / "test(" and assign that value
to the testsPassing property used by ProjectMeta) or remove the testsPassing
property from ProjectMeta and update the Stats tile and any consumers to stop
reading it. Locate the testsPassing symbol in meta.ts and update ProjectMeta
consumers (the Stats tile and any imports) to use the new dynamic getter or to
handle the absence of the stat.
- Around line 40-44: The REST endpoint counter (restEndpoints) currently uses a
too-broad regex (/api_path:/g) and a mistaken fallback (|| 49); replace that
expression to call safeCountMatches with the tighter regex
/config:\s*\{\s*api_path:\s*"/g (use repoRoot and the same target file as
before) and remove the hardcoded "|| 49" fallback so the code reports the
tighter count directly; also update any README references ("104-endpoints" /
"107 endpoints on port 3111") after you verify the corrected count so
documentation matches the new measurement.
---
Nitpick comments:
In `@website/components/Features.tsx`:
- Around line 9-13: Default numeric defaults (12, 44, 49) are duplicated across
components; export a single DEFAULT_META constant from "@/lib/meta" (e.g.,
DEFAULT_META = { hooks: 12, mcpTools: 44, restEndpoints: 49 }) and update
Features (the Features function signature and its Props), Stats, LiveTerminal
and getProjectMeta to use that constant (either by using DEFAULT_META as the
inline default for the props or by making props required and supplying
DEFAULT_META where components are instantiated) so all fallback values come from
one source of truth.
In `@website/components/Stats.tsx`:
- Around line 33-79: The effect attaching the IntersectionObserver (inside
useEffect) re-runs when mcpTools/hooks/testsPassing change but doesn’t reset
per-element state so elements keep data-done="1" and the animation never
replays; fix by clearing that flag at the start of the effect (iterate
rootRef.current.querySelectorAll("[data-num]") and delete/clear dataset.done) or
alternatively revert the dependency array to [] and read latest values via refs;
update the code paths referencing rootRef, the count function, and the
IntersectionObserver setup to ensure data-done is reset before observing.
In `@website/lib/meta.ts`:
- Around line 47-51: The computed counts (mcpTools, hooks, restEndpoints)
currently apply the default value twice (once inside the safeCountMatches
expression and again at the return), creating dead code; choose a single place
for defaults and make them consistent — either remove the trailing "|| 44" / "||
30" at the return site or remove the defaults inside the safeCountMatches
assignments so each variable (mcpTools, hooks, restEndpoints) has its fallback
applied exactly once; update the code paths that reference mcpTools, hooks, and
restEndpoints to rely on that single fallback location (functions:
safeCountMatches, and the variables mcpTools, hooks, restEndpoints) so there are
no redundant fallbacks.
- Around line 53-58: The current hook-counting logic (using hookUnion and hooks
derived from readFileSafe(... "src/types.ts")) is fragile because it counts
quotes rather than actual union members; update the extraction to first capture
the HookType union body (hookUnion), then find and count actual string-literal
variants inside that body (e.g., match double-quoted, single-quoted, or
backtick-quoted literals) instead of dividing quote occurrences by
2—alternatively split the union body on '|' and trim/filter empty entries to
compute the correct count; change the logic around hookUnion and hooks
accordingly so it robustly counts members regardless of inline comments or other
quotes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: afa0df0f-7a63-4854-9aaf-dc20a7610bad
📒 Files selected for processing (6)
website/app/page.tsxwebsite/components/Features.tsxwebsite/components/Hero.tsxwebsite/components/LiveTerminal.tsxwebsite/components/Stats.tsxwebsite/lib/meta.ts
- testsPassing no longer hardcoded to 777. New countTestCases() walks
the test/ tree and counts it()/test() call sites at build. Prerender
now shows the live count (currently 795) instead of a stale literal.
- restEndpoints regex tightened from /api_path:/ to
/config:\s*\{\s*api_path:\s*"/. Comments, example strings, and
docstrings no longer inflate the count.
- Fallback defaults consolidated into one exported DEFAULT_META
constant. Each count applies its fallback in exactly one place (at
the return, if the safeCountMatches result is 0). No more double
defaults.
- Hook counting no longer divides quote characters by 2. It parses
the HookType union body, splits on '|', and filters to string-
literal members — robust to inline comments or quotes elsewhere in
the union.
- Stats, Features, and LiveTerminal props are now required and take
their fallbacks from DEFAULT_META via getProjectMeta(). No
per-component duplication.
- Stats effect resets data-done on every rerun so a meta change at
build replays the count animation against the new target instead
of stranding a stale value.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
website/components/Features.tsx (1)
99-108: Header "TWELVE THINGS" is still hardcoded while tile counts are dynamic.The section title
TWELVE THINGS YOU DID NOT WANT TO BUILD.is a static string, but theFEATURESarray below is fixed at 12 entries and independent of the derived props — so no immediate bug. Worth a comment or a derived count (FEATURES.length→ number-to-word) if you expect the list to grow; otherwise the title will silently drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/components/Features.tsx` around lines 99 - 108, The heading "TWELVE THINGS YOU DID NOT WANT TO BUILD." is hardcoded; derive the count from the FEATURES array instead to prevent drift: replace the static string in the element with a dynamically composed title using FEATURES.length (or a small helper like numberToWords(count)) so the heading reads "<NUMBER-IN-WORDS> THINGS YOU DID NOT WANT TO BUILD." Reference the FEATURES constant and the h2 with id "feat-title" to locate the change, and add/implement a lightweight number-to-words helper (e.g., for 0–20 and tens) if none exists to render the numeric count as words.website/lib/meta.ts (1)
14-19:DEFAULT_METAfallback values will silently mask counting regressions.If any counter returns 0 (e.g., a regex breaks after a refactor of
api.tsortools-registry.ts),x || DEFAULT_META.xat Line 112-115 substitutes stale hardcoded numbers (44 tools, 49 endpoints, 777 tests) and you'll ship misleading figures without any build-time signal. Consider logging aconsole.warningetProjectMeta()when a counter yields 0 so CI logs surface the regression, or failing the build in that case since the whole point of this module is to avoid stale constants.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/lib/meta.ts` around lines 14 - 19, DEFAULT_META contains hardcoded fallback counters and current usage in getProjectMeta (where values are selected via expressions like restEndpoints || DEFAULT_META.restEndpoints) will silently mask regressions when a real counter is 0; update getProjectMeta to detect any counter that equals 0 (for keys mcpTools, hooks, restEndpoints, testsPassing) and either emit a clear console.warn with the counter name and computed value OR throw an Error to fail CI depending on build policy, ensuring you reference DEFAULT_META and the getProjectMeta function when making the change so the fallback logic is replaced by explicit zero detection rather than blind "||" fallbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/lib/meta.ts`:
- Around line 21-22: The repoRoot computed from import.meta.url (here, repoRoot
in website/lib/meta.ts) can be incorrect after Next.js server bundling; update
getProjectMeta to derive repository root from a stable anchor instead—use
process.cwd() (i.e., join(process.cwd(), "..") or similar) or implement a small
upward walk from process.cwd() to locate package.json with the expected name,
and fallback to DEFAULT_META only if that search fails; replace uses of
here/import.meta.url for repoRoot with this deterministic resolution so file
reads in getProjectMeta and related logic reliably locate project files.
- Around line 62-83: countTestCases currently treats skipped/todo and
commented-out tests as passing because the regex m =
txt.match(/(?:^|\s)(?:it|test)(?:\.\w+)?\s*\(/g) will match variants like
it.skip/test.todo and matches lines with leading comment whitespace; update
countTestCases to first strip line comments (remove //... and optionally block
comments) from the file text before matching, and tighten the regex to exclude
.skip and .todo variants (e.g., require (?:\.(?!skip|todo)\w+)? or only match
plain it/test or allowed modifiers), and then update the displayed label in
Stats.tsx (change "TESTS PASSING" to "TEST CASES" if you prefer counting all
test cases) so the UI reflects the corrected metric.
---
Nitpick comments:
In `@website/components/Features.tsx`:
- Around line 99-108: The heading "TWELVE THINGS YOU DID NOT WANT TO BUILD." is
hardcoded; derive the count from the FEATURES array instead to prevent drift:
replace the static string in the element with a dynamically composed title using
FEATURES.length (or a small helper like numberToWords(count)) so the heading
reads "<NUMBER-IN-WORDS> THINGS YOU DID NOT WANT TO BUILD." Reference the
FEATURES constant and the h2 with id "feat-title" to locate the change, and
add/implement a lightweight number-to-words helper (e.g., for 0–20 and tens) if
none exists to render the numeric count as words.
In `@website/lib/meta.ts`:
- Around line 14-19: DEFAULT_META contains hardcoded fallback counters and
current usage in getProjectMeta (where values are selected via expressions like
restEndpoints || DEFAULT_META.restEndpoints) will silently mask regressions when
a real counter is 0; update getProjectMeta to detect any counter that equals 0
(for keys mcpTools, hooks, restEndpoints, testsPassing) and either emit a clear
console.warn with the counter name and computed value OR throw an Error to fail
CI depending on build policy, ensuring you reference DEFAULT_META and the
getProjectMeta function when making the change so the fallback logic is replaced
by explicit zero detection rather than blind "||" fallbacks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ae88f3dc-688b-4161-bbb5-9f52e7f14e1d
📒 Files selected for processing (4)
website/components/Features.tsxwebsite/components/LiveTerminal.tsxwebsite/components/Stats.tsxwebsite/lib/meta.ts
| const here = dirname(fileURLToPath(import.meta.url)); | ||
| const repoRoot = join(here, "..", ".."); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm meta.ts location and inspect any build output that might reveal where it lands after compilation
fd -t f 'meta.ts' website/lib
fd -t d '.next' website 2>/dev/null
rg -nP 'import\.meta\.url|process\.cwd' website/lib/meta.tsRepository: rohitg00/agentmemory
Length of output: 140
🏁 Script executed:
cat -n website/lib/meta.tsRepository: rohitg00/agentmemory
Length of output: 4168
🏁 Script executed:
rg -n 'getProjectMeta|from.*meta\.ts|import.*meta' website/ --type ts --type tsxRepository: rohitg00/agentmemory
Length of output: 91
🏁 Script executed:
rg -n 'getProjectMeta|from.*meta\.ts|import.*meta' website/ -t tsRepository: rohitg00/agentmemory
Length of output: 554
🏁 Script executed:
cat -n website/components/Hero.tsxRepository: rohitg00/agentmemory
Length of output: 1465
🏁 Script executed:
cat -n website/app/page.tsxRepository: rohitg00/agentmemory
Length of output: 1664
repoRoot derived from import.meta.url may break under Next.js bundling.
getProjectMeta() is invoked during static generation of page.tsx and Hero component (both server components in the app/ directory). When Next.js bundles server modules into .next/server/..., the relative path resolution from import.meta.url becomes incorrect—../.. from the bundled location will not point to the repository root. While the current implementation gracefully falls back to DEFAULT_META values when file reads fail (lines 112–115), this masks a latent fragility: the behavior depends on default values remaining accurate.
For more robust runtime path resolution, consider anchoring from process.cwd() (which in next build is the website/ directory, making join(cwd, "..") deterministic) or finding a stable marker file (e.g., walk up the directory tree until package.json with the expected name field is found).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/lib/meta.ts` around lines 21 - 22, The repoRoot computed from
import.meta.url (here, repoRoot in website/lib/meta.ts) can be incorrect after
Next.js server bundling; update getProjectMeta to derive repository root from a
stable anchor instead—use process.cwd() (i.e., join(process.cwd(), "..") or
similar) or implement a small upward walk from process.cwd() to locate
package.json with the expected name, and fallback to DEFAULT_META only if that
search fails; replace uses of here/import.meta.url for repoRoot with this
deterministic resolution so file reads in getProjectMeta and related logic
reliably locate project files.
| function countTestCases(testDir: string): number { | ||
| let total = 0; | ||
| let entries: import("node:fs").Dirent[]; | ||
| try { | ||
| entries = readdirSync(testDir, { withFileTypes: true }); | ||
| } catch { | ||
| return 0; | ||
| } | ||
| for (const entry of entries) { | ||
| const full = join(testDir, entry.name); | ||
| if (entry.isDirectory()) { | ||
| total += countTestCases(full); | ||
| continue; | ||
| } | ||
| if (!/\.test\.[jt]sx?$/.test(entry.name)) continue; | ||
| const txt = readFileSafe(full); | ||
| if (!txt) continue; | ||
| const m = txt.match(/(?:^|\s)(?:it|test)(?:\.\w+)?\s*\(/g); | ||
| if (m) total += m.length; | ||
| } | ||
| return total; | ||
| } |
There was a problem hiding this comment.
countTestCases counts skipped/todo tests as "passing".
/(?:^|\s)(?:it|test)(?:\.\w+)?\s*\(/g matches it.skip(, it.todo(, test.skip(, describe.skip-nested cases, and commented-out tests (// it(...) still has leading whitespace before it). The stat is labeled "TESTS PASSING" in Stats.tsx, so the number is misleading. Either exclude .skip/.todo variants and strip line comments before matching, or relabel the stat to "TEST CASES".
♻️ Suggested tightening
- const m = txt.match(/(?:^|\s)(?:it|test)(?:\.\w+)?\s*\(/g);
- if (m) total += m.length;
+ // Strip line comments so commented-out tests don't count.
+ const stripped = txt.replace(/\/\/[^\n]*/g, "");
+ // Match it(, test(, it.each(, test.each( — exclude .skip/.todo.
+ const m = stripped.match(
+ /(?:^|[\s;{])(?:it|test)(?:\.(?!skip\b|todo\b)\w+)?\s*\(/g,
+ );
+ if (m) total += m.length;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/lib/meta.ts` around lines 62 - 83, countTestCases currently treats
skipped/todo and commented-out tests as passing because the regex m =
txt.match(/(?:^|\s)(?:it|test)(?:\.\w+)?\s*\(/g) will match variants like
it.skip/test.todo and matches lines with leading comment whitespace; update
countTestCases to first strip line comments (remove //... and optionally block
comments) from the file text before matching, and tighten the regex to exclude
.skip and .todo variants (e.g., require (?:\.(?!skip|todo)\w+)? or only match
plain it/test or allowed modifiers), and then update the displayed label in
Stats.tsx (change "TESTS PASSING" to "TEST CASES" if you prefer counting all
test cases) so the UI reflects the corrected metric.
Fixes the hardcoded-drift issue. The hero chip still said `v0.8.13` after v0.9.0 shipped, and the MCP/hook/REST counts were literal strings sprinkled across four components.
What
Result
Cutting the next release bumps the hero to v automatically. Any new MCP tool or REST endpoint raises the displayed count without a component edit.
Build: `next build` clean, single static page prerendered.
Summary by CodeRabbit