Repository navigation
Expand file tree
/
Copy pathtechpack.yaml
More file actions
404 lines (381 loc) · 18.2 KB
/
Copy pathtechpack.yaml
File metadata and controls
404 lines (381 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
schemaVersion: 1
identifier: memory
displayName: "The Memory Loop"
description: Persistent project memory that compounds across Claude Code sessions
author: Bruno Guidolim
minMCSVersion: "2026.9.3"
# ---------------------------------------------------------------------------
# Prompts — asked once per project during `mcs sync`, then remembered
# ---------------------------------------------------------------------------
prompts:
- key: KB_GATE_MODE
type: select
label: "KB gate — how strictly must Claude check the KB before delegating?"
default: "enforce"
options:
- value: "warn"
label: "Warn — remind and log when a sub-agent is spawned without KB findings"
- value: "enforce"
label: "Enforce — block the spawn until the KB has been searched this turn"
- value: "observe"
label: "Observe — never gate the main thread, just log (sub-agents are still briefed)"
- value: "off"
label: "Off — no gating and no sub-agent briefing; prompt reminder only"
# ---------------------------------------------------------------------------
# Components
# ---------------------------------------------------------------------------
components:
# ── Dependencies ────────────────────────────────────────────────────────
- id: node
displayName: Node.js
description: Node 22.6+ runs the TypeScript hooks directly, with no build step
brew: node
doctorChecks:
- type: commandExists
name: "Node.js is version 22.6 or newer"
section: Runtime
command: /bin/sh
args:
- "-c"
- |
PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
node -e 'const [major, minor] = process.versions.node.split(".").map(Number);
process.exit(major > 22 || (major === 22 && minor >= 6) ? 0 : 1)'
fixCommand: "brew upgrade node"
# ── Retrieval engine ────────────────────────────────────────────────────
# Pinned so no package manager runs inside a hook, and because the rerank and
# generate neutralisation relies on undocumented fallback behaviour a version
# bump could change. See CLAUDE.md.
- id: qmd-install
displayName: qmd
description: Local search engine over markdown — no daemon, bundled SQLite + sqlite-vec
type: configuration
shell: "npm install -g @tobilu/qmd@2.8.3"
doctorChecks:
# ONE check, deliberately: `isAlreadyInstalled` takes the first *passing*
# check as proof the component is installed, so any second check that can
# pass while the pin is unsatisfied silently disables the install.
# Asserts the version and that the binary loads (`--version` alone never
# loads the native modules); a qmd that runs proves Node is new enough.
# Anchored, so 2.8.30 and 12.8.3 do not pass. Keep the literal in step
# with `shell:` above.
#
# The load probe runs against a throwaway index. A bare `qmd status` adopts
# whatever config it finds by walking up from the working directory, so in a
# project where the user keeps their own .qmd/ it writes model keys into
# their config and creates their index.sqlite — the pack must not touch it.
# `--index` is what skips that discovery; QMD_CONFIG_DIR alone does not,
# because a discovered .qmd/ outranks it.
- type: commandExists
name: "qmd 2.8.3 installed and loading"
section: MCP Servers
command: /bin/sh
args:
- "-c"
- |
PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
qmd --version 2>/dev/null | grep -qE '^qmd 2\.8\.3( |$)' || exit 1
d=$(mktemp -d) || exit 1
QMD_CONFIG_DIR="$d" INDEX_PATH="$d/probe.sqlite" \
qmd --index probe status >/dev/null 2>&1
s=$?; rm -rf "$d"; exit $s
fixCommand: "npm install -g @tobilu/qmd@2.8.3"
# One model serves all three of qmd's slots, so this downloads a single file.
# It runs at sync time on purpose: a first-run download inside the async
# indexing hook would blow its timeout and leave the index silently empty.
- id: qmd-model
displayName: Embedding model
description: Downloads Qwen3-Embedding-0.6B once (~610 MB, shared across all projects)
type: configuration
shell: |
d=$(mktemp -d)
m="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
QMD_CONFIG_DIR="$d" INDEX_PATH="$d/pull.sqlite" \
QMD_EMBED_MODEL="$m" QMD_GENERATE_MODEL="$m" QMD_RERANK_MODEL="$m" \
qmd --index pull pull --progress
s=$?; rm -rf "$d"; exit $s
doctorChecks:
- type: commandExists
name: "Qwen3-Embedding-0.6B downloaded"
section: AI Models
command: /bin/sh
args: ["-c", 'test -f "${XDG_CACHE_HOME:-$HOME/.cache}/qmd/models/hf_Qwen_Qwen3-Embedding-0.6B-Q8_0.gguf"']
# ── MCP Server ──────────────────────────────────────────────────────────
# One static command line serves every project, so the index path is computed
# at launch — Claude spawns MCP servers with the project as their working
# directory. Using `--index` rather than a project-local .qmd/ is an invariant;
# see CLAUDE.md.
#
# `mkdir -p` because qmd cannot open an index under a directory that does not
# exist, and the server would fail to connect in every project that has not
# captured a memory yet — which is every new project. The CLAUDE.md section is
# installed globally and tells Claude to search before each task, so the tool
# has to exist everywhere that instruction does. An empty index is honest: the
# server reports "0 markdown documents" in its own instructions.
#
# keep in sync with hooks/sync-memories.mts — it writes the index this opens.
- id: memory-loop
description: Semantic search over this project's memories, backed by local embeddings
mcp:
command: /bin/sh
args:
- "-c"
- |-
r=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$r" ] || r="${CLAUDE_PROJECT_DIR:-$PWD}"
d="$r/.claude/.kb-index"
PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
mkdir -p "$d"
export QMD_CONFIG_DIR="$d" INDEX_PATH="$d/memory-loop.sqlite"
exec qmd --index memory-loop mcp
# All three pass quietly when nothing is indexed yet, and each repeats the
# launcher's project-root ladder verbatim — every check is its own process, so
# the duplication is forced. Keep the copies identical. The two that invoke qmd
# also harden PATH, because `mcs doctor` runs them through Process, which has
# no login shell.
doctorChecks:
# Tier 2, and the only thing that would catch a silent model downgrade:
# a weaker embedding model produces no error, just worse results.
- type: commandExists
name: "Memory index uses the expected embedding model"
section: AI Models
command: /bin/sh
args:
- "-c"
- |
r=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$r" ] || r="${CLAUDE_PROJECT_DIR:-$PWD}"
d="$r/.claude/.kb-index"
PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
command -v qmd >/dev/null 2>&1 || exit 0
[ -f "$d/memory-loop.yml" ] || exit 0
export QMD_CONFIG_DIR="$d" INDEX_PATH="$d/memory-loop.sqlite"
qmd --index memory-loop status 2>/dev/null | grep -q Qwen3-Embedding-0.6B
# The indexing hook removes its log on success, so the file existing at all
# means the last run failed or left documents unembedded — and the reason is
# inside it. Without this, a broken index is only visible as searches quietly
# returning nothing.
- type: commandExists
name: "Memory indexing completed"
section: AI Models
command: /bin/sh
args:
- "-c"
- |
r=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$r" ] || r="${CLAUDE_PROJECT_DIR:-$PWD}"
[ ! -f "$r/.claude/.kb-index/memory-loop.log" ]
# Queries with *default* arguments — reranking on — because that is the path
# this pack neutralises. If an upgrade made that fatal rather than a warning,
# this check is where it surfaces. The alarm is the timeout `commandExists`
# does not provide.
- type: commandExists
name: "Memory search returns results"
section: MCP Servers
command: /bin/sh
args:
- "-c"
- |
r=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$r" ] || r="${CLAUDE_PROJECT_DIR:-$PWD}"
d="$r/.claude/.kb-index"
PATH="$PATH:/opt/homebrew/bin:/usr/local/bin"
command -v qmd >/dev/null 2>&1 || exit 0
[ -f "$d/memory-loop.sqlite" ] || exit 0
export QMD_CONFIG_DIR="$d" INDEX_PATH="$d/memory-loop.sqlite"
v=$(qmd --index memory-loop status 2>/dev/null |
sed -n 's/.*Vectors: *\([0-9][0-9]*\).*/\1/p' | head -1)
[ "${v:-0}" -gt 0 ] || exit 0
perl -e 'alarm 30; exec @ARGV' \
qmd --index memory-loop query "vec: past decisions and learnings" \
-n 1 --format json 2>/dev/null | grep -q docid
# ── Skills ──────────────────────────────────────────────────────────────
- id: skill-continuous-learning
displayName: continuous-learning skill
description: Extracts learnings and decisions from sessions into memory
skill:
source: skills/continuous-learning
destination: continuous-learning
- id: skill-memory-audit
displayName: memory-audit skill
description: Reviews and audits memories to keep the knowledge base lean and valuable
skill:
source: skills/memory-audit
destination: memory-audit
# ── Hooks ───────────────────────────────────────────────────────────────
- id: hook-helpers
displayName: Shared hook helpers
description: Shared filesystem and project-path helpers for the hooks
hook:
source: hooks/shared.mts
destination: shared.mts
# Registered hooks run a small .mjs launcher that turns on Node's compile cache before
# importing these, so the TypeScript is stripped once instead of on every start.
- id: hook-sync-memories-module
displayName: Sync memories module
description: Indexing logic loaded by the sync memories launcher
hook:
source: hooks/sync-memories.mts
destination: sync-memories.mts
- id: hook-kb-gate-module
displayName: KB gate module
description: KB gate logic loaded by the KB gate launchers
hook:
source: hooks/kb-gate.mts
destination: kb-gate.mts
- id: hook-continuous-learning
displayName: Memory loop activator
description: Reminds to evaluate learnings on each prompt
hookEvent: UserPromptSubmit
hook:
source: hooks/memory-loop-activator.sh
destination: memory-loop-activator.sh
- id: hook-sync-memories
displayName: Sync memories hook
description: Indexes memories into the project's search index on session start
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: SessionStart
hookAsync: true
hookTimeout: 120
hookStatusMessage: "Indexing memories..."
hook:
source: hooks/sync-memories.mjs
destination: sync-memories.mjs
- id: hook-reindex-memories
displayName: Reindex memories hook
description: Reindexes memories when they have changed mid-session
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: UserPromptSubmit
hookAsync: true
hookTimeout: 120
hookStatusMessage: "Reindexing memories..."
hook:
source: hooks/sync-memories.mjs
destination: sync-memories.mjs
# ── KB-first delegation ─────────────────────────────────────────────────
# Several registrations of one dispatcher script, which branches on
# `hook_event_name` and then `tool_name`. Together they keep KB lookups ahead of delegated discovery,
# so sub-agents don't rediscover what a memory already recorded, and keep memory
# writes behind the skills that run the save checks. Matchers stay broad on
# purpose — which agent types count as "discovery" is decided in one place, by
# `GATED_AGENTS` in the script.
#
# mcs keeps one registration per event and command, so tools on one event share
# a matcher; one that needs different hook settings, like the async retrieval
# hook, installs the same script under another name.
- id: hook-kb-gate-turn
displayName: KB gate — turn boundary
description: Marks each new prompt so the KB check is required again per turn
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: UserPromptSubmit
hook:
source: hooks/kb-gate.mjs
destination: kb-gate.mjs
# Checked because losing this one corrupts the gate rather than disabling it:
# with no turn stamp the counter reads 0 forever, so every search ever made in
# the session counts as fresh and the barrier silently widens from per-turn to
# per-session.
doctorChecks:
- type: hookEventExists
name: "UserPromptSubmit hook registered"
section: Hooks
event: UserPromptSubmit
- id: hook-kb-gate-record
displayName: KB gate — record searches and memory skills
description: Records each KB search and memory-skill invocation so delegated discovery, memory writes and file edits can be checked against them
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: PostToolUse
hookMatcher: "mcp__memory-loop__query|Skill"
hook:
source: hooks/kb-gate.mjs
destination: kb-gate.mjs
# Same reasoning: without this, no search or skill is ever recorded, so the
# gate is pure friction until the budget releases it.
doctorChecks:
- type: hookEventExists
name: "PostToolUse hook registered"
section: Hooks
event: PostToolUse
- id: hook-kb-gate-check
displayName: KB gate — check sub-agent spawns, memory writes and file edits
description: Checks sub-agent spawns for KB findings, memory changes for an active memory skill, and file edits for memories that mention the file
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: PreToolUse
# The spawn tool is `Agent` in current Claude Code and `Task` in older
# builds. Matching only `Task` silently matches nothing.
hookMatcher: "Agent|Task|Write|Edit|NotebookEdit|Bash"
hook:
source: hooks/kb-gate.mjs
destination: kb-gate.mjs
doctorChecks:
- type: hookEventExists
name: "PreToolUse hook registered"
section: Hooks
event: PreToolUse
- id: hook-kb-gate-compact
displayName: KB gate — mark compactions
description: Marks where the conversation was compacted, so file-edit notes only count KB searches still in context
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: SessionStart
hookMatcher: "compact"
hook:
source: hooks/kb-gate.mjs
destination: kb-gate.mjs
- id: hook-kb-gate-retrieval
displayName: KB gate — record memory retrievals
description: Records the day each memory was last retrieved in .claude/memories/.usage.tsv, so a team can see which memories are still read
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: PostToolUse
hookMatcher: "mcp__memory-loop__get|mcp__memory-loop__multi_get|Read"
# Every Read starts this hook, and it never needs to block, so it stays off the turn's critical path.
hookAsync: true
hook:
source: hooks/kb-gate.mjs
destination: kb-usage.mjs
- id: hook-kb-subagent-brief
displayName: Sub-agent KB brief
description: Tells discovery sub-agents that the KB exists, so they search it instead of sweeping files
hookInterpreter: node --experimental-strip-types --disable-warning=ExperimentalWarning
hookEvent: SubagentStart
hook:
source: hooks/kb-gate.mjs
destination: kb-gate.mjs
doctorChecks:
- type: hookEventExists
name: "SubagentStart hook registered"
section: Hooks
event: SubagentStart
# ── Configuration ───────────────────────────────────────────────────────
- id: settings
displayName: Settings
description: Disables built-in auto-memory in favor of continuous learning system
settingsFile: config/settings.json
- id: gitignore
displayName: Global gitignore
description: Ignores memory files from version control
gitignore:
- ".claude/.memories-last-indexed"
- ".claude/.kb-index/"
- ".claude/.kb-gate/"
- ".claude/.kb-gate.log"
# ---------------------------------------------------------------------------
# Ignore — suppress update notifications for changes to maintainer-only files.
# Does not affect what installs; that is decided by the components above.
# ---------------------------------------------------------------------------
ignore:
- CLAUDE.md
- SYNC-BLOCKS.md
- assets/
- tests/
- scripts/
- .github/
- package.json
- package-lock.json
- tsconfig.json
- biome.json
# ---------------------------------------------------------------------------
# Templates — CLAUDE.local.md sections
# ---------------------------------------------------------------------------
templates:
- sectionIdentifier: continuous-learning
contentFile: templates/continuous-learning.md