Note
These are the general engineering guidelines for this repository: they apply to every application and every language. Domain- and app-specific rules live in separate documents — see the resolution order below. Anything not covered by a more specific document is governed here.
Read these in order. A more specific document augments a more general one; it does not replace it. Where the two differ, the more specific document takes precedence.
| Order | Document | Covers |
|---|---|---|
| 1 | CODING_GUIDELINES.md |
General engineering rules, all languages |
| 2 | FRONTEND.md |
Frontend/UI: React, styling, motion, accessibility |
| 3 | <app>/AGENTS.md |
App layout, commands, app-specific conventions |
For CODING_GUIDELINES.md and FRONTEND.md, read the root copy, then the
copy in the application directory you are working in, where it has one. An
application-level copy augments the root document; it does not replace it.
Where the two differ, the application-level copy takes precedence.
Contribution process, PR expectations, and review policy live in
CONTRIBUTING.md.
- Write the minimum code that solves the problem. Nothing speculative.
- Do not add features, parameters, options, or configuration that were not asked for. Do not build abstractions for single-use code.
- Do not handle impossible cases. Handle the ones that can actually occur.
- Prefer explicit, readable code over dense one-liners, clever regexes, and metaprogramming. Code is read and debugged far more often than written.
- Ask of every change: would a senior engineer call this overcomplicated? If a 50-line implementation works, do not write 200.
- Every changed line must trace directly to the stated goal of the change.
- Do not "improve" adjacent code, comments, or formatting that is unrelated to your change; unrelated churn expands the review surface and hides the real diff.
- Match the existing style and patterns of the file you are editing, even where you would have chosen differently.
- Clean up orphans you created (unused imports, variables, functions). Leave pre-existing dead code alone — mention it instead.
- Remove debugging logs, commented-out code, and scratch files before submitting.
- State assumptions explicitly. If a requirement has more than one plausible reading, raise it instead of silently picking one.
- If a simpler approach exists than the one requested, say so.
- If something in the codebase is unclear, stop and ask rather than guessing.
Turn tasks into verifiable goals before writing code:
- "Add validation" → write tests for the invalid inputs, then make them pass.
- "Fix the bug" → write a reproducing test, then make it pass.
- "Refactor X" → tests pass before and after, unchanged.
- Evaluate the approach before the diff: is this the right strategy, does it work with the framework and existing patterns rather than against them, and is there already a pattern in this codebase that solves it?
- Justify every new third-party dependency: can the standard library or an existing dependency do it? Is the package maintained, appropriately sized, and free of known vulnerabilities? Do not add a large dependency for a trivial utility.
All languages follow the relevant Google Style Guide. The formatter and linter configured in the application are authoritative for mechanical formatting — never fight them, and never relax a rule globally to silence a single site.
Mechanical formatting — line width, indentation, quote style — is owned by the application's formatter and is not restated here.
| Language | Style guide | Tooling |
|---|---|---|
| TypeScript / JavaScript | Google TypeScript Style Guide | Biome, ESLint, or tsc as configured per app |
| HTML / CSS / SCSS | Google HTML/CSS Style Guide | Stylelint / Biome as configured per app |
| Python | Google Python Style Guide | ruff (format + check) for new code; existing apps keep their configured toolchain |
| Go | Google Go Style Guide | gofmt, go vet, golangci-lint |
| Java | Google Java Style Guide | google-java-format |
| Shell | Google Shell Style Guide | shellcheck |
| Markdown / prose | Google Developer Documentation Style Guide | — |
Suppress a check only for the specific line that needs it, with a comment
explaining why (e.g. # pylint: disable=line-too-long,
// biome-ignore lint/suspicious/noExplicitAny: <reason>).
- Respect the existing hierarchy. New files go where comparable files already live. Do not invent a one-off directory without precedent.
- Name directories and files for their domain, not their genericity.
misc/,common/,helpers/, andutils/dumping grounds without clear ownership are rejected. - Single responsibility per file. A file that mixes transport routing, business logic, external clients, and helpers should be decomposed.
- Tight modules, concise functions. A function does exactly one thing. A module groups one coherent concern. This maximizes reuse, simplifies unit testing, and lowers cognitive load.
- Flat by default; no anticipatory nesting. Promote a file to a folder only when it actually gains a child used solely by it. Do not create folders for files that do not exist yet.
- Narrowest scope that owns it. Code used by exactly one feature lives with that feature, not in a shared directory.
- Colocate tests with the code they test (
feature.test.tsnext tofeature.ts,foo_test.pynext tofoo.py). Do not dump tests into a distant flat catch-all directory.
snake_casefor source files by default (chat_pipeline.py,use_branding.ts,card_chart.tsx).dash-caseonly where a framework requires it — e.g. Next.js App Router segments underapps/web/src/app.PascalCasefor component/class identifiers (CardChart), regardless of the file name.- Category first. Composed names lead with what the thing is, then what
makes it specific:
card_chart(notchart_card),button_close,icon_arrow_right. This groups related files in sorted listings, makes fuzzy-find match by category, and keeps imports visually parallel. Applies to files, folders, identifiers, and CSS classes. - No relative parent imports (
../). Use the project's path alias (~/,@/) so files can move without rewriting import paths.
- Self-documenting over terse.
sourcenots,indexnoti,messagenotm,calculateTotalnotcalcTotal. Single letters are acceptable only in a trivial one-line loop or lambda. - Prefer the long, explicit name over an acronym:
DataCommonsComponentTagNamebeatsDCTag. Long names document themselves and work better with autocomplete. - Verb-led function names —
getComponentAttributes,resolvePlaceDcid; not a bare noun. - Boolean prefixes —
is,has,should(isLoading,hasFooter). - Name reusable assets by what they represent, not by their current
call-site. An icon that draws a pencil is
icon_pencil, noticon_export— so it still reads correctly at the next call-site. - Follow the ecosystem's casing.
camelCaseproperties in TypeScript (brandConfigUrl, notbrand_config_url);snake_casein Python.
- Extract raw numbers, thresholds, limits, and durations into named constants at
module scope:
MAX_PREVIEW_LENGTH = 500,MS_PER_MINUTE = 60_000. - Prefer idiomatic empty values (
'',null,None) over invented sentinels ('none',-1) that force redundant checks downstream. - Do not hardcode environment-, tenant-, or event-specific values
(
"AISummit2026", a single instance's bucket name) in shared code. Take them from configuration.
interfacefor object shapes; a component's props areComponentNameProps.typeis for unions, tuples, mapped types, and function types.- Named exports only.
export defaultis permitted only where a framework requires it (Next.js files undersrc/app/**,*.config.*). import type/export typefor type-only imports and re-exports.- No
any. Useunknownand narrow. If truly unavoidable, suppress the single line with a comment explaining why. ===/!==only.constby default. Braced control statements.- Prefer arrow functions. Mark a function
asynconly when itawaits. - Avoid nested ternaries and complex inline conditions in JSX or arguments — lift them into a named constant, helper, or lookup object.
- JSDoc every exported function, hook, interface, and constant whose intent is not obvious from the signature. Explain why, not just what.
Vitest-specific conventions:
- Stub environment variables with
vi.stubEnv('KEY', 'value')and restore withvi.unstubAllEnvs(). Never assignprocess.env.KEY = undefined— Node coerces it to the string"undefined", leaving the variable truthy. - Stub globals with
vi.spyOn(globalThis, 'fetch')orvi.stubGlobal(...)paired withvi.unstubAllGlobals(). Direct assignment toglobalThisis not undone byvi.restoreAllMocks()and leaks into other test files.
- Follow the Google Python Style Guide; 4-space indent,
snake_casefunctions and modules,PascalCaseclasses. - Type-hint public function signatures; use
Optional[...]explicitly rather than relying on an implicitNonedefault. - Module-level docstring describing the file's responsibility; docstrings on public functions covering arguments, return value, and raised exceptions.
- Use the
loggingmodule, neverprint, for anything that runs in a server or pipeline. Reserveprintfor CLI output intended for the user. - Catch specific exceptions. A bare
except Exceptionis acceptable only at a process or request boundary, and must log with context and re-raise or return a well-defined failure. - Declare dependencies in the app's single canonical manifest
(
requirements.txt/pyproject.toml); pin versions.
- Fail fast and loudly. Never swallow an error silently. If a failure is deliberately ignored, a comment must say why.
- Add context when propagating. An error should tell the reader which operation failed and on which input — not just that something failed.
- Distinguish expected from exceptional. Missing data for a query is an expected outcome to be surfaced in the UI; a malformed API response is an error to be logged.
- Log at the right level:
debugfor tracing,infofor lifecycle events,warningfor degraded-but-handled conditions,errorfor failures that need attention. - Never log secrets, credentials, tokens, or user PII, and never echo a full API key or authorization header, even truncated.
- Guard re-entrancy. Think through rapid double-clicks, concurrent requests, and overlapping invocations. Where simultaneous execution would corrupt shared state, add an in-progress guard and return early.
- Check liveness before acting on a resource. For DOM work, verify the node
is still connected (
if (!target?.isConnected) return;) before mutating it; components unmount while async work is in flight. - Guarantee cleanup. Side-effects, temporary DOM tagging, subscriptions, and
allocated resources are released in
finally(or the language's equivalent) so cleanup runs even on the exception path. - Abort multi-stage pipelines on failure. When an early phase fails, halt explicitly instead of running downstream stages against half-built state.
- No arbitrary sleeps. Replace
sleep(5)with a bounded polling loop, an event, or a documented timeout. - Cancel superseded work. Long-running fetches and streams that a newer user action has invalidated should be aborted, not left to race.
- Watch algorithmic complexity: avoid accidental O(n²) nested scans; use a
Set/Map/dictfor repeated membership or key lookups. - Stream large payloads and files instead of buffering them entirely in memory.
- Close or scope every file, connection, subscription, and subprocess (context
managers,
defer,try/finally). - Do the work where it belongs — see §12.3 for the server/client boundary.
- Secrets never live in code, config files, or committed
.envfiles. Use Secret Manager or the runtime environment. Provide a.env.examplewith placeholder values instead. - Secrets never reach the client bundle. API keys stay server-side; the browser talks to your own endpoint, which talks to the upstream service.
- Least privilege. CI workflows request the minimum scopes
(
permissions: contents: readunless more is genuinely needed); service accounts get the narrowest role that works. - Treat all external input as untrusted — URL hash and query parameters, request bodies, model output, and third-party API responses. Validate and sanitize before using it in a query, a shell command, navigation, or rendered HTML.
- Never interpolate unescaped input into DOM-rendering paths (XSS) or redirect targets (open redirect).
- Pin GitHub Actions to a commit SHA with the version in a trailing comment.
Applications in this repository share the same shape: prompt → model → MCP tools → app server → client store → UI. Most quality problems come from doing work at the wrong layer.
For every field flowing through the pipeline (placeDcid, childPlaceType,
resolvedPlaceDcid, …), know where it originates: model output, MCP tool
response, REST response, or client parameter. If the model produces it, the
prompt must define the field, its type, and valid examples. An undocumented
field in a prompt is an unenforced contract.
Before adding string matching, case normalization, or lookup tables in the app server to repair a tool response, ask whether the MCP server can return that information as structured metadata, or the prompt can instruct the model to emit canonical values directly. Defensive fallbacks are sometimes necessary for reliability — when they are, note the upstream fix they stand in for.
- Shape payloads server-side. Prune and reshape heavy Data Commons responses before sending them over the wire; do not dump raw API responses into the client.
- Aggregate server-side. Joins, filtering, ranking, and time-series restructuring belong on the server.
- Keep the client light. Client work is limited to responsive UI transformations — selectors, viewport projection, formatting — so the main thread stays unblocked.
- Ordering is not guaranteed. Sort observations ascending by date before charting or computing deltas.
- Validate before arithmetic. Filter out nullish values and malformed dates
(
typeof obs?.date === 'string') before sorting or math. - Preserve place ↔ variable binding through fallbacks. When the pipeline
falls back to a parent place or
Earth, the fallback variable must be queried against the fallback place — querying the original place with the fallback variable produces false "no data". - Respect result limits. Queries returning hundreds or thousands of child entities need pagination, ranking, or top-N handling.
- Make cache and store keys collision-proof. Distinguish a parent query
(
africa) from a child query (africa:Country) and from a multi-entity comparison (KEN+UGA), and invalidate accordingly.
-
Every new feature, component, hook, endpoint, or utility ships with tests. Modifying legacy code means updating or adding its tests.
-
Every bug fix ships with a test that fails before the fix.
-
Colocate and name conventionally:
*.test.ts/*.test.tsxfor TypeScript,*_test.pyfor Python,*_test.gofor Go. -
Assert behavior, not implementation. A test that only proves a mock was called, or restates the implementation, has no value. Tests should survive an internal refactor that preserves behavior.
-
Cover the failure modes: null/undefined inputs, empty collections, malformed dates, boundary values, network and timeout errors, and permission failures — not just the happy path.
-
Be minimal. The smallest set of cases that establishes correctness; target complex and high-risk paths rather than padding coverage.
-
Be deterministic. No reliance on wall-clock sleeps, real network calls, or test execution order.
-
Golden/snapshot files are regenerated by a checked-in script, never hand-edited, and every diff is reviewed as a deliberate change.
-
Document each case with structured comments, adapted to the language:
// Test: Multi-page response. // Situation: The data source returns 3 pages linked by nextToken. // Expectation: The helper fetches all 3 pages and merges the nodes.
-
File-overview header on any file carrying a complex workflow or orchestration, stating its scope and architectural responsibility.
-
Document exported symbols — inputs, outputs, and failure cases — so callers do not have to read the body. Explain why a non-obvious approach was taken.
-
Comments explain intent and constraints. Do not narrate what the code already says.
-
Wrap Markdown and documentation at 80 characters. Exemptions, because the format forbids a line break:
- Table rows. A pipe-table row cannot be split across lines. Keep cells short — a few words — and push explanation into prose below the table. If a cell needs a sentence, the content wants a list or a subsection, not a table.
- Unbreakable tokens — a URL, an import path, or an inline code span that is itself longer than the limit. Wrap around it; do not mangle it.
- Fenced code blocks, which reproduce real code or terminal output. Keep the code itself within 80 columns where the language allows.
-
Follow the Google Developer Documentation Style Guide for prose.
-
In multi-line shell examples, ensure no trailing whitespace after a
\continuation — it breaks copy-paste. -
Every new source file carries the Apache 2.0 license header in the comment syntax of its language, with the current year:
# Copyright <current year> Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. See the License for the specific language governing # permissions and limitations under the License.
Comments explain why code exists and define its contracts; the code itself explains how it works. High-quality comments reduce cognitive load for future readers. Comments that read as conversational essays, personify code, or are otherwise not focused nor clear increase cognitive load and obscure bugs.
- Technical, declarative, and direct. Write clear, neutral technical prose. Use imperative or declarative phrasing ("Formats values for display", "Guards against negative zero"). Avoid conversational filler, stream of consciousness, or casual editorializing.
- Idiomatic, professional language. Write clear, idiomatic technical prose.
Describe system behavior directly, avoiding informal jargon and anthropomorphic
framing (attributing intent, feelings, or agency to code and data structures).
- Avoid:
// The socket wants to close, but the queue fears data loss. - Prefer:
// Drain pending messages before closing the socket connection.
- Avoid:
- Use standard domain terminology. Do not invent colloquial shorthand or use regional idioms. Use established terms from networking, compilers, TypeScript, and data structures.
- Neutral and egoless. Avoid first-person pronouns ("I", "we") or rhetorical flourishes ("leaves much to be desired", "pointless churn"). Avoid criticizing libraries or APIs, but do indicate where you had to take measures to circumvent library or API behavior. Focus strictly on the technical invariant. All comments should be measured, professional, and neutral.
- Proportionality rule. The visual and cognitive weight of a comment must match the complexity of what it annotates. A five-line function should not carry a twenty-line comment.
- Inline comments (1–2 lines). Inline comments are brief signposts explaining non-obvious branches, workarounds, or subtle regex logic. If an explanation requires multiple paragraphs, summarize the invariant in 1–2 lines in the code and document the fuller context in a PR description, issue, or design document.
- JSDocs / Docstrings (2–4 lines typical). Begin with a concise summary sentence describing what the symbol does or represents. Follow with any non-obvious constraints, side effects, or parameters.
- File-overview headers (3–5 lines). State the file's architectural scope and single responsibility. Do not list internal implementation steps.
Avoid these patterns when writing comments and docstrings:
- Avoid paraphrasing the code. Do not restate in English what
the code already expresses in syntax. Trust the reader to know the language.
- Avoid:
// Increment counter by oneabovecounter += 1;.
- Avoid:
- Avoid conflating anecdotal test queries with general invariants. Never hardcode transient data points from a specific local test run or fixture into general utility comments. Comments must describe general behavior for all callers.
- Avoid rationale without contract. A docstring should both explain the functionality and document inputs, outputs, and behavioral guarantees.
- Avoid needless complexity. Avoid double negatives or inverted clauses. State conditions positively and directly.
- Avoid commit history and review chatter. Never write
// Added per PR reviewor// Used to do X in v1. Git history tracks changes and rationale across time.
Good comments capture information that cannot be expressed through types or clean identifiers alone:
- The contract (abstraction). Document assumptions, preconditions, postconditions, and guarantees that the compiler cannot enforce (e.g. "Assumes observations are pre-sorted by date in ascending order").
- Programmatic bounds and concrete requirements. Write clearly and concisely without being clipped. Keep comments focused on programmatic bounds and concrete requirements.
- The "why" behind non-obvious decisions. Explain why an intuitive or standard approach was rejected in favor of an unexpected one.
- Platform and library workarounds. Document browser quirks, external API irregularities, or third-party AST structure behaviors that necessitate defensive handling.
- Invariants and boundary conditions. Clarify edge-case thresholds, sign preservation rules, or rounding guarantees (e.g. preventing negative zero).
- Path independence. Resolve paths relative to the script, not the caller's
working directory:
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)". set -euo pipefailin every Bash script.- Provide
--helpoutput, and iterate over all arguments ("$@") rather than inspecting only$1. - Validate required inputs and fail with a clear message, not a stack trace.
Run the application's full verification suite locally before opening or updating
a PR. Do not guess commands — each app documents its own in <app>/AGENTS.md:
| App | Lint / types | Tests | Build |
|---|---|---|---|
dataweaver |
pnpm lint |
pnpm test |
pnpm build |
narratives |
npm run lint |
npm run test |
npm run build |
For UI changes, also run the production build and verify the change manually in
the browser. Every app must be wired into the CI orchestrator so that
CI Final Status gates the merge — see
CONTINUOUS_INTEGRATION.md.
- A change that introduces a new standard, architectural pattern, command, or convention must update the relevant document in the same PR.
- Put each rule in exactly one place: general rules here, UI rules in
FRONTEND.md, app-specific rules in<app>/AGENTS.md. Cross-reference instead of duplicating. Changes to core standards must also update the flattened review checklist in.gemini/styleguide.mdso automated GitHub PR reviews stay aligned. - Reviewers — human or agent — flag changes that violate these guidelines, and flag guidelines that the codebase has outgrown.