memory-boost 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. memory_boost/__init__.py +3 -0
  2. memory_boost/cli.py +389 -0
  3. memory_boost/core.py +681 -0
  4. memory_boost/drift.py +145 -0
  5. memory_boost/example_transcripts/-work-acme-api/0a1b2c3d-1111-4aaa-8bbb-000000000001.jsonl +136 -0
  6. memory_boost/example_transcripts/-work-acme-api/0a1b2c3d-3333-4aaa-8bbb-000000000003.jsonl +46 -0
  7. memory_boost/example_transcripts/-work-acme-api/0a1b2c3d-4444-4aaa-8bbb-000000000004.jsonl +10 -0
  8. memory_boost/example_transcripts/-work-pixel-notes/0a1b2c3d-2222-4aaa-8bbb-000000000002.jsonl +54 -0
  9. memory_boost/example_wiki/concepts/money-as-integer-cents.md +15 -0
  10. memory_boost/example_wiki/concepts/retry-jobs-idempotently.md +16 -0
  11. memory_boost/example_wiki/context/preferences.md +7 -0
  12. memory_boost/example_wiki/decisions/cors-allow-all.md +9 -0
  13. memory_boost/example_wiki/decisions/float-money.md +9 -0
  14. memory_boost/example_wiki/decisions/pin-postgres-15.md +14 -0
  15. memory_boost/example_wiki/decisions/queue-in-postgres.md +15 -0
  16. memory_boost/example_wiki/decisions/sessions-in-redis.md +9 -0
  17. memory_boost/example_wiki/log.md +37 -0
  18. memory_boost/example_wiki/projects/acme-api.md +24 -0
  19. memory_boost/example_wiki/projects/legacy-dashboard.md +13 -0
  20. memory_boost/example_wiki/projects/pixel-notes.md +9 -0
  21. memory_boost/lessons.py +77 -0
  22. memory_boost/mine.py +172 -0
  23. memory_boost/server.py +102 -0
  24. memory_boost-0.1.0.dist-info/METADATA +229 -0
  25. memory_boost-0.1.0.dist-info/RECORD +28 -0
  26. memory_boost-0.1.0.dist-info/WHEEL +4 -0
  27. memory_boost-0.1.0.dist-info/entry_points.txt +2 -0
  28. memory_boost-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,54 @@
