Ctrl K
ComunidadGuías…

CONSTRAINTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Constraints

Enforced quality/security bar for this repo. Agents must not weaken any rule
here to make a change pass — fix the change instead. If a rule is genuinely
wrong, edit this file in its own commit and say why.

Enforcement mode: **block**. A failing check stops the task; fix it before
continuing.

## Floor (existing, unchanged)

| Check | Command | Rule |
|---|---|---|
| Types | `pnpm typecheck` | 0 errors across main/preload/renderer tsconfigs |
| Lint | `pnpm lint` | `eslint --max-warnings 0` |
| Format | `pnpm format:check` | Prettier clean |
| Commit messages | `pnpm lint:commits` (CI checks each PR's range) | Conventional Commits, header ≤ 72 characters (`commitlint.config.ts`) |

## Code comments

| Check | Command | Rule | Reason |
|---|---|---|---|
| Comment intent | `pnpm lint` | 0 `local/no-history-comments` errors (`eslint-rules/no-history-comments.mjs`) | Comments explain *why*. Issue/PR tags, task numbers, tooling narrative and `file:line` refs belong in the commit or PR, and go stale in the code. Narrative and duplication aren't machine-checkable — see CLAUDE.md, Code Style. |

## Security

| Check | Command | Rule | Reason |
|---|---|---|---|
| Vault/IPC boundary | `pnpm depcruise` | 0 violations of `renderer-no-vault-crypto` (`.dependency-cruiser.cjs`) | Codifies the CLAUDE.md rule: renderer must never import `src/main/vault/**`, `src/main/db/**`, `@journeyapps/sqlcipher`, or `libsodium-wrappers-sumo` directly — everything crosses the IPC boundary. |
| Sandboxed preload | `pnpm depcruise` | 0 violations of `preload-sandbox-compatible` (`.dependency-cruiser.cjs`) | The renderer runs sandboxed, so `src/preload/**` may only import `electron` at runtime. A Node builtin or any other package builds fine but fails at runtime, leaving `window.notvex` undefined. |
| Error log allowlist | `pnpm test:app` | `tests/main/app/error-log.test.ts` green; every write to `logs/notvex.log` goes through `formatEntry` in `src/main/error-log.ts` | The log is plaintext and readable without the vault password, so it may hold only what is already as exposed as `prefs.json`: never `error.message`, `cause`, IPC arguments, vault or user file paths, or note/tag data. Messages embed input (V8's `JSON.parse` errors quote the parsed text, fs errors carry the vault path), so entries are built from an allowlist (error name, stack frames trimmed to the app bundle, a fixed reason) rather than redacted. The stack's `Name: message` header is cut by the message's exact text (`src/shared/error-stack.ts`) before any line is parsed, since a multi-line message can contain frame-shaped lines. The sentinel tests in that file fail if anything outside the allowlist reaches the output. The file is `0o600` in a `0o700` folder on macOS/Linux; Windows ignores those modes, and the per-user ACL on `%APPDATA%` is what protects it there. |
| Secrets scan | *(pending, see Exceptions)* | 0 findings, `gitleaks detect --redact` | Vault key material / recovery phrases must never land in a commit. |
| Dependency vulns | *(pending, see Exceptions)* | 0 high/critical, `osv-scanner scan .` | `@journeyapps/sqlcipher`, `libsodium-wrappers-sumo`, `koffi` are native-binding deps in the crypto path — a known CVE there is high-severity for this app specifically. |

## Architecture

Same `pnpm depcruise` run as above — one dependency-cruiser config, two
concerns (boundary correctness doubles as the architecture check for now).
Add a second rule to `.dependency-cruiser.cjs` if another boundary needs
enforcing later; don't add a second tool for it.

## Test file location

Test files live under `tests/` at the project root, never colocated inside
`src/`. `pnpm lint`/`pnpm format`/`typecheck` all cover `tests/**` explicitly —
a test file outside that scope is silently unchecked by all three.

Two typecheck scopes, because React tests need `jsx` and DOM libs that the
main-process config must not carry:

| Location | tsconfig | Environment |
|---|---|---|
| `tests/main/**`, `tests/preload/**`, `tests/eslint-rules/**` | `tsconfig.node.json` | node (default) |
| `tests/renderer/*.test.ts` | `tsconfig.web.json` | jsdom, via a `// @vitest-environment jsdom` docblock |

`tsconfig.node.json` excludes `tests/renderer`; `tsconfig.web.json` includes it.
A React test placed outside `tests/renderer/` will fail to typecheck.

Folders mirror the code they test (`tests/main/vault/` tests `src/main/vault/`),
and each `pnpm test:<area>` script runs one of them. Split a file by topic once
it grows past a few hundred lines. Fixtures shared by several files live next to
them in a non-`.test.ts` module (`tests/main/vault/helpers.ts`).

`vi.mock` is hoisted only within its own file. A shared module can hold the
mocks (`tests/main/ipc/confirmation-gates/harness.ts`) only when the test files
import the mocked code dynamically, after the harness has loaded; with static
imports, keep the `vi.mock` calls in each test file.

Tests import source through the path aliases (`@main/…`, `@/…`), in `import`,
`vi.mock` and `typeof import()` alike, so a file can move without its paths
changing. Vitest resolves a mocked alias to the same module as the source's
relative import of it.

React tests that depend on effects re-running must pass `reactStrictMode: true`
to `render`/`renderHook`. Wrapping the tree in `<StrictMode>` by hand does
**not** re-run effects under Testing Library, so such a test silently proves
nothing — this was verified with a probe, not assumed.

## Coverage — measured only, not gated

Measured via `pnpm test:coverage`:

| Metric | Value | Previous floor |
|---|---|---|
| Statements | 55.99% (2387/4263) | 51.72% (2161/4178) |
| Branches | 48.63% (1019/2095) | 43.07% (887/2059) |
| Functions | 48.87% (521/1066) | 44.96% (469/1043) |
| Lines | 57.38% (2183/3804) | 53.13% (1982/3730) |

Coverage percentage alone does not prove a test asserts anything: a covered
line can still pass against deliberately broken code.

- Floor: the numbers above. From here, coverage must not regress below this
  floor — re-run `pnpm test:coverage` and update this table when it improves.
- Do not invent a target (e.g. "80%") — that's fabricated, not measured.

A percentage can also fall because the denominator shrank: less code under the
same tests (e.g. statements 666/3391 → 665/3390 and functions 131/852 → 129/850
after a simplification pass, same 37 tests). That direction is fine when it is
recorded here with its before/after counts, because the rule above exists to
stop a number being quietly edited down. A drop with no such note, or one where
the test count also fell, is the thing it is guarding against.

## Speed budget

| Tier | Commands | Target | Measured |
|---|---|---|---|
| Fast (edit loop) | `pnpm lint` (eslint `--cache`) | seconds | ~30s cold, faster warm |
| Task-end | `pnpm check:task` (= validate + depcruise + lint:commits + test) | 90s | **~2m55s — over budget** |
| Full (review/CI) | `pnpm check:full` (= check:task + coverage) | unlimited | not yet run in CI |

The 90s task-end target isn't met: `pnpm typecheck` alone measures **~2m13s**
because it runs three separate `tsc --noEmit` invocations
(`tsconfig.json` + node + web) with `--composite false`, which disables
incremental caching on every run. That's a pre-existing property of the
tsconfig split, not something this pass changed — flagging it here rather
than restructuring the TS project setup as an unscoped side quest. If task-end
speed becomes a real problem, the fix is enabling incremental `tsc` builds
(cache the `.tsbuildinfo` files instead of `--composite false`), not dropping
typecheck from the gate.

Until then: run `pnpm lint` (and `pnpm depcruise`, ~11s) during a task;
save `pnpm check:task` for before marking work done, same as
`pnpm validate` was already used per CLAUDE.md.

## Scripts

```bash
pnpm depcruise       # architecture/IPC-boundary check (.dependency-cruiser.cjs)
pnpm check:fast      # typecheck + lint — edit loop
pnpm check:task      # validate + depcruise + lint:commits + test — before calling work done
pnpm check:full      # check:task + coverage — review/CI
pnpm test            # vitest run — every test
pnpm test:watch      # vitest in watch mode
pnpm test:main       # tests/main (all main-process tests)
pnpm test:vault      # tests/main/vault
pnpm test:db         # tests/main/db
pnpm test:ipc        # tests/main/ipc
pnpm test:app        # tests/main/app (lifecycle, window, updater, guards)
pnpm test:preload    # tests/preload
pnpm test:renderer   # tests/renderer (jsdom)
pnpm test:coverage   # vitest run --coverage
```

## Exceptions

| Rule | Status | Owner | Expiry | Note |
|---|---|---|---|---|
| Secrets scan (gitleaks) | Not installed | GFrancV | 2026-10-07 | Not on npm; real binary is `winget install Gitleaks.Gitleaks`. Skipped installing system-wide during setup per explicit request — install manually, then wire `gitleaks detect --redact --source .` into `check:full`. |
| Dependency vuln scan (osv-scanner) | Not installed | GFrancV | 2026-10-07 | Not on npm; real binary is `winget install Google.OSVScanner`. Same as above — install manually, then wire `osv-scanner scan --lockfile pnpm-lock.yaml` into `check:full`. |
| `check:task` speed budget | Over 90s target (~2m55s) | GFrancV | — | See Speed budget above. No expiry: this is a measured fact about the current tsconfig setup, not a temporary exception someone forgot to clean up. Revisit only if incremental `tsc` is enabled. |

Review the two "not installed" exceptions by the expiry date above — either
install the tools and wire them in, or renew the exception with a reason.