Pinned language/tool versions and the modern-runtime features mise deliberately adopts (Go 1.26 · Node 24 LTS · the JS/TS stack). This doc owns versions + standards; the per-language idioms live in the CODE_STYLE docs, the pipeline in CI-CD.md.
See also:
- CODE_STYLE_GO.md
- CODE_STYLE_TS.md
- CODE_STYLE_VUE.md
- FOLDER_STRUCTURE.md
- CI-CD.md (build/release)
- TESTING.md
One version per tool, pinned in-repo (go.mod toolchain · package.json#engines +
packageManager · .tool-versions/mise.toml). CI fails on drift.
| Tool | Pin | Where pinned |
|---|---|---|
| Go | 1.26.x (go 1.26 + toolchain go1.26.x) |
go.mod |
| golangci-lint | v2.x | go.mod tool directive (§2) · .golangci.yml |
| sqlc | latest 1.x | go.mod tool directive |
| Node | 24 LTS (>=24.0) |
package.json#engines, .tool-versions |
| pnpm | 11.x | package.json#packageManager (corepack) |
| TypeScript | 6.0.x | root devDep, single version across the workspace |
| Vite | 8.x (Rolldown bundler) | apps/web devDep |
| Vue | 3.5.x | apps/web |
| Tailwind | 4.x | apps/web |
| Vitest | 4.x | both apps |
| typescript-eslint | v8.x (flat config) | root |
| Prettier | 3.x | root |
| lefthook | latest | root (pre-commit, §5) |
Rule: one TypeScript + one Vitest + one ESLint version for the whole pnpm workspace (hoisted to root) so
webandreasoningcan't drift.
go 1.26+toolchain go1.26.xingo.mod— every machine/CI builds with the same compiler (auto-downloaded). Bump deliberately (a reviewed change).- Dev tools via the
tooldirective (Go 1.24+) —golangci-lint,sqlc,migrateare declared ingo.modand run withgo tool <name>. No "go install latest" drift; the tool versions are part of the lockfile.
// go.mod (excerpt)
go 1.26
tool (
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
github.com/sqlc-dev/sqlc/cmd/sqlc
)These are not "nice to know" — each maps to something mise does. (Versions noted; 1.24/1.25 features are baseline given the 1.26 pin.)
| Feature (since) | Adopt for |
|---|---|
Container-aware GOMAXPROCS (1.25) |
Everything runs on GKE with CPU limits + KEDA. The runtime reads the cgroup quota and re-reads it as limits change — no automaxprocs, no manual tuning. Document that we depend on it; don't override GOMAXPROCS. |
testing/synctest GA (1.25) |
Deterministic, virtual-time tests for the Temporal worker pools / goroutine fan-out and any timeout/backoff logic — pairs with the -race rule (TESTING §2). |
encoding/json/v2 (1.25, GOEXPERIMENT=jsonv2) |
The ingest path is JSON-heavy (Doc AI · Graph delta · Vertex responses). Faster decode and correctness (no float64-by-default, stricter unmarshal). Decision: opt-in behind a build tag now; promote when it ships stable. |
| Green Tea GC default (1.26) | Page-scanning GC favours the heap-walking retrieval/vector workload — adopt the default, note it as the GC baseline for the latency budget (OBSERVABILITY §4). |
revamped go fix (1.26) |
Push-button idiom/API modernization — run in CI as a drift check (CI-CD §2). |
new(expr) (1.26) |
Ergonomic init of the many nullable metadata pointer fields in the data model (*time.Time, *string for expiry_date, superseded_by_id, …). |
os.Root (1.24) |
Sandbox GCS raw-file staging and the local folder+manifest loader to a directory — defense-in-depth against path traversal in ingest. |
| heap-address randomization, cgo −30% (1.26) | Free security/perf; no code change. |
- One linter runner, config
version: "2"at repo root (.golangci.yml). v2 splits a dedicatedformatters:block (run viagolangci-lint fmt) fromlinters:. golangci-lint runin CI is a hard gate;golangci-lint fmtreplaces ad-hocgofmt/goimportsinvocations (gofumpt + goimports, withlocal-prefixesfor import grouping).- Baseline set beyond
standard(errcheck · govet · ineffassign · staticcheck · unused): errorlint · gosec · bodyclose · noctx · contextcheck · rowserrcheck · sqlclosecheck · nilerr · prealloc · revive · gocritic · misspell · unconvert · unparam · whitespace. The bank-relevant ones aregosec(security) and thesql*/bodycloseresource-leak linters (the read path holds DB rows + HTTP bodies).gocriticcatches subtle bugs;misspell/unconvert/unparam/whitespaceare low-noise hygiene (proven in s1ctl). - gosec runs with no excludes today (
.golangci.yml— zero findings to date). Add an exclude only with a written rationale comment when a real false positive lands, not preemptively; a path-validated read still needs an inline//nolint:gosec+ reason at the call site (e.g.blob.FS, which checks key shape before ever joining a path — see its code).os.Root(1.24, TOOLCHAIN §3) remains the idiom for future file-handling code that needs directory-level sandboxing, not a blanket G304 exclude. - revive tuning: disable
exported(noisy godoc enforcement on internal types); keepvar-naming+file-length-limit. - Generated code (
*.sql.gofrom sqlc) is excluded;_test.gorelaxesgosec/noctx/errcheck/unparam.
The linter, not just the docs, keeps code current. golangci-lint v2 (≥ v2.6) bundles the
gopls modernize analyzer plus usestdlibvars · intrange · copyloopvar · perfsprint · sloglint — all enabled in .golangci.yml:
modernizeis go-version-aware: it readsgo 1.26fromgo.modand flags behavior-preserving upgrades to 1.26 idioms —min/max,slices/maps,anyoverinterface{},for range N,strings.CutPrefix,new(expr), etc. Bumping thegodirective automatically raises the idiom target.- Auto-apply:
go tool golangci-lint run --fixwrites the suggested fixes; the pre-commit hook (§6) runs it on staged files. - Belt and braces: Go 1.26's revamped
go fix ./...is the official modernizer — CI runs it as a drift check (CI-CD §2) so un-modernized code can't merge.
See the committed .golangci.yml — it also enforces size
(funlen · lll · revive file-length-limit) and naming (revive var-naming),
per CODE_STYLE_GO.
- Base = Google style. TS follows the Google TypeScript Style Guide via
gts(ESLint + Prettier + Googletsconfig;gts lint/gts fix). Google has no Vue style guide → Vue uses the official Vue Style Guide with the same gts/Prettier TS rules inside SFCs (CODE_STYLE_TS · CODE_STYLE_VUE). - ESM-only (
"type": "module",moduleResolution: "nodenext"),strict: true, noany. - ESLint flat config (
eslint.config.ts): typescript-eslint v8 + eslint-plugin-vue (templates) + Prettier-as-formatter. (Biome/oxlint can't lint Vue SFC templates yet — ESLint stays the runner.) - Size & naming, lint-enforced:
max-lines(≤300 TS / ≤400.vue),max-lines-per-function(≤80), PrettierprintWidth80; filenames viaunicorn/filename-case(kebab-case TS, PascalCase Vue components). - Vue: type-check with vue-tsc; Vite 8 (Rolldown) — confirm the Vue/Vite plugins are Rolldown-compatible before the 7→8 bump.
- Dead-code gate:
knipin CI (unused files/exports/deps).
- Format with Prettier (
.prettierrc.json,proseWrap: preserve); lint with markdownlint-cli2 (.markdownlint-cli2.jsonc). Diagrams are mermaid only (DOC_STYLE), and doc length is budgeted (≤~250 lines, split past ~300).
lefthook runs the right formatter/linter on staged files only, so Go · TS · Vue stay uniform without a per-language ritual:
# lefthook.yml
pre-commit:
parallel: true
commands:
go: { glob: '*.go', run: 'golangci-lint fmt {staged_files} && golangci-lint run' }
ts: { glob: '*.{ts,vue}', run: 'pnpm exec eslint --fix {staged_files}' }
fmt: { glob: '*.{ts,vue,json,md}', run: 'pnpm exec prettier --write {staged_files}' }
md: { glob: '*.md', run: 'pnpm exec markdownlint-cli2 {staged_files}' }
leak: { run: 'scripts/check-leaks.sh' }
lengths: { run: 'scripts/check-lengths.sh' }scripts/check-leaks.sh scans staged diffs for secrets and tenant-specific terms — blocking
commit on match. Patterns: PEM private-key block headers, service-account JSON,
API token prefixes (GCP/AWS/GitHub), and a project-local denylist
(.githooks/denylist.local, gitignored) for bank-specific terms. Excludes known
placeholders (your-*, 000000, etc.). Proven in s1ctl — critical for mise because the
ingest path handles bank internal documents.
scripts/check-lengths.sh enforces Go ≤500 lines, TS ≤300, Vue ≤400, docs ≤250 prose
(CODE_STYLE_GO/TS/VUE, DOC_STYLE). A grandfather list exempts files that existed before the
rule — new files must comply.
The same checks run in CI (CI-CD §2) — the hook is a fast local mirror, never the only gate.
- JSON: use stable
encoding/json; do not opt intoencoding/json/v2experiments unless the configured Go release makes them stable. - Version pinning: the Podman dev-container is the reference polyglot pin. Developers may use local version managers, but the container is the reproducible baseline.
- Dependency bumps: use Dependabot by default. Mend-hosted Renovate remains a later operational option if the project needs richer grouping; dependency updates are reviewed changes (TOOLCHAIN §1, CI-CD §4).