handoff-mcp-server 0.13.1 → 0.15.1

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.
package/Cargo.lock CHANGED
@@ -13,9 +13,9 @@ dependencies = [
13
13
 
14
14
  [[package]]
15
15
  name = "anyhow"
16
- version = "1.0.102"
16
+ version = "1.0.103"
17
17
  source = "registry+https://github.com/rust-lang/crates.io-index"
18
- checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
18
+ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
19
19
 
20
20
  [[package]]
21
21
  name = "autocfg"
@@ -37,9 +37,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
37
37
 
38
38
  [[package]]
39
39
  name = "cc"
40
- version = "1.2.64"
40
+ version = "1.2.65"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
- checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
42
+ checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
43
43
  dependencies = [
44
44
  "find-msvc-tools",
45
45
  "shlex",
@@ -99,12 +99,6 @@ version = "0.1.9"
99
99
  source = "registry+https://github.com/rust-lang/crates.io-index"
100
100
  checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
101
101
 
102
- [[package]]
103
- name = "foldhash"
104
- version = "0.1.5"
105
- source = "registry+https://github.com/rust-lang/crates.io-index"
106
- checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
107
-
108
102
  [[package]]
109
103
  name = "futures-core"
110
104
  version = "0.3.32"
@@ -131,20 +125,18 @@ dependencies = [
131
125
 
132
126
  [[package]]
133
127
  name = "getrandom"
134
- version = "0.4.2"
128
+ version = "0.4.3"
135
129
  source = "registry+https://github.com/rust-lang/crates.io-index"
136
- checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
130
+ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
137
131
  dependencies = [
138
132
  "cfg-if",
139
133
  "libc",
140
134
  "r-efi",
141
- "wasip2",
142
- "wasip3",
143
135
  ]
144
136
 
145
137
  [[package]]
146
138
  name = "handoff-mcp"
147
- version = "0.13.1"
139
+ version = "0.15.1"
148
140
  dependencies = [
149
141
  "anyhow",
150
142
  "chrono",
@@ -157,27 +149,12 @@ dependencies = [
157
149
  "toml_edit",
158
150
  ]
159
151
 
160
- [[package]]
161
- name = "hashbrown"
162
- version = "0.15.5"
163
- source = "registry+https://github.com/rust-lang/crates.io-index"
164
- checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
165
- dependencies = [
166
- "foldhash",
167
- ]
168
-
169
152
  [[package]]
170
153
  name = "hashbrown"
171
154
  version = "0.17.1"
172
155
  source = "registry+https://github.com/rust-lang/crates.io-index"
173
156
  checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
174
157
 
175
- [[package]]
176
- name = "heck"
177
- version = "0.5.0"
178
- source = "registry+https://github.com/rust-lang/crates.io-index"
179
- checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
180
-
181
158
  [[package]]
182
159
  name = "iana-time-zone"
183
160
  version = "0.1.65"
@@ -202,12 +179,6 @@ dependencies = [
202
179
  "cc",
203
180
  ]
204
181
 
205
- [[package]]
206
- name = "id-arena"
207
- version = "2.3.0"
208
- source = "registry+https://github.com/rust-lang/crates.io-index"
209
- checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
210
-
211
182
  [[package]]
212
183
  name = "indexmap"
213
184
  version = "2.14.0"
@@ -215,9 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
215
186
  checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
216
187
  dependencies = [
217
188
  "equivalent",
218
- "hashbrown 0.17.1",
219
- "serde",
220
- "serde_core",
189
+ "hashbrown",
221
190
  ]
222
191
 
223
192
  [[package]]
@@ -228,21 +197,15 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
228
197
 
229
198
  [[package]]
230
199
  name = "js-sys"
231
- version = "0.3.102"
200
+ version = "0.3.103"
232
201
  source = "registry+https://github.com/rust-lang/crates.io-index"
233
- checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
202
+ checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
234
203
  dependencies = [
235
204
  "cfg-if",
236
205
  "futures-util",
237
206
  "wasm-bindgen",
238
207
  ]
239
208
 
240
- [[package]]
241
- name = "leb128fmt"
242
- version = "0.1.0"
243
- source = "registry+https://github.com/rust-lang/crates.io-index"
244
- checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
245
-
246
209
  [[package]]
247
210
  name = "lexsim"
248
211
  version = "0.1.0"
@@ -267,9 +230,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
267
230
 
268
231
  [[package]]
269
232
  name = "log"
270
- version = "0.4.32"
233
+ version = "0.4.33"
271
234
  source = "registry+https://github.com/rust-lang/crates.io-index"
272
- checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
235
+ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
273
236
 
274
237
  [[package]]
275
238
  name = "memchr"
@@ -298,16 +261,6 @@ version = "0.2.17"
298
261
  source = "registry+https://github.com/rust-lang/crates.io-index"
299
262
  checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
300
263
 
301
- [[package]]
302
- name = "prettyplease"
303
- version = "0.2.37"
304
- source = "registry+https://github.com/rust-lang/crates.io-index"
305
- checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
306
- dependencies = [
307
- "proc-macro2",
308
- "syn",
309
- ]
310
-
311
264
  [[package]]
312
265
  name = "proc-macro2"
313
266
  version = "1.0.106"
@@ -319,9 +272,9 @@ dependencies = [
319
272
 
320
273
  [[package]]
321
274
  name = "quote"
322
- version = "1.0.45"
275
+ version = "1.0.46"
323
276
  source = "registry+https://github.com/rust-lang/crates.io-index"
324
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
277
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
325
278
  dependencies = [
326
279
  "proc-macro2",
327
280
  ]
@@ -351,12 +304,6 @@ version = "1.0.22"
351
304
  source = "registry+https://github.com/rust-lang/crates.io-index"
352
305
  checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
353
306
 
354
- [[package]]
355
- name = "semver"
356
- version = "1.0.28"
357
- source = "registry+https://github.com/rust-lang/crates.io-index"
358
- checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
359
-
360
307
  [[package]]
361
308
  name = "serde"
362
309
  version = "1.0.228"
@@ -424,9 +371,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
424
371
 
425
372
  [[package]]
426
373
  name = "syn"
427
- version = "2.0.117"
374
+ version = "2.0.118"
428
375
  source = "registry+https://github.com/rust-lang/crates.io-index"
429
- checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
376
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
430
377
  dependencies = [
431
378
  "proc-macro2",
432
379
  "quote",
@@ -543,35 +490,11 @@ version = "1.13.3"
543
490
  source = "registry+https://github.com/rust-lang/crates.io-index"
544
491
  checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
545
492
 
546
- [[package]]
547
- name = "unicode-xid"
548
- version = "0.2.6"
549
- source = "registry+https://github.com/rust-lang/crates.io-index"
550
- checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
551
-
552
- [[package]]
553
- name = "wasip2"
554
- version = "1.0.4+wasi-0.2.12"
555
- source = "registry+https://github.com/rust-lang/crates.io-index"
556
- checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
557
- dependencies = [
558
- "wit-bindgen 0.57.1",
559
- ]
560
-
561
- [[package]]
562
- name = "wasip3"
563
- version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
564
- source = "registry+https://github.com/rust-lang/crates.io-index"
565
- checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
566
- dependencies = [
567
- "wit-bindgen 0.51.0",
568
- ]
569
-
570
493
  [[package]]
571
494
  name = "wasm-bindgen"
572
- version = "0.2.125"
495
+ version = "0.2.126"
573
496
  source = "registry+https://github.com/rust-lang/crates.io-index"
574
- checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
497
+ checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
575
498
  dependencies = [
576
499
  "cfg-if",
577
500
  "once_cell",
@@ -582,9 +505,9 @@ dependencies = [
582
505
 
583
506
  [[package]]
584
507
  name = "wasm-bindgen-macro"
585
- version = "0.2.125"
508
+ version = "0.2.126"
586
509
  source = "registry+https://github.com/rust-lang/crates.io-index"
587
- checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
510
+ checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
588
511
  dependencies = [
589
512
  "quote",
590
513
  "wasm-bindgen-macro-support",
@@ -592,9 +515,9 @@ dependencies = [
592
515
 
593
516
  [[package]]
594
517
  name = "wasm-bindgen-macro-support"
595
- version = "0.2.125"
518
+ version = "0.2.126"
596
519
  source = "registry+https://github.com/rust-lang/crates.io-index"
597
- checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
520
+ checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
598
521
  dependencies = [
599
522
  "bumpalo",
600
523
  "proc-macro2",
@@ -605,47 +528,13 @@ dependencies = [
605
528
 
606
529
  [[package]]
607
530
  name = "wasm-bindgen-shared"
608
- version = "0.2.125"
531
+ version = "0.2.126"
609
532
  source = "registry+https://github.com/rust-lang/crates.io-index"
610
- checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
533
+ checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
611
534
  dependencies = [
612
535
  "unicode-ident",
613
536
  ]
614
537
 
615
- [[package]]
616
- name = "wasm-encoder"
617
- version = "0.244.0"
618
- source = "registry+https://github.com/rust-lang/crates.io-index"
619
- checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
620
- dependencies = [
621
- "leb128fmt",
622
- "wasmparser",
623
- ]
624
-
625
- [[package]]
626
- name = "wasm-metadata"
627
- version = "0.244.0"
628
- source = "registry+https://github.com/rust-lang/crates.io-index"
629
- checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
630
- dependencies = [
631
- "anyhow",
632
- "indexmap",
633
- "wasm-encoder",
634
- "wasmparser",
635
- ]
636
-
637
- [[package]]
638
- name = "wasmparser"
639
- version = "0.244.0"
640
- source = "registry+https://github.com/rust-lang/crates.io-index"
641
- checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
642
- dependencies = [
643
- "bitflags",
644
- "hashbrown 0.15.5",
645
- "indexmap",
646
- "semver",
647
- ]
648
-
649
538
  [[package]]
650
539
  name = "windows-core"
651
540
  version = "0.62.2"
@@ -723,100 +612,6 @@ dependencies = [
723
612
  "memchr",
724
613
  ]
725
614
 
726
- [[package]]
727
- name = "wit-bindgen"
728
- version = "0.51.0"
729
- source = "registry+https://github.com/rust-lang/crates.io-index"
730
- checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
731
- dependencies = [
732
- "wit-bindgen-rust-macro",
733
- ]
734
-
735
- [[package]]
736
- name = "wit-bindgen"
737
- version = "0.57.1"
738
- source = "registry+https://github.com/rust-lang/crates.io-index"
739
- checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
740
-
741
- [[package]]
742
- name = "wit-bindgen-core"
743
- version = "0.51.0"
744
- source = "registry+https://github.com/rust-lang/crates.io-index"
745
- checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
746
- dependencies = [
747
- "anyhow",
748
- "heck",
749
- "wit-parser",
750
- ]
751
-
752
- [[package]]
753
- name = "wit-bindgen-rust"
754
- version = "0.51.0"
755
- source = "registry+https://github.com/rust-lang/crates.io-index"
756
- checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
757
- dependencies = [
758
- "anyhow",
759
- "heck",
760
- "indexmap",
761
- "prettyplease",
762
- "syn",
763
- "wasm-metadata",
764
- "wit-bindgen-core",
765
- "wit-component",
766
- ]
767
-
768
- [[package]]
769
- name = "wit-bindgen-rust-macro"
770
- version = "0.51.0"
771
- source = "registry+https://github.com/rust-lang/crates.io-index"
772
- checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
773
- dependencies = [
774
- "anyhow",
775
- "prettyplease",
776
- "proc-macro2",
777
- "quote",
778
- "syn",
779
- "wit-bindgen-core",
780
- "wit-bindgen-rust",
781
- ]
782
-
783
- [[package]]
784
- name = "wit-component"
785
- version = "0.244.0"
786
- source = "registry+https://github.com/rust-lang/crates.io-index"
787
- checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
788
- dependencies = [
789
- "anyhow",
790
- "bitflags",
791
- "indexmap",
792
- "log",
793
- "serde",
794
- "serde_derive",
795
- "serde_json",
796
- "wasm-encoder",
797
- "wasm-metadata",
798
- "wasmparser",
799
- "wit-parser",
800
- ]
801
-
802
- [[package]]
803
- name = "wit-parser"
804
- version = "0.244.0"
805
- source = "registry+https://github.com/rust-lang/crates.io-index"
806
- checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
807
- dependencies = [
808
- "anyhow",
809
- "id-arena",
810
- "indexmap",
811
- "log",
812
- "semver",
813
- "serde",
814
- "serde_derive",
815
- "serde_json",
816
- "unicode-xid",
817
- "wasmparser",
818
- ]
819
-
820
615
  [[package]]
821
616
  name = "zmij"
822
617
  version = "1.0.21"
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "handoff-mcp"
3
- version = "0.13.1"
3
+ version = "0.15.1"
4
4
  edition = "2021"
5
5
  description = "MCP server that gives AI coding agents persistent memory across sessions"
6
6
  license = "MIT"
package/README.md CHANGED
@@ -198,6 +198,14 @@ rename) so a concurrent reader never sees a partially-written file.
198
198
  | `handoff_get_referral` | Fetch one incoming referral in full — details, suggested tasks, done_criteria, context |
199
199
  | `handoff_update_referral` | Update referral status (open → acknowledged → resolved) |
200
200
 
201
+ ### Timer Coordination
202
+
203
+ | Tool | Purpose |
204
+ |------|---------|
205
+ | `handoff_timer_start` | Start tracking time for a task — delegates to VSCode extension if alive, otherwise starts MCP fallback timer |
206
+ | `handoff_timer_stop` | Stop the timer and log elapsed hours to `actual_hours` — delegates to VSCode if alive |
207
+ | `handoff_timer_get_time` | Get current timer state (elapsed, authority, projected total) without stopping |
208
+
201
209
  ### Project Memory
202
210
 
203
211
  | Tool | Purpose |
@@ -207,6 +215,7 @@ rename) so a concurrent reader never sees a partially-written file.
207
215
  | `handoff_memory_delete` | Delete a memory by id (full id or unique prefix) |
208
216
  | `handoff_memory_cleanup` | Housekeeping (for SessionStart): silently merge exact duplicates, return near-duplicate/stale recommendations, gc old injection sidecars |
209
217
 
218
+ For usage best practices (granularity, scope_paths, conflict handling, cleanup), see `skills/handoff-memory/SKILL.md`.
210
219
  See [Project Memory](#project-memory-1) below for what it is and how to wire automatic injection.
211
220
 
212
221
  ### Task Data Model
@@ -279,6 +288,9 @@ done_task_limit = 10 # Max completed tasks to show
279
288
  auto_git_summary = true # Capture git state automatically
280
289
  require_estimate_hours = true # Require estimate_hours on leaf tasks (default true)
281
290
  ai_estimate_multiplier = 0.2 # Multiplier turning human estimates into AI-effort hours
291
+ timer_provider = "auto" # "auto" | "vscode" | "mcp" | "off"
292
+ timer_authority_ttl_secs = 30 # Heartbeat freshness TTL for authority.json
293
+ timer_idle_timeout_minutes = 10 # Idle pause threshold for MCP fallback timer
282
294
 
283
295
  [dashboard]
284
296
  scan_dirs = ["~/pro/"] # Directories to scan for dashboard
@@ -462,6 +474,45 @@ All under `[settings]` in `.handoff/config.toml`, all with safe defaults
462
474
  | `memory_stale_days` | `60` | Days without a reference before a memory is flagged stale |
463
475
  | `memory_injected_gc_days` | `14` | Age at which per-session injection sidecars are garbage-collected |
464
476
 
477
+ ## CLI API
478
+
479
+ Since v0.15.0, every MCP tool is also callable directly from the shell:
480
+
481
+ ```bash
482
+ handoff-mcp <group> <action> [--key value ...]
483
+ ```
484
+
485
+ All output is JSON on stdout, suitable for scripting and programmatic use
486
+ (e.g. `child_process.execFile` from a VSCode extension).
487
+
488
+ **Examples:**
489
+
490
+ ```bash
491
+ # Memory operations
492
+ handoff-mcp memory save --text "Always use atomic_write" --kind lesson --tags safety,io
493
+ handoff-mcp memory query --text "atomic" --limit 5
494
+ handoff-mcp memory delete --id m-20260630-...
495
+
496
+ # Task management
497
+ handoff-mcp task list --status-filter todo
498
+ handoff-mcp task update --id t1 --title "New task" --status todo --estimate-hours 2
499
+ handoff-mcp task log-time --task-id t1 --hours 0.5
500
+
501
+ # Session and metrics
502
+ handoff-mcp session load
503
+ handoff-mcp metrics
504
+ handoff-mcp dashboard
505
+ ```
506
+
507
+ **Available groups:** `init`, `task`, `session`, `config`, `memory`,
508
+ `referral`, `assignee`, `milestone`, `calendar`, `labels`, `project`,
509
+ `metrics`, `capacity`, `schedule`, `dashboard`, `timer`.
510
+
511
+ Run `handoff-mcp --help` to see all groups, or `handoff-mcp <group> --help`
512
+ for actions within a group. See the
513
+ [CLI API Reference](https://github.com/alphaelements/handoff-mcp/wiki/CLI-API-Reference)
514
+ on the wiki for the full command list.
515
+
465
516
  ## MCP Resources
466
517
 
467
518
  | URI | Description |
@@ -489,6 +540,10 @@ This project uses handoff-mcp for session continuity.
489
540
  - **Decisions**: Record decisions with confidence levels as they are made,
490
541
  not just at session end. Use `confirmed` for verified facts, `estimated`
491
542
  for reasonable assumptions, `unverified` for unknowns.
543
+ - **Timer**: Use `handoff_timer_start` / `handoff_timer_stop` to track task time.
544
+ When the VSCode extension is running, the timer delegates to it automatically.
545
+ When the extension is absent, MCP runs a fallback timer and logs hours on stop.
546
+ Use `handoff_timer_get_time` to check elapsed time without stopping.
492
547
  - **Project memory**: Use `handoff_memory_save` to record durable lessons, rules,
493
548
  conventions, and gotchas that every future session should know. Use
494
549
  `handoff_memory_query` to retrieve relevant memories. Near-duplicate memories are
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-mcp-server",
3
- "version": "0.13.1",
3
+ "version": "0.15.1",
4
4
  "description": "MCP server that gives AI coding agents persistent memory across sessions",
5
5
  "license": "MIT",
6
6
  "author": "AlphaElements <66808803+alphaelements@users.noreply.github.com>",
@@ -105,6 +105,23 @@ Use `handoff_log_time` to record hours worked on a task:
105
105
  `schedule: { milestone: "v2" }` updates only the milestone and preserves
106
106
  `actual_hours`/`remaining_hours`. It never replaces the whole schedule object.
107
107
 
108
+ ### Timer Coordination (MCP ⇄ VSCode)
109
+
110
+ Use `handoff_timer_start` / `handoff_timer_stop` / `handoff_timer_get_time` to
111
+ track task time with automatic VSCode extension coordination:
112
+
113
+ - **`handoff_timer_start`** — if the VSCode extension is running (live authority
114
+ heartbeat), the request is delegated via `.handoff/timer/requests/`. If absent,
115
+ MCP starts a fallback internal timer.
116
+ - **`handoff_timer_stop`** — if delegated, creates a stop request for the extension.
117
+ If MCP is the fallback, stops the timer and atomically logs elapsed hours to
118
+ `actual_hours` (same as `handoff_log_time`).
119
+ - **`handoff_timer_get_time`** — reads `.handoff/timer/state.json` to show
120
+ elapsed time, state (tracking/paused/stopped), and current authority (vscode/mcp).
121
+ - The `timer_provider` config setting controls behavior: `"auto"` (default) uses
122
+ the authority protocol, `"vscode"` always delegates, `"mcp"` always uses
123
+ fallback, `"off"` disables timer tools entirely.
124
+
108
125
  ### Metrics & Project Health
109
126
 
110
127
  Check project health with `handoff_get_metrics` at session start:
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: handoff-memory
3
+ description: "Project memory — save, query, merge, and clean up durable project knowledge (lessons, rules, conventions, gotchas). Triggers on 'メモリ保存', 'これ覚えて', '知見を残して', '過去の知見', 'memory save', 'memory query', 'remember this', 'recall', 'cleanup memories', or when recording post-incident learnings, recurring NG patterns, or project-specific conventions."
4
+ ---
5
+
6
+ # Handoff Memory Skill
7
+
8
+ ## When to use
9
+
10
+ - The user says "メモリ保存", "これ覚えて", "知見を残して", "remember this", "save this as a memory"
11
+ - The user asks "過去の知見を確認", "recall", "what do we know about X"
12
+ - You discover a reusable lesson, recurring NG pattern, or project-specific convention worth persisting across sessions
13
+ - Post-incident feedback or investigation results should be captured
14
+ - Existing memories need merging, updating, or pruning
15
+ - Session start — run cleanup to keep the memory store healthy
16
+
17
+ ## Granularity Rule
18
+
19
+ **One memory = one piece of knowledge.** Do not bundle multiple unrelated lessons
20
+ into a single memory. Each memory should be independently searchable and
21
+ independently deletable. If two ideas always travel together, save them together;
22
+ if they could be relevant in different contexts, split them.
23
+
24
+ ## Saving a Memory
25
+
26
+ Call `handoff_memory_save` with:
27
+
28
+ | Param | Required | Description |
29
+ |---|---|---|
30
+ | `text` | yes | The memory content. Be specific and actionable — "always use X because Y", not "X is important" |
31
+ | `kind` | no (default: `lesson`) | One of: `lesson` (something learned), `rule` (a mandated practice), `convention` (an agreed pattern), `gotcha` (a non-obvious trap) |
32
+ | `scope_paths` | no | Path prefixes this memory applies to (e.g. `["src/storage/", "src/mcp/handlers/"]`). Memories with matching scope_paths get a relevance boost when the user edits files under those paths |
33
+ | `tags` | no | Free-form tags for searchability (e.g. `["atomic-write", "config"]`). Tags are included in the similarity index |
34
+ | `force` | no | `true` to skip near-duplicate detection and save unconditionally |
35
+ | `merge_into` | no | ID of an existing memory to overwrite with merged content (see Merging below) |
36
+ | `absorb_ids` | no | IDs of memories to delete after merging into the target |
37
+
38
+ ### scope_paths Best Practices
39
+
40
+ - Use directory-level substrings, not full file paths: `"src/storage/"` not `"src/storage/config.rs"`. The matching is substring-based (`file_path.contains(scope)`), so `"storage/"` will match any file whose path contains that segment
41
+ - Scope broadly enough to catch related files, narrowly enough to avoid noise
42
+ - A memory with no scope_paths relies purely on text similarity for injection — fine for project-wide rules, but file-specific gotchas benefit from scoping
43
+
44
+ ### tags Best Practices
45
+
46
+ - Use lowercase, hyphenated terms: `"atomic-write"`, `"json-rpc"`, `"config-toml"`
47
+ - Tags supplement the body text for search — add tags for synonyms or concepts not explicitly mentioned in the body
48
+
49
+ ## Handling Near-Duplicate Conflicts
50
+
51
+ When `handoff_memory_save` returns `status: "conflict"`, it means the new memory
52
+ is similar to one or more existing memories (Jaccard similarity >= threshold). The
53
+ response includes both the new text and the similar existing memories.
54
+
55
+ **Decision flow:**
56
+
57
+ 1. **Read both texts carefully.** Are they expressing the same knowledge?
58
+ 2. **If yes — merge:** Write a combined text that captures both perspectives, then call `handoff_memory_save` again with:
59
+ - `text`: the merged content
60
+ - `merge_into`: ID of the existing memory to keep
61
+ - `absorb_ids`: IDs of any other similar memories to absorb
62
+ - Update `kind`, `tags`, `scope_paths` as needed
63
+ 3. **If no — force-save:** The memories are genuinely distinct despite textual similarity. Call `handoff_memory_save` with `force: true` to save the new one separately.
64
+
65
+ Do not ignore conflicts — they indicate the memory store may have redundancy that degrades injection quality.
66
+
67
+ ## Querying Memories
68
+
69
+ Call `handoff_memory_query` with:
70
+
71
+ | Param | Description |
72
+ |---|---|
73
+ | `text` | The prompt or question to match against (BM25 relevance ranking) |
74
+ | `file_paths` | Files being worked on — memories scoped to these paths get a boost |
75
+ | `session_id` | Current session ID — suppresses re-injection of memories already seen this session |
76
+ | `limit` | Max memories to return (default from config, typically 5) |
77
+ | `tool_name` | Tool being used (adds context tokens for matching) |
78
+ | `mark_injected` | Whether to record injection for dedup (default: true) |
79
+
80
+ The query engine uses BM25 text similarity plus a scope_path bonus. Passing
81
+ `session_id` enables per-session diff injection: a memory already injected this
82
+ session (same content hash) is filtered out, but an edited memory (new hash) is
83
+ re-injected.
84
+
85
+ ## Cleanup
86
+
87
+ ### When to Run
88
+
89
+ Run `handoff_memory_cleanup` at the start of each session (the SessionStart hook
90
+ does this automatically if configured). Optional arguments:
91
+
92
+ | Param | Default | Description |
93
+ |---|---|---|
94
+ | `apply_exact_merges` | `true` | Set `false` to skip auto-merging and only return recommendations |
95
+ | `stale_days` | from config (default 60) | Override the staleness threshold in days |
96
+
97
+ It performs three passes:
98
+
99
+ 1. **Exact duplicates** — silently auto-merged (lossless; oldest entry kept, others absorbed). Skipped when `apply_exact_merges` is `false`
100
+ 2. **Near-duplicate clusters** — returned as recommendations for AI-driven merge
101
+ 3. **Stale memories** — memories not referenced for `stale_days`, returned as recommendations
102
+
103
+ ### Acting on Cleanup Recommendations
104
+
105
+ - **similar_clusters**: Review each cluster. If the memories are truly redundant, merge them with `handoff_memory_save(merge_into=..., absorb_ids=[...])`. If distinct, leave them.
106
+ - **stale**: Consider whether the memory is obsolete (delete with `handoff_memory_delete`) or simply rarely triggered (leave it — low hit_count doesn't mean low value).
107
+
108
+ ### Manual Deletion
109
+
110
+ Call `handoff_memory_delete` with the memory `id` (full ID or unique prefix). Use
111
+ this for memories that are confirmed obsolete, wrong, or superseded by a code
112
+ change.
113
+
114
+ ## Memory Hooks (Optional Auto-Injection)
115
+
116
+ Memory tools can be called manually, but for automatic injection without AI
117
+ initiative, configure Claude Code hooks:
118
+
119
+ | Hook | Tool Call | Purpose |
120
+ |---|---|---|
121
+ | `UserPromptSubmit` | `handoff_memory_query` (prompt text) | Inject memories relevant to each user prompt |
122
+ | `PreToolUse` (`Edit\|Write\|MultiEdit`) | `handoff_memory_query` (file path) | Inject memories scoped to the file being edited |
123
+ | `SessionStart` | `handoff_memory_cleanup` | Auto-merge exact duplicates, surface recommendations |
124
+
125
+ Run `handoff-mcp setup` to install these hooks automatically, or
126
+ `handoff-mcp setup --uninstall` to remove them. See the README for the full hook
127
+ JSON configuration.
128
+
129
+ ## Memory vs Session vs CLAUDE.md
130
+
131
+ | Layer | Lifespan | Purpose |
132
+ |---|---|---|
133
+ | Sessions (`.handoff/sessions/`) | Per-conversation | "What was I doing last time?" |
134
+ | Memory (`.handoff/memory/`) | Cross-session, long-lived | "What has this project learned?" |
135
+ | `CLAUDE.md` | Permanent | Foundational rules, build commands, repo structure |
136
+ | `.claude/skills/` | Permanent | Operational procedures triggered by intent |
137
+
138
+ Rule changes: discover via memory → codify in skills/CLAUDE.md. Memory is the
139
+ source of evidence; skills and CLAUDE.md are the source of authority.