@fenglimg/fabric-cli 2.0.0-rc.10 → 2.0.0-rc.13

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.
@@ -14,18 +14,111 @@ This skill is invoked when one of the following holds:
14
14
  - The user typed an explicit archive request (e.g. "archive what we just did", "fabric archive")
15
15
  - A task wrap-up moment where the agent itself判定 a worth-keeping insight has surfaced
16
16
 
17
- If none of the above hold, stop the skill immediately and tell the user `没有触发归档信号;如需手动归档请显式调用 fabric-archive`.
17
+ If none of the above hold, stop the skill immediately and tell the user (UX i18n Policy class 2 — errors/preconditions):
18
+
19
+ - zh-CN: `没有触发归档信号;如需手动归档请显式调用 fabric-archive`
20
+ - en: `No archive signal detected; to manually archive, explicitly invoke fabric-archive`
21
+
22
+ (Render per `fabric_language` resolved in Phase 0.6 Config Load below.)
18
23
 
19
24
  This skill is `Check-not-Ask`, not a preference interview:
20
25
 
21
26
  - Phase 0 proactively gathers candidate evidence from the session
22
27
  - Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
23
28
  - Phase 1 classifies / layers / slugs each candidate and presents one batch review for user correction
24
- - Phase 1.5 assigns `scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
29
+ - Phase 1.5 assigns `relevance_scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
25
30
  - Phase 2 calls `fab_extract_knowledge` once per confirmed candidate
26
31
 
27
32
  ## 执行流程 (5 Phase / 1 User Review Round)
28
33
 
