Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
638 lines
35 KiB
Markdown
638 lines
35 KiB
Markdown
# Static Analysis
|
|
|
|
This document defines the repository-managed static analysis baseline.
|
|
|
|
## Purpose
|
|
|
|
Static analysis should catch:
|
|
|
|
- common correctness issues in TypeScript and Go
|
|
- repository boundary violations that are cheap to detect mechanically
|
|
- oversized business files before they become long-term maintenance hotspots
|
|
|
|
It should not:
|
|
|
|
- turn formatting concerns already handled by Oxfmt, Prettier, or `gofmt` into duplicate failures
|
|
- enforce broad stylistic preferences with weak product value
|
|
- apply business-file limits to tests, generated code, or type-only surfaces
|
|
|
|
## Commands
|
|
|
|
Repository entrypoints:
|
|
|
|
- `pnpm setup:dev`
|
|
- `pnpm check:backdrop-filter-authoring`
|
|
- `pnpm check:css-has-performance`
|
|
- `pnpm check:golangci-version`
|
|
- `pnpm lint`
|
|
- `pnpm lint:ts`
|
|
- `pnpm lint:go`
|
|
- `pnpm typecheck`
|
|
- `pnpm check:codexproto-generated`
|
|
- `pnpm check:agent-live-protocol-generated`
|
|
- `pnpm check:agent-gui-provider-catalog-generated`
|
|
- `pnpm check:agent-host-boundary`
|
|
- `pnpm check:agent-provider-strategy-boundaries`
|
|
- `pnpm check:runtime-image-budgets`
|
|
|
|
`pnpm check:full` remains the full local validation command and includes
|
|
linting, typechecking, repository checks, and blocking tests. PR CI selects the
|
|
equivalent affected surfaces instead of running `check:full` as one job.
|
|
|
|
The pull-request workflow and `check:changed` share changed-file classification
|
|
from `tools/scripts/change-classification.mjs` and repository-check ownership
|
|
from `tools/scripts/repository-checks.mjs`. Repository checks are grouped by
|
|
responsibility: policy, tool contracts, generated contracts, and architecture
|
|
boundaries. TypeScript and Go jobs own language lint/tests only; a check written
|
|
in TypeScript does not make it a TypeScript check. Package sources and assets
|
|
select packing independently of language. Documentation-only changes skip code
|
|
validation while still producing workflow check results through job-level
|
|
conditions. Do not use workflow-level `paths-ignore` for this gate because
|
|
missing required checks can leave documentation-only PRs waiting on branch
|
|
protection.
|
|
|
|
PR workflows execute on a synthetic merge ref so tests cover the result that
|
|
would land on `main`. Their selectors must still calculate the changed set from
|
|
the exact pull-request range, `${base.sha}...${head.sha}`, and read manifest
|
|
comparisons from the pull-request head. Do not diff the merge ref against the
|
|
original base SHA: if `main` advances while a PR is open, unrelated main-branch
|
|
changes would select extra TypeScript, package, or repository checks.
|
|
|
|
PR CI keeps the existing `Tooling Consistency` required context as the owner of
|
|
repository policy, contract, generated, and boundary checks. This preserves
|
|
branch-protection compatibility while keeping those checks out of language
|
|
jobs.
|
|
|
|
Windows validation separates Agent process adapters, daemon adapters, and
|
|
desktop packaging. Agent daemon changes run the process and downstream daemon
|
|
adapter workflows in parallel; `services/tuttid` changes run only the daemon
|
|
adapter workflow. The Agent process lane needs only Go, while the daemon lane
|
|
prepares the builtin Onboarding package before its Go tests. Each lane invokes
|
|
its selected Go packages together so independent packages can build and test in
|
|
parallel. The Agent process lane also crosses a native Windows child-process
|
|
boundary to verify case-insensitive environment-key precedence.
|
|
It also proves that the account-usage Node boundary reuses a read-locked,
|
|
content-addressed snapshot across calls and runner recreation, and that
|
|
snapshot construction observes cancellation. The daemon lane covers durable
|
|
companion failure backoff and recovery through a real restart.
|
|
|
|
Both adapter workflows also run for matching pushes to `main`. Those trusted
|
|
runs maintain default-branch Go and pnpm caches that new pull requests can
|
|
restore; pull-request caches remain isolated to their merge refs. Adapter
|
|
workflows use shallow checkouts because their tests do not inspect Git history.
|
|
Desktop and builtin-app changes run the full unsigned Windows package build.
|
|
Use the desktop workflow's manual dispatch for a full package check when a
|
|
daemon-only change needs release-package confidence. Workflow definition files
|
|
are validated by repository tool contracts and do not trigger runtime
|
|
workflows themselves; this prevents a selector-only PR from starting a Windows
|
|
runner.
|
|
|
|
Changes to shared Go selector scripts are covered by the repository tool
|
|
contract suite and continue to select only the affected Go modules. Go
|
|
workspace, module, or lint configuration changes still select all relevant Go
|
|
modules because they can change the validity of every target.
|
|
|
|
Agent Session Replay has an additional changed-file lane,
|
|
`run_agent_session_replay`. Changes to its core, provider transport, daemon
|
|
recording/replay adapters, Desktop replay surfaces, deterministic fixture, or
|
|
runner select this classification output. The PR replay job is currently
|
|
disabled, so the output is advisory and does not execute a blocking gate.
|
|
Keep the classification aligned with the real composition boundary so it can
|
|
be enabled without replacing the closed loop with mock-only package selection.
|
|
|
|
Validation runners that spawn nested pnpm commands should read the root
|
|
`packageManager` field and invoke that pinned version through Corepack. Do not
|
|
let runner-spawned lanes resolve a bare `pnpm` from `PATH`, because local
|
|
package-manager shims can differ from the repository pin.
|
|
|
|
Changed-aware lane fingerprints include base, staged, working-tree, and
|
|
untracked content for each lane. Git subprocess buffers must accommodate large
|
|
binary or generated diffs; the runner uses a bounded 128 MiB buffer so a normal
|
|
large pull request does not fail before its validation lanes start.
|
|
|
|
Tests and checks that create temporary Git repositories must also isolate
|
|
repository-local Git environment variables before invoking Git. In particular,
|
|
remove inherited `GIT_DIR`, `GIT_WORK_TREE`, `GIT_COMMON_DIR`, index/object
|
|
overrides, and command-scoped `GIT_CONFIG_*` entries using case-insensitive
|
|
name matching for Windows compatibility; set
|
|
`GIT_CEILING_DIRECTORIES` to the fixture root; fail immediately when fixture Git
|
|
commands fail; and verify the initialized Git directory before staging,
|
|
committing, fetching, or checking out. A temporary cwd alone is not isolation
|
|
because `GIT_DIR` takes precedence and can redirect `git init` and later
|
|
commands into a caller's linked-worktree metadata.
|
|
|
|
Repository validation lane runners must remove the same inherited repository
|
|
selectors before spawning checks or tests. Git hooks export repository-local
|
|
environment variables, so a pre-push validation run otherwise gives every
|
|
child test authority over the caller's linked-worktree metadata. Spawned lanes
|
|
rediscover the intended repository from their cwd; temporary Git fixtures still
|
|
apply their own per-command environment and ceiling.
|
|
|
|
## TypeScript Baseline
|
|
|
|
TypeScript linting uses Oxlint.
|
|
|
|
The current baseline includes:
|
|
|
|
- Oxlint correctness checks
|
|
- `noUncheckedIndexedAccess` in the shared TypeScript base config
|
|
|
|
Generated TypeScript is not linted by the human-authored TypeScript rule set. Generated output should be controlled through its generator and generation checks instead of hand-edited to satisfy repository lint style.
|
|
|
|
Generated Codex app-server protocol artifacts under
|
|
`packages/agent/daemon/runtime/codexproto` are checked by
|
|
`pnpm check:codexproto-generated`. The check fetches the pinned Codex source
|
|
commit, compares the committed upstream schema snapshot as canonical JSON,
|
|
reruns the local Go generator, and fails when generated files drift. The schema
|
|
comparison intentionally ignores JSON formatting differences so vendored
|
|
upstream artifacts can coexist with repository Prettier formatting. Do not
|
|
hand-edit generated `*_gen.go` files; update the vendored schema or generator,
|
|
then regenerate.
|
|
|
|
The codexproto generator runs during `pnpm check:full` alongside full-repository
|
|
boundary scanners. Generator scratch files must stay outside the repository
|
|
tree, even when they are removed before the generator exits, so parallel checks
|
|
cannot observe transient files and fail nondeterministically.
|
|
|
|
The optimistic AgentGUI live fast lane is schema-backed separately from the
|
|
canonical cloud event. `pnpm check:agent-live-protocol-generated` hashes the
|
|
live `message_delta` schema, the declarative protobuf-wire/control contract,
|
|
and the reused canonical `turn_update`, `interaction_update`, and
|
|
`session_audit` variants. It also generates the Go wire field and delivery-kind
|
|
constants and checks the committed Go and TypeScript revision outputs. Change
|
|
either contract and run `pnpm generate:agent-live-protocol`; do not hand-edit
|
|
the generated revision or wire-constant files.
|
|
|
|
The Agent GUI provider identity catalog under
|
|
`packages/agent/gui/generated/providerIdentityCatalog.ts` is generated from the
|
|
daemon provider registry. `pnpm check:agent-gui-provider-catalog-generated`
|
|
fails when the checked-in TypeScript catalog drifts from the descriptor source
|
|
of truth, when a generated locale key is absent from any AgentGUI locale, or
|
|
when a generated icon key has no complete asset set. The same check also keeps
|
|
the descriptor set equal to closed OpenAPI provider-keyed preference schemas
|
|
while verifying that `AgentTargetProvider` and `WorkspaceAgentProvider` remain
|
|
open, bounded identifier contracts that accept extension providers. It runs as part of
|
|
`pnpm check:full`. Change provider identity, locale keys, icons, and target metadata in the registry, then run
|
|
`pnpm generate:agent-gui-provider-catalog`; do not hand-edit the generated
|
|
catalog.
|
|
|
|
Provider strategy is an independent architecture boundary enforced by
|
|
`pnpm check:agent-provider-strategy-boundaries`. Cross-provider daemon, service,
|
|
and desktop production code must dispatch behavior through
|
|
`providerregistry` strategy, capability, and integration descriptors instead
|
|
of branching on Codex, Claude, Cursor, Hermes, Nexight, OpenClaw, OpenCode, or
|
|
Tutti Agent identity. The checker reads the complete provider ID set from the
|
|
daemon registry and rejects identity constants, literal equality comparisons,
|
|
literal switch cases, and provider-specific Set or array membership dispatch in
|
|
Go, TypeScript, and TSX production sources. Plain provider catalogs and enum
|
|
validation remain allowed when they do not select behavior.
|
|
It keeps an explicit exemption list for registry declarations, generated API
|
|
enums, and exact provider-owned adapter/parser implementations (including
|
|
format-specific external-import parsers); additions to that list require an
|
|
ownership reason and must not hide cross-provider policy.
|
|
Its fixture suite must exercise every registered provider so a newly migrated
|
|
provider cannot silently remain outside the boundary rule.
|
|
|
|
Historical or ported-source snapshots that are intentionally kept outside a
|
|
package's active `tsconfig.json` during migration should also stay out of the
|
|
type-aware TypeScript lint target. Treat those directories as migration inputs,
|
|
not as first-class analyzed source, until they are promoted into the active
|
|
package seam.
|
|
|
|
`exactOptionalPropertyTypes` is intentionally not part of the shared TypeScript baseline yet. The current generated `@hey-api/client-fetch` runtime emits optional properties with explicit `undefined` values that do not typecheck under that option, and the available generator settings do not remove those conflicts. Revisit this after changing the generator version or generated-client strategy.
|
|
|
|
Every TypeScript workspace package that contains source files should provide:
|
|
|
|
- a package-local `tsconfig.json` extending the repository TypeScript base config
|
|
- a package-local `typecheck` script that runs the repository `tsgo` typecheck wrapper
|
|
|
|
This keeps `pnpm typecheck` authoritative across desktop, shared clients, contracts, and UI packages instead of relying on incidental imports from another package to expose type errors.
|
|
|
|
The wrapper runs native TypeScript with `--noEmit --incremental` and stores
|
|
package `.tsbuildinfo` files under `.tmp/tsbuildinfo`. This keeps warm local
|
|
typecheck runs fast without committing cache files.
|
|
|
|
The root `pnpm typecheck` command uses a compact runner that executes package
|
|
typechecks concurrently, prints only a short summary on success, and stores
|
|
package logs under `.tmp/typecheck-runs`.
|
|
|
|
TypeScript package `tsconfig.json` files must not use `baseUrl`; use explicit relative `paths` entries when aliases are needed so the configuration stays compatible with native TypeScript.
|
|
|
|
The repository-specific UI boundary policy remains in `pnpm check:ui-boundaries`.
|
|
Its full-repository walker excludes `apps/mobile/ios/Pods`, because that
|
|
CocoaPods-generated tree can vendor JavaScript fixtures, SVGs, and icon imports
|
|
that are not Tutti-authored UI source.
|
|
|
|
Bounded raster UI assets are checked by
|
|
`pnpm check:runtime-image-budgets`. The policy reads image headers and file
|
|
sizes without decoding pixels or invoking platform tools. It excludes design
|
|
masters and window-sized media; the governed paths and resolution rationale
|
|
are documented in [Runtime Image Assets](runtime-image-assets.md).
|
|
|
|
## Backdrop Filter Build Contract
|
|
|
|
Production CSS and Tailwind arbitrary-property authoring under `apps/`,
|
|
`packages/`, and `services/` must use the standard `backdrop-filter` property.
|
|
Do not handwrite `-webkit-backdrop-filter`: Tailwind/Lightning CSS owns
|
|
target-aware prefix generation, and duplicate declarations can be folded into a
|
|
prefix-only result during production optimization.
|
|
|
|
`pnpm check:backdrop-filter-authoring` enforces the source rule. Desktop builds
|
|
also run `tools/scripts/verify-renderer-css-contracts.mjs` against final renderer
|
|
assets. The artifact gate permits standard-only output or a generated prefix
|
|
followed by the standard property; it rejects prefix-only output, reversed
|
|
ordering, and a launchpad dismiss layer without a non-`none` standard filter.
|
|
The React `WebkitBackdropFilter` inline-style property is outside this stylesheet
|
|
optimization boundary and is not rejected by the authoring check.
|
|
|
|
## CSS Relational Selector Performance
|
|
|
|
`pnpm check:css-has-performance` scans production CSS under `apps/`,
|
|
`packages/`, and `services/`. It rejects `:has()` when the selector subject is
|
|
the document root, a Workbench window/surface, an AgentGUI root layout,
|
|
timeline, transcript row, or another listed large dynamic surface.
|
|
|
|
Those subjects contain editors, streamed transcript content, dock animation, or
|
|
other frequently mutating descendants. Relational matching there can make the
|
|
entire subject subtree a style-invalidation candidate. Project the semantic
|
|
state onto the subject with a class or data attribute instead.
|
|
|
|
The check intentionally permits bounded local subjects such as buttons, icons,
|
|
dialogs, and individual conversation rows. Add a subject to the policy only
|
|
when its descendant scope or mutation frequency makes relational invalidation
|
|
structurally unsafe; do not turn this into a blanket ban on `:has()`.
|
|
|
|
The staged form runs in `pre-commit`. The full form is selected by
|
|
`check:changed` for production CSS or policy implementation changes and is part
|
|
of `check:full`.
|
|
|
|
Renderer feature implementation boundaries are checked by `pnpm check:renderer-boundaries`.
|
|
That check also enforces the Workbench-specific rule that
|
|
`workspace-workbench/ui/**` imports public service or controller seams instead
|
|
of `workspace-workbench/services/internal/**`.
|
|
Workspace Workbench launch coordinators must also route workspace-keyed Shell
|
|
registrations through the private `WorkspaceScopedRegistrationRegistry` rather
|
|
than declaring their own `Map`. The rule covers top-level
|
|
`*LaunchCoordinator.ts` services and the Message Center coordinator, while
|
|
leaving non-launch coordinator state such as Agent GUI open-session reference
|
|
counts outside this registration-specific policy. Because the rule is part of
|
|
`check:renderer-boundaries`, it runs for staged renderer changes, the
|
|
renderer-boundary lane selected by `check:changed`, and `check:full`. Changes
|
|
to the renderer-boundary checker or its fixture suite also select that
|
|
`check:changed` lane, so policy edits validate both their fixtures and the live
|
|
renderer tree.
|
|
|
|
Connector ownership is checked by `pnpm check:connector-boundaries`. The check
|
|
keeps Contracts independent from Renderer, keeps Renderer Application free of
|
|
React and host runtimes, prevents Connector packages from importing product
|
|
owners, keeps Daemon Core independent from Application/adapters, and prevents
|
|
Runtime from depending on Daemon Application, SQLite, or Control Plane. It also
|
|
rejects a Renderer root barrel so Application and UI remain separately
|
|
importable. Connector package, AgentGUI Connector integration, Desktop
|
|
Connector adapter, or checker changes select this lane through
|
|
`repository-checks.mjs`.
|
|
|
|
`pnpm check:ui-boundaries` has a package-scoped temporary migration exception
|
|
for `packages/agent/gui` while the carried agent activity renderer is
|
|
being ported into tutti. During that migration the package may keep its
|
|
existing local SVG and icon-library imports, but new reusable icons and
|
|
design-system primitives should still move through `@tutti-os/ui-system`
|
|
before being shared elsewhere. Remove or replace the package-wide exception
|
|
once the carried renderer no longer duplicates its original UI asset tree.
|
|
|
|
Desktop user-visible copy and locale resources are checked by `pnpm check:i18n`.
|
|
|
|
`pnpm check:agent-activity-runtime-boundaries` scans Agent GUI and desktop
|
|
renderer production code. Agent activity commands must go through
|
|
`AgentGUIRuntime`; session-engine consumers must use exported selectors
|
|
instead of reading `sessionsById`, `turnsById`, `interactionsById`, pending
|
|
intent maps, or prompt-queue records directly. Entity storage keys and reducer
|
|
layout are engine implementation details, not consumer contracts. The check
|
|
also rejects the deleted `workspaceAgentActivityTypes` aggregate, handwritten
|
|
session/snapshot/presence mirrors, module-global runtime resolver access, and
|
|
deprecated session lifecycle reads. The desktop reconcile diagnostics module
|
|
has a narrow serialization-only exception for legacy lifecycle evidence.
|
|
Direct React external-store subscription enforcement follows the actual
|
|
`useSyncExternalStore(...)` argument dependency: an argument that directly or
|
|
indirectly resolves to `getSessionEngine(...)` is rejected, while another store
|
|
subscription in a file that separately reads the engine is allowed. Keep both
|
|
the passing unrelated-store fixture and failing direct-engine fixture when this
|
|
analysis changes; file-level token coexistence is not a valid substitute for
|
|
the call relationship.
|
|
|
|
`pnpm check:changed` schedules this activity-runtime boundary lane whenever a
|
|
change touches `packages/agent/gui`, `packages/agent/activity-core`, Desktop's
|
|
`workspace-agent` or `workspace-workbench` features, or the checker/fixture
|
|
implementation itself. This keeps the same boundary in the normal changed-file
|
|
loop instead of discovering violations only in `check:full`.
|
|
|
|
`pnpm check:agent-host-boundary` protects the Agent Host boundary. Agent
|
|
application-core lifecycle semantics (session/turn/goal/runtime-operation
|
|
creation, sendability, terminal state, recovery) belong to
|
|
`packages/agent/host`; `services/tuttid/service/agent` is an adapter that
|
|
delegates through `ApplicationHost()`. The check scans production (non
|
|
`_test.go`) Go files under `services/tuttid/service/agent` and rejects new
|
|
`*Coordinator`, `*Worker`, or `*Actor` orchestration surfaces, detected as a
|
|
type declaration whose name ends in one of those words or a file whose name
|
|
ends in `_coordinator.go`, `_worker.go`, or `_actor.go`. It is a ratchet: the
|
|
`ALLOWLIST` in the checker is the current snapshot (only
|
|
`composer_live_model_coordinator.go`, a provider-catalog adapter concern), may
|
|
only shrink, and rejects stale entries whose files no longer exist. A new
|
|
violation fails until the orchestration moves to `packages/agent/host` or the
|
|
file is added to `ALLOWLIST` with a reviewed ownership reason. The rule runs in
|
|
`pnpm check:full`, in the `check:changed` `boundary:agent-host` lane whenever a
|
|
change touches `services/tuttid/service/agent` or the checker/fixture itself,
|
|
and in the PR `go-lint` job. The boundary rationale and adapter rules live in
|
|
the root `AGENTS.md` `Agent Host Boundary` section and
|
|
`services/tuttid/service/agent/AGENTS.md`.
|
|
|
|
The same check also verifies production reachability: `services/tuttid/wiring.go`
|
|
must compose Host with `daemon/hostadapter.RuntimeController`,
|
|
`host.SQLiteWorkspaceStore`, and `NewApplicationHostWithPorts`. This prevents
|
|
shared lifecycle adapters from existing only in tests while production keeps a
|
|
parallel service implementation. Production agent service files also reject
|
|
service-local `serviceHostStore` / `serviceHostRuntime` composition and lazy
|
|
`NewApplicationHost` factories; isolated in-memory adapters may exist only in
|
|
`_test.go` fixtures.
|
|
|
|
## Agent GUI Degradation Ratchet
|
|
|
|
The agent GUI refactor
|
|
([docs/architecture/agent-gui-refactor-plan.md](../architecture/agent-gui-refactor-plan.md))
|
|
is protected by a degradation ratchet:
|
|
|
|
- `pnpm check:agent-gui-degradation` measures entropy metrics over
|
|
`packages/agent/gui` and `packages/agent/activity-core` (per-file line counts
|
|
over the business limit, package-wide effect totals, per-component
|
|
memoization overages, render-time ref mirrors/caches, provider behavior
|
|
branches, timers, swallowed catch blocks, view-embedded stores, direct
|
|
`useSyncExternalStore` calls, module-level mutable globals, presentation
|
|
schedulers, inline compositor hints, CSS infinite animations/compositor
|
|
hints, and daemon Go file-length exemptions) and compares them against the
|
|
committed baseline in `tools/degradation-baseline/agent-gui.json`.
|
|
Render-mirror counting also covers Desktop's
|
|
`DesktopAgentGUIWorkbenchBody` host boundary because unstable host callbacks
|
|
can invalidate the entire Agent GUI subtree.
|
|
- Effect counts remain package-wide because hooks move with a vertical module
|
|
during decomposition. Memoization follows the architecture boundary instead:
|
|
`.tsx` component modules have a five-call budget, while controller/read hooks
|
|
may own stable projections. Read-layer memoization must stabilize data-owner
|
|
output; it must not become a second view cache.
|
|
- Any metric increase fails. Any decrease also fails until the same change
|
|
updates the baseline with
|
|
`node tools/scripts/check-agent-gui-degradation.mjs --update-baseline`, so
|
|
refactor wins stay locked in.
|
|
- The metric counting rules live in the script; numbers quoted in architecture
|
|
documents are illustrative only.
|
|
- `identityExemptFiles` in the baseline lists identity-display files (provider
|
|
icons, labels, title projections) whose provider branches are tracked in a
|
|
separate bucket. The list may only shrink, and the checker rejects entries
|
|
whose files no longer exist so removed seams cannot leave permanent stale
|
|
exemptions.
|
|
- CSS `will-change`, `translateZ`/`translate3d`,
|
|
`backface-visibility: hidden`, and infinite animations are tracked by exact
|
|
stylesheet selector/property/value fingerprints. Every fingerprint requires
|
|
a non-empty `presentationHintReasons` entry describing its bounded mounted,
|
|
visible, active, loading, or interaction lifetime. New hints cannot be
|
|
accepted by running `--update-baseline` alone; add the reviewed reason first.
|
|
Stale reasons fail after a hint is removed.
|
|
- The same CSS policy rejects `transition: all` and requires the root
|
|
`data-agent-gui-visible="false"` animation pause and
|
|
`content-visibility: hidden` declarations. It also requires the
|
|
`data-agent-gui-active="false"` prompt-tip animation and `will-change`
|
|
release declarations. These are behavior contracts, not baselined debt, so
|
|
deleting them always fails.
|
|
- Raw `requestAnimationFrame`, `requestIdleCallback`, and `ResizeObserver`
|
|
calls, plus inline `willChange` and `translateZ`/`translate3d` hints, are
|
|
counted per production file. A newly added call or hint must carry a
|
|
`// presentation-work: <visible/active/lifetime reason>` comment on the same
|
|
or previous line. Ordinary two-dimensional transforms are not banned.
|
|
- `pnpm check:agent-gui-degradation:staged` runs in `pre-commit` and blocks
|
|
new degradation patterns on staged added lines: uncommented timers (a
|
|
`// timing: <reason>` comment is required outside engine/reducer/selector
|
|
code, where timers are forbidden entirely), silently swallowed catch blocks,
|
|
component memoization beyond budget, render-time ref mirrors/caches, store
|
|
creation in component files, new provider behavior branches, direct
|
|
`useSyncExternalStore` calls outside the single engine binding file, and new
|
|
module-level mutable globals, unexplained presentation schedulers/inline
|
|
compositor hints, unreviewed CSS presentation hints, `transition: all`, and
|
|
removal of the visibility/active pruning declarations. Ref-mirror and
|
|
component-cache diagnostics explicitly route business state to the
|
|
engine/controller and stable projections to selectors/read hooks; refs
|
|
remain valid for imperative DOM, timer, abort, and external-lifecycle
|
|
handles.
|
|
- During a merge commit, staged mode compares the resolved index with
|
|
`MERGE_HEAD` instead of treating every incoming-parent line as newly added.
|
|
This keeps the hook focused on branch-authored degradation while the full
|
|
baseline check still measures the complete merged tree.
|
|
|
|
The business file size limit below also applies to TypeScript under
|
|
`packages/agent/gui` and `packages/agent/activity-core` through this ratchet:
|
|
files over the limit that are not in the baseline fail the check, and
|
|
baselined files may not grow.
|
|
|
|
Render budget tests are the companion mechanism for performance work: the
|
|
probe utility in `packages/agent/gui/shared/testing/renderBudget.tsx` asserts
|
|
React commit counts for typical interactions, and budget test cases are
|
|
delivered with each feature-module slice.
|
|
|
|
Electron `main` and `preload` runtime import graphs are checked by `pnpm check:electron-runtime-boundaries`.
|
|
That script is intentionally narrow: it ignores type-only imports and test files, then follows reachable runtime imports to catch React/TSX leaks and Electron-externalized workspace packages that still resolve to raw source files.
|
|
|
|
The i18n check enforces:
|
|
|
|
- locale key parity across supported desktop locales
|
|
- interpolation placeholder parity across locale values
|
|
- valid references for literal i18n keys used from desktop code
|
|
- likely hardcoded user-visible copy in desktop renderer and Electron UI surfaces
|
|
|
|
The i18n check discovers locale-resource modules through manifest exports
|
|
instead of a central script-side registry. Desktop-owned resources use
|
|
`tuttiI18nModule`; reusable packages may use a package-specific manifest name
|
|
when that keeps the package vocabulary product-neutral, such as
|
|
`browserNodeI18nModule` or `agentGuiI18nModule`.
|
|
|
|
Prefer constructing that manifest through the shared helpers exported from
|
|
`@tutti-os/ui-i18n-runtime`:
|
|
|
|
- `createLocaleObjectI18nModuleManifest(...)`
|
|
- `createScopedLocaleObjectsI18nModuleManifest(...)`
|
|
|
|
Current manifest modes:
|
|
|
|
- `locale-object`
|
|
Use this when a module owns top-level locale files such as
|
|
`locales/en.ts` and `locales/zh-CN.ts`.
|
|
- `scoped-locale-objects`
|
|
Use this when a reusable package owns a scoped default dictionary under one
|
|
namespace and the host merges that package resource into the app-level i18n
|
|
runtime.
|
|
|
|
Current manifest expectations:
|
|
|
|
- desktop-owned locale resources should expose a manifest from
|
|
`apps/desktop/src/shared/i18n/*`
|
|
- reusable package default i18n resources should expose a manifest from the
|
|
owning package source tree under `packages/*`
|
|
- reusable package `scoped-locale-objects` manifests should include:
|
|
`name`, `namespace`, `sourceRoot`, and `localeObjectByLocale`
|
|
- desktop or package `locale-object` manifests should include:
|
|
`name` and `fileByLocale`; package-owned `locale-object` manifests should
|
|
also include `sourceRoot`
|
|
|
|
When adding a new reusable package that owns default i18n resources, do all of
|
|
the following in the same change:
|
|
|
|
- keep the default resource in the owning package instead of copying it into
|
|
`apps/desktop`
|
|
- export an i18n manifest next to that package's i18n resource
|
|
- merge the package resource into the desktop app-level i18n runtime when the
|
|
desktop host consumes that package
|
|
- run `pnpm check:i18n`
|
|
|
|
## Go Baseline
|
|
|
|
Go linting uses `golangci-lint` across the repository's current Go modules.
|
|
The current root entrypoint runs the linter from:
|
|
|
|
- `packages/agent/activity-replication`
|
|
- `packages/agent/host`
|
|
- `packages/agent/store-sqlite/canonical`
|
|
- `packages/appcli/core`
|
|
- `packages/clients/device-authority-go`
|
|
- `packages/clients/market-go`
|
|
- `packages/connector/daemon/application`
|
|
- `packages/connector/daemon/core`
|
|
- `packages/connector/runtime`
|
|
- `packages/connector/daemon/adapters/sqlite`
|
|
- `packages/connector/daemon/adapters/controlplane`
|
|
- `packages/device-link`
|
|
- `packages/agent/runtimeprep`
|
|
- `packages/workspace/files`
|
|
- `packages/workbench/service`
|
|
- `services/tuttid`
|
|
|
|
The shared lint configuration currently lives in `services/tuttid/.golangci.yml`.
|
|
|
|
The shared agent daemon runtime under `packages/agent/daemon` is included by
|
|
changed-aware Go lint when daemon files change, but is not yet part of the root
|
|
`pnpm lint:go` module list. During the migration, selected historical files carry file-local
|
|
`revive:disable:file-length-limit` comments. New tutti-owned daemon
|
|
service/API code should stay outside those exceptions and must continue to
|
|
satisfy the normal Go lint baseline.
|
|
|
|
Changed-aware Go validation includes the nested
|
|
`packages/agent/activity-replication`, `packages/agent/daemon`,
|
|
`packages/agent/host`,
|
|
`packages/agent/runtimeprep`, `packages/agent/store-sqlite`, and
|
|
`packages/agent/store-sqlite/canonical`, `packages/clients/device-authority-go`,
|
|
`packages/clients/market-go`,
|
|
the five Connector Go modules, and `packages/device-link` modules.
|
|
Codex app-server protocol changes should also run
|
|
`pnpm check:codexproto-generated` when schema, generator, or generated protocol
|
|
files are touched.
|
|
|
|
Every change under `packages/device-link/**`, including Makefiles, Java probe
|
|
sources, and Android manifests, also selects
|
|
`pnpm check:device-link-android`. That contract runs the Go suite, Android
|
|
arm64 cross-compile, and the transport-only Java gomobile binding generation.
|
|
`pnpm mobile:check` separately generates the Mobile-owned composite binding
|
|
surface for DeviceLink plus the Agent live Subscriber without requiring an
|
|
Android SDK. The Mobile package's composite `pnpm check` also runs
|
|
`check:ios-bindings`, which uses Go's Objective-C binding generator to verify
|
|
the DeviceLink and live Subscriber headers and expected exported symbols. This
|
|
binding check needs the repository Go toolchain and macOS Command Line Tools,
|
|
but not the full iOS SDK; building the XCFramework still requires full Xcode.
|
|
AAR assembly remains an explicit Android-SDK validation locally.
|
|
The manually dispatched Mobile Internal Build workflow accepts `android`,
|
|
`ios`, or `all`. Its Android job installs the pinned SDK/NDK versions,
|
|
assembles the Mobile composite AAR and internal mobile APK, and uploads a
|
|
private validation artifact. Android release assembly requires the repository's
|
|
stable keystore and credentials through the four `ANDROID_RELEASE_*` Actions
|
|
secrets; the workflow fails closed when they are absent, and verifies both APK
|
|
alignment and signing certificate before upload. It never generates a temporary
|
|
signing identity. Each CI build also uses the repository-level workflow run
|
|
number as its monotonically increasing Android `versionCode`. It validates the
|
|
DeviceLink consumer build but does not publish Go module tags; the stable package
|
|
release workflow owns those tags. Its iOS job runs on the pinned macOS 26 runner,
|
|
assembles the same Mobile
|
|
binding surface as an XCFramework, archives the React Native app, and uses the
|
|
repository App Store Connect API key plus the `IOS_DEVELOPMENT_TEAM` repository
|
|
variable for Xcode-managed cloud signing. It loads the Mobile Podfile's pnpm
|
|
path compatibility shim before generating the Pods project, combines the GitHub
|
|
Actions run number and attempt as the unique iOS build number, exports an App
|
|
Store Connect IPA, verifies that the signed app contains its release
|
|
`main.jsbundle`, and uploads the archive to TestFlight without registering test
|
|
device UDIDs. The exported IPA and checksum remain available as a 14-day private
|
|
validation artifact rather than creating a GitHub Release. Both jobs remain
|
|
manual so pull request code does not receive mobile signing credentials
|
|
automatically.
|
|
|
|
Local runs resolve `golangci-lint` from `$(go env GOPATH)/bin` first and fall
|
|
back to `PATH`. This matches the repository install command without requiring a
|
|
shell-specific `PATH` edit. The repository pins the CI version through
|
|
`services/tuttid/.golangci-lint-version`. CI downloads the official installer
|
|
to a temporary file with transient-network retries, runs it only after the
|
|
download succeeds, and adds the install directory to `PATH` only after verifying
|
|
that the pinned binary is executable.
|
|
|
|
If you plan to run `pnpm lint:go` or `pnpm check:full` locally, install
|
|
`golangci-lint` first. A compatible binary already available on `PATH` remains
|
|
supported as a fallback.
|
|
|
|
Use `pnpm check:golangci-version` when you only want to verify that the installed binary matches the repository pin without running the broader setup checks.
|
|
|
|
Recommended local install command, using the pinned repository version:
|
|
|
|
```sh
|
|
pnpm install:golangci-lint
|
|
```
|
|
|
|
This installs the pinned binary into `$(go env GOPATH)/bin`; repository-managed
|
|
setup, version, changed-aware, and full lint commands resolve that location
|
|
automatically. It follows the current official binary-install guidance from
|
|
golangci-lint and keeps local runs aligned with the version pinned for CI.
|
|
|
|
The current baseline enables a small, high-value set of linters:
|
|
|
|
- `errcheck`
|
|
- `govet`
|
|
- `ineffassign`
|
|
- `nolintlint`
|
|
- `staticcheck`
|
|
- `unused`
|
|
- `revive`
|
|
|
|
In golangci-lint v2, `staticcheck` also covers checks that were previously exposed as separate `gosimple` and `stylecheck` linters.
|
|
|
|
`nolintlint` keeps lint suppressions explicit and valid when an exception is necessary.
|
|
|
|
## Business File Size Limit
|
|
|
|
Business-code files must stay at or below `800` lines.
|
|
|
|
This limit is a refactoring trigger:
|
|
|
|
- when a business file exceeds the limit, prefer splitting responsibilities or extracting focused helpers before adding more logic
|
|
- do not treat the limit as a suggestion to bypass with casual exceptions
|
|
|
|
The limit does not apply to:
|
|
|
|
- test files
|
|
- generated files
|
|
- pure type declarations
|
|
- contracts packages
|
|
- bootstrap or helper surfaces outside the configured business paths
|
|
|
|
Current first-pass scope:
|
|
|
|
- TypeScript business paths under `apps/desktop/src/main/*`, `apps/desktop/src/preload/*`, and `packages/clients/*`
|
|
- Go business paths under `packages/workspace/files/*` and `services/tuttid/app/*`, `api/*`, `biz/*`, `data/*`, `server/*`, and `service/*`
|
|
|
|
## Workflow Rules
|
|
|
|
- keep `pre-commit` focused on staged formatting and cheap boundary checks
|
|
- keep linting in `pre-push` and pull-request CI through the shared root scripts
|
|
- prefer extending the existing lint configs before adding new one-off repository scripts
|
|
- add repository-specific scripts only when standard lint tooling cannot express the rule cleanly
|