diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml
new file mode 100644
index 0000000..e5531e3
--- /dev/null
+++ b/.github/workflows/secret-scan.yml
@@ -0,0 +1,21 @@
+name: secret scan
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ scan:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-python@v7
+ with:
+ python-version: "3.12"
+ - name: Scan tracked files for private credentials
+ run: python scripts/scan_secrets.py --tracked-only
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9e79eb5..cedfc1e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v6
+ uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
diff --git a/.gitignore b/.gitignore
index f2a6be3..8066574 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,9 +15,18 @@ dist/
.env
.env.*
!.env.example
+.envrc
*.db
*.sqlite
*.sqlite3
*.log
*.bak
+*.key
+*.pem
secrets.json
+.codex/
+.claude/
+.loop_memory/
+credentials.json
+work/
+.worktrees
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..ac5fb58
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+ "recommendations": [
+ "ms-python.python",
+ "ms-python.vscode-pylance",
+ "charliermarsh.ruff",
+ "tamasfe.even-better-toml",
+ "redhat.vscode-yaml"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..23cc308
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,30 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Loop Memory: server",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "loop_memory.cli.main",
+ "args": [
+ "serve",
+ "--port",
+ "7767"
+ ],
+ "console": "integratedTerminal",
+ "justMyCode": true
+ },
+ {
+ "name": "Python: current test file",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "pytest",
+ "args": [
+ "${file}",
+ "-q"
+ ],
+ "console": "integratedTerminal",
+ "justMyCode": true
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..ac712c8
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,27 @@
+{
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
+ "python.terminal.activateEnvironment": true,
+ "python.testing.pytestEnabled": true,
+ "python.testing.pytestArgs": [
+ "tests"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.analysis.typeCheckingMode": "basic",
+ "python.analysis.inlayHints.functionReturnTypes": true,
+ "python.analysis.inlayHints.variableTypes": true,
+ "editor.formatOnSave": true,
+ "[python]": {
+ "editor.defaultFormatter": "charliermarsh.ruff",
+ "editor.codeActionsOnSave": {
+ "source.fixAll.ruff": "explicit",
+ "source.organizeImports.ruff": "explicit"
+ }
+ },
+ "ruff.nativeServer": "on",
+ "files.exclude": {
+ "**/__pycache__": true,
+ "**/.pytest_cache": true,
+ "**/.mypy_cache": true,
+ "**/.ruff_cache": true
+ }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..5e21fe2
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,44 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Python: tests",
+ "type": "shell",
+ "command": "${workspaceFolder}/.venv/bin/pytest",
+ "args": [
+ "-q"
+ ],
+ "group": {
+ "kind": "test",
+ "isDefault": true
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Python: Ruff check",
+ "type": "shell",
+ "command": "${workspaceFolder}/.venv/bin/ruff",
+ "args": [
+ "check",
+ "loop_memory",
+ "tests"
+ ],
+ "group": "build",
+ "problemMatcher": []
+ },
+ {
+ "label": "Loop Memory: server",
+ "type": "shell",
+ "command": "${workspaceFolder}/.venv/bin/python",
+ "args": [
+ "-m",
+ "loop_memory.cli.main",
+ "serve",
+ "--port",
+ "7767"
+ ],
+ "isBackground": true,
+ "problemMatcher": []
+ }
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dcef9c..b5e693d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,192 @@
+## [Unreleased]
+
+### Settings drawer — UX pass
+- **Security section** now ships a clear "Enabled / Disabled" status line plus a
+ one-line hint explaining what a bearer token does on this machine, instead
+ of leaking the raw i18n key when the translation was missing.
+- **Processing toggles** (filter / re-score / distil / dry-run) and the
+ redaction switches (`Enable redaction`, `Process ...`)
+ use a unified card style: checkbox + title on the first row, description on
+ the second row, description text aligned with the card's left edge.
+- **Storage & compaction** card pairs the interval input with a compact
+ "Last compressed" status card of matching height so the two read as one
+ row.
+- **Manual actions** section explains the difference between `Run now` (writes)
+ and `Preview` (dry-run) inline.
+- All new strings are localised in `loop_memory/serve/static/i18n/{en,zh}.json`
+ so the English and Chinese drawers stay in lockstep.
+- Docs: new `docs/settings.md` is the canonical reference for every drawer
+ control; `docs/auto-capture.md` now lists the real launchd labels
+ (`com.loopmemory.codex`, `com.loopmemory.claude`, `com.loopmemory.openclaw`)
+ and the `kickstart -k` / log commands for the Claude watcher.
+
+### Visual layout regression fix
+Restored the pre-`security-fix` visual layout of the Dashboard, LLM audit,
+Settings subsections, and the four top-level tabs (timeline / dashboard /
+ wiki / graph). The four `