34
+ ### Phase 0.6 — Config Load
35
+
36
+ Before any candidate-gathering work, the skill MUST read
37
+ `.fabric/fabric-config.json` to resolve the following tunables (with documented
38
+ defaults if absent):
39
+
40
+ | Config field | Default | Used by |
41
+ |---|---|---|
42
+ | `archive_max_candidates_per_batch` | 8 | Phase 0 hard budget on candidates per Phase 1 batch |
43
+ | `archive_max_recent_paths` | 20 | Phase 0 cap on `recent_paths` enumeration |
44
+ | `archive_digest_max_sessions` | 10 | Phase 0.0 cap on cross-session digest load |
45
+
46
+ If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
47
+
48
+ ### UX i18n Policy (5-class bilingualization)
49
+
50
+ The skill consults `fabric_language` from `.fabric/fabric-config.json`
51
+ (固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
52
+ CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
53
+ `"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
54
+ following 5 categories MUST be rendered in the resolved language:
55
+
56
+ 1. **Roll-up templates** — the `# Archive Review — N candidates` batch
57
+ review block (one per candidate) AND any final session summary the
58
+ skill emits after Phase 2 completes. zh-CN ↔ en mirror.
59
+ 2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
60
+ the "没有触发归档信号…" trigger-miss and the "本次会话为常规执行…"
61
+ viability-gate-FAIL message). zh-CN ↔ en mirror.
62
+ 3. **Confirmation prompts** — the per-candidate `Confirm? (Y to accept,
63
+ edit … inline, N to skip)` line in the batch review template. zh-CN
64
+ ↔ en mirror.
65
+ 4. **Dry-run table headers** — fabric-archive does not currently expose
66
+ a dry-run mode; this slot is reserved for parity with fabric-import.
67
+ IF a future revision adds dry-run, the table header MUST be
68
+ bilingualized per this policy. zh-CN ↔ en mirror.
69
+ 5. **AskUserQuestion** — `header` + `question` fields (NOT `options[]`).
70
+ zh-CN ↔ en mirror. fabric-archive itself does not surface
71
+ AskUserQuestion in the current contract (Phase 1 batch review is a
72
+ single markdown screen, not a structured question), but if a future
73
+ version adds one — e.g. to confirm layer flip — this rule applies.
74
+
75
+ Rendering rule:
76
+
77
+ - `fabric_language === "zh-CN"` → emit the zh-CN variant; pure monolingual, no language mixing inside a single user-facing block.
78
+ - `fabric_language === "en"` → emit the en variant; pure monolingual, no language mixing inside a single user-facing block.
79
+ - `fabric_language === "zh-CN-hybrid"` → emit Chinese narrative prose with English technical terms preserved. Protected tokens (always EN): MCP tool names (e.g. `fab_get_knowledge_sections`), CLI command names (e.g. `fab install`), file paths, technical concepts (`Skill`, `SessionStart`, `hook`, `MCP`, `revision_hash`, `pending`, `proven`, `verified`, `draft`).
80
+ - `fabric_language === "match-existing"` or any other value → emit the en variant; pure monolingual.
81
+
82
+ Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
83
+ `relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
84
+ `session_context`, `pending_path`, `layer`, `team`, `personal`,
85
+ `knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, the verbatim
86
+ `强 team` / `强 personal` / `默认 team` heuristic block, etc.) are NEVER
87
+ translated — they appear verbatim in both language variants. The
88
+ bilingualization scope is prose ONLY.
89
+
90
+ ### AskUserQuestion i18n Policy (value vs label)
91
+
92
+ When a skill (this one or any sibling skill the user is composing with)
93
+ issues an `AskUserQuestion`, the `header` and `question` strings are
94
+ user-facing prose → translated per `fabric_language`. The `options[]`
95
+ array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
96
+ fabric-review, or `["team", "personal"]` for a layer-flip target) are
97
+ **routing keys** consumed by the skill state machine — they MUST remain
98
+ English regardless of `fabric_language`.
99
+
100
+ ```ts
101
+ // EN (fabric_language === "en")
102
+ AskUserQuestion({
103
+ header: "Layer-flip target",
104
+ question: "Move '{title}' to which layer? (current: {current_layer})",
105
+ options: ["team", "personal"]
106
+ })
107
+
108
+ // zh-CN (fabric_language === "zh-CN")
109
+ AskUserQuestion({
110
+ header: "Layer 切换目标",
111
+ question: "将 '{title}' 切换到哪一层?(当前: {current_layer})",
112
+ options: ["team", "personal"] // 不翻译 — routing key
113
+ })
114
+ ```
115
+
116
+ Rationale: localizing routing keys would force every routing branch to
117
+ dual-string match (e.g. `if (choice === "team" || choice === "团队")`),
118
+ which doubles the surface area for protected-token regressions and breaks
119
+ the option-list invariants that downstream tooling depends on. Keeping
120
+ `options[]` English-only is contract-locked across all three skills.
121
+
29
122
  ### Phase 0.0 — Collect Cross-Session Digests (v2.0.0-rc.7 T5)
30
123
 
31
124
  Before any single-session collection or viability gating, stitch together
@@ -48,7 +141,8 @@ messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
48
141
  `.fabric/.cache/session-digests/<session_id>.md`. Missing digest files
49
142
  degrade silently (the digest write was best-effort, so a Stop hook crash
50
143
  can produce a session_id without a digest). Cap the loaded digest set at
51
- 10 most-recent sessions to bound LLM context (~50KB worst-case).
144
+ `archive_digest_max_sessions` most-recent sessions (config-resolved, default
145
+ 10) to bound LLM context (~50KB worst-case at default).
52
146
  5. **Build cross-session context.** Concatenate the loaded digests into a
53
147
  single `### Cross-session digest` block to carry into Phase 0.5 + Phase 1.
54
148
  Use this block to:
@@ -71,11 +165,11 @@ Gather raw evidence from the recent session before any classification:
71
165
  1. Read the tail of `.fabric/events.jsonl` since the last `knowledge_proposed` event.
72
166
  - Use `Bash` with `tail -n 200 .fabric/events.jsonl` if the file is large.
73
167
  - Tolerate ENOENT — empty ledger is a normal first-run state.