1
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:00:35Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0000", "name": "Read", "input": {"file_path": "/work/pixel-notes/app/sync.ts"}}]}}
2
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:00:38Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0000", "content": "(synthetic output)"}]}}
3
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:01:16Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0001", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
4
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:01:17Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0001", "content": "(synthetic output)"}]}}
5
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:01:35Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0002", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
6
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:01:44Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0002", "content": "(synthetic output)"}]}}
7
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:02:12Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0003", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
8
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:02:15Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0003", "content": "(synthetic output)"}]}}
9
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:02:54Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0004", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
10
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:02:55Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0004", "content": "(synthetic output)"}]}}
11
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:03:33Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0005", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
12
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:03:38Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0005", "content": "(synthetic output)"}]}}
13
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:03:48Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0006", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
14
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:03:53Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0006", "content": "(synthetic output)"}]}}
15
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:04:31Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0007", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
16
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:04:37Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0007", "content": "(synthetic output)"}]}}
17
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:04:52Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0008", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
18
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:04:58Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0008", "content": "(synthetic output)"}]}}
19
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:05:17Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0009", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
20
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:05:26Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0009", "content": "(synthetic output)"}]}}
21
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:06:05Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0010", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
22
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:06:14Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0010", "content": "(synthetic output)"}]}}
23
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:06:40Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0011", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
24
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:06:44Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0011", "content": "(synthetic output)"}]}}
25
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:07:01Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0012", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
26
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:07:05Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0012", "content": "(synthetic output)"}]}}
27
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:07:35Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0013", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
28
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:07:39Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0013", "content": "(synthetic output)"}]}}
29
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:07:56Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0014", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
30
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:08:05Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0014", "content": "(synthetic output)"}]}}
31
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:08:41Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0015", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
32
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:08:47Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0015", "content": "(synthetic output)"}]}}
33
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:08:53Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0016", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
34
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:08:54Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0016", "content": "(synthetic output)"}]}}
35
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:09:16Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0017", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
36
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:09:24Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0017", "content": "(synthetic output)"}]}}
37
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:09:45Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0018", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
38
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:09:49Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0018", "content": "(synthetic output)"}]}}
39
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:10:16Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0019", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
40
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:10:24Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0019", "content": "(synthetic output)"}]}}
41
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:10:51Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0020", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/sync.ts", "old_string": "a", "new_string": "b"}}]}}
42
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:10:57Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0020", "content": "(synthetic output)"}]}}
43
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:11:07Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0021", "name": "Bash", "input": {"command": "npm test -- --run"}}]}}
44
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:11:11Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0021", "content": "(synthetic output)"}]}}
45
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:11:22Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0022", "name": "mcp__memory__memory_checkpoint", "input": {"project": "acme-api", "session_id": "x", "task": "t"}}]}}
46
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:11:26Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0022", "content": "(synthetic output)"}]}}
47
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:12:01Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0023", "name": "Edit", "input": {"file_path": "/work/pixel-notes/app/store.ts", "old_string": "a", "new_string": "b"}}]}}
48
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:12:05Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0023", "content": "(synthetic output)"}]}}
49
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:12:31Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0024", "name": "Bash", "input": {"command": "npm run build"}}]}}
50
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:12:35Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0024", "content": "(synthetic output)"}]}}
51
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:13:10Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0025", "name": "mcp__memory__memory_checkpoint", "input": {"project": "acme-api", "session_id": "x", "task": "t"}}]}}
52
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:13:20Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0025", "content": "(synthetic output)"}]}}
53
+ {"type": "assistant", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:13:25Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "(synthetic)"}, {"type": "tool_use", "id": "toolu_0026", "name": "mcp__memory__memory_save", "input": {"project": "acme-api", "action": "a"}}]}}
54
+ {"type": "user", "cwd": "/work/pixel-notes", "sessionId": "0a1b2c3d-2222-4aaa-8bbb-000000000002", "timestamp": "2026-09-10T15:13:33Z", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_0026", "content": "(synthetic output)"}]}}
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Store money as integer minor units, never float
3
+ created: 2026-01-12
4
+ applies_when: [payments, invoices, postgres, typescript]
5
+ learned_on: acme-api
6
+ ---
7
+ # Store money as integer minor units, never float
8
+
9
+ Sum 0.1 + 0.2 in a float column and an invoice is off by a cent. Store amounts as integers in
10
+ the smallest unit (`amount_cents BIGINT`) and format at the edge. Decimal types work too but the
11
+ integer rule survives every language and serializer.
12
+
13
+ ## When it bit us
14
+
15
+ - acme-api, 2026-01-12: rounding drift on invoice totals; fixed by migrating to `amount_cents`.
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: Make background jobs idempotent before adding retries
3
+ created: 2026-08-30
4
+ applies_when: [background-jobs, offline-sync, queues]
5
+ learned_on: pixel-notes
6
+ ---
7
+ # Make background jobs idempotent before adding retries
8
+
9
+ Retrying a non-idempotent job duplicated notes on every flaky sync. Give each job a stable key
10
+ (entity id + version) and make the handler a no-op when the key was already applied; only then
11
+ add retries with backoff. Tools that helped: a `--replay <job-id>` CLI flag and a test that runs
12
+ the same job twice and asserts one side effect.
13
+
14
+ ## When it bit us
15
+
16
+ - pixel-notes, 2026-08-30: offline edits synced twice after a timeout, users saw duplicate notes.
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Working preferences
3
+ ---
4
+ # Working preferences
5
+
6
+ - Minimal diffs; no speculative abstractions.
7
+ - Tests run with `pytest -q --tb=short`.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Allow all CORS origins in staging, for now
3
+ created: 2026-04-01
4
+ tags: [acme-api, infra]
5
+ ---
6
+ # Allow all CORS origins in staging, for now
7
+
8
+ The mobile preview builds use random hostnames. `allow_origins=["*"]` in staging until we have a
9
+ fixed preview domain. Temporary.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Store money as float
3
+ status: superseded
4
+ superseded_by: integer-cents
5
+ created: 2026-01-12
6
+ ---
7
+ # Store money as float
8
+
9
+ Superseded by integer cents (see acme-api gotchas).
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Pin PostgreSQL 15 until the pgvector upgrade is tested
3
+ created: 2026-02-20
4
+ review_by: 2026-06-01
5
+ tags: [acme-api, postgres, infra]
6
+ ---
7
+ # Pin PostgreSQL 15 until the pgvector upgrade is tested
8
+
9
+ PostgreSQL 16 changed the default collation provider; our ORDER BY tests broke. Pin 15 in the
10
+ compose file and revisit once the pgvector image for 16 is tested in staging.
11
+
12
+ ## Revisit
13
+
14
+ - Review by 2026-06-01: is the 16 image tested? If yes, unpin and delete this decision.
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Job queue in PostgreSQL
3
+ status: current
4
+ created: 2026-03-04
5
+ review_by: 2027-03-01
6
+ ---
7
+ # Job queue in PostgreSQL
8
+
9
+ ## Decision
10
+
11
+ acme-api keeps its job queue in PostgreSQL (`SKIP LOCKED`) instead of adding Redis.
12
+
13
+ ## Why
14
+
15
+ One less service to run; throughput needed is below 50 jobs/s.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Keep user sessions in Redis
3
+ status: superseded
4
+ created: 2026-02-02
5
+ tags: [acme-api]
6
+ ---
7
+ # Keep user sessions in Redis
8
+
9
+ Superseded in April. By what, nobody wrote down.
@@ -0,0 +1,37 @@
1
+ # Log
2
+
3
+ ## [2026-03-12] claude-code | legacy-dashboard | Bumped jQuery from 1.12 to 3.7
4
+ - Two charts stopped rendering; reverted one of them.
5
+ - Pending: the rewrite.
6
+
7
+ ## [2026-03-14] claude-code | legacy-dashboard | Pinned Node 14 in .nvmrc
8
+ - The build only works on Node 14. Nobody knows why.
9
+
10
+ ## [2026-07-10] claude-code | pixel-notes | Offline queue for note edits
11
+ - Edits made offline are queued in SQLite and replayed on reconnect.
12
+
13
+ ## [2026-07-15] codex | pixel-notes | Fixed duplicated notes after flaky sync
14
+ - Retried jobs were creating a note each time; added a stable job key.
15
+
16
+ ## [2026-07-22] claude-code | pixel-notes | Background sync every 15 minutes
17
+ - Uses the OS background task API; iOS decides when "15 minutes" is.
18
+
19
+ ## [2026-07-29] claude-code | pixel-notes | Conflict banner when two devices edit a note
20
+
21
+ ## [2026-08-05] codex | pixel-notes | Reverted the conflict banner
22
+ - It appeared on every note opened on two devices, conflict or not.
23
+
24
+ ## [2026-08-12] claude-code | pixel-notes | Per-field merge prototype
25
+ - Pending: decide between per-field merge and CRDT.
26
+
27
+ ## [2026-08-20] claude-code | pixel-notes | Sync server moved to its own container
28
+
29
+ ## [2026-08-28] claude-code | acme-api | Added idempotency keys to POST /invoices
30
+ - Keys stored 24h in the `idempotency` table.
31
+
32
+ ## [2026-08-30] codex | pixel-notes | Fixed sync conflict on offline edits
33
+ - Last-writer-wins replaced by per-field merge.
34
+ - Pending: test on Android 15.
35
+
36
+ ## [2026-09-01] claude-code | acme-api | Moved PDF rendering to the job queue
37
+ - Pending: load test with 10k invoices.
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: acme-api
3
+ created: 2026-01-10
4
+ updated: 2026-09-01
5
+ tags: [python, fastapi, postgres, background-jobs]
6
+ ---
7
+ # acme-api
8
+
9
+ Invoice API for a fictional shop. FastAPI + PostgreSQL, deployed as a container.
10
+ Run locally with `make dev`; tests with `pytest -q`.
11
+
12
+ ## Architecture
13
+
14
+ - `app/routes/` HTTP layer, `app/billing/` pure domain logic, `app/db/` repositories.
15
+ - Background jobs use a PostgreSQL table with `SELECT ... FOR UPDATE SKIP LOCKED`, not Redis.
16
+
17
+ ## Gotchas
18
+
19
+ - The staging database is reset every Sunday: never keep fixtures there.
20
+ - Currency amounts are stored as integer cents; never read the legacy `amount_float` column.
21
+
22
+ ## History
23
+
24
+ - 2026-02: migrated from Flask to FastAPI.
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: legacy-dashboard
3
+ created: 2025-11-03
4
+ tags: [javascript, jquery, dashboards]
5
+ ---
6
+ # legacy-dashboard
7
+
8
+ Internal sales dashboard in jQuery. Everyone agrees it will be rewritten next sprint.
9
+ It has been next sprint since November.
10
+
11
+ ## Gotchas
12
+
13
+ - The build needs Node 14. Do not ask why; nobody remembers.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: pixel-notes
3
+ created: 2026-05-02
4
+ tags: [typescript, react-native, offline-sync, background-jobs]
5
+ ---
6
+ # pixel-notes
7
+
8
+ Offline-first note-taking mobile app (React Native). Sync server lives in `sync/`.
9
+ Release builds are made in CI only; local builds are debug.
@@ -0,0 +1,77 @@
1
+ """Lessons that travel between projects.
2
+
3
+ A lesson is a page in wiki/concepts/ with `applies_when: [tag, ...]` in its
4
+ frontmatter. A project page declares `tags: [...]`. brief() surfaces the lessons
5
+ whose applies_when overlaps the current project's tags and that were NOT learned
6
+ on this project, so what worked in one place reaches the next."""
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from memory_boost import core
12
+ from memory_boost.drift import parse_tags
13
+
14
+
15
+ def project_tags(project: str) -> set[str]:
16
+ p = core.wiki_dir() / "projects" / f"{core.norm_project(project)}.md"
17
+ if not p.exists():
18
+ return set()
19
+ meta, _ = core.parse_frontmatter(p.read_text(encoding="utf-8"))
20
+ return parse_tags(meta)
21
+
22
+
23
+ def all_lessons() -> list[dict]:
24
+ out = []
25
+ for p in sorted((core.wiki_dir() / "concepts").glob("*.md")):
26
+ meta, body = core.parse_frontmatter(p.read_text(encoding="utf-8"))
27
+ applies = parse_tags(meta, "applies_when")
28
+ if not applies:
29
+ continue # a concept page without applies_when is reference, not a lesson
30
+ summary = next((ln.strip() for ln in body.splitlines()
31
+ if ln.strip() and not ln.startswith("#")), "")
32
+ out.append({"name": p.stem, "title": meta.get("title", p.stem), "applies_when": applies,
33
+ "learned_on": core.norm_project(meta.get("learned_on", "")),
34
+ "date": meta.get("updated") or meta.get("created"), "summary": summary})
35
+ return out
36
+
37
+
38
+ def lessons_for(project: str, limit: int = 3) -> list[dict]:
39
+ """Lessons from other projects whose applies_when matches this project's tags,
40
+ best overlap first."""
41
+ tags = project_tags(project)
42
+ slug = core.norm_project(project)
43
+ if not tags:
44
+ return []
45
+ scored = []
46
+ for lesson in all_lessons():
47
+ overlap = lesson["applies_when"] & tags
48
+ if overlap and lesson["learned_on"] != slug:
49
+ scored.append((len(overlap), lesson["date"] or "", {**lesson, "matched": sorted(overlap)}))
50
+ scored.sort(key=lambda t: (t[0], t[1]), reverse=True)
51
+ return [lesson for _, _, lesson in scored[:limit]]
52
+
53
+
54
+ def format_lessons(lessons: list[dict]) -> list[str]:
55
+ return [f"- {lesson['title']} (from {lesson['learned_on'] or '?'}, matches "
56
+ f"{', '.join(lesson['matched'])}) — {lesson['summary'][:160]} → memory_page(\"{lesson['name']}\")"
57
+ for lesson in lessons]
58
+
59
+
60
+ def save_lesson(name: str, title: str, applies_when: list[str], body: str,
61
+ learned_on: str | None = None) -> Path:
62
+ """Write (or overwrite) wiki/concepts/<name>.md as a lesson."""
63
+ slug = core.safe_slug(name, "lesson name")
64
+ tags = sorted({core.norm_project(t) for t in applies_when if t.strip()})
65
+ if not tags:
66
+ raise ValueError("applies_when is empty: a lesson needs at least one tag to travel")
67
+ if not body.strip():
68
+ raise ValueError("body is empty")
69
+ p = core.wiki_dir() / "concepts" / f"{slug}.md"
70
+ p.parent.mkdir(parents=True, exist_ok=True)
71
+ meta = [f"title: {core._one_line(title) or slug}", f"created: {core._today()}",
72
+ f"applies_when: [{', '.join(tags)}]"]
73
+ if learned_on:
74
+ meta.append(f"learned_on: {core.safe_slug(learned_on, 'learned_on')}")
75
+ p.write_text("---\n" + "\n".join(meta) + f"\n---\n# {core._one_line(title) or slug}\n\n"
76
+ + body.strip() + "\n", encoding="utf-8")
77
+ return p
memory_boost/mine.py ADDED
@@ -0,0 +1,172 @@
1
+ """Mine local agent transcripts for how you actually work.
2
+
3
+ Reads Claude Code session files (~/.claude/projects/<cwd-slug>/<session>.jsonl)
4
+ and keeps only aggregates: tool counts, failed commands, re-reads, subagent
5
+ fan-out, checkpoint discipline. Prompt and tool *contents* are never stored
6
+ or printed, so the digest is safe to share."""
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from collections import Counter
12
+ from pathlib import Path
13
+
14
+ from memory_boost.core import _today, plural
15
+
16
+ MIN_TURNS = 20 # shorter sessions are one-shot questions, not work sessions
17
+ LONG_SESSION = 60 # from here on a checkpoint is expected
18
+ REREAD_MIN = 4 # a file read this many times in one session is worth naming
19
+
20
+
21
+ def transcript_roots() -> list[Path]:
22
+ if env := os.environ.get("MEMORY_BOOST_TRANSCRIPTS"):
23
+ return [Path(p).expanduser() for p in env.split(os.pathsep) if p]
24
+ return [Path.home() / ".claude" / "projects"]
25
+
26
+
27
+ def _empty(path: Path) -> dict:
28
+ return {"file": path.name, "project": None, "turns": 0, "first": None, "last": None,
29
+ "tools": Counter(), "bash_failed": 0, "failed_cmds": Counter(), "reads": Counter(),
30
+ "checkpoints": 0, "saves": 0, "agents": 0}
31
+
32
+
33
+ def scan_session(path: Path) -> dict:
34
+ """One JSONL transcript -> aggregate counters. Tolerates broken lines."""
35
+ s = _empty(path)
36
+ pending: dict[str, tuple[str, str]] = {} # tool_use_id -> (tool name, first word of command)
37
+ with path.open(encoding="utf-8", errors="replace") as fh:
38
+ for line in fh:
39
+ try:
40
+ ev = json.loads(line)
41
+ except json.JSONDecodeError:
42
+ continue
43
+ if not isinstance(ev, dict):
44
+ continue
45
+ if cwd := ev.get("cwd"):
46
+ s["project"] = s["project"] or Path(cwd).name
47
+ if ts := ev.get("timestamp"):
48
+ s["first"] = s["first"] or ts
49
+ s["last"] = ts
50
+ content = (ev.get("message") or {}).get("content")
51
+ if not isinstance(content, list):
52
+ continue
53
+ if ev.get("type") == "assistant":
54
+ s["turns"] += 1
55
+ for c in content:
56
+ if not isinstance(c, dict):
57
+ continue
58
+ if c.get("type") == "tool_use":
59
+ name, inp = c.get("name", "?"), c.get("input") or {}
60
+ s["tools"][name] += 1
61
+ cmd = (inp.get("command") or "").split()
62
+ pending[c.get("id", "")] = (name, cmd[0] if cmd else "?")
63
+ if name == "Read":
64
+ s["reads"][inp.get("file_path", "")] += 1
65
+ elif name == "Agent":
66
+ s["agents"] += 1
67
+ elif name.endswith("memory_checkpoint"):
68
+ s["checkpoints"] += 1
69
+ elif name.endswith("memory_save"):
70
+ s["saves"] += 1
71
+ elif c.get("type") == "tool_result" and c.get("is_error"):
72
+ name, cmd = pending.get(c.get("tool_use_id", ""), ("?", "?"))
73
+ if name == "Bash":
74
+ s["bash_failed"] += 1
75
+ s["failed_cmds"][cmd] += 1
76
+ return s
77
+
78
+
79
+ def scan(roots: list[Path] | None = None, since: str | None = None,
80
+ min_turns: int = MIN_TURNS) -> list[dict]:
81
+ """All sessions under the roots with at least min_turns assistant turns,
82
+ optionally only those started on/after `since` (YYYY-MM-DD)."""
83
+ out = []
84
+ for root in roots or transcript_roots():
85
+ for f in sorted(root.glob("*/*.jsonl")) if root.exists() else ():
86
+ s = scan_session(f)
87
+ if s["turns"] < min_turns:
88
+ continue
89
+ if since and (s["first"] or "")[:10] < since:
90
+ continue
91
+ out.append(s)
92
+ return out
93
+
94
+
95
+ def aggregate(sessions: list[dict]) -> dict:
96
+ tools: Counter = Counter()
97
+ failed: Counter = Counter()
98
+ rereads: Counter = Counter()
99
+ per_project: dict[str, dict] = {}
100
+ reread_total = 0
101
+ for s in sessions:
102
+ tools.update(s["tools"])
103
+ failed.update(s["failed_cmds"])
104
+ for f, n in s["reads"].items():
105
+ if n > 1:
106
+ reread_total += n - 1
107
+ if n >= REREAD_MIN:
108
+ rereads[f] += n
109
+ p = per_project.setdefault(s["project"] or "?", {"sessions": 0, "turns": 0, "bash_failed": 0})
110
+ p["sessions"] += 1
111
+ p["turns"] += s["turns"]
112
+ p["bash_failed"] += s["bash_failed"]
113
+ long = [s for s in sessions if s["turns"] >= LONG_SESSION]
114
+ edits = tools["Edit"] + tools["Write"]
115
+ extremes = sorted(((s["tools"]["Bash"] / max(s["tools"]["Edit"] + s["tools"]["Write"], 1),
116
+ s["turns"], s["project"] or "?", s["file"][:8]) for s in sessions), reverse=True)
117
+ return {
118
+ "sessions": len(sessions),
119
+ "tools": dict(tools.most_common(10)),
120
+ "bash": {"calls": tools["Bash"], "failed": sum(s["bash_failed"] for s in sessions),
121
+ "top_failed_cmds": dict(failed.most_common(5))},
122
+ "bash_per_edit": round(tools["Bash"] / max(edits, 1), 1),
123
+ "extreme_sessions": [{"ratio": round(r, 1), "turns": t, "project": p, "session": f}
124
+ for r, t, p, f in extremes[:3] if r >= 20],
125
+ "reads": {"calls": tools["Read"], "rereads": reread_total,
126
+ "most_reread": [{"file": _short(f), "times": n} for f, n in rereads.most_common(3)]},
127
+ "long_sessions": {"count": len(long),
128
+ "without_checkpoint": sum(s["checkpoints"] == 0 for s in long),
129
+ "without_save": sum(s["saves"] == 0 for s in long)},
130
+ "subagents": {"sessions_using": sum(s["agents"] > 0 for s in sessions),
131
+ "total": sum(s["agents"] for s in sessions),
132
+ "max_in_one_session": max((s["agents"] for s in sessions), default=0)},
133
+ "projects": dict(sorted(per_project.items(), key=lambda kv: -kv[1]["turns"])[:8]),
134
+ }
135
+
136
+
137
+ def _short(path: str) -> str:
138
+ home = str(Path.home())
139
+ return path.replace(home, "~") if path.startswith(home) else path
140
+
141
+
142
+ def _tool_name(tool: str) -> str:
143
+ """mcp__memory__memory_save -> memory_save: the server prefix is noise in a digest."""
144
+ return tool.split("__", 2)[-1] if tool.startswith("mcp__") else tool
145
+
146
+
147
+ def format_digest(agg: dict, since: str | None = None) -> str:
148
+ if not agg["sessions"]:
149
+ return f"No sessions found (need ≥{MIN_TURNS} assistant turns). Set MEMORY_BOOST_TRANSCRIPTS?\n"
150
+ b, r, ls, sa = agg["bash"], agg["reads"], agg["long_sessions"], agg["subagents"]
151
+ pct = 100 * b["failed"] / max(b["calls"], 1)
152
+ lines = [f"# Session digest{' since ' + since if since else ''} — {_today()}",
153
+ f"sessions {agg['sessions']} with ≥{MIN_TURNS} turns",
154
+ "tools " + " · ".join(f"{_tool_name(k)} {v}" for k, v in list(agg["tools"].items())[:6]),
155
+ f"bash {b['calls']} calls, {b['failed']} failed ({pct:.1f}%)"
156
+ + ("; most failed: " + ", ".join(f"{k} ({v})" for k, v in b["top_failed_cmds"].items())
157
+ if b["top_failed_cmds"] else ""),
158
+ f"bash/edit {agg['bash_per_edit']}x"
159
+ + ("; extreme: " + ", ".join(f"{e['project']} {e['ratio']}x" for e in agg["extreme_sessions"])
160
+ if agg["extreme_sessions"] else ""),
161
+ f"reads {r['calls']} calls, {r['rereads']} re-reads of a file already read "
162
+ f"({100 * r['rereads'] / max(r['calls'], 1):.0f}%)"
163
+ + ("; top: " + ", ".join(f"{m['file']} ×{m['times']}" for m in r["most_reread"])
164
+ if r["most_reread"] else ""),
165
+ f"discipline {plural(ls['count'], 'long session')} (≥{LONG_SESSION} turns): "
166
+ f"{ls['without_checkpoint']} never checkpointed, {ls['without_save']} never saved",
167
+ f"subagents {sa['total']} spawned in {plural(sa['sessions_using'], 'session')}; "
168
+ f"max in one session: {sa['max_in_one_session']}",
169
+ "projects " + ", ".join(f"{p} ({v['sessions']}s/{v['turns']}t)"
170
+ for p, v in agg["projects"].items()),
171
+ "next: memory-boost lesson --help # turn one line above into a lesson"]
172
+ return "\n".join(lines) + "\n"