@fenglimg/fabric-cli 2.0.0 → 2.0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -5
  3. package/dist/chunk-BATF4PEJ.js +361 -0
  4. package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
  5. package/dist/chunk-D25XJ4BC.js +880 -0
  6. package/dist/chunk-MF3OTILQ.js +544 -0
  7. package/dist/chunk-PWLW3B57.js +18 -0
  8. package/dist/config-XJIPZNUP.js +13 -0
  9. package/dist/doctor-EJDSEJSS.js +810 -0
  10. package/dist/index.js +15 -8
  11. package/dist/{init-BIRSIOXO.js → install-EKWMFLUU.js} +622 -711
  12. package/dist/metrics-ACEQFPDU.js +122 -0
  13. package/dist/onboard-coverage-MFCAEBDO.js +220 -0
  14. package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
  15. package/dist/uninstall-MH7ZIB6M.js +1064 -0
  16. package/package.json +30 -5
  17. package/templates/hooks/cite-policy-evict.cjs +231 -0
  18. package/templates/hooks/configs/README.md +29 -6
  19. package/templates/hooks/configs/claude-code.json +14 -3
  20. package/templates/hooks/configs/codex-hooks.json +6 -3
  21. package/templates/hooks/configs/cursor-hooks.json +8 -10
  22. package/templates/hooks/fabric-hint.cjs +833 -105
  23. package/templates/hooks/knowledge-hint-broad.cjs +509 -135
  24. package/templates/hooks/knowledge-hint-narrow.cjs +791 -26
  25. package/templates/hooks/lib/banner-i18n.cjs +309 -0
  26. package/templates/hooks/lib/cite-contract-reminder.cjs +173 -0
  27. package/templates/hooks/lib/cite-line-parser.cjs +158 -0
  28. package/templates/hooks/lib/client-adapter.cjs +106 -0
  29. package/templates/hooks/lib/config-cache.cjs +107 -0
  30. package/templates/hooks/lib/state-store.cjs +84 -0
  31. package/templates/hooks/lib/summary-fallback.cjs +210 -0
  32. package/templates/skills/fabric-archive/SKILL.md +93 -419
  33. package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
  34. package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
  35. package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
  36. package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
  37. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
  38. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
  39. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
  40. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
  41. package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
  42. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
  43. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
  44. package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
  45. package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
  46. package/templates/skills/fabric-import/SKILL.md +75 -516
  47. package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
  48. package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
  49. package/templates/skills/fabric-import/ref/output-contract.md +61 -0
  50. package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
  51. package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
  52. package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
  53. package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
  54. package/templates/skills/fabric-review/SKILL.md +86 -284
  55. package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
  56. package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
  57. package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
  58. package/templates/skills/fabric-review/ref/output-contract.md +58 -0
  59. package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
  60. package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
  61. package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
  62. package/templates/skills/lib/shared-policy.md +69 -0
  63. package/dist/chunk-6ICJICVU.js +0 -10
  64. package/dist/chunk-74SZWYPH.js +0 -658
  65. package/dist/chunk-EYIDD2YS.js +0 -1000
  66. package/dist/doctor-T7JWODKG.js +0 -282
  67. package/dist/hooks-Y74Y5LQS.js +0 -12
  68. package/dist/scan-LMK3UCWL.js +0 -22
  69. package/dist/serve-H554BHLG.js +0 -124
  70. package/templates/agents-md/AGENTS.md.template +0 -59
  71. package/templates/bootstrap/CLAUDE.md +0 -8
  72. package/templates/bootstrap/codex-AGENTS-header.md +0 -6
  73. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
@@ -1,401 +1,143 @@
1
1
  ---
2
2
  name: fabric-archive
3
- description: Use this skill when the Stop-hook signals an archive opportunity (events.jsonl shows ≥5 plan_context entries since the last knowledge_proposed event, or ≥24h elapsed since the last archive), OR when the user explicitly invokes archival. The skill classifies recent session candidates into one of five knowledge types (model/decision/guideline/pitfall/process), assigns a layer (team/personal) via the verbatim heuristic, proposes a slug, presents one batch review, and persists confirmed entries through the fab_extract_knowledge MCP tool to .fabric/knowledge/pending/.
3
+ description: 归档对话洞察到 .fabric/knowledge/pending (NOT code review). Triggers 以后/always/never/下次/记一下;wrong-turn-revert;decision-confirm;dismissal-reason;/fabric-archive.
4
4
  allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_extract_knowledge
5
5
  ---
6
6
 