74
- 2. Enumerate `recent_paths`: workspace files touched by Read/Edit/Write in the current session. Cap at 20 most-recent paths.
168
+ 2. Enumerate `recent_paths`: workspace files touched by Read/Edit/Write in the current session. Cap at `archive_max_recent_paths` most-recent paths (config-resolved, default 20).
75
169
  3. Distill `user_messages_summary`: a compact (≤500 char) prose summary of what the user asked for and what was decided. NOT a verbatim transcript.
76
170
  4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
77
171
 
78
- Hard budget: 8 candidates max per Phase 1 batch. If more surface, keep the 8 with strongest worth-archiving signals (see Phase 1 type definitions) and drop the rest.
172
+ Hard budget: `archive_max_candidates_per_batch` candidates max per Phase 1 batch (config-resolved, default 8). If more surface, keep the configured-N with strongest worth-archiving signals (see Phase 1 type definitions) and drop the rest.
79
173
 
80
174
  ### Phase 0.5 — Viability Gate (Anti-Archive Guard)
81
175
 
@@ -120,14 +214,46 @@ ELSE:
120
214
 
121
215
  #### On gate FAIL
122
216
 
123
- Stop the skill with the exact user-facing message:
217
+ Stop the skill with the gate-FAIL message (UX i18n Policy class 2 — errors/preconditions; render per `fabric_language`):
218
+
219
+ zh-CN variant:
124
220
 
125
221
  ```
126
222
  本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
127
223
  ```
128
224
 
225
+ en variant:
226
+
227
+ ```
228
+ Current session is routine execution; no new knowledge to archive (gate=<reason>). To force-archive, explicitly invoke fabric-archive.
229
+ ```
230
+
129
231
  Optionally append a one-line event to `.fabric/events.jsonl` of shape `{"ts":"...","kind":"knowledge_archive_aborted","reason":"<reason>","session":"<id>"}` if the events ledger is writable; otherwise just log to stderr. Do NOT proceed to Phase 1, do NOT call any MCP tool.
130
232
 
233
+ ##### events.jsonl Constraint Note
234
+
235
+ Event lines appended to `.fabric/events.jsonl` are subject to POSIX
236
+ single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed
237
+ atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk
238
+ interleaved corruption under concurrent skill + server writes to the
239
+ same ledger.
240
+
241
+ Skills MUST ensure:
242
+
243
+ - Each event JSON line is a **single line** (no embedded newlines;
244
+ escape `\n` in any string value).
245
+ - `session_context` and other free-form text fields **self-truncate** to
246
+ keep the entire serialized line under 4KB. Suggested per-field caps:
247
+ `session_context` first 500 chars; `source_sessions` cap at 5
248
+ entries; `recent_paths` cap at 20 entries; `user_messages_summary`
249
+ first 500 chars.
250
+ - If approaching the 4KB ceiling after the per-field caps, drop optional
251
+ fields (e.g. tags / extra metadata) **before** truncating semantic
252
+ content (the summary / context that carries the actual observation).
253
+ - This constraint applies to any event the skill itself appends (e.g.
254
+ the abort signal above); MCP-server-side appends (via
255
+ `appendEventLedgerEvent`) are already line-length-bounded server-side.
256
+
131
257
  #### On gate PASS
132
258
 
133
259
  Proceed to Phase 1 with the candidates carried over from Phase 0.
@@ -189,19 +315,21 @@ Recent session contains an observation worth keeping?
189
315
 
190
316
  #### Batch Review Template
191
317
 
192
- Present all candidates in a single screen using this exact structure:
318
+ Present all candidates in a single screen. UX i18n Policy classes 1 + 3 — the roll-up structure AND the per-candidate `Confirm?` prompt are bilingualized; protected tokens (`relevance_scope`, `relevance_paths`, `narrow`, `broad`, `layer`, `team`, `personal`, `pending_path`, etc.) appear verbatim in BOTH variants. Field VALUES (slugs, file paths, type/layer enum strings like `decision` / `team`) are data and are NOT translated.
319
+
320
+ en variant (`fabric_language === "en"`):
193
321
 
