@lh8ppl/claude-memory-kit 0.1.0

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 (81) hide show
  1. package/bin/cmk-compress-lazy.mjs +59 -0
  2. package/bin/cmk-daily-distill.mjs +67 -0
  3. package/bin/cmk-weekly-curate.mjs +56 -0
  4. package/bin/cmk.mjs +12 -0
  5. package/package.json +50 -0
  6. package/src/audit-log.mjs +103 -0
  7. package/src/auto-extract.mjs +742 -0
  8. package/src/capture-prompt.mjs +61 -0
  9. package/src/capture-turn.mjs +273 -0
  10. package/src/claude-md.mjs +212 -0
  11. package/src/compress-session.mjs +349 -0
  12. package/src/compressor.mjs +376 -0
  13. package/src/conflict-queue.mjs +796 -0
  14. package/src/cooldown.mjs +61 -0
  15. package/src/daily-distill.mjs +252 -0
  16. package/src/doctor.mjs +528 -0
  17. package/src/forget.mjs +335 -0
  18. package/src/frontmatter.mjs +73 -0
  19. package/src/import-anthropic-memory.mjs +266 -0
  20. package/src/index-db.mjs +154 -0
  21. package/src/index-rebuild.mjs +597 -0
  22. package/src/index.mjs +90 -0
  23. package/src/inject-context.mjs +484 -0
  24. package/src/install.mjs +327 -0
  25. package/src/lazy-compress.mjs +326 -0
  26. package/src/lock-discipline.mjs +166 -0
  27. package/src/mcp-server.mjs +498 -0
  28. package/src/memory-write.mjs +565 -0
  29. package/src/merge-facts.mjs +213 -0
  30. package/src/observe-edit.mjs +87 -0
  31. package/src/platform-commands.mjs +138 -0
  32. package/src/poison-guard.mjs +245 -0
  33. package/src/privacy.mjs +21 -0
  34. package/src/provenance.mjs +217 -0
  35. package/src/register-crons.mjs +354 -0
  36. package/src/reindex.mjs +134 -0
  37. package/src/repair.mjs +316 -0
  38. package/src/result-shapes.mjs +155 -0
  39. package/src/review-queue.mjs +345 -0
  40. package/src/roll.mjs +115 -0
  41. package/src/scratchpad.mjs +335 -0
  42. package/src/search.mjs +311 -0
  43. package/src/subcommands.mjs +1252 -0
  44. package/src/tier-paths.mjs +74 -0
  45. package/src/transcripts.mjs +234 -0
  46. package/src/trust.mjs +226 -0
  47. package/src/weekly-curate.mjs +454 -0
  48. package/src/write-fact.mjs +205 -0
  49. package/template/.claude/hooks/pre-tool-memory.js +78 -0
  50. package/template/.claude/hooks/transcript-capture.js +69 -0
  51. package/template/.claude/settings.json +27 -0
  52. package/template/.claude/skills/memory-write/SKILL.md +117 -0
  53. package/template/.gitignore.fragment +12 -0
  54. package/template/CLAUDE.md.template +49 -0
  55. package/template/docs/journey/journey-log.md.template +292 -0
  56. package/template/local/machine-paths.md.template +37 -0
  57. package/template/local/overrides.md.template +36 -0
  58. package/template/project/.index/.gitkeep +0 -0
  59. package/template/project/MEMORY.md.template +47 -0
  60. package/template/project/SOUL.md.template +35 -0
  61. package/template/project/memory/INDEX.md.template +47 -0
  62. package/template/project/memory/archive/superseded/.gitkeep +0 -0
  63. package/template/project/memory/archive/tombstones/.gitkeep +0 -0
  64. package/template/project/queues/.gitkeep +0 -0
  65. package/template/project/sessions/.gitkeep +0 -0
  66. package/template/project/transcripts/.gitkeep +0 -0
  67. package/template/support/cron-jobs/daily-memory-distill.md +15 -0
  68. package/template/support/cron-jobs/nightly-memsearch-index.md +17 -0
  69. package/template/support/cron-jobs/weekly-memory-curator.md +15 -0
  70. package/template/support/milvus-deploy/README.md +57 -0
  71. package/template/support/milvus-deploy/docker-compose.yml +66 -0
  72. package/template/support/scripts/auto-extract-memory.sh +102 -0
  73. package/template/support/scripts/memsearch-index-with-flush.sh +59 -0
  74. package/template/support/scripts/refresh-distill-timestamp.py +35 -0
  75. package/template/support/scripts/register-crons.py +242 -0
  76. package/template/support/scripts/run-daily-distill.sh +67 -0
  77. package/template/support/scripts/run-weekly-curate.sh +58 -0
  78. package/template/user/HABITS.md.template +18 -0
  79. package/template/user/LESSONS.md.template +18 -0
  80. package/template/user/USER.md.template +18 -0
  81. package/template/user/fragments/INDEX.md.template +23 -0
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ register-crons.py — reads cron/jobs/*.md and registers the active jobs with
4
+ the host scheduler (Task Scheduler on Windows, crontab on Unix).
5
+
6
+ Idempotent: re-running re-registers any missing tasks without disturbing existing ones.
7
+
8
+ Usage:
9
+ python scripts/register-crons.py [--dry-run] [--unregister NAME]
10
+
11
+ The task name prefix defaults to the project directory's basename
12
+ (slugified). Override with the env var CMK_TASK_PREFIX, e.g.:
13
+
14
+ CMK_TASK_PREFIX=ytslide- python scripts/register-crons.py
15
+
16
+ Job file format (YAML frontmatter):
17
+ ---
18
+ name: Daily Memory Distillation
19
+ time: '23:00'
20
+ days: daily | mon | tue | wed | thu | fri | sat | sun
21
+ active: 'true'
22
+ job_type: timestamp_refresh | shell_command | claude_prompt
23
+ command: 'bash scripts/run-daily-distill.sh'
24
+ working_directory: '${CLAUDE_PROJECT_DIR}'
25
+ ---
26
+ [task body / prompt]
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import argparse
31
+ import os
32
+ import platform
33
+ import re
34
+ import subprocess
35
+ import sys
36
+ from pathlib import Path
37
+
38
+ try:
39
+ import yaml
40
+ except ImportError:
41
+ print("ERROR: pyyaml not installed. Run: python -m pip install pyyaml", file=sys.stderr)
42
+ sys.exit(1)
43
+
44
+ REPO_ROOT = Path(__file__).resolve().parent.parent
45
+ JOBS_DIR = REPO_ROOT / "cron" / "jobs"
46
+
47
+
48
+ def _default_prefix() -> str:
49
+ slug = re.sub(r"[^a-z0-9]+", "-", REPO_ROOT.name.lower()).strip("-")
50
+ return f"{slug}-" if slug else "cmk-"
51
+
52
+
53
+ TASK_NAME_PREFIX = os.environ.get("CMK_TASK_PREFIX", _default_prefix())
54
+
55
+ DAY_MAP = {"daily": "DAILY", "mon": "MON", "tue": "TUE", "wed": "WED",
56
+ "thu": "THU", "fri": "FRI", "sat": "SAT", "sun": "SUN"}
57
+
58
+
59
+ def slugify(name: str) -> str:
60
+ return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
61
+
62
+
63
+ def parse_job_file(path: Path) -> dict | None:
64
+ text = path.read_text(encoding="utf-8")
65
+ m = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)$", text, re.DOTALL)
66
+ if not m:
67
+ print(f"WARN: {path.name} has no YAML frontmatter, skipping")
68
+ return None
69
+ front = yaml.safe_load(m.group(1)) or {}
70
+ front["__body__"] = m.group(2).strip()
71
+ front["__source__"] = path.name
72
+ return front
73
+
74
+
75
+ def _windows_bash_path() -> str | None:
76
+ """Find Git Bash on Windows. Task Scheduler runs `cmd /c` with System32
77
+ high on PATH, so a bare `bash` command resolves to Windows' WSL bash
78
+ launcher (which fails if no WSL distro is configured)."""
79
+ candidates = [
80
+ Path(r"C:\Program Files\Git\usr\bin\bash.exe"),
81
+ Path(r"C:\Program Files\Git\bin\bash.exe"),
82
+ Path(r"C:\Program Files (x86)\Git\usr\bin\bash.exe"),
83
+ ]
84
+ for p in candidates:
85
+ if p.exists():
86
+ return str(p)
87
+ return None
88
+
89
+
90
+ def build_task_command(job: dict) -> str | None:
91
+ jt = job.get("job_type")
92
+ wd = job.get("working_directory", "").replace("${CLAUDE_PROJECT_DIR}", str(REPO_ROOT))
93
+ if jt == "shell_command":
94
+ cmd = job.get("command", "")
95
+ if not cmd:
96
+ return None
97
+ if platform.system() == "Windows":
98
+ if cmd.startswith("bash "):
99
+ gitbash = _windows_bash_path()
100
+ if gitbash:
101
+ cmd = f'"{gitbash}" {cmd[5:]}'
102
+ if wd:
103
+ return f'cmd /c "cd /d \"{wd}\" && {cmd}"'
104
+ return f'cmd /c "{cmd}"'
105
+ if wd:
106
+ return f'sh -c "cd \"{wd}\" && {cmd}"'
107
+ return cmd
108
+ if jt == "timestamp_refresh":
109
+ helper = REPO_ROOT / "scripts" / "refresh-distill-timestamp.py"
110
+ py = "python" if platform.system() == "Windows" else "python3"
111
+ return f'{py} "{helper}"'
112
+ if jt == "claude_prompt":
113
+ msg = f"Job {job.get('name')!r} is registered but its body needs Claude CLI to run."
114
+ if platform.system() == "Windows":
115
+ return f'cmd /c "echo {msg}"'
116
+ return f'sh -c "echo {msg}"'
117
+ return None
118
+
119
+
120
+ def register_windows(job: dict, dry_run: bool) -> tuple[bool, str]:
121
+ name = TASK_NAME_PREFIX + slugify(job["name"])
122
+ time = job.get("time", "")
123
+ days = job.get("days", "daily")
124
+ cmd = build_task_command(job)
125
+ if not cmd:
126
+ return False, "no command for this job_type"
127
+
128
+ if days == "daily":
129
+ schedule = ["/sc", "daily"]
130
+ elif days in DAY_MAP:
131
+ schedule = ["/sc", "weekly", "/d", DAY_MAP[days]]
132
+ else:
133
+ return False, f"unknown days value: {days!r}"
134
+
135
+ args = [
136
+ "schtasks", "/create", "/tn", name,
137
+ "/tr", cmd,
138
+ "/st", time,
139
+ *schedule,
140
+ "/f",
141
+ ]
142
+ if dry_run:
143
+ return True, "DRY-RUN: " + " ".join(args)
144
+ result = subprocess.run(args, capture_output=True, text=True, check=False)
145
+ if result.returncode != 0:
146
+ return False, f"schtasks failed: {result.stderr.strip() or result.stdout.strip()}"
147
+ return True, f"registered as {name}"
148
+
149
+
150
+ def register_unix(job: dict, dry_run: bool) -> tuple[bool, str]:
151
+ name = TASK_NAME_PREFIX + slugify(job["name"])
152
+ time = job.get("time", "")
153
+ days = job.get("days", "daily")
154
+ cmd = build_task_command(job)
155
+ if not cmd:
156
+ return False, "no command for this job_type"
157
+
158
+ if not re.match(r"^\d{2}:\d{2}$", time):
159
+ return False, f"bad time format: {time!r}"
160
+ hh, mm = time.split(":")
161
+
162
+ if days == "daily":
163
+ day_field = "*"
164
+ elif days in DAY_MAP:
165
+ day_field = {"mon": "1", "tue": "2", "wed": "3", "thu": "4",
166
+ "fri": "5", "sat": "6", "sun": "0"}[days]
167
+ else:
168
+ return False, f"unknown days value: {days!r}"
169
+
170
+ cron_line = f"{mm} {hh} * * {day_field} {cmd} # {name}"
171
+ if dry_run:
172
+ return True, "DRY-RUN: would append to crontab: " + cron_line
173
+
174
+ existing = subprocess.run(["crontab", "-l"], capture_output=True, text=True).stdout
175
+ if name in existing:
176
+ return True, f"{name} already in crontab"
177
+ new = existing.rstrip() + "\n" + cron_line + "\n"
178
+ proc = subprocess.run(["crontab", "-"], input=new, text=True, capture_output=True)
179
+ if proc.returncode != 0:
180
+ return False, f"crontab failed: {proc.stderr.strip()}"
181
+ return True, f"appended {name}"
182
+
183
+
184
+ def main() -> int:
185
+ p = argparse.ArgumentParser()
186
+ p.add_argument("--dry-run", action="store_true", help="Print what would happen without changing anything")
187
+ p.add_argument("--unregister", help="Remove a task by name (with or without prefix)")
188
+ args = p.parse_args()
189
+
190
+ if args.unregister:
191
+ name = args.unregister if args.unregister.startswith(TASK_NAME_PREFIX) else TASK_NAME_PREFIX + slugify(args.unregister)
192
+ if platform.system() == "Windows":
193
+ r = subprocess.run(["schtasks", "/delete", "/tn", name, "/f"], capture_output=True, text=True)
194
+ print(r.stdout or r.stderr)
195
+ return r.returncode
196
+ existing = subprocess.run(["crontab", "-l"], capture_output=True, text=True).stdout
197
+ new = "\n".join(line for line in existing.splitlines() if name not in line) + "\n"
198
+ subprocess.run(["crontab", "-"], input=new, text=True)
199
+ print(f"Removed lines matching {name}")
200
+ return 0
201
+
202
+ jobs = sorted(JOBS_DIR.glob("*.md"))
203
+ if not jobs:
204
+ print(f"No job files found in {JOBS_DIR}")
205
+ return 0
206
+
207
+ print(f"Found {len(jobs)} job file(s) in {JOBS_DIR}")
208
+ print(f"Platform: {platform.system()}; using {'schtasks' if platform.system() == 'Windows' else 'crontab'}")
209
+ print(f"Task name prefix: {TASK_NAME_PREFIX}")
210
+ print()
211
+
212
+ n_ok = 0
213
+ n_skip = 0
214
+ n_fail = 0
215
+ for path in jobs:
216
+ job = parse_job_file(path)
217
+ if not job:
218
+ continue
219
+ name = job.get("name", "(unnamed)")
220
+ active = str(job.get("active", "")).lower() == "true"
221
+ if not active:
222
+ print(f"SKIP {name} (active: false)")
223
+ n_skip += 1
224
+ continue
225
+ if platform.system() == "Windows":
226
+ ok, msg = register_windows(job, args.dry_run)
227
+ else:
228
+ ok, msg = register_unix(job, args.dry_run)
229
+ marker = "OK" if ok else "FAIL"
230
+ print(f"{marker:6s} {name}: {msg}")
231
+ if ok:
232
+ n_ok += 1
233
+ else:
234
+ n_fail += 1
235
+
236
+ print()
237
+ print(f"Done. registered: {n_ok}, skipped: {n_skip}, failed: {n_fail}")
238
+ return 0 if n_fail == 0 else 1
239
+
240
+
241
+ if __name__ == "__main__":
242
+ sys.exit(main())
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Daily memory distill — invokes Claude headlessly with a prompt that reads
4
+ # today's session log and extracts durable facts into context/MEMORY.md.
5
+ #
6
+ # Wired by cron/jobs/daily-memory-distill.md → schtasks/cron at 23:00 daily.
7
+ # Logs to context/sessions/{today}.md under a "## Automated distill" heading
8
+ # so the work is traceable.
9
+
10
+ # When run from Task Scheduler / cron / launchd, PATH may not include the
11
+ # directories where bash builtins or Claude CLI live. Set up explicitly.
12
+ case ":$PATH:" in
13
+ *":/usr/bin:"*) ;;
14
+ *) export PATH="/usr/bin:/usr/local/bin:/opt/homebrew/bin:/c/Program Files/Git/usr/bin:$PATH" ;;
15
+ esac
16
+
17
+ set -euo pipefail
18
+
19
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
20
+ cd "$REPO_ROOT"
21
+
22
+ TODAY=$(date +%Y-%m-%d)
23
+ SESSION_LOG="context/sessions/${TODAY}.md"
24
+
25
+ if [ ! -f "$SESSION_LOG" ]; then
26
+ echo "[$(date -Iseconds)] daily-distill: no session log at $SESSION_LOG; nothing to distill" >&2
27
+ # Still refresh the timestamp so HC-3 stays green.
28
+ python "${REPO_ROOT}/scripts/refresh-distill-timestamp.py" || true
29
+ exit 0
30
+ fi
31
+
32
+ PROMPT=$(cat <<EOF
33
+ You are running as a scheduled background task at $(date -Iseconds) for this project. No human is watching. Be concise and idempotent.
34
+
35
+ GOAL: distill durable facts from today's session log into the bounded scratchpad.
36
+
37
+ STEPS:
38
+ 1. Read \`${SESSION_LOG}\`. This is today's session log with deliverables, decisions, and open threads.
39
+ 2. Read \`context/MEMORY.md\` to see what's already captured (active threads, environment notes, pending decisions).
40
+ 3. Read \`context/memory/INDEX.md\` to see what's already in the granular archive.
41
+ 4. For each meaningful item in the session log:
42
+ - If it's transient working state (current discussion, today's iteration) → if not already in MEMORY.md "Active Threads", add a one-line bullet there.
43
+ - If it's a durable typed fact (user preference learned, project decision made, feedback rule, external reference) → create or update a granular file at \`context/memory/<type>_<slug>.md\` with frontmatter + Why + How to apply. Add a one-line entry to INDEX.md. Don't duplicate in MEMORY.md.
44
+ - If it's an environment change (new tool installed, path discovered) → MEMORY.md "Environment Notes".
45
+ - If it's a decision the user still needs to make → MEMORY.md "Pending Decisions".
46
+ 5. Before writing MEMORY.md, run \`wc -c context/MEMORY.md\`. If over 2500 chars, FIRST consolidate by merging similar bullets and dropping stale ones (anything older than 14 days that has no current relevance). Then add the new content.
47
+ 6. Update the \`<!-- Last distilled: YYYY-MM-DD -->\` line at the top of MEMORY.md to ${TODAY}.
48
+ 7. Append a one-line entry to \`${SESSION_LOG}\` under a new "## Automated distill" heading describing what changed.
49
+
50
+ CONSTRAINTS:
51
+ - Use the Read, Edit, and Bash tools only. Do NOT use Write to overwrite MEMORY.md — use Edit so the metadata comment is preserved.
52
+ - Do NOT create new files outside context/memory/ or modify anything outside context/.
53
+ - Do NOT commit, push, or run git operations.
54
+ - If the session log has nothing distillable, still update the timestamp on MEMORY.md so HC-3 stays green, and append a "## Automated distill" note saying "nothing new to distill."
55
+ - Keep total output under 3 paragraphs.
56
+ EOF
57
+ )
58
+
59
+ echo "$PROMPT" | claude --print \
60
+ --add-dir "$REPO_ROOT" \
61
+ --allowed-tools "Read" "Edit" "Bash(wc *)" "Bash(date *)" \
62
+ --output-format text \
63
+ 2>&1 | tee -a "${REPO_ROOT}/context/sessions/${TODAY}.distill.log"
64
+
65
+ EXIT=${PIPESTATUS[0]}
66
+ echo "[$(date -Iseconds)] daily-distill: exit=$EXIT" >&2
67
+ exit "$EXIT"
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Weekly memory curator — invokes Claude headlessly with a prompt that
4
+ # prunes, merges, and consolidates entries in context/MEMORY.md.
5
+ #
6
+ # Wired by cron/jobs/weekly-memory-curator.md → schtasks/cron at Sun 09:00.
7
+
8
+ case ":$PATH:" in
9
+ *":/usr/bin:"*) ;;
10
+ *) export PATH="/usr/bin:/usr/local/bin:/opt/homebrew/bin:/c/Program Files/Git/usr/bin:$PATH" ;;
11
+ esac
12
+
13
+ set -euo pipefail
14
+
15
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
16
+ cd "$REPO_ROOT"
17
+
18
+ TODAY=$(date +%Y-%m-%d)
19
+ SESSION_LOG="context/sessions/${TODAY}.md"
20
+
21
+ PROMPT=$(cat <<EOF
22
+ You are running as a scheduled weekly background task at $(date -Iseconds) for this project. No human is watching. Be conservative — if unsure whether something should be kept, KEEP IT.
23
+
24
+ GOAL: prune, merge, and consolidate \`context/MEMORY.md\` so it stays informative and well under the 2500-char cap.
25
+
26
+ STEPS:
27
+ 1. Read \`context/MEMORY.md\` end to end.
28
+ 2. Read \`context/memory/INDEX.md\` and at least the most recent 3 session logs (\`ls -t context/sessions/ | head -3\`) for context on what's currently relevant.
29
+ 3. For each entry in MEMORY.md "Active Threads":
30
+ - Is it still being worked on? Check session logs. If resolved → REMOVE.
31
+ - Are there duplicates? → MERGE into one.
32
+ 4. For each "Environment Note":
33
+ - Is it still accurate? Drop only if clearly stale.
34
+ 5. For each "Pending Decision":
35
+ - Has it been resolved (look for matching deliverables in recent session logs)? → REMOVE.
36
+ - Has new context arrived? → UPDATE the bullet to reflect current state.
37
+ 6. After curating, run \`wc -c context/MEMORY.md\` and report the new size.
38
+ 7. Update the \`<!-- Last health check: YYYY-MM-DD -->\` line to ${TODAY}.
39
+ 8. If today doesn't have a session log yet, create \`${SESSION_LOG}\` with a "## Session — automated curation" heading. Append a summary of what was changed (e.g., "Removed 2 resolved threads, merged 3 environment-notes duplicates, new size 1840/2500 chars.").
40
+
41
+ CONSTRAINTS:
42
+ - Use Read, Edit, and Bash(wc *), Bash(ls *), Bash(date *) only.
43
+ - NEVER add new content during curation — this is a cleanup pass, not a writing pass. If you find something missing, NOTE it in the session log but don't add to MEMORY.md.
44
+ - Do NOT touch granular archive files in context/memory/<type>_*.md unless they're obviously orphaned (no INDEX entry AND no references in MEMORY.md AND no recent session log mention).
45
+ - Do NOT commit, push, or run git operations.
46
+ - Keep total output under 5 lines: summary of changes only.
47
+ EOF
48
+ )
49
+
50
+ echo "$PROMPT" | claude --print \
51
+ --add-dir "$REPO_ROOT" \
52
+ --allowed-tools "Read" "Edit" "Bash(wc *)" "Bash(ls *)" "Bash(date *)" \
53
+ --output-format text \
54
+ 2>&1 | tee -a "${REPO_ROOT}/context/sessions/${TODAY}.curate.log"
55
+
56
+ EXIT=${PIPESTATUS[0]}
57
+ echo "[$(date -Iseconds)] weekly-curate: exit=$EXIT" >&2
58
+ exit "$EXIT"
@@ -0,0 +1,18 @@
1
+ <!-- Cap: 1800 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
+
3
+ # Habits (cross-project working style)
4
+
5
+ ## Iteration Cadence
6
+
7
+ - (U-CEKUY3H4) commits incrementally as work progresses; not in one big push at the end
8
+ <!-- source: HABITS.md, source_line: 7, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
9
+
10
+ ## Destructive Operations
11
+
12
+ - (U-GXB2C4JZ) always confirms before git push, git reset --hard, or other irreversible operations
13
+ <!-- source: HABITS.md, source_line: 12, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
14
+
15
+ ## Communication Style
16
+
17
+ - (U-aUMQHVCV) reads the diff before the commit message; trusts code over claims
18
+ <!-- source: HABITS.md, source_line: 17, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
@@ -0,0 +1,18 @@
1
+ <!-- Cap: 1800 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
+
3
+ # Lessons (cross-project)
4
+
5
+ ## Tooling Lessons
6
+
7
+ - (U-RDBNQSL7) fix the code, not the test
8
+ <!-- source: LESSONS.md, source_line: 7, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
9
+
10
+ ## Process Lessons
11
+
12
+ - (U-APHTKHMQ) correlation is not causation; measured profiling beats educated guessing
13
+ <!-- source: LESSONS.md, source_line: 12, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
14
+
15
+ ## Anti-patterns
16
+
17
+ - (U-U5RM2FXH) premature abstraction outlives the requirement that motivated it
18
+ <!-- source: LESSONS.md, source_line: 17, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
@@ -0,0 +1,18 @@
1
+ <!-- Cap: 1375 chars · Last distilled: {{TODAY}} · Last health check: {{TODAY}} -->
2
+
3
+ # User Profile
4
+
5
+ ## About
6
+
7
+ - (U-PRNQKRaC) engineer comfortable with TDD and incremental code review
8
+ <!-- source: USER.md, source_line: 7, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
9
+
10
+ ## Preferences
11
+
12
+ - (U-K3Z73EAQ) prefers terse responses; no preamble before the answer
13
+ <!-- source: USER.md, source_line: 12, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
14
+
15
+ ## Working Style
16
+
17
+ - (U-PUYHL2BL) one PR per parent task; squash-merge into main
18
+ <!-- source: USER.md, source_line: 17, sha1: 0000000000000000000000000000000000000000, write: manual-edit, trust: medium, at: 2020-01-01T00:00:00Z -->
@@ -0,0 +1,23 @@
1
+ <!--
2
+ File: ~/.claude-memory-kit/fragments/INDEX.md (or $MEMORY_KIT_USER_DIR/fragments/INDEX.md).
3
+ Purpose: pointer index for the user-tier granular archive.
4
+ Format: one line per non-tombstoned fact file:
5
+ - ({id}) [type] [title](filename.md) — <hook>
6
+
7
+ This file is auto-maintained by `cmk reindex`. Do not hand-edit unless
8
+ you also delete the fact file it points to (or `cmk reindex` will
9
+ overwrite your changes).
10
+
11
+ Type taxonomy per design §2.2:
12
+ - user_* facts about the user
13
+ - feedback_* corrections / preferences
14
+ - project_* decisions with rationale (rare in user tier — most
15
+ are project-specific)
16
+ - reference_* pointers to external systems
17
+ -->
18
+
19
+ # User-tier granular memory — INDEX
20
+
21
+ ## Files
22
+
23
+ <!-- Auto-generated by `cmk reindex`. No entries yet on a fresh install. -->