7
- > **Surface**: This is a Skill (AI-driven, LLM judgment over session digests). See [`docs/surfaces.md`](https://github.com/fenglimg/fabric/blob/main/docs/surfaces.md) for the CLI / Skill / MCP boundary.
7
+ > **Surface**: Skill (LLM judgment over session digests). See [`docs/surfaces.md`](https://github.com/fenglimg/fabric/blob/main/docs/surfaces.md).
8
8
 
9
9
  ## Precondition
10
10
 
11
- This skill is invoked when one of the following holds:
11
+ Invoke this skill ONLY when ONE of the following holds:
12
12
 
13
- - The Stop-hook printed a stdout JSON pointer of shape `{"decision":"block","reason":"..."}` mentioning fabric-archive
14
- - The user typed an explicit archive request (e.g. "archive what we just did", "fabric archive")
13
+ - Stop-hook printed stdout JSON `{"decision":"block","reason":"..."}` mentioning fabric-archive
14
+ - 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`.
18
-
19
- This skill is `Check-not-Ask`, not a preference interview:
20
-
21
- - Phase 0 proactively gathers candidate evidence from the session
22
- - Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
23
- - 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)
25
- - Phase 2 calls `fab_extract_knowledge` once per confirmed candidate
26
-
27
- ## 执行流程 (5 Phase / 1 User Review Round)
28
-
29
- ### Phase 0.0 — Collect Cross-Session Digests (v2.0.0-rc.7 T5)
30
-
31
- Before any single-session collection or viability gating, stitch together
32
- context from every session that has accumulated since the last
33
- `knowledge_proposed` event. The rc.7 Stop hook writes a per-session digest to
34
- `.fabric/.cache/session-digests/<session_id>.md` (≤5KB, contains top 10 user
35
- messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
17
+ If none hold, stop the skill and tell the user (UX i18n Policy class 2):
36
18
 
37
- 1. **Read events.jsonl tail.** Use `Bash` with
38
- `tail -n 200 .fabric/events.jsonl` (tolerate ENOENT empty ledger is a
39
- normal first-run state).
40
- 2. **Find the anchor.** Walk the tail backwards to locate the most recent
41
- `knowledge_proposed` event (`event_type === "knowledge_proposed"`). The
42
- anchor's `ts` becomes the lower bound for digest selection. If NO anchor
43
- exists, treat all digests in the cache as in-scope.
44
- 3. **Collect session_ids since anchor.** Scan the tail forward from the
45
- anchor and collect every distinct `session_id` field that appears on any
46
- event newer than the anchor. Distinct ordering preserved.
47
- 4. **Load digests.** For each collected `session_id`, read
48
- `.fabric/.cache/session-digests/<session_id>.md`. Missing digest files
49
- degrade silently (the digest write was best-effort, so a Stop hook crash
50
- 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).
52
- 5. **Build cross-session context.** Concatenate the loaded digests into a
53
- single `### Cross-session digest` block to carry into Phase 0.5 + Phase 1.
54
- Use this block to:
55
- - Detect session-spanning patterns (e.g. a discussion that started in
56
- session A and continued in session B).
57
- - Populate the `source_sessions` array on every fab_extract_knowledge
58
- call — the array form (T5) replaces the legacy `source_session` string.
59
- - Inform the `session_context` blob written to each pending entry's body
60
- (3-5 lines summarizing goal + key turning point, per T6).
19
+ - zh-CN: `没有触发归档信号;如需手动归档请显式调用 fabric-archive`
20
+ - en: `No archive signal detected; to manually archive, explicitly invoke fabric-archive`
61
21
 
62
- Graceful degradation: if `.fabric/.cache/session-digests/` is missing
63
- entirely, this phase reports an empty context and Phase 0 falls back to the
64
- single-session behaviour. Tests that synthesize events.jsonl without
65
- populating the digest cache continue to work.
22
+ Render per `fabric_language` resolved in Phase 0.5.
66
23
 
67
- ### Phase 0Collect Candidates
24
+ This skill runs automatically it does not interview the user for preferences. It gathers evidence, aborts if no archive signal exists, then classifies + persists.
68
25
 
69
- Gather raw evidence from the recent session before any classification:
26
+ ## 执行流程 (1 User Review Round)
70
27
 
71
- 1. Read the tail of `.fabric/events.jsonl` since the last `knowledge_proposed` event.
72
- - Use `Bash` with `tail -n 200 .fabric/events.jsonl` if the file is large.
73
- - 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.
75
- 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
- 4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
28
+ v2.0.0-rc.37 NEW-9 collapsed the flow to **3 macro-phases**; the legacy fine-grained phases survive as labelled sub-steps (back-compat for `ref/` navigation + existing traces):
77
29
 
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.
30
+ - **GATHER** = Phase 0 (range) 0.5 (config) 1 (ledger scan via `fab_archive_scan`) [1.5 onboard] → 2 (candidates). Resolve scope, load config, deterministically scan the ledger for in-scope sessions, collect candidate observations.
31
+ - **REVIEW** = Phase 2.5 (viability gate — abort guard) → 3 (classify / layer / slug + batch review) → 3.5 (scope + relevance_paths). The single user review round lives here.
32
+ - **PERSIST** = Phase 4 (`fab_extract_knowledge`, one call per candidate) → 4.5 (archive-attempt ledger).
79
33
 
80
- ### Phase 0.5 — Viability Gate (Anti-Archive Guard)
34
+ Sub-step chain: `0 → 0.5 → 1 → [1.5] → 2 → 2.5 → 3 → 3.5 → 4 → 4.5`. Each below is a navigator stub full procedure, decision tables, and worked examples live in `ref/`.
81
35
 
82
- Before producing any candidate output, run a coarse viability check on the session as a whole. The goal is to short-circuit obvious no-archive sessions (routine execution, typo fixes, narrow renames) so that Phase 1 batch review is never spent on noise.
36
+ ### Phase 0 Range Resolution
83
37
 
84
- #### Archive signals ( 1 hit gate PASSES, proceed to Phase 1)
38
+ Parse user's prompt for time-window (`今日` / `last week`), topic keyword (`rc.20`), or literal `session_id` reference; emit `session_id[]` OR `"all"` sentinel that constrains Phase 1 collection. LLM-as-parser contract — no parser code.
85
39
 
86
- Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 0:
40
+ `Read ref/phase-0-range-resolution.md` for the HIGH/MEDIUM/LOW/PARSE-MISS confidence decision rule (rc.33), Step 1 inspection table, bilingual time-window patterns (zh-CN + en), topic-keyword extraction, session_id resolution pseudocode, AskUserQuestion fallback (E2/E4 only), and 3 worked examples.
87
41
 
88
- 1. Explicit normative language: user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`.
89
- 2. Wrong-turn-and-revert: a path was edited, then reverted (or partially undone) after diagnosis — indicates a pitfall worth recording.
90
- 3. Long diagnostic loop: an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution.
91
- 4. New dependency adoption: a new package / library / external tool was introduced (e.g. `package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
92
- 5. New pattern emergence: a reusable abstraction or naming convention was named ("the X phase", "the Y pattern", "let's call this Z").
93
- 6. Decision confirmation: ≥ 2 alternatives were weighed AND a rationale was given before settling.
94
- 7. Explicit dismissal-with-reason: user rejected an approach AND stated why (the why is the archivable knowledge, not the dismissal itself).
95
- 8. Process formalization: a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
42
+ ### Phase 0.5 Config Load
96
43
 
97
- #### Anti-archive signals (forces gate to FAIL unless an archive signal also fires)
44
+ Read `.fabric/fabric-config.json`; resolve `archive_max_candidates_per_batch` (default 8), `archive_max_recent_paths` (default 20), `archive_digest_max_sessions` (default 10). Missing file → defaults silently.
98
45
 
99
- 1. Typo-only edits: the entire session is whitespace / spelling / formatting changes.
100
- 2. Pure refactor: rename / move / extract with no behavior change AND no naming convention being established.
101
- 3. Narrow rename request: user asked to rename one symbol / file with no rationale.
102
- 4. Duplicate of existing canonical: the observation is already covered by an existing entry under `.fabric/knowledge/<type>/` (do a quick Glob before deciding).
46
+ ### UX i18n Policy
103
47
 
104
- #### Gate decision
48
+ Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`); emit user-facing prose in resolved variant. Protected tokens (MCP tool names, schema fields, the verbatim `强 team` / `强 personal` / `默认 team` heuristic) NEVER translated. `AskUserQuestion` policy: `header` + `question` translate; `options[]` stay English (routing keys).
105
49
 
106
- ```
107
- archive_signals_hit = count of archive signals fired
108
- anti_signals_hit = count of anti-archive signals fired
109
- user_explicit_invoke = user typed "archive what we just did" / "fabric archive" / similar
50
+ `Read ref/i18n-policy.md` for the full 5-class taxonomy + edge cases.
110
51
 
111
- IF user_explicit_invoke:
112
- gate = PASS # explicit invocation bypasses all gates
113
- ELIF archive_signals_hit == 0:
114
- gate = FAIL (reason="no_signal")
115
- ELIF anti_signals_hit > 0 AND archive_signals_hit == 0:
116
- gate = FAIL (reason="anti_signal_dominates")
117
- ELSE:
118
- gate = PASS
119
- ```
52
+ ### Phase 1 — Collect Cross-Session Digests (server-side ledger scan, rc.37 NEW-9)
120
53
 
121
- #### On gate FAIL
122
-
123
- Stop the skill with the exact user-facing message:
54
+ The deterministic ledger scan now runs **server-side** — call `fab_archive_scan({ range, session_id })` (range = Phase 0's `session_id[]` or `"all"`/omitted). It returns:
124
55
 
125
- ```
126
- 本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
127
- ```
56
+ - `anchor_ts` — ts of the last `knowledge_proposed` (the lower bound).
57
+ - `session_ids[]` — distinct in-scope sessions since the anchor, ALREADY filtered through the outcome-ledger state machine (drops `user_dismissed`, sessions inside the 12h anti-loop cooldown, and watermarked sessions with no new high-value signal). First-seen order.
58
+ - `dropped[]` — `{session_id, reason}` for transparency.
59
+ - `covered_through_ts` — max ts examined (becomes the next watermark).
60
+ - `already_proposed_keys[]` — idempotency keys already proposed but not yet reviewed; drop matching candidates in Phase 3 (cross-session pending dedupe).
128
61
 
129
- 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
-
131
- #### On gate PASS
132
-
133
- Proceed to Phase 1 with the candidates carried over from Phase 0.
134
-
135
- ### Phase 1 — Classify, Layer, Slug, Review
62
+ Then (LLM side, Boundary B): for each returned `session_id`, load `.fabric/.cache/session-digests/<session_id>.md`, concatenate into a `### Cross-session digest` block, and populate `source_sessions[]` + `session_context` for Phase 4. Cap at `archive_digest_max_sessions`. Missing digest files degrade silently.
136
63
 
137
- For each candidate, the skill proposes:
138
-
139
- - **type** ∈ {model, decision, guideline, pitfall, process}
140
- - **layer** ∈ {team, personal} via the verbatim heuristic below
141
- - **slug** per the 5-rule naming guideline below
142
- - **summary** (1-2 sentences, will become the entry body's lead paragraph)
64
+ `Read ref/phase-1-cross-session.md` for the (now server-side) filter state machine rules + the digest-stitch + graceful-degradation notes. The hand-rolled `tail -n 200` events.jsonl scan is retired — `fab_archive_scan` is the source of truth for which sessions to load.
143
65
 
144
- #### Five Knowledge Types (singular noun = type concept)
66
+ Graceful degradation: missing digest cache → single-session fallback. Missing `session_archive_attempted` events (pre-rc.25) legacy "scan everything since anchor" behaviour.
145
67
 
146
- - **model**A reusable mental abstraction or domain object schema. Worth-archive signal: the user names something ("the X pattern", "the Y phase"). Skip-it signal: ad-hoc terminology used once. Positive: "Wave-1/Wave-2 task DAG decomposition for parallel-safe planning". Negative: "the thing we did just now" (too thin, no reusable abstraction).
147
- - **decision** — A choice between alternatives with rationale. Worth-archive signal: ≥2 options were weighed AND a rationale was given. Skip-it signal: the choice was forced by external constraint with no real alternative. Positive: "Single .cjs hook script over three per-client scripts — rationale: identical stdout JSON shape across Claude/Codex". Negative: "Used the existing fab_extract_knowledge schema" (no alternative was considered).
148
- - **guideline** — A normative rule for future similar situations. Worth-archive signal: the user said "always" / "never" / "from now on". Skip-it signal: a one-off preference that won't generalize. Positive: "Slug naming: kebab-case, 2-5 words, 20-40 chars, semantic core only". Negative: "Use 4-space indent in this one file" (too narrow).
149
- - **pitfall** — A trap that wasted time and is non-obvious. Worth-archive signal: a bug took >15 min to diagnose AND is repeatable. Skip-it signal: a typo or one-time API quirk. Positive: "deepMerge replaces arrays — hooks.Stop[] needs special-case append-with-dedupe". Negative: "Forgot a comma in JSON" (too obvious).
150
- - **process** — A multi-step procedure with a stable shape. Worth-archive signal: the steps were executed in a specific order AND the order matters. Skip-it signal: a one-shot script with no reusable structure. Positive: "fab_review approve = counter++ → frontmatter inject → git mv → meta rebuild → event append (5 atomic steps)". Negative: "Ran the tests, then committed" (trivial, no reusable shape).
68
+ ### Phase 1.5 First-run Onboard (ref-only)
151
69
 
152
- #### Layer Classification Heuristic (强 team 信号 / personal 信号 / 默认 team)
70
+ **SKIP this phase entirely unless** entry_point {E2_explicit_user_invoke, E4_user_range_rollback} AND `fabric onboard-coverage --json` reports `missing.length > 0`. For E1/E3/E5, silently fall through to Phase 0.
71
+
72
+ `Read ref/phase-1-5-onboard.md` for the Step 1-4 coverage check → user prompt → tour-and-propose procedure.
73
+
74
+ ### Phase 2 — Collect Candidates
75
+
76
+ Gather raw evidence: tail `.fabric/events.jsonl` since last `knowledge_proposed`; enumerate `recent_paths` (workspace files touched by Read/Edit/Write); distill `user_messages_summary` (≤500 char prose, NOT verbatim transcript); build candidate list. Hard budget: `archive_max_candidates_per_batch` per batch (default 8); drop weaker overage.
77
+
78
+ ### Phase 2.5 — Viability Gate (Anti-Archive Guard)
79
+
80
+ Coarse viability check. **PASS conditions**: user_explicit_invoke OR ≥1 archive signal hit. v2.0.0-rc.37 NEW-4 simplifies the legacy 8-signal list into **3 major categories** (each maps to multiple legacy signals for back-compat scoring):
81
+
82
+ 1. **User-driven knowledge expression** — user message contains normative language (`always`/`never`/`from now on`/`下次注意`/`记一下`/`以后`/`永远不要`), OR user weighed ≥2 alternatives and gave rationale (decision confirmation), OR user explicitly dismissed an approach AND stated why (dismissal-with-reason). Legacy signals #1 + #6 + #7.
83
+ 2. **Reflective discovery** — AI tried path X, reflected, then took path Y (wrong-turn-and-revert); OR a long diagnostic loop (>15 min / >10 turns) surfaced a non-obvious cause; OR a reusable pattern was named in the session ("the X phase", "the Y pattern"). Legacy signals #2 + #3 + #5.
84
+ 3. **Concrete artifact change** — a new dependency was added (package.json/pyproject.toml/Cargo.toml diff), OR a load-bearing multi-step procedure was formalized in a specific order. Legacy signals #4 + #8.
85
+
86
+ Pre-PASS MUST step (rc.37 NEW-4): for each candidate, run a quick `Glob` over `.fabric/knowledge/**/*.md` keyed on slug-stem to check for duplicate canonical entry. If duplicate found → drop candidate (treat as anti-signal #4 'duplicate of existing canonical'). This is a HARD gate, not advisory — silently writing a near-duplicate is the highest-noise failure mode.
87
+
88
+ **FAIL → branch by entry_point**: E1/E3/E5 silent-skip (emit Phase 4.5 event `outcome='skipped_no_signal'`); E2/E4 render gate-FAIL message (emit `outcome='viability_failed'`). Gate-FAIL message for E2/E4 MUST include the "to force-archive, explicitly invoke fabric-archive" remediation pointer so the user has an unambiguous escape hatch when the gate misclassifies (zh-CN: `如需强制归档,请显式调用 fabric-archive` / en: `To force-archive, explicitly invoke fabric-archive`).
89
+
90
+ `Read ref/phase-2-5-viability.md` for verbose signal definitions, zh-CN/en gate-FAIL message bodies, anti-archive signals (typo / refactor / rename / duplicate), and the events.jsonl 4KB POSIX atomicity constraint.
91
+
92
+ ### Phase 3 — Classify, Layer, Slug, Review
93
+
94
+ For each candidate, propose **type** ∈ {model, decision, guideline, pitfall, process}, **layer** ∈ {team, personal} via the verbatim heuristic below, **slug** (kebab-case 2-5 words, 20-40 chars, unique within type+layer bucket), **summary** (1-2 sentences).
95
+
96
+ #### Layer Classification Heuristic (verbatim, contract-locked)
153
97
 
154
98
  > - **强 team**: 引用本项目代码、团队共识用语("we decided")、fabric-import 路径产物、业务领域、绑定本项目代码的 pitfall
155
99
  > - **强 personal**: 第一人称偏好、跨项目通用、工具/编辑器偏好、个人工作流
156
100
  > - **默认 team**: 安全偏置——错标 team 在 PR review 中会被发现,错标 personal 静默丢失
157
101
 
158
- Resolution order: check 强 team signals first; only assign personal if 强 personal signals dominate AND no 强 team signal applies; otherwise default to team.
102
+ Resolution: 强 team first; assign personal only if 强 personal dominates AND no 强 team applies; else default team.
103
+
104
+ `Read ref/phase-3-classify.md` for per-type worth-archive vs skip signals with positive/negative examples, slug pass/fail samples, decision tree, en + zh-CN batch review templates. User MAY inline-edit `type` / `layer` / `slug` / `relevance_scope` / `relevance_paths` before confirming; scope edits trigger Phase 3.5 re-derivation.
105
+
106
+ ### Phase 3.5 — Scope Decision + relevance_paths Derivation
159
107
 
160
- #### Slug Naming Guideline (5 Rules)
108
+ Assign `relevance_scope` {narrow, broad} + derive `relevance_paths` BEFORE batch review. **narrow** = candidate tied to specific module/file with single-module evidence in edit_paths; **broad** = cross-cutting/methodological/general (default on uncertainty). **Personal layer ALWAYS forces broad + `relevance_paths=[]`** (cross-project, paths don't generalize).
161
109
 
162
- 1. kebab-case (lowercase letters, digits, hyphens only no underscores, no CamelCase)
163
- 2. 2-5 words separated by hyphens
164
- 3. 20-40 characters total length
165
- 4. semantic core only (drop articles "the/a", drop generic suffixes "stuff/thing")
166
- 5. unique within its (type, layer) bucket — if collision, the LLM must add a discriminating word, NOT a counter
110
+ `Read ref/phase-3-5-scope.md` for the 6-step relevance_paths derivation pseudocode (COLLECT DEDUPE → BLACKLIST → PUBLIC-PREFIX GENERALIZE → SCOPE GATE → ATTACH READ-ONLY EVIDENCE), worked example (5 sample paths → glob + literal output), and narrow↔broad inline-edit re-derivation rules.
167
111
 
168
- Examples passing: `wave-1-parallel-task-dag` (4 words, 24 chars), `deepmerge-array-replace-trap` (4 words, 28 chars). Examples failing: `the_solution` (underscore + article), `fix` (1 word, too short), `how-we-decided-to-handle-the-merge-conflict-in-stop-hook-config` (overlong).
169
-
170
- #### Decision Tree (是否值得归档)
171
-
172
- ```
173
- Recent session contains an observation worth keeping?
174
- ├─ NO → skip (do nothing, no MCP call)
175
- └─ YES → does it fit one of {model, decision, guideline, pitfall, process}?
176
- ├─ NO → skip (not classifiable = not yet ripe)
177
- └─ YES → assign type
178
-
179
- Apply layer heuristic
180
-
181
- Propose slug per 5 rules
182
-
183
- Present in batch review
184
-
185
- User confirms / corrects / rejects
186
-
187
- Phase 2: call fab_extract_knowledge once per confirmed candidate
188
- ```
189
-
190
- #### Batch Review Template
191
-
192
- Present all candidates in a single screen using this exact structure:
193
-
194
- ```md
195
- # Archive Review — N candidates
196
-
197
- ## C1 [type=decision] [layer=team] [scope=narrow] slug=wave-1-parallel-task-dag
198
- Summary: <1-2 sentences capturing the observation>
199
- Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
200
- Scope reasoning: <why narrow or broad — see Phase 1.5>
201
- 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)
203
-
204
- ## C2 [type=pitfall] [layer=team] [scope=broad] slug=deepmerge-array-replace-trap
205
- Summary: ...
206
- Layer reasoning: ...
207
- Scope reasoning: ...
208
- relevance_paths: []
209
- Confirm? ...
210
- ```
211
-
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 `[]`).
213
-
214
- ### Phase 1.5 — Scope Decision + relevance_paths Derivation
112
+ ### Phase 4 Persist via MCP
215
113
 
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.
217
-
218
- #### Scope decision (narrow vs broad)
114
+ For each user-confirmed candidate, call `fab_extract_knowledge` ONCE (NEVER batch). Required: `source_sessions[]`, `recent_paths[]` (cap 20), `user_messages_summary`, `type` (plural form: decisions/pitfalls/guidelines/models/processes), `slug`, `layer`, `relevance_scope`, `relevance_paths[]`, `proposed_reason` (enum), `session_context` (3-5 line narrative). Four OPTIONAL rc.23 triage fields (`intent_clues`, `tech_stack`, `impact`, `must_read_if`) populate when clean, **omit rather than guess**.
219
115
 
220
- ```
221
- scope =
222
- narrow IF the candidate is tied to a specific module / file / subsystem
223
- AND there is explicit single-module evidence in edit_paths
224
- (i.e. all worth-keeping edits in this session concentrated in one
225
- module tree, OR the candidate explicitly references that module)
116
+ Server returns `{ pending_path, idempotency_key }`. Display `pending_path` for the user. `idempotency_key = sha256({source_session, type, slug})` — repeated calls SAFE (server merges evidence).
226
117
 
227
- broad IF the candidate is cross-cutting / methodological / general
228
- (applies regardless of which path the agent is working in)
118
+ `Read ref/phase-4-mcp-persist.md` for full TypeScript call shape, C1 triage-field inference table, Phase 2.5 signal → `proposed_reason` mapping, `session_context` format, T5 array-form idempotency notes.
229
119
 
230
- broad (default, on uncertaintysafe偏置 per Q-1 in handoff)
231
- ```
232
-
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`.
234
-
235
- ##### Examples
236
-
237
- - `decision: single-cjs-hook-script` → `narrow` (tied to `templates/claude-hooks/` + `packages/cli/src/commands/hooks.ts`)
238
- - `pitfall: deepmerge-array-replace-trap` → `broad` (cross-cutting JSON merge gotcha, applies anywhere deepMerge is used)
239
- - `guideline: slug-naming-rules` → `broad` (methodology, no specific module)
240
- - `model: wave-1-parallel-task-dag` → `narrow` (tied to `packages/cli/src/commands/plan.ts`)
241
- - `guideline: indent-style-by-language` (personal layer) → `broad + []` (personal forces broad)
242
-
243
- #### relevance_paths derivation algorithm (rc.5 single-signal: edit_paths only)
244
-
245
- rc.5 uses ONLY the `edit_paths` signal — list of paths modified by `Edit` / `Write` / `MultiEdit` tool calls in the current session. Multi-signal (read_paths + body regex + symbols) is explicitly deferred to rc.7 per design decision.
246
-
247
- ```
248
- Step 1: COLLECT
249
- edit_paths = []
250
- Scan session transcript for tool_use entries where
251
- tool_use.name ∈ {Edit, Write, MultiEdit}
252
- Extract the file_path argument from each, push into edit_paths.
253
-
254
- Step 2: DEDUPE
255
- edit_paths = unique(edit_paths)
256
-
257
- Step 3: BLACKLIST FILTER
258
- Drop paths matching any of:
259
- - **/*.<ext> where <ext> is a single trivial extension on a single file
260
- (i.e. avoid emitting bare **/*.md as a relevance pattern)
261
- - Repo-root single files: README.md, package.json, package-lock.json,
262
- pnpm-lock.yaml, tsconfig.json, .gitignore, LICENSE, CHANGELOG.md
263
- - Read-only paths (never modified) — those go to ## Evidence, not relevance_paths
264
-
265
- Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
266
- Group remaining paths by common prefix.
267
- For each group of ≥ 2 sibling paths sharing a prefix:
268
- - Compute longest common directory prefix
269
- - Limit generalization depth: at most 2 levels below the common prefix
270
- - Emit glob: <common-prefix>/**/*.<ext> (or <common-prefix>/**/<filename>)
271
- Singleton paths (group size = 1) are kept as-is (literal path, no glob).
272
-
273
- 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
276
-
277
- Step 6: ATTACH READ-ONLY EVIDENCE
278
- Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
279
- block in the pending entry body — NOT in relevance_paths. They document
280
- what the agent consulted without making them part of the activation gate.
281
- ```
282
-
283
- ##### Worked generalization example
284
-
285
- Edit history during session:
286
-
287
- ```
288
- packages/server/src/services/extract.ts
289
- packages/server/src/services/review.ts
290
- packages/server/src/services/promote.ts
291
- packages/cli/src/commands/plan.ts
292
- README.md
293
- ```
294
-
295
- Step 1-2 (collect + dedupe): all 5 unique.
296
- Step 3 (blacklist): drop `README.md` (repo-root single file).
297
- Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
298
- - `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
- - `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
300
-
301
- Step 5 (assume `scope=narrow`):
302
-
303
- ```json
304
- "relevance_paths": [
305
- "packages/server/src/services/**/*.ts",
306
- "packages/cli/src/commands/plan.ts"
307
- ]
308
- ```
309
-
310
- If `scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
120
+ ### Phase 4.5Persist Archive Attempt
311
121
 
312
- #### Inline-edit support during batch review
313
-
314
- The user MAY inline-edit `[scope=...]` in the batch review. When this happens:
315
-
316
- - Edit changes `narrow broad`: clear `relevance_paths` to `[]`.
317
- - Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
318
- - The user MAY also directly inline-edit `relevance_paths` to a custom array; treat this as authoritative and skip auto-derivation.
319
-
320
- ### Phase 2 — Persist via MCP
321
-
322
- For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT batch multiple candidates into one call.
323
-
324
- #### Output Contract (MCP tool call shape)
325
-
326
- ```ts
327
- mcp__fabric__fab_extract_knowledge({
328
- source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 0.0)
329
- recent_paths: ["<path1>", "<path2>", ...], // capped at 20
330
- user_messages_summary: "<compact prose ≤500 chars>",
331
- type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
332
- slug: "<kebab-case-2-to-5-words>",
333
- layer: "team" | "personal",
334
- scope: "narrow" | "broad", // from Phase 1.5
335
- relevance_paths: ["<glob1>", "<literal2>", ...], // narrow ⇒ derived; broad ⇒ []
336
- // v2.0.0-rc.7 T6: required fields for future-self reviewability.
337
- proposed_reason:
338
- "explicit-user-mark" // user said "always / never / 下次注意" etc.
339
- | "diagnostic-then-fix" // long debug loop surfaced a new pattern/pitfall
340
- | "decision-confirmation" // ≥2 options weighed AND rationale stated → decision/model
341
- | "wrong-turn-revert" // tried path X, reverted → pitfall
342
- | "new-dependency-or-pattern" // new dep/lib/abstraction introduced
343
- | "dismissal-with-reason", // user rejected approach AND said why
344
- session_context: "<3-5 line markdown: session goal + key turning point>",
345
- // tags? — NOT in current schema; reserved for future
346
- })
347
- ```
348
-
349
- The Skill infers `proposed_reason` from the classification + viability-gate
350
- signal that fired:
351
-
352
- | Signal fired (Phase 0.5) | Classification | Default proposed_reason |
353
- |--------------------------------|----------------|-----------------------------|
354
- | Explicit normative language | guideline | `explicit-user-mark` |
355
- | Wrong-turn-and-revert | pitfall | `wrong-turn-revert` |
356
- | Long diagnostic loop | pitfall/model | `diagnostic-then-fix` |
357
- | New dependency adoption | decision/model | `new-dependency-or-pattern` |
358
- | New pattern emergence | model | `new-dependency-or-pattern` |
359
- | Decision confirmation | decision | `decision-confirmation` |
360
- | Explicit dismissal-with-reason | decision | `dismissal-with-reason` |
361
- | Process formalization | process | `new-dependency-or-pattern` |
362
-
363
- The `session_context` is a 3-5 line summary distilled from the Phase 0.0
364
- cross-session digest (see Phase 0.0 below for digest source). Format:
365
-
366
- ```
367
- Session goal: <one-line of what the user was trying to accomplish>
368
- Turning point: <one-line of the key moment that produced the worth-archive observation>
369
- [optional 1-3 more lines of supporting context]
370
- ```
371
-
372
- Future-self reviewing the pending entry MUST be able to understand WHY this
373
- entry was proposed without conversation transcript access — proposed_reason
374
- is the structured why, session_context is the narrative why.
375
-
376
- Note on type plurality: the MCP enum uses plural directory-form (decisions / pitfalls / guidelines / models / processes), while the conceptual classification above uses singular nouns (decision / pitfall / guideline / model / process) for natural English. They map 1:1.
377
-
378
- The server returns `{ pending_path, idempotency_key }`. Display `pending_path` to the user so they can `Read` the persisted entry if they wish.
379
-
380
- #### Idempotency Note
381
-
382
- The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. Calling fab_extract_knowledge twice with the same `(source_session, type, slug)` triple is SAFE: the server appends new evidence to the existing pending file rather than overwriting or producing duplicates. This means the skill MAY be re-invoked on the same session without producing junk.
383
-
384
- If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
122
+ MANDATORY closing step on EVERY invocation (Phase 4 success path + every early-exit). Append ONE `session_archive_attempted` line to `.fabric/events.jsonl` per `session_id` in run scope (single-line JSON ≤4KB POSIX atomicity). Outcome ∈ {`proposed` | `viability_failed` | `user_dismissed` | `skipped_no_signal`}. `covered_through_ts` = max ts of events examined. Best-effort write: failure → stderr log only, skill still exits successfully.
123
+
124
+ **Dry-run override**: SKIPS the `session_archive_attempted` emit entirely; read-side runs normally so user previews what WOULD have been written. See unified `## Dry-run Scope` pointer below.
125
+
126
+ `Read ref/phase-4-5-emit.md` for the full event jsonc shape, 4-state outcome decision matrix (outcome × candidates_proposed × knowledge_proposed_ids), `covered_through_ts` watermark spec, multi-session emission rule, bash echo append pattern, and E5-cron silent-skip worked trace.
385
127
 
386
128
  ## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
387
129
 
388
130
  ### DISPLAY Rules
389
131
 
390
- - MUST complete Phase 0 AND Phase 0.5 viability gate before any batch-review output.
132
+ - MUST complete Phase 2 AND Phase 2.5 viability gate before any batch-review output.
391
133
  - 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.
134
+ - MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[relevance_scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
393
135
  - MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
394
136
  - MUST include a one-line `Scope reasoning:` for each candidate citing why narrow or broad was chosen (or that personal forced broad).
395
137
  - 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.
138
+ - MUST cap the batch at `archive_max_candidates_per_batch` candidates (config-resolved, default 8); drop weaker ones over the cap.
397
139
  - 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.
140
+ - MUST treat user inline edits to type/layer/slug/relevance_scope/relevance_paths as authoritative replacements before Phase 2.
399
141
  - MUST skip rather than guess when an observation does not fit any of the 5 types.
400
142
 
401
143
  ### WRITE Rules
@@ -404,83 +146,15 @@ If the skill needs to record a genuinely separate observation in the same sessio
404
146
  - NEVER write outside `.fabric/knowledge/pending/` — promotion to `.fabric/knowledge/<type>/` is rc.3 fab_review concern, NOT this skill.
405
147
  - NEVER include an `id` field anywhere — pending entries have no id (late-bind on approve).
406
148
  - 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=[]`.
409
- - 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+.
149
+ - NEVER emit a non-empty `relevance_paths` when `relevance_scope=broad` — broad MUST always carry `relevance_paths=[]`.
150
+ - NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `relevance_scope=broad` + `relevance_paths=[]`.
151
+ - v2.0.0-rc.37 NEW-7 widened Phase 3.5: `edit_paths` `user_mentioned_paths` drives `relevance_paths`; `read_paths` flows separately to `evidence_paths` (structured frontmatter, not body markdown). NEVER lift body regex / symbol extraction into `relevance_paths` — those remain reserved for v2.1+.
410
152
  - NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
411
153
  - 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`.
413
-
414
- ## Worked Examples
415
-
416
- ### Example 1 decision (team)
417
-
418
- Session: User and agent debated whether the Stop-hook should be one .cjs script or three per-client scripts. Settled on one because stdout JSON shape `{"decision":"block","reason"}` is identical across Claude / Codex.
419
-
420
- Skill output:
421
-
422
- ```ts
423
- mcp__fabric__fab_extract_knowledge({
424
- source_sessions: ["WFS-2026-05-10-rc2"],
425
- recent_paths: ["templates/claude-hooks/", "packages/cli/src/commands/hooks.ts"],
426
- user_messages_summary: "User pushed back on three-script proposal; agreed single .cjs because stdout JSON shape is universal across Claude Code and Codex CLI.",
427
- type: "decisions",
428
- slug: "single-cjs-hook-script",
429
- layer: "team",
430
- scope: "narrow",
431
- relevance_paths: [
432
- "templates/claude-hooks/**/*.cjs",
433
- "packages/cli/src/commands/hooks.ts"
434
- ],
435
- proposed_reason: "decision-confirmation",
436
- session_context: "Session goal: ship Stop-hook for v2 release.\nTurning point: user rejected 3-script proposal after seeing identical stdout JSON across Claude / Codex.\nResult: single .cjs path locked in."
437
- })
438
- ```
439
-
440
- Layer = team (引用本项目代码 + fabric-import 路径产物 signals). Scope = narrow (tied to hook templates + hooks command module; single-module evidence in edit_paths).
441
-
442
- ### Example 2 — pitfall (team)
443
-
444
- Session: deepMerge silently replaced the existing `hooks.Stop[]` array in `.claude/settings.json` instead of appending. Cost ~30 min to diagnose.
445
-
446
- Skill output:
447
-
448
- ```ts
449
- mcp__fabric__fab_extract_knowledge({
450
- source_sessions: ["WFS-2026-05-10-rc2"],
451
- recent_paths: ["packages/cli/src/config/json.ts"],
452
- user_messages_summary: "deepMerge default behavior REPLACES arrays. hooks.Stop[] needs an array-append-with-dedupe special case keyed on .command string match.",
453
- type: "pitfalls",
454
- slug: "deepmerge-array-replace-trap",
455
- layer: "team",
456
- scope: "broad",
457
- relevance_paths: [],
458
- proposed_reason: "diagnostic-then-fix",
459
- 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."
460
- })
461
- ```
462
-
463
- Layer = team (绑定本项目代码的 pitfall signal). Scope = broad (deepMerge gotcha is cross-cutting — applies anywhere JSON merge is used, not just `json.ts`).
464
-
465
- ### Example 3 — guideline (personal)
466
-
467
- Session: User mentioned across three projects that they prefer 2-space indent in TypeScript and 4-space in Python.
468
-
469
- Skill output:
470
-
471
- ```ts
472
- mcp__fabric__fab_extract_knowledge({
473
- source_sessions: ["WFS-2026-05-10-rc2"],
474
- recent_paths: [".editorconfig"],
475
- user_messages_summary: "Personal indent preference: 2-space TS / 4-space Py. Stable across multiple projects, not project-specific.",
476
- type: "guidelines",
477
- slug: "indent-style-by-language",
478
- layer: "personal",
479
- scope: "broad",
480
- relevance_paths: [],
481
- proposed_reason: "explicit-user-mark",
482
- 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."
483
- })
484
- ```
485
-
486
- Layer = personal (跨项目通用 + 工具/编辑器偏好 signals dominate; no 强 team signal applies). Scope = broad with `relevance_paths=[]` (personal layer ALWAYS forces broad — paths don't generalize across projects per Phase 1.5 special case).
154
+ - 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`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`, `pending_path`, `layer`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
155
+
156
+ ## Worked Examples / E5 Cron / Dry-run (ref-only)
157
+
158
+ - **Worked examples** (3 end-to-end fab_extract_knowledge calls: decision/team, pitfall/team, guideline/personal): `Read ref/worked-examples.md`
159
+ - **E5 Scheduled Daily Recap** (only when entry_point=E5_cron — OS cron, `/loop`, or scheduled trigger): `Read ref/e5-cron-recap.md`
160
+ - **Dry-run Scope** (authoritative catalogue of all writes suspended by `--dry-run`): `Read ref/dry-run-scope.md`
@@ -0,0 +1,16 @@
1
+ # Dry-run Scope (unified)
2
+
3
+ `dry_run = true` (per Phase 4.5 detection rule — invocation MUST carry the verbatim token `--dry-run`; v2.0.0-rc.37 NEW-10 dropped the legacy substring fallback on bare `dry-run` / `dry_run` / `预览` to eliminate false positives on incidental mentions of those words mid-prompt) suspends ALL side-effecting writes below; read-side machinery (Phase 1 digest collection, Phase 2.5 viability gate evaluation, Phase 3 candidate render) executes normally so the user can preview what WOULD happen.
4
+
5
+ | Write operation | Normal mode | Dry-run mode |
6
+ |---|---|---|
7
+ | `fab_extract_knowledge` MCP call (Phase 4) | One call per confirmed candidate, writes to `.fabric/knowledge/pending/<slug>.md` | SKIPPED. Phase 4 renders "would write N pending entries" preview table instead. |
8
+ | `session_archive_attempted` event (Phase 4.5) | Appended to `.fabric/events.jsonl` for every session in scope | SKIPPED entirely. No ledger entry. |
9
+ | `fab_review reject` (Phase 3 user-dismissed branch) | Invoked when user types `撤销` / `reject` after self-archive proposal | SKIPPED. The dismissal is rendered to console but no MCP write occurs. |
10
+ | `fabric onboard-coverage` slot writes (Phase 1.5 fill-all / dismiss-all) | Each `Bash("fabric config dismiss-slot <slot>")` invocation runs | SKIPPED. Slot decisions are shown as "would dismiss/propose" preview. |
11
+ | `.fabric/.cache/session-digests/<session_id>.md` reads | Read freely (read-side, safe) | Read freely — same as normal. |
12
+ | Stop-hook / archive-hint stdin/stdout | Read-only inspection of `.fabric/events.jsonl` | Same — no change. |
13
+
14
+ All user-facing output in dry-run mode MUST prefix `[DRY-RUN]` at the start of each Phase header (e.g. `[DRY-RUN] Phase 3 — Batch Review`). Exit message: `[DRY-RUN complete] would have written N entry/entries; no .fabric/ files were modified. Re-invoke without --dry-run to commit.`
15
+
16
+ Cross-reference: Phase 4.5 `Dry-run override` section in SKILL.md holds the rationale. When adding a new write side-effect to any phase, update BOTH the phase section AND this table.