Extending Personhog Test Harness
PostHog/posthog
When and how to add scenarios, chaos events, and invariants to the personhog e2e test harness (rust/personhog-test-harness).
Mini Diarium's opinionated security, encryption, and privacy stance.
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install fjrevoredo/mini-diarium security-stance --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/security-stance .claude/skills/security-stance && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .claude/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stanceType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install fjrevoredo/mini-diarium security-stance --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/security-stance .agents/skills/security-stance && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .agents/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install fjrevoredo/mini-diarium security-stance --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/security-stance .cursor/skills/security-stance && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .cursor/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/fjrevoredo/mini-diarium.git --path .agents/skills/security-stance--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install fjrevoredo/mini-diarium security-stance --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/security-stance .gemini/skills/security-stance && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .gemini/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install fjrevoredo/mini-diarium security-stanceInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/security-stance .github/skills/security-stance && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .github/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install fjrevoredo/mini-diarium security-stance --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/security-stance .opencode/skills/security-stance && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "security-stance" agent skill from https://github.com/fjrevoredo/mini-diarium/tree/master/.agents/skills/security-stance into .opencode/skills/security-stance/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-stance", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
security-stanceMini Diarium's opinionated security, encryption, and privacy stance.
Security Stance is an agent skill from fjrevoredo/mini-diarium. Mini Diarium's opinionated security, encryption, and privacy stance. Load when touching crypto (crates/mini-diarium-crypto/src/crypto/), auth (crates/mini-diarium-core/src/auth/, commands/auth/), IPC boundaries (src/lib/errors.ts, tauri.ts), auto-lock (screenlock.rs, App.tsx idle timer), database schema/migrations (db/schema.rs), backups (backup.rs), config.json / JournalConfig, import/export, plugin sandbox (Rhai), search (intentionally stubbed), debug dump, Tauri capabilities (src-tauri/capabilities/) or CSP…
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts. Compatibility notes: Designed for Claude Code. Applies to Mini Diarium only.
It sits in Databases, covering Backup and disaster recovery, React components and Database schema design. It works with Tauri. The repository describes itself as: A local-only journal with serious encryption. Free, open source, and never touches the internet. The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit eed457f. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
cargobungitFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Designed for Claude Code. Applies to Mini Diarium only.
From compatibility in the SKILL.md frontmatter.
Security Stance loads about 11k tokens when it runs. Until then it costs about 196 tokens; SKILL.md has 4,867 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from fjrevoredo/mini-diarium at commit eed457f, republished under its MIT licence (© fjrevoredo). 4,867 words, ~10,929 tokens.
.claude/skills/security-stance/SKILL.md (or your agent's skills folder).The opinionated, repo-specific security/privacy/encryption stance for Mini Diarium. Use this skill alongside (not instead of) /security-review. This document is passive context that loads whenever security-adjacent work appears; /security-review is a one-shot review command.
The authoritative sources are PHILOSOPHY.md and SECURITY.md. This skill distills the operational details: invariants, file:line anchors, footguns, and what-breaks-if-you-change-this. When this document and an authoritative source disagree, the source wins — file an update to this skill.
Load when work touches any of:
crates/mini-diarium-crypto/src/crypto/cipher.rs, crates/mini-diarium-crypto/src/crypto/password.rscrates/mini-diarium-core/src/auth/mod.rs (composed slot ops + AuthMethodInfo), crates/mini-diarium-crypto/src/auth/{password,keypair,auto_key}.rs (the pure wrapping methods), src-tauri/src/commands/auth/src/lib/tauri/, src/lib/errors.ts, any new #[tauri::command]src/App.tsx idle timer, src-tauri/src/screen_lock.rs, src/lib/focus-lock.ts, src-tauri/src/window_focus.rs, src/lib/dialog.tscrates/mini-diarium-core/src/db/schema/mod.rs (SCHEMA_VERSION), crates/mini-diarium-core/src/db/schema/compat.rs (forward-compatibility guard), crates/mini-diarium-core/src/db/schema/migrations/crates/mini-diarium-core/src/backup.rscrates/mini-diarium-core/src/config.rs (JournalConfig, auto_key, require_all_auth)src-tauri/src/commands/import.rs, commands/export.rs, import/*.rs, export/*.rscrates/mini-diarium-core/src/plugin/rhai_loader/ (runtime.rs = engine + limits, metadata.rs, mod.rs = loader), plugin/registry.rs, plugin/builtins.rssrc-tauri/src/commands/search.rs, src/state/search.ts, src/components/search/src-tauri/src/commands/debug.rssrc-tauri/src/commands/files.rssrc-tauri/capabilities/*.jsonsrc-tauri/tauri.conf.json (security.csp, dangerousDisableAssetCspModification)src-tauri/src/lib.rs (init script, on_navigation, on_new_window, Windows COM handler, macOS WKContentRuleList handler); src/lib/network-isolation-script.ts (TS copy — must stay in sync with Rust)src-tauri/src/lib.rs (MINI_DIARIUM_E2E*)src/lib/markdown.ts (DOMPurify)Examples
From PHILOSOPHY.md:165-169. These are not preferences — they are red lines.
| # | Rule | Enforcement / Where it shows up first |
|---|---|---|
| 1 | No network access. Mini Diarium never initiates any network connection. OS-opener links (About screen, Onboarding) hand a URL to the system browser — the app makes no network call itself. | None of the three crate manifests — src-tauri/Cargo.toml (app crate), crates/mini-diarium-core/Cargo.toml (business layer), or crates/mini-diarium-crypto/Cargo.toml (crypto crate) — contains reqwest, hyper, socket2, ureq, etc. (PHILOSOPHY.md:225). Capabilities allowlist (src-tauri/capabilities/default.json) has no http:*. CI static check scripts/check-no-network.ps1 (globs and scans all crate manifests) enforces this on every push. Adding a network crate or capability is the visible violation. |
| 2 | No custom cryptography. Standard algorithms and established libraries only. | Code review: any homegrown MAC, KDF, cipher mode, nonce scheme, or "encryption helper" is the violation. We use aes-gcm, argon2, x25519-dalek, hkdf, zeroize. |
| 3 | No password recovery. If credentials are lost, data is gone. Mitigation = register a second auth method. | Refusing to add a "reset password without old password" path. The only re-wrap is change_password (requires current creds). remove_auth_method refuses to delete the last slot. |
| 4 | No vendor lock-in. Users must be able to export and migrate freely. | JSON + Markdown exports remain plaintext, with documented schema (PHILOSOPHY.md:82-87). Removing or proprietizing an export format is the violation. |
| 5 | Honest threat documentation. Document what IS protected and what IS NOT. Never overstate. | SECURITY.md table is the contract. Adding sync, sharing, or "encrypted by your account" without updating the threat model is the violation. |
If an agent proposes a change that visibly conflicts with any non-negotiable: STOP. Surface the conflict to the user. Do not implement, even partially.
Compact form of SECURITY.md:30-43. This is the "what can I honestly promise?" cheat sheet.
| Threat | Protected? | By what mechanism | Where documented |
|---|---|---|---|
Offline file access (stolen device, dumped backup, raw diary.db) | YES | AES-256-GCM per field, fresh random 12-byte nonce from OsRng per write | SECURITY.md:32, crypto/cipher.rs:70-74 |
| Password guessing at rest | YES | Argon2id m=64 MiB, t=3, p=4 (exceeds OWASP 2023 minimums) | SECURITY.md:33, crypto/password.rs:8-10 |
| Weak-credential key-file attack | YES | X25519 ECDH + HKDF-SHA256 — strength = 256-bit curve regardless of file location/name | SECURITY.md:34, auth/keypair.rs:8 |
| Multi-credential compromise (revoke one, others unaffected) | YES | Each auth method wraps its own copy of the master key in auth_slots | SECURITY.md:35 |
| In-memory key leakage after lock | YES | Lock drops DB connection, clears keys; SecretBytes/Key use ZeroizeOnDrop; explicit .zeroize() on success and error paths | SECURITY.md:36, crates/mini-diarium-crypto/src/auth/mod.rs:21-53, crypto/cipher.rs:14-22, crypto/password.rs:60,103 |
| Running session on the device (unlocked) | NO | Out of scope — content lives in process memory; mitigated by locking | SECURITY.md:40 |
| Malicious software on the device (keylogger, screen-cap, ptrace) | NO | Out of scope — protect the device first | SECURITY.md:41 |
| Cloud-sync provider compromise of the DB file alone | PARTIAL | Sees only ciphertext. If the same provider also holds the key file or config.json with auto_key, the journal is compromised. | SECURITY.md:42, docs/decisions/2026-04-passwordless-journal.md |
| Coercion / credential theft from an unlocked device | NO | Out of scope — encryption cannot defend against an attacker who can observe or compel | SECURITY.md:43 |
| Invariant | Value / shape | File:line | What breaks if changed |
|---|---|---|---|
| AES-GCM key size | 32 bytes (AES-256) | crates/mini-diarium-crypto/src/crypto/cipher.rs:9 | Incompatible with all existing ciphertext |
| AES-GCM nonce size | 12 bytes | crates/mini-diarium-crypto/src/crypto/cipher.rs:12 | AES-GCM spec violation; existing blobs unparseable |
| AES-GCM nonce generation | Random per encrypt via OsRng.fill_bytes | crates/mini-diarium-crypto/src/crypto/cipher.rs:70-74 | Nonce reuse → catastrophic keystream-XOR leak |
| Ciphertext blob layout | [nonce(12) ‖ ciphertext ‖ tag(16)] | crates/mini-diarium-crypto/src/crypto/cipher.rs:83-104 | All existing entries unrecoverable |
| Argon2id memory | 65536 KiB (64 MB) | crates/mini-diarium-crypto/src/crypto/password.rs:8 | Weakening = faster offline crack; strengthening = slow unlock on older hardware. Either way: schema migration + CHANGELOG Security entry. |
| Argon2id iterations | 3 | crates/mini-diarium-crypto/src/crypto/password.rs:9 | Same — migration class change |
| Argon2id parallelism | 4 | crates/mini-diarium-crypto/src/crypto/password.rs:10 | Same — migration class change |
| X25519 HKDF info string | b"mini-diarium-v1" (constant) | crates/mini-diarium-crypto/src/auth/keypair.rs:8 | All existing keypair slots unrecoverable |
| Wrapped-key blob (keypair) | [eph_pub(32) ‖ nonce(12) ‖ ciphertext(32) ‖ tag(16)] = 92 bytes for a 32-byte master key | crates/mini-diarium-crypto/src/auth/keypair.rs:12-14 | Existing keypair slots unparseable |
SecretBytes | #[derive(ZeroizeOnDrop)]; Debug shows SecretBytes([REDACTED; N]) | crates/mini-diarium-crypto/src/auth/mod.rs:21-53 | Keys linger in heap; Debug would leak bytes |
Key struct | #[derive(Zeroize, ZeroizeOnDrop)]; Debug shows [REDACTED] | crates/mini-diarium-crypto/src/crypto/cipher.rs:14-22 | Same leak class |
| Master-key generation | 32 random bytes from aes_gcm::aead::OsRng.fill_bytes, raw bytes zeroized after wrap | crates/mini-diarium-core/src/db/schema/create.rs | Predictable / reused master key would be catastrophic |
| Schema version | pub const SCHEMA_VERSION: i32 (current value in source) | crates/mini-diarium-core/src/db/schema/mod.rs | Bump on every schema change; each bump must decide min_reader_version (db/schema/compat.rs) |
| Max import file size | 100 MB (MAX_IMPORT_FILE_SIZE) | src-tauri/src/commands/import.rs:5 | Too high → memory DoS; too low → legitimate imports fail |
| Max text file read | 1 MiB (MAX_TEXT_FILE_BYTES) | src-tauri/src/commands/files.rs:19 | Same DoS class |
| Backup retention policy | tiered: 10 recent + 1/day × 14 d + 1/week × 8 w + 1/month × 12 mo; storage budget max(2 GiB, 3 × journal size) | crates/mini-diarium-core/src/backup/policy.rs:25-37 | Disk-use guarantee, not a crypto invariant; changing it is a user-facing policy change |
Rule for any change to a row in this table:
SCHEMA_VERSION bump in db/schema/mod.rs.### Security section in the next CHANGELOG entry, plus user-facing notes.SECURITY.md update if the threat model shifts.A random 256-bit master key is generated at journal creation (db/schema/create.rs). It encrypts every entry field with AES-256-GCM. The master key itself is never stored in plaintext — it is wrapped per registered auth slot in the auth_slots table (schema v3+). See SECURITY.md:47-69.
Three wrap methods exist:
| Method | File | Wrap mechanism | Stores |
|---|---|---|---|
PasswordMethod | crates/mini-diarium-crypto/src/auth/password.rs | Argon2id-derived key + AES-256-GCM | PHC hash + AES-GCM blob |
KeypairMethod | crates/mini-diarium-crypto/src/auth/keypair.rs | X25519 ECIES + HKDF-SHA256 + AES-256-GCM | X25519 public key + 92-byte ECIES blob |
AutoKeyMethod | crates/mini-diarium-crypto/src/auth/auto_key.rs | 32-byte device-bound random key + AES-256-GCM (no KDF — already 32 bytes of entropy) | Wrapping key hex lives in config.json, not in the DB |
Hard rules:
change_password is O(1): re-wraps the master key only. Never re-encrypt entries on password change — that conversation always ends in a production bug. See SECURITY.md:61.remove_auth_method refuses to delete the last slot. That guard lives at src-tauri/src/commands/auth/auth_methods.rs:197+. Never remove or weaken it. Removing the only auth method = unrecoverable journal.require_all_auth (per journal) forces unlock_diary_all_methods. Single-method unlock paths must keep the guard. The guard lives in commands/auth/auth_core.rs:80-90 and again at :136-146 (one per single-method unlock path). When adding a new single-method unlock path, add the guard there too.AutoKeyMethod) is accepted per docs/decisions/2026-04-passwordless-journal.md. Trade-off: OS-account compromise = journal compromise. The UI surfaces this via an explicit acknowledgement checkbox in PasswordCreation.tsx. The auto_key hex must stay in config.json — moving it into the DB creates a decryption circularity (you would need the key to decrypt the key).SecretBytes (crates/mini-diarium-crypto/src/auth/mod.rs:21-53) so they zeroize on drop even if the caller forgets..zeroize() on derived wrapping keys. See crypto/password.rs:60 (success) and :103 (error).Auto-lock is a triple-path mechanism. A change to one path is implicitly a change to all three — the agent must verify the other two still work.
src/App.tsx:20-55)mousemove, keydown, click, touchstart, scroll (App.tsx:22).autoLockTimeout seconds of inactivity, calls lockJournal() (App.tsx:42).onCleanup removes listeners and clears the timer (App.tsx:48-54).autoLockEnabled + autoLockTimeout preferences.src-tauri/src/screen_lock.rs)WM_WTSSESSION_CHANGE (lock/logoff) + WM_POWERBROADCAST (suspend) via Win32 subclass (screen_lock.rs:1-100).com.apple.screenIsLocked notifications.commands::auth::auto_lock_diary_if_unlocked(...) (screen_lock.rs:79, :208) and emit 'journal-locked'.src/lib/focus-lock.ts + src-tauri/src/window_focus.rs, wired in App.tsx)visibilitychange — the original design, disproven by manual testing (TODO-0068): WebView2 does not reliably update document.visibilityState on window minimize (confirmed with the CDP debugger fully detached, ruling out an automation-tooling confound).window_focus.rs): on_window_event on WindowEvent::Focused, emitting "window-unfocused" (false) / "window-focused" (true). A single push-based event pair covers minimize, Alt+Tab/Cmd+Tab away, clicking another app's window, and (on macOS) Cmd+H "Hide" — all of these resign/regain the window's key/active status at the OS level: WM_KILLFOCUS/WM_NCACTIVATE (Windows), windowDidResignKey (macOS — its own doc comment in tao explicitly calls out the Cmd+Tab case), GTK focus-out-event (Linux, forwarded as a genuine WindowEvent unlike minimize/iconify, which GTK only tracks internally). No polling needed — this superseded an earlier is_minimized()-polling design once source inspection confirmed Focused is reliably forwarded on all three platforms.FOCUS_LOSS_DEBOUNCE_MS, focus-lock.ts, default 3000ms, parametrized as an options.debounceMs field so it's overridable in tests and easy to retune): user feedback after shipping the immediate-lock version reported that a misclick outside the window locked the journal instantly, which was too aggressive. window-unfocused now starts a setTimeout instead of locking; window-focused clears it. isDialogOpen() and isUnlocked() are (re-)checked when the timer actually fires, not at the initial event, since either can change during the debounce window.visibilitychange/blur — disproven, WebView2 doesn't fire it reliably on minimize; (2) Rust is_minimized() polling + a macOS-specific NSApplication.isHidden poll for Cmd+H — replaced because it missed alt-tab/click-away entirely, which the user confirmed was undesired (intent is "lock on any focus loss"); (3) WindowEvent::Focused(false)-only, immediate lock — replaced after the user reported it was too aggressive (locks on a misclick); (4) current: Focused(false)/Focused(true) pair + debounce.src/lib/dialog.ts wraps open/save/confirm from @tauri-apps/plugin-dialog with a shared open-dialog counter (isDialogOpen()); focus-lock.ts checks it when the debounce fires. Every dialog call site must import from dialog.ts, never the plugin directly — src/CLAUDE.md gotcha #10 — or it reopens the false-positive gap. A dialog that closes within the debounce window also self-cancels via the window-focused event when focus returns to the main window.isDialogOpen() is true (Task 3.5 of docs/archive/project-support-overlay-plan.md, 2026-08-21): the debounce check used to return outright when isDialogOpen() was true at fire time, which meant a dialog left open longer than the debounce window disabled the lock for the rest of that open period, however long. It now reschedules itself (scheduleCheck) and re-checks every debounceMs until isDialogOpen() returns false, at which point it locks if the window is still unfocused and the journal is still unlocked — the lock is delayed, not skipped. dialog.ts's openUrlSuppressingFocusLoss() (added by the same task, used only by ProjectSupportOverlay's external-link buttons) is a second, time-bounded producer of isDialogOpen() === true: it increments the same counter for a fixed EXTERNAL_HANDOFF_SUPPRESS_MS (3500ms, kept in dialog.ts and hand-synced to stay strictly greater than FOCUS_LOSS_DEBOUNCE_MS to avoid a circular import) rather than for a dialog's actual open/close lifetime, since openUrl() resolves immediately and there is no promise to await for the browser handoff itself.visibilitychange design. Not attempted; flagged as a known limitation instead.focus-lock.ts) listens for "window-unfocused"/"window-focused" via @tauri-apps/api/event's listen() and calls lockJournal() when the debounce timer fires, autoLockOnFocusLoss is enabled (preferences.ts, default false), the journal is still unlocked, and isDialogOpen() is false.createEffect only registers both listeners while enabled() && isUnlocked() (cleaning up both unlisten fns and any pending debounce timer — including handling the case where disposal races the pending listen() promise — when either goes false); window-focused cancels a pending debounce as the primary "don't lock" path; isDialogOpen()/isUnlocked() are re-checked at fire time as defense-in-depth.preferences.ts being frontend/localStorage-only.src/state/auth.ts:240-263)setupAuthEventListeners() listens for 'journal-locking' (pre-lock cleanup) and 'journal-locked' (post-lock state reset).App.tsx on mount.Checklist for any change here:
App.tsx, did backend OS lock (Path B) and the focus-loss lock (Path C) still result in a clean UI lock?screen_lock.rs, did the idle-timer path (Path A) and the focus-loss lock (Path C) still call lockJournal cleanly?focus-lock.ts, did the idle-timer path (Path A) and backend OS lock (Path B) still fire independently?'journal-locked' event still propagate?open/save/confirm from src/lib/dialog.ts (not @tauri-apps/plugin-dialog directly)? A direct import reopens the false-positive-lock gap Path C's dialog guard exists to close.invoke() failure that reaches user-visible UI must go through mapTauriError(err, t) from src/lib/errors.ts:13-51.os error N), rusqlite/sqlite/argon2 internals."file is too large", wrong-password messages — see the regex matches at errors.ts:17-32).t: module-level state (e.g., src/state/auth.ts) where no i18n context is available — mapTauriError falls back to defaultT (English) per the JSDoc at errors.ts:7-12. Those errors go to an error signal, not raw display.Red flag patterns to grep for during review:
catch { setError(err.toString()) }
catch { setError(err.message) }
catch (err) { setError(String(err)) }Any of these without mapTauriError is a bug. Fix at the call site.
Useful grep:
invoke\(|from '@tauri-apps/api'For each match, confirm the surrounding catch block routes through mapTauriError. See src/CLAUDE.md "Error Handling" for the canonical pattern.
| Surface | Encrypted? | Allowed contents | Forbidden contents |
|---|---|---|---|
diary.db | YES — per-field AES-256-GCM | Encrypted entry title/body, auth_slots (wrapped master key copies), db_settings (schema v6: require_all_auth + HKDF-SHA256 MAC), schema metadata | Plaintext entry content; FTS index (removed in schema v4); plaintext title/body |
config.json | NO — plaintext by design | Journal metadata (id, name, path), optional auto_key hex (32-byte random wrapping key), active_journal_id | Entry content; passwords; master key (plaintext); Argon2 PHC hashes; X25519 private keys; relative paths in journal entries (silently rejected — path-traversal guard); require_all_auth flag (moved to diary.db db_settings table in schema v6) |
backups/ | YES — direct file copies of diary.db | Up to 30 timestamped copies (backup.rs:6); inherit encryption | Anything not derived from a verbatim fs::copy of the live DB |
| Key files | N/A — owned by user | The X25519 private key (hex). Mode 0o600 on Unix; NTFS ACLs on Windows. App never keeps a copy. | App-side caching of the file or its contents. There is no "reveal key" UI — do not add one. |
| Exports (JSON / Markdown) | NO — intentionally plaintext | The portability contract from Principle 4 (PHILOSOPHY.md:67-88). UI must warn before write. | Encrypted-export formats. Don't add one without an explicit design review — conflicts with Easy In, Easy Out. |
localStorage | NO | 'preferences' (autoLockEnabled, autoLockTimeout, hideTitles, enableSpellcheck, etc.); 'theme-preference' (auto/light/dark); 'theme-overrides' (CSS token JSON). See src/CLAUDE.md Gotcha #6 for the three-key fan-out. | Entry content; passwords; master key; auth material; decrypted anything |
Debug dump (commands/debug.rs) | NO — written to user-chosen path | Entry counts, distinct-day count, word totals, date range, plugin counts, schema version, OS info, build type, preferences JSON passed from frontend (debug.rs:8-43) | Entry titles or bodies; passwords; keys; auth material; filesystem paths beyond the diary directory root |
Reconciliation: auto_key in config.json vs. "no plaintext on disk"
The non-negotiable from PHILOSOPHY.md:166 is about diary content, not wrapping keys. auto_key is a 32-byte random AES wrapping key; the entries it wraps remain AES-256-GCM ciphertext on disk. The ADR (docs/decisions/2026-04-passwordless-journal.md:38-40) makes this explicit. Do not "fix" this by moving plaintext entries somewhere else, or by removing auto_key. The trade-off is documented and acknowledged in the UI.
WAL-mode caveat: SQLite is in default journal mode (DELETE), not WAL. fs::copy is therefore safe (backup.rs:22-28). A backup taken mid-write could theoretically capture a partial transaction; documented in SECURITY.md:87-88. Switching to WAL requires switching backups to the SQLite Online Backup API at the same time.
FTS was removed in v0.2.0 (schema v4) because the plaintext entries_fts table defeated encryption at rest. Search was reintroduced as an in-memory scan: each query decrypts entries (same decrypt path as export/stats), matches case- and accent-folded terms, builds HTML-escaped <mark> snippets, and discards everything. Nothing searchable is written to disk. Do not delete any of these:
| Layer | Path | Purpose |
|---|---|---|
| Rust command | src-tauri/src/commands/search.rs | SearchResult struct + search_entries (in-memory decrypt + scan) |
| TS wrapper | src/lib/tauri/search.ts | SearchResult interface + searchEntries(query) |
| State | src/state/search.ts | searchQuery, searchResults, isSearching signals |
| UI | src/components/search/SearchOverlay.tsx (+ SearchBar.tsx, SearchResults.tsx) | Palette-style dialog mounted in MainLayout |
| Reindex anchors | // Search index hook: comments in crates/mini-diarium-core/src/db/queries/entries.rs (insert/update/delete) and src-tauri/src/commands/import.rs (bulk) | Unused today (in-memory scan needs no index); mark where a future encrypted index would plug in |
Constraints for any future implementation (non-negotiable):
SCHEMA_VERSION in db/schema/mod.rs, add a migration step, and decide min_reader_version (db/schema/compat.rs).// Search index hook: site must call into the new module.SearchBar/SearchResults into Sidebar.tsx or a new component; do not assume the old layout. See src-tauri/CLAUDE.md "Implementing Search" for the design constraints.{diary_dir}/plugins/*.rhai. No filesystem access, no network access, Rhai set_max_operations and other safety limits applied (plugin/rhai_loader/runtime.rs:13-15, inside create_sandboxed_engine).ImportPlugin/ExportPlugin traits (plugin/mod.rs). There is no privileged built-in path — built-ins implement the same interface as user-provided extensions (see PHILOSOPHY.md:204).unsafe impl Send for RhaiImportPlugin {} / unsafe impl Sync (and the matching pair on RhaiExportPlugin) at plugin/rhai_loader/runtime.rs:126-127,150-151 is justified because the AST is immutable after compilation and the Engine is created fresh per invocation. Adding cached mutable engines, plugin state, or shared mutable AST invalidates the justification — flag for redesign and update or remove the unsafe blocks accordingly. See src-tauri/CLAUDE.md Gotcha #7.export is a reserved keyword → export scripts must define fn format_entries(entries) (not fn export). The RhaiExportPlugin wrapper calls "format_entries" internally. See src-tauri/CLAUDE.md Gotcha #6.src-tauri/CLAUDE.md Gotcha #5.unsafe impl Send + Sync should only ever appear on Rhai wrappers. Any new unsafe impl on a plugin type requires explicit review.PHILOSOPHY.md:73). Re-import = duplicates; that is the correct behaviour.MAX_IMPORT_FILE_SIZE at src-tauri/src/commands/import.rs:5).MAX_TEXT_FILE_BYTES at src-tauri/src/commands/files.rs:19).read_file_bytes accepts only jpg, jpeg, png, gif, webp, bmp (commands/files.rs:7).read_text_file accepts only .md (commands/files.rs:27).src/lib/markdown.ts:18-21 before TipTap. Do not bypass. TipTap's own sanitization is not sufficient for untrusted markdown.PHILOSOPHY.md:67-88).commands/import.rs::import_entries (// Search index hook:) must be preserved when refactoring imports — see Section 9.src-tauri/tauri.conf.json:24)Current CSP:
default-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost; worker-src 'none'; child-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'none'Any broadening — external script-src, wildcard origins, 'unsafe-eval', remote connect-src — is a breaking security change. Requires non-negotiable review and a ### Security CHANGELOG entry.
dangerousDisableAssetCspModification: ["style-src"] (tauri.conf.json:25)This flag is intentional. Do not remove because the name looks scary.
Reason: Tauri injects a runtime nonce into every CSP directive. When a nonce is present in style-src, browsers ignore 'unsafe-inline' per the CSP spec. This silently breaks TipTap's inline style="text-align: X" node-attribute rendering — alignment looks broken in production builds with no console error in dev. The flag disables nonce injection only for style-src; script-src remains nonce-protected.
See src/CLAUDE.md Gotcha #7 (issue #63). Do not remove or restructure without testing alignment in a production build (dev mode masks the bug).
src-tauri/capabilities/default.json)Current allowlist:
core:default
core:window:allow-close
opener:default
dialog:default
window-state:defaultNo network, no unrestricted fs, no shell, no updater. Adding any of:
http:*fs:allow-* without a tight scopeshell:*updater:*…is a security decision. Justify in the PR description and add to SECURITY.md Operational Security.
MINI_DIARIUM_E2E, MINI_DIARIUM_APP_DIR, MINI_DIARIUM_DATA_DIR are read only in src-tauri/src/lib.rs (lines 40, 54, 92, 113) at setup time. Verified by grep -rn 'MINI_DIARIUM_E2E\|MINI_DIARIUM_APP_DIR\|MINI_DIARIUM_DATA_DIR' src-tauri/src/ returning matches in lib.rs only.
Never read these env vars in commands or business logic. Doing so leaks test-only escape hatches into production builds (anyone setting the env var bypasses the protection). Any new reference outside lib.rs is a bug — move it back to lib.rs setup and pass the result through State<> if needed.
DiaryState = Mutex<Option<DatabaseConnection>>. None = locked, Some(db) = unlocked. See src-tauri/CLAUDE.md "Security Rules".let db = db_state.as_ref().ok_or("Diary not unlocked")?;errors.ts:23 journal (must be|is not) unlocked — so mapTauriError can route it to errors.journalNotUnlocked.)with_unlocked_db (canonical errors: "Journal state lock failed" / "Journal must be unlocked"). For commands that must open-code the preamble, use .map_err(|_| "Journal state lock failed".to_string()). Do not use .unwrap() on a Mutex — that converts a poisoned lock into a process abort.db/schema/migrations/) must be idempotent and wrapped in a transaction. Historical v3→v4 and v4→v5 followed this; new migrations must too. Bump SCHEMA_VERSION (db/schema/mod.rs) and document the migration step inline. Every bump must also decide the forward-compatibility marker db_settings.min_reader_version (set it in both the migration step and create_schema, or delete it) and update the tripwire in db/schema/compat.rs — see the checklist there and docs/decisions/2026-09-schema-forward-compatibility.md. Any new open path must call the compat guard before its first write.src-tauri/src/commands/mod.rs (module) and lib.rs generate_handler![]. Missing either causes silent failure or compile error. Add the typed wrapper in the matching command-category sub-file under src/lib/tauri/. See src-tauri/CLAUDE.md "Adding a New Tauri Command".unsafe blocks outside crypto crates appear in four places: screen_lock.rs (Win32 subclass / WTS APIs), the two Rhai wrapper Send + Sync impls in rhai_loader/runtime.rs:126-151, and the two network-isolation platform handlers in lib.rs — install_webresource_requested_handler (Windows COM, lib.rs:373+) and install_content_rule_list (macOS ObjC2, lib.rs:448+). Each unsafe block has a // SAFETY: comment justifying it. Any new unsafe block elsewhere requires explicit security review and a SAFETY: block matching that pattern.Run these before merging anything that touched a Section 1 surface. A surprising result is usually a bug.
| Goal | Command |
|---|---|
| Network crates snuck into Cargo.toml | grep -nE 'reqwest|hyper|socket2|ureq' src-tauri/Cargo.toml crates/mini-diarium-core/Cargo.toml |
| Raw error display without sanitization (frontend) | grep -rnE "setError\((err\.toString\(\)|err\.message|String\(err\))" src/ |
invoke() call sites that may need mapTauriError review | grep -rn "invoke(" src/lib/tauri/ and audit downstream callers |
MINI_DIARIUM_E2E* env vars leaking outside lib.rs | grep -rn "MINI_DIARIUM_E2E|MINI_DIARIUM_APP_DIR|MINI_DIARIUM_DATA_DIR" src-tauri/src/ (must show lib.rs only) |
unwrap() on the diary mutex (Mutex-poison panic risk) | grep -rn "state.db.lock().unwrap" src-tauri/src/ |
New unsafe impl blocks | git grep -n "unsafe impl" -- src-tauri/src crates (expect: 4 in plugin/rhai_loader/runtime.rs — Send/Sync for RhaiImportPlugin and RhaiExportPlugin; zero in lib.rs — the platform handlers use unsafe {} blocks, not unsafe impl) |
| New file-read commands without allowlist | grep -rn "std::fs::read" src-tauri/src/commands/ and confirm extension/size guards |
| Plaintext logging of secret material | grep -rnE "info!|debug!|println!|eprintln!" crates/mini-diarium-core/src/auth/ crates/mini-diarium-crypto/src/auth/ crates/mini-diarium-crypto/src/crypto/ and confirm no secrets in format args |
| FTS reintroduction | grep -rn "fts5|entries_fts" src-tauri/src/ (must return empty) |
Run through this list when modifying anything in Section 1's load-trigger surfaces.
SecretBytes are logged, printed, serialized into telemetry, or copied into an unzeroized buffer? Fix before merging.invoke() call? Is its error piped through mapTauriError(err, t)?### Security + SECURITY.md review.localStorage if it's sensitive?db_state.as_ref().ok_or("Diary not unlocked")? where applicable? Does it handle Mutex poisoning without panicking? Is it registered in both commands/mod.rs and generate_handler![]?SCHEMA_VERSION? Did I decide min_reader_version (migration step and create_schema) and update the db/schema/compat.rs tripwire?dangerousDisableAssetCspModification? Did I test text alignment in a production build (not dev)?SECURITY.md?MINI_DIARIUM_E2E* env vars outside src-tauri/src/lib.rs setup? Move the read to lib.rs.remove_auth_method? Is the last-slot guard still intact?require_all_auth enforcement? Are all single-method unlock paths still guarded?SECURITY.md before shipping.PHILOSOPHY.md:146-157)Six principle questions, plus two Mini-Diarium-specific ones. Answer all eight honestly.
PHILOSOPHY.md:11-26)PHILOSOPHY.md:28-44)PHILOSOPHY.md:47-64)PHILOSOPHY.md:67-88)PHILOSOPHY.md:92-117)PHILOSOPHY.md:121-142)SCHEMA_VERSION; write the migration step.mapTauriError coverage at every new call site.If any principle is violated without strong justification → reconsider.
Security-critical changes to the listed surfaces must keep these test suites green. If a test fails, do not delete or weaken the test — it is asserting an invariant. Fix the code instead.
| Suite | Command | What it asserts |
|---|---|---|
| Cipher round-trip + tamper | cargo test --workspace crypto::cipher | Encrypt/decrypt round-trip, ciphertext-uniqueness (per-call random nonce), tag-mismatch on tampered ciphertext, tag-mismatch on tampered nonce, wrong-key rejection (crypto/cipher.rs:148-296). |
| Password hash + verify | cargo test --workspace crypto::password | Argon2id parameter shape (m=65536, t=3, p=4 literal in PHC string), hash determinism for same salt, salt uniqueness, unicode + empty-string passwords, wrong-password rejection (crypto/password.rs:108-232). |
| Auth slots round-trip | cargo test --workspace auth | Wrap/unwrap of master key for password, keypair, and auto-key methods; last-slot guard; change_password re-wraps without entry re-encryption. |
| File-read allowlists | cargo test --workspace files | read_file_bytes rejects non-image extensions; read_text_file rejects non-.md and oversized files (commands/files.rs:44-110). |
| Schema + migration | cargo test --workspace db::schema | Schema creation; v3→v4→v5→v6 migrations idempotent. |
| Frontend error mapping | bun run test:run -- errors | mapTauriError strips paths and OS codes; passes through user-friendly "file is too large" (see errors.ts:30-32). |
| E2E unlock + lock + re-unlock | bun run test:e2e:local | Critical user flow: create journal, lock, unlock again. End-to-end against the real binary. |
If a security change makes one of these assertions impossible to keep, that is a signal to stop and discuss with the user — not to delete the test. The benchmarks in src-tauri/benches/cipher_bench.rs are also a useful sanity check that performance hasn't degraded after a crypto-parameter change.
Compact pointers — do not duplicate content from these.
PHILOSOPHY.md — six principles + non-negotiables (authoritative)SECURITY.md — threat model, crypto architecture, known limitations (authoritative)docs/decisions/2026-04-passwordless-journal.md — Option B-prime rationale; threat-model shift; future migration to OS keychainCLAUDE.md (root) — command registry, cross-cutting conventions, dual-path auto-lock note (Gotcha #3)src-tauri/CLAUDE.md — backend security rules; Tauri command pattern; FTS removal (Gotcha #1); auth slots (Gotcha #4); plugin registry init (Gotcha #5); Rhai reserved keyword (Gotcha #6); unsafe impl Send + Sync justification (Gotcha #7); search-implementation constraintssrc/CLAUDE.md — frontend error handling; mapTauriError pattern; base64 images in encrypted text field (Gotcha #5); three localStorage keys (Gotcha #6); dangerousDisableAssetCspModification (Gotcha #7)docs/diagrams/unlock.mmd, docs/diagrams/architecture.svg — visual walkthroughs of unlock flow and layered architectureCHANGELOG.md — ### Security sections per release (v0.2.0 FTS removal; v0.3.0 zeroization; v0.4.19 mandatory multi-auth; etc.)© fjrevoredo, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .agents/skills/security-stance of fjrevoredo/mini-diarium.
Open the folder on GitHubat commit eed457f
Security Stance next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Security Stance this skillfjrevoredo/mini-diarium | 308 | — | ~11k | Automated safety check: Pass | MIT | |
| Extending Personhog Test HarnessPostHog/posthog | 40k | — | ~1.2k | Automated safety check: Pass | Custom licence | |
| Postgresmagnus919/agent-skills | 116 | — | ~4k | Automated safety check: Pass | MIT | |
| Position Encodingpikax/verter | 113 | — | ~4.6k | Automated safety check: Pass | MIT | |
| Local Platform E2Ecomputesdk/benchmarks | 126 | — | ~3k | Automated safety check: Notes | MIT | |
| Desktop App Driving with Reticlereticlehq/reticle | 1.2k | — | ~1.3k | Automated safety check: Pass | Apache-2.0 |
PostHog/posthog
When and how to add scenarios, chaos events, and invariants to the personhog e2e test harness (rust/personhog-test-harness).
magnus919/agent-skills
Operate PostgreSQL instances safely: configuration review, index and query-plan analysis, vacuum and bloat management, WAL archiving and point-in-time recovery, replication and failover, extensions…
pikax/verter
Position encoding, span types, coordinate systems, path normalization tables for Verter's multi-layer architecture (OXC, Rust, LSP, FFI, VS Code)
computesdk/benchmarks
Stand up benchmarks-platform locally (Postgres + MinIO + ClickHouse in docker) and run a real @benchsdk/runner benchmark against it, with no cloud or provider credentials.
reticlehq/reticle
Drives and verifies Electron or Tauri desktop apps through Reticle, which sees the renderer and the IPC calls that a browser-based testing tool cannot observe.
jerrywu001/cc-sessions-viewer
Launches a Tauri app's dev build with the MCP bridge compiled in, so an agent can take screenshots, read the live DOM, click real buttons and call real backend commands.
fjrevoredo/mini-diarium
Create, update, review, and execute manual Markdown implementation plans when harness planning mode is not being used.
fjrevoredo/mini-diarium
CRITICAL: Use for performance optimization. An agent skill from fjrevoredo/mini-diarium.
fjrevoredo/mini-diarium
SolidJS framework development skill for building reactive web applications with fine-grained reactivity.
fjrevoredo/mini-diarium
Tauri v2 cross-platform app development with Rust backend. An agent skill from fjrevoredo/mini-diarium.
fjrevoredo/mini-diarium
Enter exploration mode: a thinking partner for researching and thinking through ideas and problems before implementation.
fjrevoredo/mini-diarium
A skill your agent uses when asking about Rust code style or best practices.
Works with
Categories
Mini Diarium's opinionated security, encryption, and privacy stance. Security Stance is an agent skill from fjrevoredo/mini-diarium. Mini Diarium's opinionated security, encryption, and privacy stance.
Security Stance fits situations like: tasks that involve Backup and disaster recovery; tasks that involve React components; tasks that involve Database schema design.
Run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a claude-code`. Or copy the skill folder (.agents/skills/security-stance in fjrevoredo/mini-diarium) into .claude/skills/security-stance in your project. Claude Code loads it when a task matches its description.
Run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a codex`. Or copy the skill folder (.agents/skills/security-stance in fjrevoredo/mini-diarium) into .agents/skills/security-stance in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/security-stance, .gemini/skills/security-stance, .github/skills/security-stance and .opencode/skills/security-stance in your project.
Going by SKILL.md and its folder, Security Stance needs the command-line tools its instructions call (cargo, bun and git). Compatibility (from SKILL.md): Designed for Claude Code. Applies to Mini Diarium only..
SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Security Stance is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 44k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Security Stance: Extending Personhog Test Harness (PostHog/posthog, 40k stars), Postgres (magnus919/agent-skills, 116 stars), Position Encoding (pikax/verter, 113 stars) and Local Platform E2E (computesdk/benchmarks, 126 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
fjrevoredo (a GitHub user) maintains it in fjrevoredo/mini-diarium, which has 308 GitHub stars. The repository holds 41 skills in this directory. The repository was last updated on October 9, 2026.
Source: fjrevoredo/mini-diarium on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.