194
322
  ```md
195
323
  # Archive Review — N candidates
196
324
 
197
- ## C1 [type=decision] [layer=team] [scope=narrow] slug=wave-1-parallel-task-dag
325
+ ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
198
326
  Summary: <1-2 sentences capturing the observation>
199
327
  Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
200
328
  Scope reasoning: <why narrow or broad — see Phase 1.5>
201
329
  relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
202
- Confirm? (Y to accept, edit type/layer/slug/scope/relevance_paths inline, N to skip)
330
+ Confirm? (Y to accept, edit type/layer/slug/relevance_scope/relevance_paths inline, N to skip)
203
331
 
204
- ## C2 [type=pitfall] [layer=team] [scope=broad] slug=deepmerge-array-replace-trap
332
+ ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
205
333
  Summary: ...
206
334
  Layer reasoning: ...
207
335
  Scope reasoning: ...
@@ -209,16 +337,36 @@ relevance_paths: []
209
337
  Confirm? ...
210
338
  ```
211
339
 
212
- The user MAY edit type/layer/slug/scope/relevance_paths inline before confirming. The user MAY skip individual candidates without rejecting the whole batch. Inline-editing `[scope=...]` triggers a re-derivation of `relevance_paths` per the Phase 1.5 rules (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).
340
+ zh-CN variant (`fabric_language === "zh-CN"`):
341
+
342
+ ```md
343
+ # 归档 Review — N 条候选
344
+
345
+ ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
346
+ 摘要: <1-2 句捕捉该观察>
347
+ Layer 判定: <命中哪条 强 team / 强 personal 信号,或默认 team>
348
+ Scope 判定: <为什么 narrow 或 broad — 见 Phase 1.5>
349
+ relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
350
+ 确认?(Y 接受 / 内联编辑 type/layer/slug/relevance_scope/relevance_paths / N 跳过)
351
+
352
+ ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
353
+ 摘要: ...
354
+ Layer 判定: ...
355
+ Scope 判定: ...
356
+ relevance_paths: []
357
+ 确认?...
358
+ ```
359
+
360
+ The user MAY edit type/layer/slug/relevance_scope/relevance_paths inline before confirming. The user MAY skip individual candidates without rejecting the whole batch. Inline-editing `[relevance_scope=...]` triggers a re-derivation of `relevance_paths` per the Phase 1.5 rules (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).
213
361
 
214
362
  ### Phase 1.5 — Scope Decision + relevance_paths Derivation
215
363
 
216
- After classify/layer/slug but BEFORE batch review output, assign a `scope` to each candidate and derive its `relevance_paths` array. These two fields drive rc.6 hint injection: narrow knowledge is gated by working in matching paths, broad knowledge is project-wide.
364
+ After classify/layer/slug but BEFORE batch review output, assign a `relevance_scope` to each candidate and derive its `relevance_paths` array. These two fields drive rc.6 hint injection: narrow knowledge is gated by working in matching paths, broad knowledge is project-wide.
217
365
 
218
366
  #### Scope decision (narrow vs broad)
219
367
 
220
368
  ```
221
- scope =
369
+ relevance_scope =
222
370
  narrow IF the candidate is tied to a specific module / file / subsystem
223
371
  AND there is explicit single-module evidence in edit_paths
224
372
  (i.e. all worth-keeping edits in this session concentrated in one
@@ -230,7 +378,7 @@ scope =
230
378
  broad (default, on uncertainty — safe偏置 per Q-1 in handoff)
231
379
  ```
232
380
 
233
- Special case — Personal layer ALWAYS resolves to `scope=broad` with `relevance_paths=[]`. Rationale: personal knowledge crosses projects; paths from one project do not generalize. If `layer=personal` and a narrow scope was tentatively chosen, auto-flip to `broad` and clear `relevance_paths`.
381
+ Special case — Personal layer ALWAYS resolves to `relevance_scope=broad` with `relevance_paths=[]`. Rationale: personal knowledge crosses projects; paths from one project do not generalize. If `layer=personal` and a narrow scope was tentatively chosen, auto-flip to `broad` and clear `relevance_paths`.
234
382
 
