handoff-mcp-server 0.20.0 → 0.22.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.
package/Cargo.lock CHANGED
@@ -37,9 +37,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
37
37
 
38
38
  [[package]]
39
39
  name = "cc"
40
- version = "1.2.65"
40
+ version = "1.2.67"
41
41
  source = "registry+https://github.com/rust-lang/crates.io-index"
42
- checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
42
+ checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
43
43
  dependencies = [
44
44
  "find-msvc-tools",
45
45
  "shlex",
@@ -123,6 +123,15 @@ dependencies = [
123
123
  "slab",
124
124
  ]
125
125
 
126
+ [[package]]
127
+ name = "getopts"
128
+ version = "0.2.24"
129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
130
+ checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
131
+ dependencies = [
132
+ "unicode-width",
133
+ ]
134
+
126
135
  [[package]]
127
136
  name = "getrandom"
128
137
  version = "0.4.3"
@@ -136,11 +145,12 @@ dependencies = [
136
145
 
137
146
  [[package]]
138
147
  name = "handoff-mcp"
139
- version = "0.20.0"
148
+ version = "0.22.0"
140
149
  dependencies = [
141
150
  "anyhow",
142
151
  "chrono",
143
152
  "lexsim",
153
+ "pulldown-cmark",
144
154
  "serde",
145
155
  "serde_json",
146
156
  "tempfile",
@@ -208,9 +218,9 @@ dependencies = [
208
218
 
209
219
  [[package]]
210
220
  name = "lexsim"
211
- version = "0.4.0"
221
+ version = "0.6.0"
212
222
  source = "registry+https://github.com/rust-lang/crates.io-index"
213
- checksum = "14b08f26a4b07325e83c0211adb36d0223fae644534dacd4eae72153c1ccd873"
223
+ checksum = "b64f48de8c9d203c0ed3ac8c68e9ba1072206d5072551c08889a4bbae9d84677"
214
224
  dependencies = [
215
225
  "unicode-normalization",
216
226
  "unicode-segmentation",
@@ -236,9 +246,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
236
246
 
237
247
  [[package]]
238
248
  name = "memchr"
239
- version = "2.8.2"
249
+ version = "2.8.3"
240
250
  source = "registry+https://github.com/rust-lang/crates.io-index"
241
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
251
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
242
252
 
243
253
  [[package]]
244
254
  name = "num-traits"
@@ -270,6 +280,25 @@ dependencies = [
270
280
  "unicode-ident",
271
281
  ]
272
282
 
283
+ [[package]]
284
+ name = "pulldown-cmark"
285
+ version = "0.13.4"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
288
+ dependencies = [
289
+ "bitflags",
290
+ "getopts",
291
+ "memchr",
292
+ "pulldown-cmark-escape",
293
+ "unicase",
294
+ ]
295
+
296
+ [[package]]
297
+ name = "pulldown-cmark-escape"
298
+ version = "0.11.0"
299
+ source = "registry+https://github.com/rust-lang/crates.io-index"
300
+ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
301
+
273
302
  [[package]]
274
303
  name = "quote"
275
304
  version = "1.0.46"
@@ -300,9 +329,9 @@ dependencies = [
300
329
 
301
330
  [[package]]
302
331
  name = "rustversion"
303
- version = "1.0.22"
332
+ version = "1.0.23"
304
333
  source = "registry+https://github.com/rust-lang/crates.io-index"
305
- checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
334
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
306
335
 
307
336
  [[package]]
308
337
  name = "serde"
@@ -415,9 +444,9 @@ dependencies = [
415
444
 
416
445
  [[package]]
417
446
  name = "tinyvec"
418
- version = "1.11.0"
447
+ version = "1.12.0"
419
448
  source = "registry+https://github.com/rust-lang/crates.io-index"
420
- checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
449
+ checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
421
450
  dependencies = [
422
451
  "tinyvec_macros",
423
452
  ]
@@ -469,6 +498,12 @@ version = "0.1.2"
469
498
  source = "registry+https://github.com/rust-lang/crates.io-index"
470
499
  checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
471
500
 
501
+ [[package]]
502
+ name = "unicase"
503
+ version = "2.9.0"
504
+ source = "registry+https://github.com/rust-lang/crates.io-index"
505
+ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
506
+
472
507
  [[package]]
473
508
  name = "unicode-ident"
474
509
  version = "1.0.24"
@@ -490,6 +525,12 @@ version = "1.13.3"
490
525
  source = "registry+https://github.com/rust-lang/crates.io-index"
491
526
  checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
492
527
 
528
+ [[package]]
529
+ name = "unicode-width"
530
+ version = "0.2.2"
531
+ source = "registry+https://github.com/rust-lang/crates.io-index"
532
+ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
533
+
493
534
  [[package]]
494
535
  name = "wasm-bindgen"
495
536
  version = "0.2.126"
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "handoff-mcp"
3
- version = "0.20.0"
3
+ version = "0.22.0"
4
4
  edition = "2021"
5
5
  description = "MCP server that gives AI coding agents persistent memory across sessions"
6
6
  license = "MIT"
@@ -21,6 +21,7 @@ chrono = { version = "0.4", features = ["serde"] }
21
21
  anyhow = "1"
22
22
  thiserror = "2"
23
23
  lexsim = ">=0.1.0, <1.0.0"
24
+ pulldown-cmark = "0.13"
24
25
 
25
26
  [dev-dependencies]
26
27
  tempfile = "3.27.0"
package/README.md CHANGED
@@ -419,6 +419,26 @@ rename) so a concurrent reader never sees a partially-written file.
419
419
  For usage best practices (granularity, scope_paths, conflict handling, cleanup), see `skills/handoff-memory/SKILL.md`.
420
420
  See [Project Memory](#project-memory-1) below for what it is and how to wire automatic injection.
421
421
 
422
+ ### Document Management
423
+
424
+ | Tool | Purpose |
425
+ |------|---------|
426
+ | `handoff_doc_save` | Create or update a document (auto-splits into fragments) |
427
+ | `handoff_doc_get` | Read a document — full, meta, or single fragment |
428
+ | `handoff_doc_list` | List/search documents with BM25 and filters |
429
+ | `handoff_doc_delete` | Delete a document and its fragments |
430
+ | `handoff_doc_reassemble` | Reconstruct original Markdown from fragments |
431
+ | `handoff_doc_tree` | Walk family tree (ancestors/descendants/related) |
432
+ | `handoff_doc_query` | Context injection — staged full/outline, hook-driven |
433
+ | `handoff_doc_analyze` | Read-only heuristic scan (import step 1) |
434
+ | `handoff_doc_import` | Atomic bulk write after analysis (import step 3) |
435
+
436
+ Documents live in `.handoff/docs/` (`_doc.*.json` metadata + `_frag.*.{json,md}`
437
+ fragments). Large Markdown is split into fragments on save; `handoff_doc_reassemble`
438
+ reconstructs the original with drift detection, and `handoff_doc_query` feeds
439
+ staged (outline-first, then full-text) context to the agent — the same mechanism
440
+ that powers the hook-driven injection described below.
441
+
422
442
  ### Task Data Model
423
443
 
424
444
  Tasks are stored as a directory tree with status encoded in filenames:
@@ -584,14 +604,23 @@ what the agent intends, call `handoff_memory_query`, and inject the matching mem
584
604
  the same memory is **not injected twice in one session** — and an *edited* memory
585
605
  (new content hash) is re-injected.
586
606
 
607
+ The same hooks also call `handoff_doc_query`, so relevant documents (saved via
608
+ `handoff_doc_save`) are staged into context alongside memories — outline first,
609
+ then full text as relevance/budget allows.
610
+
587
611
  | Event | Calls | Effect |
588
612
  |-------|-------|--------|
589
- | `UserPromptSubmit` | `handoff_memory_query` (prompt text) | Inject memories relevant to the prompt |
590
- | `PreToolUse` (`Edit\|Write\|MultiEdit`) | `handoff_memory_query` (file path) | Inject memories scoped to the file being edited |
613
+ | `UserPromptSubmit` | `handoff_memory_query` (prompt text), `handoff_doc_query` (prompt text) | Inject memories and documents relevant to the prompt |
614
+ | `PreToolUse` (`Edit\|Write\|MultiEdit`) | `handoff_memory_query` (file path), `handoff_doc_query` (file path) | Inject memories and documents scoped to the file being edited |
591
615
 
592
616
  `handoff_memory_cleanup` (merge exact duplicates, gc old sidecars) is not wired
593
617
  to a hook — call it manually or from a CLI/cron job when you want housekeeping.
594
618
 
619
+ > The bundled `plugin-hooks/hooks/hooks.json` (used by the "Handoff MCP — Memory
620
+ > & Document Hooks" plugin) already wires both `handoff_memory_query` and
621
+ > `handoff_doc_query` on `UserPromptSubmit` and `PreToolUse`; the JSON examples
622
+ > below show the equivalent hand-written config for non-plugin setups.
623
+
595
624
  > **Wire hooks in your *user/global* settings, not in the repo.** Hooks are a
596
625
  > personal workflow choice; the handoff-mcp repo does not ship a `.claude/`
597
626
  > hooks config, and you should not commit one into a shared project. Put the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-mcp-server",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
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>",
@@ -239,6 +239,30 @@ Use `handoff_bulk_update_tasks` for:
239
239
  when moving a task out of `blocked`/`skipped`. Parent tasks and the statuses
240
240
  `blocked`/`skipped` are exempt.
241
241
 
242
+ ### Document Management
243
+
244
+ For structured, multi-section documents (specs, designs, ADRs, guides) that
245
+ are too large for a single memory entry, use the doc tools instead — see the
246
+ `handoff-docs` skill for full parameter details and workflows:
247
+
248
+ | Tool | Purpose |
249
+ |---|---|
250
+ | `handoff_doc_save` | Create/update a document — auto-splits `body` into fragments at `##` (configurable via `split_level`) |
251
+ | `handoff_doc_get` | Read a document — `full` (reassembled), `meta` (manifest only), or `fragment` (one section) |
252
+ | `handoff_doc_list` | List/search documents (BM25), filter by `doc_type`/`tags`/`task_id` |
253
+ | `handoff_doc_delete` | Delete a document and its fragments (also unlinks it from tasks) |
254
+ | `handoff_doc_reassemble` | Reconstruct the original Markdown from fragments, with drift detection |
255
+ | `handoff_doc_tree` | Traverse the family tree — parent/child (structural) + related (semantic: `supersedes`/`references`/`implements`/`extends`/`conflicts`) |
256
+ | `handoff_doc_query` | Context injection (hook-driven) — staged `full`/`outline` results by fragment size |
257
+ | `handoff_doc_analyze` | Read-only heuristic scan of a file/directory — step 1 of importing existing docs |
258
+ | `handoff_doc_import` | Atomic bulk write of analyzed + AI-reviewed documents — step 3 of importing existing docs |
259
+
260
+ `handoff_doc_save(task_ids: [...])` creates a **bidirectional** doc↔task
261
+ link: the document gains a `task_ids` entry and each linked task gains a
262
+ `TaskLink` in its `task_links`. Look it up from either side with
263
+ `handoff_doc_list(task_id: ...)` (docs linked to a task) or
264
+ `handoff_get_task(id: ...)` (inspect `task_links` on the task record).
265
+
242
266
  ### Configuration Management
243
267
 
244
268
  Use `handoff_update_config` to manage project settings via dot-notation keys:
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: handoff-docs
3
+ description: "Document management — save, read, search, import, and traverse structured project documents (specs, designs, ADRs, guides, notes). Triggers on 'ドキュメント保存', '仕様書を管理', '設計書をインポート', 'save this doc', 'import specs', 'document management', or when the user asks to persist/organize/search multi-section markdown that is too structured for a single memory entry."
4
+ ---
5
+
6
+ # Handoff Docs Skill
7
+
8
+ ## When to use
9
+
10
+ - The user asks to save a spec, design doc, ADR, guide, or note so it survives across sessions
11
+ - The user wants to import an existing pile of Markdown files (e.g. a `wiki/` or `specs/` directory) into structured document management
12
+ - You need to read a specific section of a large document without pulling the whole thing into context
13
+ - You need to trace how documents relate to each other (parent/child hierarchy, or semantic links like "this design implements that spec")
14
+ - A document should be linked to one or more tasks so it surfaces automatically while that task is being worked on
15
+
16
+ If the knowledge is a short, standalone lesson/rule/convention/gotcha (< 1 page,
17
+ no internal sections), use `handoff-memory` instead — see "Memory vs Documents"
18
+ in `skills/handoff-memory/SKILL.md` for the boundary.
19
+
20
+ ## The 9 Doc Tools
21
+
22
+ | Tool | Purpose |
23
+ |---|---|
24
+ | `handoff_doc_save` | Create or update a document. Splits `body` into fragments automatically. |
25
+ | `handoff_doc_get` | Read a document — `full` (reassembled body), `meta` (manifest only), or `fragment` (one section). |
26
+ | `handoff_doc_list` | List/search documents (BM25 over title + fragment bodies), filter by `doc_type`, `tags`, `task_id`. |
27
+ | `handoff_doc_delete` | Delete a document and all its fragments; unlinks it from any linked tasks. |
28
+ | `handoff_doc_reassemble` | Reconstruct the original Markdown from fragments, with drift detection. |
29
+ | `handoff_doc_tree` | Walk the family tree (ancestors/descendants/related) for a document. |
30
+ | `handoff_doc_query` | Context injection — hook-driven, staged `full`/`outline` results ranked by relevance. |
31
+ | `handoff_doc_analyze` | Read-only heuristic scan of a file or directory — step 1 of the import flow. |
32
+ | `handoff_doc_import` | Atomic bulk write of analyzed + AI-reviewed documents — step 3 of the import flow. |
33
+
34
+ ### `handoff_doc_save`
35
+
36
+ | Param | Required | Description |
37
+ |---|---|---|
38
+ | `title` | yes | Document title |
39
+ | `body` | yes | Full Markdown source — this is what gets split into fragments |
40
+ | `doc_type` | no | One of `spec`, `design`, `adr`, `guide`, `note` |
41
+ | `tags` | no | Free-form tags, folded into the BM25 index |
42
+ | `scope_paths` | no | Path prefixes this doc applies to — boosts relevance in `doc_query` when the matching file is being edited |
43
+ | `parent_id` | no | Places this document under a parent in the family tree |
44
+ | `related` | no | Array of `{ id, rel }` — semantic links to other documents (see Family Tree below) |
45
+ | `task_ids` | no | Task IDs to bidirectionally link (see Task Linking below) |
46
+ | `split_level` | no | ATX heading level to split on (default: `2`, i.e. `##`) |
47
+ | `auto_inject` | no | Injection hint: `auto` (default) \| `full` \| `outline` \| `none` |
48
+ | `doc_id` | no | Provide to update an existing document; omit to create a new one |
49
+
50
+ ### `handoff_doc_get`
51
+
52
+ | Param | Required | Description |
53
+ |---|---|---|
54
+ | `doc_id` | yes | Document to read |
55
+ | `format` | no | `full` (default-equivalent reassembly) \| `meta` (manifest only, no body) \| `fragment` |
56
+ | `seq` | when `format=fragment` | Fragment sequence number to return |
57
+
58
+ Use `meta` when you only need to walk the graph (titles, tags, relations)
59
+ without paying the token cost of fragment bodies. Use `fragment` with a `seq`
60
+ from an outline injection (see Staged Injection) to fetch exactly the section
61
+ you need.
62
+
63
+ ### `handoff_doc_list`
64
+
65
+ | Param | Required | Description |
66
+ |---|---|---|
67
+ | `query` | no | BM25 search over title + fragment bodies |
68
+ | `doc_type` | no | Filter by type |
69
+ | `tags` | no | Filter by tags |
70
+ | `task_id` | no | Documents linked to this task |
71
+ | `include_body` | no | Default `false` — metadata-only listing |
72
+
73
+ ### `handoff_doc_delete`
74
+
75
+ | Param | Required | Description |
76
+ |---|---|---|
77
+ | `doc_id` | yes | Document to delete, along with all its fragments |
78
+
79
+ Deleting also removes the document from any linked task's `task_links`.
80
+
81
+ ### `handoff_doc_reassemble`
82
+
83
+ | Param | Required | Description |
84
+ |---|---|---|
85
+ | `doc_id` | yes | Document to reconstruct |
86
+ | `output_path` | no | If set, also writes the reassembled Markdown to this path |
87
+
88
+ Fragments are concatenated in `seq` order with original heading markers
89
+ preserved — `save(body) → reassemble()` is byte-identical. If a fragment was
90
+ edited directly after the split, its `content_hash` no longer matches and
91
+ `reassemble` reports the drift instead of silently returning stale content.
92
+
93
+ ### `handoff_doc_tree`
94
+
95
+ | Param | Required | Description |
96
+ |---|---|---|
97
+ | `doc_id` | yes | Root of the traversal |
98
+ | `depth` | no | How many parent/child levels to return |
99
+ | `include_related` | no | Whether to also include semantically `related` documents |
100
+
101
+ ### `handoff_doc_query`
102
+
103
+ | Param | Required | Description |
104
+ |---|---|---|
105
+ | `text` | no | Prompt/query text (BM25 relevance ranking) |
106
+ | `file_paths` | no | Files being worked on — boosts documents whose `scope_paths` prefix-match |
107
+ | `task_id` | no | Boosts documents linked to this task |
108
+ | `session_id` | no | Enables per-session dedup — a fragment already injected this session is skipped unless its content changed |
109
+ | `limit` | no | Max fragments to return |
110
+ | `mark_injected` | no | Whether to record injection for dedup (default `true`) |
111
+ | `suppress_doc_ids` | no | Document ids to exclude entirely from this call's results |
112
+ | `suppress_until_changed` | no | With `suppress_doc_ids` and `session_id`: persists the suppression in the session sidecar so those documents stay excluded from future calls until their `content_hash` changes (default `false`) |
113
+
114
+ This is the hook-driven tool — see Staged Injection below for how results are shaped.
115
+
116
+ ### `handoff_doc_analyze`
117
+
118
+ | Param | Required | Description |
119
+ |---|---|---|
120
+ | `path` | yes | File or directory to scan |
121
+ | `recursive` | no | Recurse into subdirectories |
122
+ | `flatten` | no | Skip hierarchy inference (no `parent_id` guessing) |
123
+
124
+ Read-only — writes nothing. Returns a conditioning report with
125
+ `auto_resolved` entries (high-confidence `doc_type`/`tags`/`scope_paths`) and
126
+ `needs_review` entries (broken links, missing relationships, near-duplicates)
127
+ each carrying a concrete `suggestion` the AI can approve, edit, or reject.
128
+
129
+ ### `handoff_doc_import`
130
+
131
+ | Param | Required | Description |
132
+ |---|---|---|
133
+ | `analyzed` | yes | The `handoff_doc_analyze` output (possibly after AI review) |
134
+ | `overrides` | no | Per-file corrections (`doc_type`, relationship resolutions, etc.) |
135
+ | `task_ids` | no | Link every imported document to these tasks |
136
+
137
+ Writes all `_doc.*.json` + `_frag.*.{json,md}` atomically in one transaction,
138
+ including any task links — matches the "validate whole tree, then write"
139
+ pattern used by `handoff_import_context` for tasks.
140
+
141
+ ## Staged Injection (outline vs full)
142
+
143
+ `handoff_doc_query` avoids flooding context with large documents:
144
+
145
+ | Mode | When | What's injected |
146
+ |---|---|---|
147
+ | `full` | Fragment body <= `doc_inline_threshold` tokens (default 300) | Metadata + the entire fragment body |
148
+ | `outline` | Fragment body > threshold | Metadata + heading list only — no body. Read a specific section with `handoff_doc_get(format="fragment", seq=N)` |
149
+
150
+ This means short documents (ADRs, conventions, short notes) get pulled in
151
+ ready-to-use, while long documents (full specs, design docs) only announce
152
+ their structure — the AI decides which section is actually worth fetching.
153
+
154
+ ### `auto_inject` override
155
+
156
+ Set on `handoff_doc_save` (or later via an update) to force behavior
157
+ regardless of size:
158
+
159
+ | Value | Effect |
160
+ |---|---|
161
+ | `auto` (default) | Size-based automatic choice between `full`/`outline` |
162
+ | `full` | Always inject the full body |
163
+ | `outline` | Always inject headings only, even if small |
164
+ | `none` | Never auto-inject — only surfaced via explicit `handoff_doc_get` |
165
+
166
+ ## Family Tree
167
+
168
+ Two distinct kinds of document relationship:
169
+
170
+ - **parent/child** (`parent_id`) — structural hierarchy, e.g. a directory of
171
+ specs where each file's document is a child of the directory's document.
172
+ - **related** (`related: [{ id, rel }]`) — semantic links between documents
173
+ that are not structurally nested. `rel` is one of:
174
+
175
+ | `rel` | Meaning |
176
+ |---|---|
177
+ | `supersedes` | Replaces the target (a version bump) |
178
+ | `references` | Loose cross-reference |
179
+ | `implements` | This document implements the spec the target describes |
180
+ | `extends` | Extends the target without replacing it |
181
+ | `conflicts` | Known contradiction that needs resolving |
182
+
183
+ Use `handoff_doc_tree` to walk both kinds together (`include_related: true`)
184
+ or just the structural hierarchy.
185
+
186
+ ## Task Linking
187
+
188
+ `handoff_doc_save(task_ids: [...])` creates a **bidirectional** link:
189
+
190
+ 1. The document's own `task_ids` field is set.
191
+ 2. Each linked task gets a `TaskLink { target: doc_id, link_type: "doc", label: <doc title> }` entry in its `task_links`.
192
+ 3. Deleting the document removes it from the linked tasks' `task_links` automatically.
193
+
194
+ Look up the relationship from either side:
195
+ - `handoff_doc_list(task_id: "T-79")` — documents linked to a task.
196
+ - `handoff_get_task(task_id: "T-79")` — inspect `task_links` on the task record to
197
+ see which documents (and other targets) it links to.
198
+
199
+ Note: there is no dedicated `doc_id` filter on `handoff_list_tasks` — the
200
+ `task_links` field is populated and readable per-task via `handoff_get_task`,
201
+ but a document → "which tasks link to me" listing must be done by scanning
202
+ `task_links` yourself, not via a built-in filter.
203
+
204
+ ## Fragment Granularity
205
+
206
+ - Default split boundary: ATX heading level 2 (`##`).
207
+ - Override per-call with `split_level` on `handoff_doc_save` (e.g. `1` to
208
+ split only on `#`, or `3` for finer-grained `###` sections).
209
+ - Content before the first qualifying heading becomes fragment `seq: 0` (the
210
+ preamble). Nested headings below `split_level` stay inside their parent
211
+ fragment rather than becoming their own fragment.
212
+
213
+ ## Import Workflow (3 steps)
214
+
215
+ Use this instead of calling `doc_save` file-by-file when bringing in an
216
+ existing pile of Markdown — cross-document relationships and duplicates can
217
+ only be validated when every file is visible at once.
218
+
219
+ 1. **`handoff_doc_analyze(path, recursive, flatten)`** — read-only scan.
220
+ Returns `auto_resolved` (confident guesses) and `needs_review` (broken
221
+ links, missing relationships, near-duplicates), each with a `suggestion`.
222
+ 2. **AI reviews the report** — approve `auto_resolved` entries as-is, and for
223
+ each `needs_review` item either accept the `suggestion`, correct it, or
224
+ reject it. Nothing is written yet.
225
+ 3. **`handoff_doc_import(analyzed, overrides, task_ids)`** — takes the
226
+ analyzed payload plus the AI's overrides and writes the whole tree
227
+ atomically, including task links.
228
+
229
+ ## `doc_type` Values
230
+
231
+ `spec` (requirements/behavior contracts) · `design` (architecture/design
232
+ docs) · `adr` (architecture decision records) · `guide` (how-to/operational
233
+ docs) · `note` (fallback — anything that doesn't fit the above).
@@ -33,5 +33,13 @@ user-invocable: true
33
33
  - **決定事項**: confidence が `unverified` のものを警告
34
34
  - **申し送り**: `caution` を最初に、`context` → `suggestion` の順
35
35
  - **コンテキストポインタ**: 次に読むべきファイル一覧
36
- 6. ユーザーに現状サマリーを日本語で報告
37
- 7. 次のアクションを提案(「何から始めますか?」)
36
+ 6. **関連ドキュメントの確認**:
37
+ - `in_progress` タスクにリンクされたドキュメントがあれば、フックの
38
+ `handoff_doc_query`(`UserPromptSubmit`/`PreToolUse`)が自動注入する
39
+ — セッション開始時点で明示的に呼ぶ必要はない
40
+ - 特定のセクションだけ読みたい場合は `handoff_doc_get(doc_id, format="fragment", seq=N)`
41
+ を手動で呼ぶ(outline 注入で見出し一覧だけ渡された場合など)
42
+ - ドキュメント管理の詳細(段階的注入・家系図・インポート手順)は
43
+ `handoff-docs` スキルを参照
44
+ 7. ユーザーに現状サマリーを日本語で報告
45
+ 8. 次のアクションを提案(「何から始めますか?」)
@@ -137,3 +137,23 @@ JSON configuration.
137
137
 
138
138
  Rule changes: discover via memory → codify in skills/CLAUDE.md. Memory is the
139
139
  source of evidence; skills and CLAUDE.md are the source of authority.
140
+
141
+ ## Memory vs Documents
142
+
143
+ | | Memory | Documents |
144
+ |---|---|---|
145
+ | Shape | Short, atomic lesson/rule/convention/gotcha | Structured, multi-section Markdown (specs, designs, ADRs, guides) |
146
+ | Storage | One flat entry (`.handoff/memory/`) | Split into fragments with a family tree (`.handoff/docs/`) |
147
+ | Relationships | None | parent/child (structural) + `related` (semantic: supersedes/references/implements/extends/conflicts) |
148
+ | Task linking | Via `scope_paths` boost only | Bidirectional `task_ids` ↔ `task_links` |
149
+ | Injection | Always inline (BM25 similarity) | Staged — `full` body for small fragments, `outline` (headings only) for large ones |
150
+
151
+ **Boundary rule**: if it is a standalone piece of knowledge that fits in a
152
+ paragraph or two (< 1 page, no internal sections), save it as a memory. If it
153
+ has structure — sections, a hierarchy, cross-references to other documents —
154
+ use document management (`handoff_doc_save`; see the `handoff-docs` skill).
155
+
156
+ There is **no migration path** between the two stores — they are independent.
157
+ If a memory grows into something that needs sections and cross-references,
158
+ recreate it as a document with `handoff_doc_save` and delete the memory with
159
+ `handoff_memory_delete` rather than expecting an automatic conversion.