Ctrl K
ComunidadGuías…

CLAUDE.md

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

Archivo original ↗Preguntar a KiBot
# Havemind, agent instructions

Havemind is a private synchronisation layer for Obsidian (see `README.md`). This file applies
within this repository and overrides conflicting upstream context (global CLAUDE.md, skills
other than those listed below) to the extent that `plan/01-rules-and-glossary.md` rule 8-9
explicitly describes it (access to `sapserver`, local commit within the `/loop` loop).

## Repo structure

- `specs/001-mvp.md`, product requirements (canonical, approved 2026-07-15).
- `plans/001-technical-plan.md`, architecture and engineering contract (canonical).
- `plans/004-006`, follow-up plans. 005 and 006 shipped; 004 (E2EE) is a design
  study, not scheduled.
- `plan/`, planning package (generated by the `loopstart` skill) closing out T018-T033 and
  the pilot on `sapserver`. If you're continuing this project, start with `plan/README.md`.

## Canonical data

Do not guess requirements or architecture. Everything is in `specs/` and `plans/`.
Where `plan/` conflicts with these files → `specs/`/`plans/` win (see
`plan/01-rules-and-glossary.md` rule 1), except for explicitly documented deviations in the
`plan/README.md` "Open decisions" section.

## Commands

```bash
npm ci
npm run build
npm run typecheck
npm run lint
npm test
npm run test:coverage
npm run dev:server
npm run dev:plugin
npm run compose:smoke
npm run test:e2e
```

## Hard-rules summary (full version: `plan/01-rules-and-glossary.md`)

1. TDD red-green-refactor, no exceptions.
2. The server is opaque, it never computes diff/provenance/merge.
3. Zero silent overwrites, zero trust in a client-supplied `actor_id`.
4. Secrets never go into the repo, logs, or subagent reports.
5. The agent has `ssh sapserver` access and may modify it (routine operations), but steps that
   require the `sudo` password, Tailscale Funnel, the `docker` group, or are irreversible,
   always ask the user first. A local `git commit` within the `/loop` loop, no need to ask.
   `git push`, always ask first.
6. Zero home-grown cryptography, zero Kubernetes/Portainer/Watchtower/databases "just in case".
7. All repository content (docs, comments, commit messages, plans) must be written in English.
8. One selector, one declaration, one place in `styles.css`. Never append a second block for a
   selector that already exists, edit the existing rule. A property set from a `--havemind-*`
   design token must not also be set from a generic variable elsewhere.
9. Never edit a plugin copy inside a vault. Edit the repository, build, then deploy to **every**
   installed copy and verify by hash.

## Pane styling

The pane's geometry is transcribed from a Claude Design handoff that uses inline styles and no
classes, so nothing but a test can keep the two in agreement:

- `scripts/extract-design-tokens.mjs` reads the recurring values into `tokens.json`
  (`--check` in CI fails when it goes stale).
- `src/ui/design-tokens.test.ts` fails when `styles.css` disagrees with those tokens.
- `src/ui/stylesheet-conflicts.test.ts` fails when a selector sets one property to two different
  values, or styles a class nothing renders.

When the pane "doesn't look like the design", check in this order: (1) does the stylesheet reach
the screen at all, add a marker rule to the *installed* file and reload the plugin; (2) does the
sheet contradict itself; (3) only then, are the values wrong. Rounds of value edits on a sheet
that contradicts itself change nothing, because the winner is decided by source order.

## Dependencies: the lockfile is resolved on LINUX, never on macOS

`package-lock.json` records a tree that differs by platform. `@emnapi/core` and
`@emnapi/runtime` 1.11.3 exist as TOP-LEVEL entries only on Linux; on macOS the
tree carries just 1.11.1, nested under rolldown. A lockfile regenerated on a Mac
is therefore missing entries the Docker build needs, and `npm ci` fails with
`Missing: @emnapi/core@1.11.3 from lock file`. The server then cannot be rebuilt
at all, which is how this rule got written (2026-09-05, three failed attempts on
the wrong platform).

`npm ci --dry-run` on macOS PASSES against that broken lockfile, so it proves
nothing. Only a real `npm ci` inside the build image does.

To change a dependency:

1. Edit `package.json` as usual.
2. Regenerate inside the image the Dockerfile builds from, not on the host:

```bash
docker run --rm -v "$PWD":/w -w /w \
  node:22.23.1-bookworm-slim npm install --package-lock-only
```

3. Verify `npm ci` exits 0 locally too, so the file is not Linux-only.
4. Confirm the Docker build succeeds before committing.

Never run `rm package-lock.json && npm install` to fix a dependency problem. It
silently drops every platform-optional package the host cannot resolve.

## Vault notes, update on every push

The project's running notes live in the user's Obsidian vault, in
`~/ObsidianVaults/Mikolaj Private/Havemind/`:

| Note | Holds |
|---|---|
| `Havemind - indeks` | entry point, quick facts, repo map, the checking-off convention |
| `Havemind - roadmapa` | phases F0-F9, what closed, what is left, suggested order |
| `Havemind - 10 otwartych pozycji` | every open item: what it is, where in the code, what to do, how to verify |
| `Havemind - stan backlogu` | review and any disagreement between the backlog and the code |
| `Havemind - dziennik wydan` | release log, newest first |

## Releasing: TWO repositories, both or the release is invisible

This project has two repos and a release is not finished until it lands in both.

| Repo | Role |
|---|---|
| `MikolajSapek/havemind` | source. All work happens here. |
| `mikolajsapek/obsidian-havemind` | distribution. Built artefacts in the root; **this is what the Obsidian catalogue reads.** |

The catalogue entry for `havemind-sync` points at the DISTRIBUTION repo. Tagging
only the source repo publishes a release nobody can install: 1.2.2 through
1.4.0 all shipped that way and users stayed on 1.2.1 for two weeks, which is how
this rule got written (2026-09-04).

After tagging the source repo, mirror it:

1. Clone or pull `mikolajsapek/obsidian-havemind`.
2. Copy from `apps/obsidian-plugin/`: `main.js`, `styles.css`, `manifest.json`,
   `manifest-beta.json`, and `versions.json` from the repo root.
3. Commit, push, tag with the SAME version, push the tag. Its own release
   workflow builds the GitHub release from there.
4. Verify with `gh release list --repo mikolajsapek/obsidian-havemind`, not just
   the source repo's.
5. Give the distribution release its notes:
   `gh release edit X.Y.Z --repo mikolajsapek/obsidian-havemind --notes-file docs/release-notes/X.Y.Z.md`.
   Its workflow creates the release without a body, and the catalogue review
   flags a release with no description (2026-10-14).

A release is reported as done only after step 5.

### The README is part of the release, not documentation about it

Bump the version in `apps/obsidian-plugin/manifest.json` and `manifest-beta.json`
AND in the README's `**Version X.Y.Z` line, in the same commit. The README's
first paragraph is the first thing a visitor reads, and it sat at "Version 1.2.3"
while 1.4.7 was live in the catalogue: seven releases, every one of them
announcing the wrong version (2026-09-10).

`tests/readme-version.test.ts` fails when the README version and the manifest
disagree, so `npm run verify` catches it before the tag exists. Do not delete
that test to make a release pass, fix the README.

Before tagging, re-read the README for anything the release made untrue:

- the version line, including its short description of what the release is;
- **What it does**, when the release added or removed a user-visible capability;
- the platform claims, when support changes;
- screenshots and `design/brand/*.png`, when the pane's appearance changed.

### A push is not done until CI is green

**After every `git push`, watch the run to completion before reporting the push
as finished:**

```bash
gh run list --repo MikolajSapek/havemind --limit 3 \
  --json status,conclusion,name --jq '.[] | "\(.status) \(.conclusion // "-") \(.name)"'
```

A green `npm run verify` locally does NOT mean CI passes. The property battery
in `diff3.property.test.ts` draws fresh random cases on every run: the merge fix
in 1.4.9 passed 25 local draws three times in a row and failed in CI after 437,
having silently dropped a line. Anything random, timing-dependent, or
platform-dependent can only be caught by looking at the run that actually ran
(2026-09-14).

When CI fails, pull the counterexample out of the log rather than guessing:

```bash
gh run view <id> --repo MikolajSapek/havemind --log-failed | grep -aE "Counterexample|seed:"
```

Reproduce it in a scratch test first, fix, then re-run the property file several
times before pushing again.

**After every push to `origin/main`, append an entry to
`Havemind - dziennik wydan`** before reporting the push as done: the date,
the commits with one line each, what changed, and the state of
`npm run verify`. A push that changed the backlog also updates
`Havemind - 10 otwartych pozycji` and `Havemind - roadmapa`, and a release
adds its tag, the release URL and whether the attestation verified.

Rules for these notes:

- Polish, matching the vault. Code, paths and identifiers stay in English.
- No em-dashes, the vault follows the same rule as this repository.
- Cite a file and line only after reading it in the current tree; a stale
  line number is worse than none.
- Never check an item off in `plan/11-BACKLOG.md` before its AC passes, and
  record the evidence next to it. An item that no longer applies is closed as
  out of scope with a reason, never checked off (see T033, SRV-04/05).
- Every note links at least one other note in the folder, and the folder is
  reachable from `HOME.md`.

## Ignore conflicting upstream context

If other context (an older project, another CLAUDE.md higher up the directory tree) suggests a
different stack, a different architecture, or different commit rules than the above, this file
and `plan/` win within this repository.