235
383
  ##### Examples
236
384
 
@@ -271,8 +419,8 @@ Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
271
419
  Singleton paths (group size = 1) are kept as-is (literal path, no glob).
272
420
 
273
421
  Step 5: SCOPE GATE
274
- IF scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
275
- IF scope == narrow → relevance_paths = result of Step 4
422
+ IF relevance_scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
423
+ IF relevance_scope == narrow → relevance_paths = result of Step 4
276
424
 
277
425
  Step 6: ATTACH READ-ONLY EVIDENCE
278
426
  Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
@@ -298,7 +446,7 @@ Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
298
446
  - `packages/server/src/services/{extract,review,promote}.ts` → group size 3 ≥ 2, common prefix `packages/server/src/services/`, glob: `packages/server/src/services/**/*.ts`
299
447
  - `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
300
448
 
301
- Step 5 (assume `scope=narrow`):
449
+ Step 5 (assume `relevance_scope=narrow`):
302
450
 
303
451
  ```json
304
452
  "relevance_paths": [
@@ -307,11 +455,11 @@ Step 5 (assume `scope=narrow`):
307
455
  ]
308
456
  ```
309
457
 
310
- If `scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
458
+ If `relevance_scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
311
459
 
312
460
  #### Inline-edit support during batch review
313
461
 
314
- The user MAY inline-edit `[scope=...]` in the batch review. When this happens:
462
+ The user MAY inline-edit `[relevance_scope=...]` in the batch review. When this happens:
315
463
 
316
464
  - Edit changes `narrow → broad`: clear `relevance_paths` to `[]`.
317
465
  - Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
@@ -326,12 +474,12 @@ For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT bat
326
474
  ```ts
327
475
  mcp__fabric__fab_extract_knowledge({
328
476
  source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 0.0)
329
- recent_paths: ["<path1>", "<path2>", ...], // capped at 20
477
+ recent_paths: ["<path1>", "<path2>", ...], // capped at archive_max_recent_paths (config-resolved, default 20)
330
478
  user_messages_summary: "<compact prose ≤500 chars>",
331
479
  type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
332
480
  slug: "<kebab-case-2-to-5-words>",
333
481
  layer: "team" | "personal",
334
- scope: "narrow" | "broad", // from Phase 1.5
482
+ relevance_scope: "narrow" | "broad", // from Phase 1.5
335
483
  relevance_paths: ["<glob1>", "<literal2>", ...], // narrow ⇒ derived; broad ⇒ []
336
484
  // v2.0.0-rc.7 T6: required fields for future-self reviewability.
337
485
  proposed_reason:
@@ -383,19 +531,25 @@ The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. C
383
531
 
384
532
  If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
385
533
 
534
+ **T5 array-form note (rc.7+)**: when `source_sessions` is passed as an array (the rc.7 T5 contract), only `source_sessions[0]` participates in the server-side idempotency hash. The actual server formula at `packages/server/src/services/extract-knowledge.ts:78` is `sha256(JSON.stringify({source_session: sourceSessions[0], type, slug}))`. Implications:
535
+
536
+ - Same `(type, slug)` but a different **first** session → distinct idempotency key → produces two pending files.
537
+ - Same first session but different tail sessions → evidence-merge into the SAME pending file; tail `session_id`s are NOT recorded as independent evidence keys.
538
+ - The formula is intentionally stable across the rc.5 → rc.7 migration; adding or removing tail entries does NOT change the idempotency key, preserving rc.5 single-session compat.
539
+
386
540
  ## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
387
541
 
388
542
  ### DISPLAY Rules
389
543
 
390
544
  - MUST complete Phase 0 AND Phase 0.5 viability gate before any batch-review output.
391
545
  - MUST abort with the gate-FAIL message (no MCP call) when the viability gate fails AND the user did not explicitly invoke fabric-archive.
392
- - MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
546
+ - MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[relevance_scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
393
547
  - MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
394
548
  - MUST include a one-line `Scope reasoning:` for each candidate citing why narrow or broad was chosen (or that personal forced broad).
395
549
  - MUST classify against the canonical singular nouns: model / decision / guideline / pitfall / process. NEVER invent new types.
396
- - MUST cap the batch at 8 candidates; drop weaker ones over the cap.
550
+ - MUST cap the batch at `archive_max_candidates_per_batch` candidates (config-resolved, default 8); drop weaker ones over the cap.
397
551
  - MUST display the resolved `pending_path` returned by `fab_extract_knowledge` so the user can verify.
398
- - MUST treat user inline edits to type/layer/slug/scope/relevance_paths as authoritative replacements before Phase 2.
552
+ - MUST treat user inline edits to type/layer/slug/relevance_scope/relevance_paths as authoritative replacements before Phase 2.
399
553
  - MUST skip rather than guess when an observation does not fit any of the 5 types.
400
554
 
401
555
  ### WRITE Rules
@@ -404,12 +558,12 @@ If the skill needs to record a genuinely separate observation in the same sessio
404
558
  - NEVER write outside `.fabric/knowledge/pending/` — promotion to `.fabric/knowledge/<type>/` is rc.3 fab_review concern, NOT this skill.
405
559
  - NEVER include an `id` field anywhere — pending entries have no id (late-bind on approve).
406
560
  - NEVER classify a candidate as `personal` when a 强 team signal applies. Default to team on ambiguity.
407
- - NEVER emit a non-empty `relevance_paths` when `scope=broad` — broad MUST always carry `relevance_paths=[]`.
408
- - NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `scope=broad` + `relevance_paths=[]`.
561
+ - NEVER emit a non-empty `relevance_paths` when `relevance_scope=broad` — broad MUST always carry `relevance_paths=[]`.
562
+ - NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `relevance_scope=broad` + `relevance_paths=[]`.
409
563
  - NEVER use multi-signal sources for relevance_paths in rc.5 — `edit_paths` is the SOLE source. `read_paths`, body regex, and symbol extraction are reserved for rc.7+.
410
564
  - NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
411
565
  - NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
412
- - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `scope`, `narrow`, `broad`, `edit_paths`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
566
+ - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `pending_path`, `layer`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
413
567
 
414
568
  ## Worked Examples
415
569
 
@@ -427,7 +581,7 @@ mcp__fabric__fab_extract_knowledge({
427
581
  type: "decisions",
428
582
  slug: "single-cjs-hook-script",
429
583
  layer: "team",
430
- scope: "narrow",
584
+ relevance_scope: "narrow",
431
585
  relevance_paths: [
432
586
  "templates/claude-hooks/**/*.cjs",
433
587
  "packages/cli/src/commands/hooks.ts"
@@ -453,7 +607,7 @@ mcp__fabric__fab_extract_knowledge({
453
607
  type: "pitfalls",
454
608
  slug: "deepmerge-array-replace-trap",
455
609
  layer: "team",
456
- scope: "broad",
610
+ relevance_scope: "broad",
457
611
  relevance_paths: [],
458
612
  proposed_reason: "diagnostic-then-fix",
459
613
  session_context: "Session goal: wire hook installer for v2.\nTurning point: spent ~30 min chasing why prior Stop[] entries vanished — root cause was deepMerge replacing arrays silently.\nResult: array-append-with-dedupe special case added."
@@ -476,7 +630,7 @@ mcp__fabric__fab_extract_knowledge({
476
630
  type: "guidelines",
477
631
  slug: "indent-style-by-language",
478
632
  layer: "personal",
479
- scope: "broad",
633
+ relevance_scope: "broad",
480
634
  relevance_paths: [],
481
635
  proposed_reason: "explicit-user-mark",
482
636
  session_context: "Session goal: align editor config.\nTurning point: user said '一直 prefer 2-space TS / 4-space Py,across projects'.\nResult: personal-layer guideline; not bound to this project."