@fenglimg/fabric-cli 2.0.0-rc.30 → 2.0.0-rc.34

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 (37) hide show
  1. package/dist/{chunk-PNRWNUFX.js → chunk-5N3KXIVI.js} +73 -4
  2. package/dist/{doctor-TTDTKOFJ.js → doctor-E26YO67D.js} +8 -2
  3. package/dist/index.js +4 -4
  4. package/dist/{install-OEBNSCS5.js → install-XCRX34CX.js} +4 -2
  5. package/dist/{uninstall-VLLJG7JT.js → uninstall-Q7V55BXH.js} +1 -1
  6. package/package.json +3 -3
  7. package/templates/hooks/cite-policy-evict.cjs +242 -0
  8. package/templates/hooks/configs/claude-code.json +11 -0
  9. package/templates/hooks/fabric-hint.cjs +11 -1
  10. package/templates/hooks/knowledge-hint-broad.cjs +276 -21
  11. package/templates/hooks/knowledge-hint-narrow.cjs +466 -14
  12. package/templates/skills/fabric-archive/SKILL.md +53 -864
  13. package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
  14. package/templates/skills/fabric-archive/ref/e5-cron-recap.md +5 -5
  15. package/templates/skills/fabric-archive/ref/i18n-policy.md +3 -3
  16. package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
  17. package/templates/skills/fabric-archive/ref/{phase-0-4-onboard.md → phase-1-5-onboard.md} +21 -21
  18. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +60 -0
  19. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +54 -0
  20. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +80 -0
  21. package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
  22. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
  23. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
  24. package/templates/skills/fabric-archive/ref/rc-history.md +6 -6
  25. package/templates/skills/fabric-archive/ref/worked-examples.md +1 -1
  26. package/templates/skills/fabric-import/SKILL.md +29 -556
  27. package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
  28. package/templates/skills/fabric-import/ref/output-contract.md +61 -0
  29. package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
  30. package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
  31. package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
  32. package/templates/skills/fabric-review/SKILL.md +56 -414
  33. package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
  34. package/templates/skills/fabric-review/ref/modify-flow.md +95 -0
  35. package/templates/skills/fabric-review/ref/output-contract.md +58 -0
  36. package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
  37. package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
@@ -1,920 +1,113 @@
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 (UX i18n Policy class 2 — errors/preconditions):
17
+ If none hold, stop the skill and tell the user (UX i18n Policy class 2):
18
18
 
19
19
  - zh-CN: `没有触发归档信号;如需手动归档请显式调用 fabric-archive`
20
20
  - en: `No archive signal detected; to manually archive, explicitly invoke fabric-archive`
21
21
 
22
- (Render per `fabric_language` resolved in Phase 0.6 Config Load below.)
22
+ Render per `fabric_language` resolved in Phase 0.5.
23
23
 
24
- This skill is `Check-not-Ask`, not a preference interview:
24
+ This skill is `Check-not-Ask`, not a preference interview. Phase 2 proactively gathers evidence; Phase 2.5 viability gate aborts if no archive signal exists; Phase 3 classifies / layers / slugs + batch-review; Phase 4 persists via `fab_extract_knowledge` (one call per candidate).
25
25
 
26
- - **Phase 0.4 (rc.23 F8c) first-run onboard phase** — checks S5 onboard-slot coverage; if unclaimed slots remain, prompts user to fill / dismiss / skip before proceeding to normal archive flow
27
- - Phase 0 proactively gathers candidate evidence from the session
28
- - Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
29
- - Phase 1 classifies / layers / slugs each candidate and presents one batch review for user correction
30
- - Phase 1.5 assigns `relevance_scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
31
- - Phase 2 calls `fab_extract_knowledge` once per confirmed candidate
26
+ ## 执行流程 (1 User Review Round)
32
27
 
33
- ## 执行流程 (6 Phase / 1 User Review Round)
28
+ Phase chain: `0 0.5 1 [1.5] → 2 → 2.5 → 3 → 3.5 → 4 → 4.5`. Each phase below is a navigator stub — full procedure, decision tables, and worked examples live in `ref/`.
34
29
 
35
- ### Phase -0.5 — Range Resolution
30
+ ### Phase 0 — Range Resolution
36
31
 
37
- When the skill is invoked, the user's prompt may carry an explicit range hint
38
- a time window (`今日` / `last week`), a topic keyword (`rc.20`, `cite policy`),
39
- or a literal session_id reference. This phase parses those hints and resolves
40
- them to a concrete `session_id[]` set that constrains Phase 0.0 cross-session
41
- digest collection. **Falls through silently** when no hint is detected — Phase
42
- 0.0 then sees the legacy "all distinct sessions since last anchor" behaviour.
32
+ 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.
43
33
 
44
- This is the foundation of the **E4 (user-language range selection) entry
45
- point** per rc.25 Q3.3. AI (Claude/Codex) interprets the rules below at runtime
46
- — there is no parser code; the LLM IS the parser. Time-window patterns +
47
- keyword extraction are LLM-native tasks; an `AskUserQuestion` fallback covers
48
- the low-confidence case.
49
-
50
- #### Step 1 — Invocation context inspection
34
+ `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.
51
35
 
52
- Read three sources to determine whether a range hint is present:
36
+ ### Phase 0.5 Config Load
53
37
 
54
- | Source | Inspection | Yields |
55
- |---|---|---|
56
- | User prompt text (the natural-language string that triggered the skill) | Free-form parse for time words + topic keywords + literal `session_id=...` | Candidate `time_window`, `topic_keywords[]`, `explicit_session_ids[]` |
57
- | Hook-context-marker (only when entry = E1 hook-triggered) | Already-parsed `{count, hours_since_last, sessions_since_last_proposed}` block emitted by archive-hint.cjs | Optional default scope = "since last archive" |
58
- | User invocation type | E1 / E2 / E3 / E4 / E5 (per rc.25 5-entry model) | Decides whether to fall back to `AskUserQuestion` (E2/E4 only) |
59
-
60
- If NONE of the three yields a usable hint AND `user_invocation_type ∉ {E2, E4}`,
61
- fall through directly to Phase 0.6 with `range = "all"` sentinel (legacy
62
- behaviour). E2 / E4 with no hint → proceed to Step 5 fallback.
63
-
64
- #### Step 2 — Time-window parsing
65
-
66
- Match the user prompt against the following bilingual patterns (case-insensitive
67
- substring match, leftmost-longest wins). The matched span yields a
68
- `[ts_start, ts_end]` pair in Unix milliseconds. `now` = the skill invocation
69
- timestamp.
70
-
71
- zh-CN pattern table:
72
-
73
- | Pattern | ts_start | ts_end |
74
- |---|---|---|
75
- | `今日` / `今天` | `floor(now, day)` (本地时区 00:00) | `now` |
76
- | `上周` / `过去一周` | `now - 7d` | `now` |
77
- | `过去 N 天` / `近 N 天` (N ∈ 1..30) | `now - N*24h` | `now` |
78
- | `自上次归档` / `自上次 archive` | tail-scan events.jsonl → most recent `knowledge_proposed.ts` (fallback `events[0].ts`) | `now` |
79
-
80
- en pattern table:
81
-
82
- | Pattern | ts_start | ts_end |
83
- |---|---|---|
84
- | `today` | `floor(now, day)` (local TZ 00:00) | `now` |
85
- | `last week` / `past week` | `now - 7d` | `now` |
86
- | `past N days` / `last N days` (N ∈ 1..30) | `now - N*24h` | `now` |
87
- | `since last archive` / `since last archived` | tail-scan events.jsonl → most recent `knowledge_proposed.ts` (fallback `events[0].ts`) | `now` |
88
-
89
- Notes:
90
-
91
- - Patterns are non-exclusive — if the prompt matches multiple (e.g. "今日 cite policy"),
92
- apply time-window THEN topic-keyword as AND.
93
- - Numeric N must parse as a positive integer ≤ 30; reject anything else as parse-miss.
94
- - All other date phrasings (specific dates like `5月10日`, relative phrasings
95
- like `三天前下午`) are NOT handled here — emit parse-miss and let Step 5
96
- fallback collect a structured answer.
97
-
98
- #### Step 3 — Topic-keyword extraction
99
-
100
- After time-window matching (or alongside it when both apply), extract content
101
- keywords from the prompt:
102
-
103
- 1. Strip recognised time-window tokens (e.g. remove `今日` / `last week` from
104
- the residual prompt).
105
- 2. Tokenize residual on whitespace + CJK boundary. Combine adjacent CJK
106
- characters into one token; split en words on spaces.
107
- 3. Filter **stop-words**: skill control verbs (`archive`, `归档`, `下`, `的`),
108
- articles / particles (`the`, `a`, `an`, `了`, `吧`), pronouns (`it`, `this`,
109
- `that`, `这个`, `那个`), and 1-character en tokens.
110
- 4. Retain **2-5 word tokens** (or 1-token CJK content words ≥ 2 chars like
111
- `rc.20`, `cite`). Cap at 8 keywords; drop weaker (later-position) ones.
112
-
113
- The retained set is `topic_keywords[]`. Empty set = no keyword filter.
114
-
115
- #### Step 4 — session_id resolution algorithm
116
-
117
- Given `time_window = [ts_start, ts_end] | null` and `topic_keywords[] | []`:
118
-
119
- ```
120
- Step a — Read events.jsonl tail (last 500 events) via `Bash: tail -n 500
121
- .fabric/events.jsonl`. ENOENT → empty list (no resolution possible
122
- → emit parse-miss → Step 5 fallback).
123
-
124
- Step b — Per distinct session_id present in the tail, compute:
125
- ts_min = min(ts) over events with this session_id
126
- ts_max = max(ts) over events with this session_id
127
- digest_path = .fabric/.cache/session-digests/<session_id>.md
128
- digest_body = Read(digest_path) if exists, else ""
129
-
130
- Step c — TIME-WINDOW FILTER (skip when time_window is null):
131
- Keep session_id IFF [ts_min, ts_max] intersects [ts_start, ts_end]
132
- (i.e. ts_max >= ts_start AND ts_min <= ts_end).
133
- Multiple time intervals are OR'd within the time-window filter
134
- category (none currently supported; reserved for future ranges).
135
-
136
- Step d — TOPIC-KEYWORD FILTER (skip when topic_keywords is empty):
137
- Keep session_id IFF digest_body (case-insensitive) contains
138
- AT LEAST ONE keyword from topic_keywords[].
139
- Multiple keywords are OR'd within the keyword filter category.
140
-
141
- Step e — AND across filter categories:
142
- A session must pass BOTH filters when BOTH are present.
143
- Pass either filter alone when only one is present.
144
- Pass-through (all sessions) when neither is present.
145
-
146
- Step f — Result: distinct session_id[] (preserve event-order); if empty AND
147
- a parse hit was claimed → degrade to Step 5 fallback (user wanted a
148
- range that resolved to zero sessions).
149
- ```
150
-
151
- #### Step 5 — AskUserQuestion fallback (E2 / E4 only)
152
-
153
- When Step 2/3 emit parse-miss OR Step 4 resolves to zero sessions AND the
154
- invocation type permits prompting (E2 user-active or E4 user回溯-active —
155
- NEVER E1 hook / E3 AI-self / E5 cron), surface a structured question. UX i18n
156
- Policy class 5 applies: `header` + `question` translate per `fabric_language`;
157
- `options[]` routing keys stay English.
158
-
159
- ```ts
160
- AskUserQuestion({
161
- header: "Archive range", // zh-CN: "归档范围"
162
- question:
163
- "Which session range should this archive cover? " +
164
- "(today = current calendar day; last-week = past 7 days; " +
165
- "since-last-archive = newer than last knowledge_proposed event; " +
166
- "custom = type a free-form range)",
167
- options: ["today", "last-week", "since-last-archive", "custom"]
168
- })
169
- ```
170
-
171
- Routing:
172
-
173
- | Choice | Action |
174
- |---|---|
175
- | `today` | Re-enter Step 2 with synthetic prompt `今日` / `today` (per `fabric_language`); resolve session_ids; proceed to Phase 0.6. |
176
- | `last-week` | Re-enter Step 2 with synthetic prompt `上周` / `last week`; proceed to Phase 0.6. |
177
- | `since-last-archive` | Re-enter Step 2 with synthetic prompt `自上次归档` / `since last archive`; proceed to Phase 0.6. |
178
- | `custom` | Surface a one-line text prompt to the user ("type a range, e.g. 'rc.20', 'past 3 days', '上周 cite policy'"). Re-enter Phase -0.5 Step 1 with the user-typed sub-prompt. Loop max 1 time — second parse-miss falls through to `range = "all"` with a warning. |
179
-
180
- #### Step 6 — Carry-forward contract
181
-
182
- Phase -0.5 produces ONE of:
183
-
184
- - `session_id[]` (non-empty array of distinct session_ids) — passed to Phase
185
- 0.0 as the explicit scope filter; Phase 0.0 skips its own anchor-walk and
186
- uses this list directly.
187
- - `"all"` (sentinel string) — no range hint detected; Phase 0.0 falls back to
188
- the legacy anchor-walk behaviour ("all distinct sessions since last
189
- `knowledge_proposed`").
190
-
191
- NEVER pass an empty `session_id[]` forward — that case must degrade to Step 5
192
- fallback (or, when fallback is forbidden by invocation type, to `"all"` with
193
- a one-line stderr warning).
194
-
195
- #### Worked examples
196
-
197
- **Example A — time-only: `今日复盘`**
198
-
199
- ```
200
- Step 1: prompt = "今日复盘"; user_invocation_type = E2.
201
- Step 2: matches `今日` → time_window = [floor(now, day), now].
202
- Step 3: residual "复盘" survives stop-word filter → topic_keywords = ["复盘"].
203
- (Edge case: the residual content word may also filter; if 复盘 is
204
- in the stop list it becomes []. Treat as topic-keyword empty.)
205
- Step 4: tail-scan events.jsonl; keep sessions whose [ts_min, ts_max]
206
- intersects today's window. Say 3 sessions match.
207
- Step 5: skipped (resolution succeeded).
208
- Step 6: emit session_id[] = ["sess-a", "sess-b", "sess-c"] → Phase 0.6.
209
- ```
210
-
211
- **Example B — keyword-only: `rc.20 的归档下`**
212
-
213
- ```
214
- Step 1: prompt = "rc.20 的归档下"; user_invocation_type = E2.
215
- Step 2: no time pattern matches → time_window = null.
216
- Step 3: strip "归档"/"下"/"的" stop-words → topic_keywords = ["rc.20"].
217
- Step 4: tail-scan events.jsonl; for each session_id, Read its digest;
218
- keep those whose digest body matches /rc\.20/i. Say 2 sessions
219
- match (one was the rc.20 grilling session, one had a tangential
220
- mention).
221
- Step 5: skipped.
222
- Step 6: emit session_id[] = ["sess-x", "sess-y"] → Phase 0.6.
223
- ```
224
-
225
- **Example C — combined: `上周 rc.20`**
226
-
227
- ```
228
- Step 1: prompt = "上周 rc.20"; user_invocation_type = E4.
229
- Step 2: matches `上周` → time_window = [now - 7d, now].
230
- Step 3: strip "上周" → topic_keywords = ["rc.20"].
231
- Step 4: AND filter — keep sessions whose [ts_min, ts_max] intersects last
232
- week AND whose digest matches /rc\.20/i. Say 1 session matches.
233
- Step 5: skipped.
234
- Step 6: emit session_id[] = ["sess-z"] → Phase 0.6.
235
- ```
236
-
237
- If Example C had resolved to zero sessions (e.g. user types `上周 rc.99`),
238
- Step 4 would degrade into Step 5 — surfacing AskUserQuestion since E4 permits
239
- prompting.
240
-
241
- ### Phase 0.6 — Config Load
242
-
243
- Before any candidate-gathering work, the skill MUST read
244
- `.fabric/fabric-config.json` to resolve the following tunables (with documented
245
- defaults if absent):
246
-
247
- | Config field | Default | Used by |
248
- |---|---|---|
249
- | `archive_max_candidates_per_batch` | 8 | Phase 0 hard budget on candidates per Phase 1 batch |
250
- | `archive_max_recent_paths` | 20 | Phase 0 cap on `recent_paths` enumeration |
251
- | `archive_digest_max_sessions` | 10 | Phase 0.0 cap on cross-session digest load |
252
-
253
- If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
38
+ 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.
254
39
 
255
40
  ### UX i18n Policy
256
41
 
257
- Read `.fabric/fabric-config.json` → `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`). Emit all user-facing prose in the resolved variant. Protected tokens (MCP tool names like `fab_extract_knowledge`, schema fields like `relevance_scope`, the verbatim `强 team` / `强 personal` / `默认 team` heuristic block) are NEVER translated.
258
-
259
- `AskUserQuestion` policy: `header` + `question` translate; `options[]` are routing keys — stay English regardless of locale.
260
-
261
- **For the full 5-class taxonomy + edge cases:** `Read packages/cli/templates/skills/fabric-archive/ref/i18n-policy.md` (or `.claude/skills/fabric-archive/ref/i18n-policy.md` post-install).
262
-
263
-
264
- ### Phase 0.0 — Collect Cross-Session Digests
265
-
266
- Before any single-session collection or viability gating, stitch together
267
- context from every session that has accumulated since the last
268
- `knowledge_proposed` event. The rc.7 Stop hook writes a per-session digest to
269
- `.fabric/.cache/session-digests/<session_id>.md` (≤5KB, contains top 10 user
270
- messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
271
-
272
- 1. **Read events.jsonl tail.** Use `Bash` with
273
- `tail -n 200 .fabric/events.jsonl` (tolerate ENOENT — empty ledger is a
274
- normal first-run state).
275
- 2. **Find the anchor.** Walk the tail backwards to locate the most recent
276
- `knowledge_proposed` event (`event_type === "knowledge_proposed"`). The
277
- anchor's `ts` becomes the lower bound for digest selection. If NO anchor
278
- exists, treat all digests in the cache as in-scope.
279
- 3. **Collect session_ids since anchor.** Scan the tail forward from the
280
- anchor and collect every distinct `session_id` field that appears on any
281
- event newer than the anchor. Distinct ordering preserved.
282
- 4. **Load digests.** For each collected `session_id`, read
283
- `.fabric/.cache/session-digests/<session_id>.md`. Missing digest files
284
- degrade silently (the digest write was best-effort, so a Stop hook crash
285
- can produce a session_id without a digest). Cap the loaded digest set at
286
- `archive_digest_max_sessions` most-recent sessions (config-resolved, default
287
- 10) to bound LLM context (~50KB worst-case at default).
288
- 4.5. **Filter via session_archive_attempted ledger (rc.25 TASK-05).** Before
289
- step 5 builds the cross-session context, drop sessions that the outcome
290
- ledger says we should not re-scan. For each `session_id` collected in
291
- steps 1-3, scan `.fabric/events.jsonl` for events where
292
- `event_type === "session_archive_attempted"` AND `session_id` matches,
293
- keep the most-recent one by `ts`, and apply this state machine:
294
-
295
- - **(a) Look up the most recent `session_archive_attempted`** event for
296
- this `session_id` (none found → fall through to (e)).
297
- - **(b) `outcome === "user_dismissed"` → drop (permanent skip).** The
298
- user explicitly rejected this session's candidates; never auto-re-scan
299
- it. Respect the dismissal forever — re-scanning would re-propose the
300
- same content the user already declined.
301
- - **(c) `(nowMs - attempted_event.ts) < ANTI_LOOP_HOURS * 3_600_000` →
302
- drop (cooldown skip).** Anti-loop window: even if outcome is otherwise
303
- re-scannable, never re-scan a session within 12 hours of the last
304
- attempt. Aligns 心智 with the Stop-hook cooldown so a single user does
305
- not see the same session repeatedly within one work day.
306
- - **(d) `covered_through_ts` present → check for high-value signal in
307
- `ts > covered_through_ts` events for this `session_id`.** Tail-scan
308
- `events.jsonl` for events newer than the watermark whose
309
- `session_id` matches. A session passes this gate iff at least ONE of:
310
- - ≥1 event with `event_type ∈ HIGH_VALUE_EVENT_TYPES`
311
- (`knowledge_context_planned`, `edit_paths_recorded`), OR
312
- - the latest `assistant_turn_observed` event body contains ≥1 of
313
- `NORMATIVE_KEYWORDS` (substring match, case-insensitive for
314
- English entries).
315
-
316
- No high-value signal → drop (no new content worth re-scanning, even
317
- though the cooldown has expired). Has signal → keep for re-scan.
318
- - **(e) Never attempted (no `session_archive_attempted` event found for
319
- this `session_id`) → keep.** First-time scan; nothing to filter
320
- against.
321
- - **(f) Cross-session pending dedupe** (operates on candidate
322
- observations, not on `session_id` filter): gather all
323
- `knowledge_proposed_ids` from `session_archive_attempted` events with
324
- `outcome === "proposed"` across ALL sessions in the recent window
325
- (NOT just the current candidate session). This builds a global set of
326
- idempotency keys already proposed by prior archive runs but not yet
327
- reviewed by the user (`.fabric/knowledge/pending/` may still contain
328
- them). When classifying new observations in Phase 1, drop any
329
- candidate whose computed `idempotency_key` matches an id already in
330
- this set — it was already proposed by an earlier archive run, the
331
- user just hasn't reviewed it yet, so re-proposing would duplicate
332
- pending entries and inflate `candidates_proposed` counts. Per Phase
333
- 2.5 line 1112 — this is the dedupe consumer of `knowledge_proposed_ids`.
334
-
335
- The resulting filtered `session_id[]` proceeds into step 5's digest
336
- concatenation. Sessions filtered out in this step do NOT contribute to
337
- `### Cross-session digest`, are NOT included in `source_sessions` on any
338
- fab_extract_knowledge call, and are NOT referenced in `session_context`
339
- bodies.
340
-
341
- **Constants (rc.25 — verbatim):**
342
-
343
- - `ANTI_LOOP_HOURS = 12` — cooldown window in hours between consecutive
344
- re-scans of the same `session_id`. Rationale: 心智对齐 hook cooldown
345
- (`stop_hook_cooldown_hours = 12`); identical mental model avoids user
346
- confusion when a session shows up in both hook reminders and
347
- archive re-scan candidates.
348
- - `HIGH_VALUE_EVENT_TYPES = ['knowledge_context_planned', 'edit_paths_recorded']`
349
- — event types that count as "new substantive activity worth
350
- re-scanning" past `covered_through_ts`. Chat accumulation
351
- (`assistant_turn_observed` alone) does NOT count — it would let mere
352
- conversation noise trigger re-scans.
353
- - `NORMATIVE_KEYWORDS = ['以后','always','never','from now on','下次','记一下','永远不要']`
354
- — substring patterns scanned against the latest
355
- `assistant_turn_observed` body for the session. Mixed CN/EN to cover
356
- bilingual users. If any keyword hits, the session is flagged as
357
- having high-value chat-only signal even without code edits.
358
-
359
- **Worked examples:**
360
-
361
- - **Session X (user_dismissed)** — last `session_archive_attempted` ts
362
- = 3 days ago, outcome = `user_dismissed`. Rule (b) fires → permanent
363
- skip. Session X is dropped even if 50 new `knowledge_context_planned`
364
- events have accumulated since.
365
- - **Session Y (proposed 6h ago)** — last `session_archive_attempted`
366
- ts = 6h ago, outcome = `proposed`. Rule (c) fires: 6h < 12h cooldown
367
- window → drop (cooldown skip). Y becomes eligible again after the
368
- 12h window closes, provided high-value signal accumulates by then.
369
- - **Session Z (viability_failed 14h ago + 3 new plan_context)** — last
370
- `session_archive_attempted` ts = 14h ago, outcome = `viability_failed`,
371
- `covered_through_ts` = T₀. Rules (b)(c) pass. Rule (d) tail-scans for
372
- `session_id === Z AND ts > T₀`: finds 3 `knowledge_context_planned`
373
- events. HIGH_VALUE_EVENT_TYPES match → keep Z for re-scan. The
374
- previous viability failure does not block a re-scan once new
375
- substantive activity has accumulated.
376
- 5. **Build cross-session context.** Concatenate the loaded digests into a
377
- single `### Cross-session digest` block to carry into Phase 0.5 + Phase 1.
378
- Use this block to:
379
- - Detect session-spanning patterns (e.g. a discussion that started in
380
- session A and continued in session B).
381
- - Populate the `source_sessions` array on every fab_extract_knowledge
382
- call — the array form (T5) replaces the legacy `source_session` string.
383
- - Inform the `session_context` blob written to each pending entry's body
384
- (3-5 lines summarizing goal + key turning point, per T6).
385
-
386
- Graceful degradation: if `.fabric/.cache/session-digests/` is missing
387
- entirely, this phase reports an empty context and Phase 0 falls back to the
388
- single-session behaviour. Tests that synthesize events.jsonl without
389
- populating the digest cache continue to work. If `session_archive_attempted`
390
- events are missing entirely (pre-rc.25 ledger or rotation has trimmed older
391
- events), treat all sessions as never-attempted (current default behavior) —
392
- step 4.5 rule (e) applies uniformly, so the filter degrades to the legacy
393
- "scan everything since anchor" semantics without raising errors.
394
-
395
- ### Phase 0.4 — First-run Onboard (ref-only)
396
-
397
- **SKIP this phase entirely unless** entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback} AND a fresh `fab onboard-coverage --json` reports `missing.length > 0`. For E1 (hook), E3 (AI self-trigger), and E5 (cron), onboard is non-applicable — silently fall through to Phase 0.
398
-
399
- When the gate above does fire (live user + missing slots), `Read packages/cli/templates/skills/fabric-archive/ref/phase-0-4-onboard.md` (or `.claude/skills/fabric-archive/ref/phase-0-4-onboard.md` post-install) for the full Step 1-4 (coverage check → user prompt → tour-and-propose) procedure.
400
-
401
- ### Phase 0 — Collect Candidates
402
-
403
- Gather raw evidence from the recent session before any classification:
404
-
405
- 1. Read the tail of `.fabric/events.jsonl` since the last `knowledge_proposed` event.
406
- - Use `Bash` with `tail -n 200 .fabric/events.jsonl` if the file is large.
407
- - Tolerate ENOENT — empty ledger is a normal first-run state.
408
- 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).
409
- 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.
410
- 4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
411
-
412
- 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.
413
-
414
- ### Phase 0.5 — Viability Gate (Anti-Archive Guard)
415
-
416
- 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.
417
-
418
- #### Archive signals (≥ 1 hit ⇒ gate PASSES, proceed to Phase 1)
419
-
420
- Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 0:
421
-
422
- 1. Explicit normative language: user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`.
423
- 2. Wrong-turn-and-revert: a path was edited, then reverted (or partially undone) after diagnosis — indicates a pitfall worth recording.
424
- 3. Long diagnostic loop: an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution.
425
- 4. New dependency adoption: a new package / library / external tool was introduced (e.g. `package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
426
- 5. New pattern emergence: a reusable abstraction or naming convention was named ("the X phase", "the Y pattern", "let's call this Z").
427
- 6. Decision confirmation: ≥ 2 alternatives were weighed AND a rationale was given before settling.
428
- 7. Explicit dismissal-with-reason: user rejected an approach AND stated why (the why is the archivable knowledge, not the dismissal itself).
429
- 8. Process formalization: a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
430
-
431
- #### Anti-archive signals (forces gate to FAIL unless an archive signal also fires)
432
-
433
- 1. Typo-only edits: the entire session is whitespace / spelling / formatting changes.
434
- 2. Pure refactor: rename / move / extract with no behavior change AND no naming convention being established.
435
- 3. Narrow rename request: user asked to rename one symbol / file with no rationale.
436
- 4. Duplicate of existing canonical: the observation is already covered by an existing entry under `.fabric/knowledge/<type>/` (do a quick Glob before deciding).
437
-
438
- #### Gate decision
439
-
440
- ```
441
- archive_signals_hit = count of archive signals fired
442
- anti_signals_hit = count of anti-archive signals fired
443
- user_explicit_invoke = user typed "archive what we just did" / "fabric archive" / similar
444
-
445
- IF user_explicit_invoke:
446
- gate = PASS # explicit invocation bypasses all gates
447
- ELIF archive_signals_hit == 0:
448
- gate = FAIL (reason="no_signal")
449
- ELIF anti_signals_hit > 0 AND archive_signals_hit == 0:
450
- gate = FAIL (reason="anti_signal_dominates")
451
- ELSE:
452
- gate = PASS
453
- ```
454
-
455
- #### On gate FAIL
456
-
457
- Branching by `entry_point` (resolved at Phase -0.5):
458
-
459
- ```
460
- IF entry_point ∈ {E1_hook, E3_ai_self_trigger, E5_cron}:
461
- → SILENT-SKIP path: do NOT emit the gate-FAIL message; do NOT trigger AskUserQuestion.
462
- → Still write ONE `session_archive_attempted` event per session in scope
463
- with outcome='skipped_no_signal' (see Phase 2.5 for the emission contract).
464
- → Exit the skill silently. Rationale: hook / AI self-trigger / cron are
465
- non-user-active contexts — a verbose message there is pure noise.
466
- ELSE (entry_point ∈ {E2_explicit, E4_user_range}):
467
- → User-active path: render the gate-FAIL message below (UX i18n Policy
468
- class 2 — errors/preconditions; render per `fabric_language`).
469
- → Still write ONE `session_archive_attempted` event per session in scope
470
- with outcome='viability_failed' (see Phase 2.5 Outcome Decision Matrix
471
- row 2 — user-active gate failure populates `viability_failed`, NOT
472
- `skipped_no_signal` which is reserved for the SILENT-SKIP branch
473
- above).
474
- → Exit the skill.
475
- ```
476
-
477
- For the user-active branch (E2 / E4), the gate-FAIL message variants are:
478
-
479
- zh-CN variant:
480
-
481
- ```
482
- 本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
483
- ```
484
-
485
- en variant:
486
-
487
- ```
488
- Current session is routine execution; no new knowledge to archive (gate=<reason>). To force-archive, explicitly invoke fabric-archive.
489
- ```
490
-
491
- In BOTH branches: do NOT proceed to Phase 1, do NOT call any MCP tool. The legacy `knowledge_archive_aborted` event line (`{"ts":"...","kind":"knowledge_archive_aborted","reason":"<reason>","session":"<id>"}`) MAY be appended in addition to the mandatory Phase 2.5 `session_archive_attempted` event — they serve different audit purposes (legacy abort reason vs new outcome state machine) and the two coexist during the rc.25 transition window.
492
-
493
- ##### events.jsonl Constraint Note
42
+ 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).
494
43
 
495
- Event lines appended to `.fabric/events.jsonl` are subject to POSIX
496
- single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed
497
- atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk
498
- interleaved corruption under concurrent skill + server writes to the
499
- same ledger.
44
+ `Read ref/i18n-policy.md` for the full 5-class taxonomy + edge cases.
500
45
 
501
- Skills MUST ensure:
502
-
503
- - Each event JSON line is a **single line** (no embedded newlines;
504
- escape `\n` in any string value).
505
- - `session_context` and other free-form text fields **self-truncate** to
506
- keep the entire serialized line under 4KB. Suggested per-field caps:
507
- `session_context` first 500 chars; `source_sessions` cap at 5
508
- entries; `recent_paths` cap at 20 entries; `user_messages_summary`
509
- first 500 chars.
510
- - If approaching the 4KB ceiling after the per-field caps, drop optional
511
- fields (e.g. tags / extra metadata) **before** truncating semantic
512
- content (the summary / context that carries the actual observation).
513
- - This constraint applies to any event the skill itself appends (e.g.
514
- the abort signal above); MCP-server-side appends (via
515
- `appendEventLedgerEvent`) are already line-length-bounded server-side.
516
-
517
- #### On gate PASS
518
-
519
- Proceed to Phase 1 with the candidates carried over from Phase 0.
520
-
521
- ### Phase 1 — Classify, Layer, Slug, Review
522
-
523
- For each candidate, the skill proposes:
524
-
525
- - **type** ∈ {model, decision, guideline, pitfall, process}
526
- - **layer** ∈ {team, personal} via the verbatim heuristic below
527
- - **slug** per the 5-rule naming guideline below
528
- - **summary** (1-2 sentences, will become the entry body's lead paragraph)
529
-
530
- #### Five Knowledge Types (singular noun = type concept)
531
-
532
- - **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).
533
- - **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).
534
- - **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).
535
- - **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).
536
- - **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).
537
-
538
- #### Layer Classification Heuristic (强 team 信号 / 强 personal 信号 / 默认 team)
539
-
540
- > - **强 team**: 引用本项目代码、团队共识用语("we decided")、fabric-import 路径产物、业务领域、绑定本项目代码的 pitfall
541
- > - **强 personal**: 第一人称偏好、跨项目通用、工具/编辑器偏好、个人工作流
542
- > - **默认 team**: 安全偏置——错标 team 在 PR review 中会被发现,错标 personal 静默丢失
46
+ ### Phase 1 — Collect Cross-Session Digests
543
47
 
544
- Resolution order: check team signals first; only assign personal if personal signals dominate AND no team signal applies; otherwise default to team.
48
+ Stitch context from every session accumulated since the last `knowledge_proposed` event. Tail-scan `.fabric/events.jsonl` find anchor forward-collect distinct `session_id`s load per-session digests from `.fabric/.cache/session-digests/<session_id>.md` → concatenate into `### Cross-session digest` block, populating `source_sessions[]` + `session_context` for Phase 4. Cap at `archive_digest_max_sessions`.
545
49
 
546
- #### Slug Naming Guideline (5 Rules)
50
+ `Read ref/phase-1-cross-session.md` for the Step 4.5 ledger filter state machine (rules a-f), `ANTI_LOOP_HOURS` / `HIGH_VALUE_EVENT_TYPES` / `NORMATIVE_KEYWORDS` constants, and 3 worked examples (user_dismissed / cooldown / re-scan-with-signal).
547
51
 
548
- 1. kebab-case (lowercase letters, digits, hyphens only no underscores, no CamelCase)
549
- 2. 2-5 words separated by hyphens
550
- 3. 20-40 characters total length
551
- 4. semantic core only (drop articles "the/a", drop generic suffixes "stuff/thing")
552
- 5. unique within its (type, layer) bucket — if collision, the LLM must add a discriminating word, NOT a counter
52
+ Graceful degradation: missing digest cache → single-session fallback. Missing `session_archive_attempted` events (pre-rc.25) legacy "scan everything since anchor" behaviour.
553
53
 
554
- 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).
54
+ ### Phase 1.5 First-run Onboard (ref-only)
555
55
 
556
- #### Decision Tree (是否值得归档)
56
+ **SKIP this phase entirely unless** entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback} AND `fab onboard-coverage --json` reports `missing.length > 0`. For E1/E3/E5, silently fall through to Phase 0.
557
57
 
558
- ```
559
- Recent session contains an observation worth keeping?
560
- ├─ NO → skip (do nothing, no MCP call)
561
- └─ YES → does it fit one of {model, decision, guideline, pitfall, process}?
562
- ├─ NO → skip (not classifiable = not yet ripe)
563
- └─ YES → assign type
564
-
565
- Apply layer heuristic
566
-
567
- Propose slug per 5 rules
568
-
569
- Present in batch review
570
-
571
- User confirms / corrects / rejects
572
-
573
- Phase 2: call fab_extract_knowledge once per confirmed candidate
574
- ```
575
-
576
- #### Batch Review Template
577
-
578
- 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.
579
-
580
- en variant (`fabric_language === "en"`):
581
-
582
- ```md
583
- # Archive Review — N candidates
584
-
585
- ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
586
- Summary: <1-2 sentences capturing the observation>
587
- Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
588
- Scope reasoning: <why narrow or broad — see Phase 1.5>
589
- relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
590
- Confirm? (Y to accept, edit type/layer/slug/relevance_scope/relevance_paths inline, N to skip)
591
-
592
- ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
593
- Summary: ...
594
- Layer reasoning: ...
595
- Scope reasoning: ...
596
- relevance_paths: []
597
- Confirm? ...
598
- ```
599
-
600
- zh-CN variant (`fabric_language === "zh-CN"`):
601
-
602
- ```md
603
- # 归档 Review — N 条候选
604
-
605
- ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
606
- 摘要: <1-2 句捕捉该观察>
607
- Layer 判定: <命中哪条 强 team / 强 personal 信号,或默认 team>
608
- Scope 判定: <为什么 narrow 或 broad — 见 Phase 1.5>
609
- relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
610
- 确认?(Y 接受 / 内联编辑 type/layer/slug/relevance_scope/relevance_paths / N 跳过)
611
-
612
- ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
613
- 摘要: ...
614
- Layer 判定: ...
615
- Scope 判定: ...
616
- relevance_paths: []
617
- 确认?...
618
- ```
619
-
620
- 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 `[]`).
621
-
622
- ### Phase 1.5 — Scope Decision + relevance_paths Derivation
623
-
624
- 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.
625
-
626
- #### Scope decision (narrow vs broad)
627
-
628
- ```
629
- relevance_scope =
630
- narrow IF the candidate is tied to a specific module / file / subsystem
631
- AND there is explicit single-module evidence in edit_paths
632
- (i.e. all worth-keeping edits in this session concentrated in one
633
- module tree, OR the candidate explicitly references that module)
58
+ `Read ref/phase-1-5-onboard.md` for the Step 1-4 coverage check → user prompt → tour-and-propose procedure.
634
59
 
635
- broad IF the candidate is cross-cutting / methodological / general
636
- (applies regardless of which path the agent is working in)
60
+ ### Phase 2 Collect Candidates
637
61
 
638
- broad (default, on uncertainty safe偏置 per Q-1 in handoff)
639
- ```
640
-
641
- 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`.
642
-
643
- ##### Examples
644
-
645
- - `decision: single-cjs-hook-script` → `narrow` (tied to `templates/claude-hooks/` + `packages/cli/src/commands/hooks.ts`)
646
- - `pitfall: deepmerge-array-replace-trap` → `broad` (cross-cutting JSON merge gotcha, applies anywhere deepMerge is used)
647
- - `guideline: slug-naming-rules` → `broad` (methodology, no specific module)
648
- - `model: wave-1-parallel-task-dag` → `narrow` (tied to `packages/cli/src/commands/plan.ts`)
649
- - `guideline: indent-style-by-language` (personal layer) → `broad + []` (personal forces broad)
650
-
651
- #### relevance_paths derivation algorithm (rc.5 single-signal: edit_paths only)
652
-
653
- 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.
654
-
655
- ```
656
- Step 1: COLLECT
657
- edit_paths = []
658
- Scan session transcript for tool_use entries where
659
- tool_use.name ∈ {Edit, Write, MultiEdit}
660
- Extract the file_path argument from each, push into edit_paths.
661
-
662
- Step 2: DEDUPE
663
- edit_paths = unique(edit_paths)
664
-
665
- Step 3: BLACKLIST FILTER
666
- Drop paths matching any of:
667
- - **/*.<ext> where <ext> is a single trivial extension on a single file
668
- (i.e. avoid emitting bare **/*.md as a relevance pattern)
669
- - Repo-root single files: README.md, package.json, package-lock.json,
670
- pnpm-lock.yaml, tsconfig.json, .gitignore, LICENSE, CHANGELOG.md
671
- - Read-only paths (never modified) — those go to ## Evidence, not relevance_paths
672
-
673
- Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
674
- Group remaining paths by common prefix.
675
- For each group of ≥ 2 sibling paths sharing a prefix:
676
- - Compute longest common directory prefix
677
- - Limit generalization depth: at most 2 levels below the common prefix
678
- - Emit glob: <common-prefix>/**/*.<ext> (or <common-prefix>/**/<filename>)
679
- Singleton paths (group size = 1) are kept as-is (literal path, no glob).
680
-
681
- Step 5: SCOPE GATE
682
- IF relevance_scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
683
- IF relevance_scope == narrow → relevance_paths = result of Step 4
684
-
685
- Step 6: ATTACH READ-ONLY EVIDENCE
686
- Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
687
- block in the pending entry body — NOT in relevance_paths. They document
688
- what the agent consulted without making them part of the activation gate.
689
- ```
690
-
691
- ##### Worked generalization example
692
-
693
- Edit history during session:
694
-
695
- ```
696
- packages/server/src/services/extract.ts
697
- packages/server/src/services/review.ts
698
- packages/server/src/services/promote.ts
699
- packages/cli/src/commands/plan.ts
700
- README.md
701
- ```
702
-
703
- Step 1-2 (collect + dedupe): all 5 unique.
704
- Step 3 (blacklist): drop `README.md` (repo-root single file).
705
- Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
706
- - `packages/server/src/services/{extract,review,promote}.ts` → group size 3 ≥ 2, common prefix `packages/server/src/services/`, glob: `packages/server/src/services/**/*.ts`
707
- - `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
708
-
709
- Step 5 (assume `relevance_scope=narrow`):
710
-
711
- ```json
712
- "relevance_paths": [
713
- "packages/server/src/services/**/*.ts",
714
- "packages/cli/src/commands/plan.ts"
715
- ]
716
- ```
717
-
718
- If `relevance_scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
719
-
720
- #### Inline-edit support during batch review
721
-
722
- The user MAY inline-edit `[relevance_scope=...]` in the batch review. When this happens:
723
-
724
- - Edit changes `narrow → broad`: clear `relevance_paths` to `[]`.
725
- - Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
726
- - The user MAY also directly inline-edit `relevance_paths` to a custom array; treat this as authoritative and skip auto-derivation.
727
-
728
- ### Phase 2 — Persist via MCP
729
-
730
- For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT batch multiple candidates into one call.
731
-
732
- #### Output Contract (MCP tool call shape)
733
-
734
- ```ts
735
- mcp__fabric__fab_extract_knowledge({
736
- source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 0.0)
737
- recent_paths: ["<path1>", "<path2>", ...], // capped at archive_max_recent_paths (config-resolved, default 20)
738
- user_messages_summary: "<compact prose ≤500 chars>",
739
- type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
740
- slug: "<kebab-case-2-to-5-words>",
741
- layer: "team" | "personal",
742
- relevance_scope: "narrow" | "broad", // from Phase 1.5
743
- relevance_paths: ["<glob1>", "<literal2>", ...], // narrow ⇒ derived; broad ⇒ []
744
- // v2.0.0-rc.7 T6: required fields for future-self reviewability.
745
- proposed_reason:
746
- "explicit-user-mark" // user said "always / never / 下次注意" etc.
747
- | "diagnostic-then-fix" // long debug loop surfaced a new pattern/pitfall
748
- | "decision-confirmation" // ≥2 options weighed AND rationale stated → decision/model
749
- | "wrong-turn-revert" // tried path X, reverted → pitfall
750
- | "new-dependency-or-pattern" // new dep/lib/abstraction introduced
751
- | "dismissal-with-reason", // user rejected approach AND said why
752
- session_context: "<3-5 line markdown: session goal + key turning point>",
753
- // v2.0.0-rc.23 TASK-006 (a-C1): four OPTIONAL structured triage fields.
754
- // Lift implicit signals out of `## Session context` prose so future-self
755
- // reviewers / plan-context retrievers can triage relevance from
756
- // frontmatter alone, without re-reading the body. Omit any field the
757
- // skill cannot infer cleanly — guessing is worse than omitting.
758
- intent_clues: ["<short trigger>", "<negative trigger e.g. 'NOT for X'>"], // when this rule applies / when NOT
759
- tech_stack: ["<lang/framework>", "..."], // inferred from recent_paths (see table below)
760
- impact: ["<consequence of ignoring>"], // why future-self should care
761
- must_read_if: "<one-line strong trigger>" // single condition; if it holds, the entry is required reading
762
- // tags? — NOT in current schema; reserved for future
763
- })
764
- ```
765
-
766
- ##### C1 triage-field inference table
767
-
768
- | Field | Inference source | Skip when |
769
- |----------------|----------------------------------------------------------------------------------|------------------------------------|
770
- | `intent_clues` | Pull from `session_context` turning point + negative phrasing in the transcript ("not for", "don't do X when") | No clear trigger phrasing surfaced |
771
- | `tech_stack` | Map `recent_paths` extensions: `.ts`→`typescript`, `.tsx`→`typescript`+`react`, `.go`→`go`, `package.json`→`nodejs`, `pyproject.toml`→`python`, `Cargo.toml`→`rust`. Add framework markers from path heuristics (`cocos`→`cocos-creator`, `next.config`→`nextjs`) | Rule is stack-agnostic |
772
- | `impact` | Pull from the diagnostic-loop body — "wasted 30 min", "production outage", "silent data loss" | No observable consequence stated |
773
- | `must_read_if` | Strongest single trigger from the worth-archive signal: a file path, a routine, a recurring condition; ≤160 chars | No single dominant trigger fits |
774
-
775
- All four fields are STRICTLY OPTIONAL. The schema accepts the call without any of them — omit rather than guess. None of the four participate in the idempotency_key hash (server formula at `extract-knowledge.ts:100-106` is frozen to `{source_session, type, slug}`), so partial-vs-full fill of these fields on the same triple is safe.
776
-
777
- The Skill infers `proposed_reason` from the classification + viability-gate
778
- signal that fired:
779
-
780
- | Signal fired (Phase 0.5) | Classification | Default proposed_reason |
781
- |--------------------------------|----------------|-----------------------------|
782
- | Explicit normative language | guideline | `explicit-user-mark` |
783
- | Wrong-turn-and-revert | pitfall | `wrong-turn-revert` |
784
- | Long diagnostic loop | pitfall/model | `diagnostic-then-fix` |
785
- | New dependency adoption | decision/model | `new-dependency-or-pattern` |
786
- | New pattern emergence | model | `new-dependency-or-pattern` |
787
- | Decision confirmation | decision | `decision-confirmation` |
788
- | Explicit dismissal-with-reason | decision | `dismissal-with-reason` |
789
- | Process formalization | process | `new-dependency-or-pattern` |
62
+ 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.
790
63
 
791
- The `session_context` is a 3-5 line summary distilled from the Phase 0.0
792
- cross-session digest (see Phase 0.0 below for digest source). Format:
793
-
794
- ```
795
- Session goal: <one-line of what the user was trying to accomplish>
796
- Turning point: <one-line of the key moment that produced the worth-archive observation>
797
- [optional 1-3 more lines of supporting context]
798
- ```
64
+ ### Phase 2.5 Viability Gate (Anti-Archive Guard)
799
65
 
800
- Future-self reviewing the pending entry MUST be able to understand WHY this
801
- entry was proposed without conversation transcript access — proposed_reason
802
- is the structured why, session_context is the narrative why.
66
+ Coarse viability check. **PASS conditions**: user_explicit_invoke OR ≥1 archive signal hit (normative language, wrong-turn-and-revert, long diagnostic loop, new dependency, new pattern, decision confirmation, dismissal-with-reason, process formalization). **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'`).
803
67
 
804
- 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.
68
+ `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.
805
69
 
806
- The server returns `{ pending_path, idempotency_key }`. Display `pending_path` to the user so they can `Read` the persisted entry if they wish.
70
+ ### Phase 3 Classify, Layer, Slug, Review
807
71
 
808
- #### Idempotency Note
809
-
810
- 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.
811
-
812
- If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
72
+ 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).
813
73
 
814
- **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:
74
+ #### Layer Classification Heuristic (verbatim, contract-locked)
815
75
 
816
- - Same `(type, slug)` but a different **first** session → distinct idempotency key → produces two pending files.
817
- - 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.
818
- - 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.
819
-
820
- ### Phase 2.5 — Persist Archive Attempt
821
-
822
- MANDATORY closing step on every skill invocation — runs AFTER Phase 2 (success path) AND on every early-exit path (Phase 0.0 dropped-all, Phase 0.5 gate-FAIL silent-skip or user-active, Phase 1 batch user-dismissed). Drives the Q3.4 outcome state machine + cross-session digest rescan filter.
823
-
824
- #### Dry-run override (v2.0.0-rc.27 TASK-007 / audit §2.25)
825
-
826
- When the user's invocation explicitly carries a dry-run intent — the prompt or `/fabric-archive` invocation contains a literal `--dry-run`, `dry-run`, `dry_run`, or `预览` token — the skill MUST skip Phase 2.5's ledger write. The mandatory contract above is suspended only in this single case; every other early-exit path still emits the event.
827
-
828
- Rationale: pre-rc.27 the spec read as "MANDATORY on every invocation" which created an irreconcilable conflict when the user explicitly requested a no-mutation preview (audit §2.25). The dry-run override resolves the deadlock by treating dry-run as an entry-context override that disables the ledger side-effect while preserving the rest of the skill's read-side machinery (Phase 0.0 digest collection, Phase 0.5 viability gate, Phase 1 candidate preview render). The user sees what WOULD have happened without the audit trail recording an attempt that never produced a pending entry.
829
-
830
- Detection rule (substring match, case-insensitive): if the originating prompt contains `--dry-run` | `dry-run` | `dry_run` | `预览` as a standalone token, set `dry_run = true` for the entire skill run and skip the Phase 2.5 event emission. All other phases run normally; their user-facing output should prefix `[DRY-RUN]` to make the mode visible.
831
-
832
- When `dry_run = true`:
833
- - Phase 1 batch review header MUST include `[DRY-RUN — no writes will occur]`
834
- - Phase 2 candidate emission is REPLACED with a "would write N pending entries" preview rendered as a numbered table (`would-write` shape — same columns as the real Phase 1 review)
835
- - Phase 2.5 event emission is SKIPPED entirely (the rationale above)
836
- - No `fab_extract_knowledge` MCP call is issued (dry-run is purely read-side)
837
-
838
- #### What to emit
839
-
840
- For EACH `session_id` in the run's scope (multi-session E4 runs emit MULTIPLE events — one per session_id; single-session E1/E2/E3/E5 runs emit ONE event), append ONE `session_archive_attempted` line to `.fabric/events.jsonl`:
841
-
842
- ```jsonc
843
- {
844
- "kind": "fabric-event",
845
- "id": "<uuid or ts-derived>",
846
- "ts": <epoch ms>,
847
- "schema_version": 1,
848
- "session_id": "<the session this event pertains to>",
849
- "event_type": "session_archive_attempted",
850
- "outcome": "proposed" | "viability_failed" | "user_dismissed" | "skipped_no_signal",
851
- "covered_through_ts": <max event ts scanned for this session>,
852
- "candidates_proposed": <integer, default 0>,
853
- "knowledge_proposed_ids": ["<idempotency_key_1>", "..."] // default []
854
- }
855
- ```
856
-
857
- #### Outcome decision matrix
858
-
859
- | Skill terminal state | outcome | candidates_proposed | knowledge_proposed_ids |
860
- |----------------------------------------------------------------------|----------------------|---------------------|-------------------------------------------------|
861
- | Phase 2 wrote ≥ 1 pending entry | `proposed` | N (count written) | `[idempotency_key_1, idempotency_key_2, ...]` (from each fab_extract_knowledge response) |
862
- | Phase 0.5 viability_failed AND entry_point ∈ {E2_explicit, E4_user_range} AND user saw + accepted the gate-FAIL message | `viability_failed` | 0 | `[]` |
863
- | Phase 1 batch review — user dismissed ALL presented candidates | `user_dismissed` | 0 | `[]` |
864
- | Phase 0.0 filter dropped every session in scope OR Phase 0.5 silent-skip path (E1_hook / E3_ai_self_trigger / E5_cron) | `skipped_no_signal` | 0 | `[]` |
865
-
866
- Rationale highlights:
867
- - `user_dismissed` is the ONLY outcome that suppresses future auto-rescan (respects user decision per Q3.4).
868
- - `proposed` populates `knowledge_proposed_ids` so the cross-session digest in Phase 0.0 can dedupe future runs against already-proposed entries.
869
- - `viability_failed` vs `skipped_no_signal` distinguishes "user was prompted but the gate stopped us" from "we never bothered the user" — both allow rescan but the doctor history report differentiates them.
76
+ > - **强 team**: 引用本项目代码、团队共识用语("we decided")、fabric-import 路径产物、业务领域、绑定本项目代码的 pitfall
77
+ > - **强 personal**: 第一人称偏好、跨项目通用、工具/编辑器偏好、个人工作流
78
+ > - **默认 team**: 安全偏置——错标 team PR review 中会被发现,错标 personal 静默丢失
870
79
 
871
- #### covered_through_ts watermark
80
+ Resolution: team first; assign personal only if 强 personal dominates AND no 强 team applies; else default team.
872
81
 
873
- ```
874
- covered_through_ts = max(events_in_scope[*].ts)
875
- ```
82
+ `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.
876
83
 
877
- where `events_in_scope` is the set of events the skill actually examined for THAT session_id (Phase 0 + Phase 0.0 digest input). On rescan, Phase 0.0 compares the current `max(ts)` against this stored watermark only sessions with new events past the watermark are eligible candidates.
84
+ ### Phase 3.5Scope Decision + relevance_paths Derivation
878
85
 
879
- #### Multi-session emission rule
86
+ 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).
880
87
 
881
- When the run scope spans multiple session_ids (E4 user-range with `--since` / topic-keyword matching multiple sessions), emit ONE `session_archive_attempted` event PER session_id. Each event's `covered_through_ts` is computed against that session's own event subset. The `knowledge_proposed_ids` for a multi-session `proposed` run lists ALL idempotency_keys produced by the run; ledger consumers that want per-session breakdown should join against `source_sessions` on each pending entry.
88
+ `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.
882
89
 
883
- #### Append pattern (Bash echo, 4KB-safe, fail-tolerant)
90
+ ### Phase 4 Persist via MCP
884
91
 
885
- Reuse the Phase 0.5 `events.jsonl Constraint Note` pattern: single-line JSON 4KB, no embedded newlines. Best-effort write if the append fails (disk full, permission denied, race), the skill MUST still exit successfully. Log the failure to stderr only; do NOT surface it to the user. Rationale: a missing `session_archive_attempted` event degrades gracefully the next Phase 0.0 digest treats the session as "never archived" and re-evaluates it, which is the safe-default behavior.
92
+ 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**.
886
93
 
887
- ```bash
888
- # Pseudo — actual implementation uses the same pattern as the legacy
889
- # knowledge_archive_aborted emit at the end of Phase 0.5.
890
- echo '{"kind":"fabric-event","id":"...","ts":..., "schema_version":1, "session_id":"...", "event_type":"session_archive_attempted","outcome":"...","covered_through_ts":...,"candidates_proposed":0,"knowledge_proposed_ids":[]}' >> .fabric/events.jsonl
891
- ```
94
+ 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).
892
95
 
893
- The per-field caps from Phase 0.5's constraint note carry over: `knowledge_proposed_ids` capped at 20 entries (drop tail with `...` marker in `id` field if truncated); other fields are bounded by schema.
96
+ `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.
894
97
 
895
- #### Worked example: E5 cron silent-skip
98
+ ### Phase 4.5 Persist Archive Attempt
896
99
 
897
- Setup: An OS cron job runs `fabric-archive` at 03:00 daily for the "today" range (E5 entry_point). Today's session was routine config edits no archive signals fire.
100
+ 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.
898
101
 
899
- Trace:
900
- 1. Phase -0.5 resolves `entry_point=E5_cron`, range = "today" → 1 session_id in scope.
901
- 2. Phase 0.0 digest collects events for that session_id; nothing dropped.
902
- 3. Phase 0.4 onboard is skipped (E5 is not E2).
903
- 4. Phase 0.5 viability gate runs — `archive_signals_hit=0` → `gate=FAIL (reason=no_signal)`.
904
- 5. `entry_point=E5_cron` ∈ {E1, E3, E5} → SILENT-SKIP branch. No message rendered.
905
- 6. Phase 2.5 (mandatory) appends ONE event:
906
- ```
907
- {"kind":"fabric-event","id":"...","ts":<now>,"schema_version":1,"session_id":"<today-session-id>","event_type":"session_archive_attempted","outcome":"skipped_no_signal","covered_through_ts":<max ts of today's events>,"candidates_proposed":0,"knowledge_proposed_ids":[]}
908
- ```
909
- 7. Skill exits silently. Cron output is empty.
102
+ **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.
910
103
 
911
- Next day's cron rescan: Phase 0.0 sees `covered_through_ts < max(ts of session's new events)` session is rescan-eligible loop continues without `user_dismissed` block.
104
+ `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.
912
105
 
913
106
  ## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
914
107
 
915
108
  ### DISPLAY Rules
916
109
 
917
- - MUST complete Phase 0 AND Phase 0.5 viability gate before any batch-review output.
110
+ - MUST complete Phase 2 AND Phase 2.5 viability gate before any batch-review output.
918
111
  - MUST abort with the gate-FAIL message (no MCP call) when the viability gate fails AND the user did not explicitly invoke fabric-archive.
919
112
  - MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[relevance_scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
920
113
  - MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
@@ -938,12 +131,8 @@ Next day's cron rescan: Phase 0.0 sees `covered_through_ts < max(ts of session's
938
131
  - NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
939
132
  - 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`.
940
133
 
941
- ## Worked Examples (ref-only)
942
-
943
- Three end-to-end fab_extract_knowledge call examples (decision/team, pitfall/team, guideline/personal) live in `packages/cli/templates/skills/fabric-archive/ref/worked-examples.md` (or `.claude/skills/fabric-archive/ref/worked-examples.md` post-install). Load when you want to see all required + optional fields populated together in a realistic shape.
944
-
945
- ## E5 Scheduled Daily Recap (ref-only)
946
-
947
- Only relevant when entry_point=E5_cron (OS cron, `/loop`, or scheduled trigger). For interactive invocations, Phase -0.5 has already routed past this — nothing to load.
134
+ ## Worked Examples / E5 Cron / Dry-run (ref-only)
948
135
 
949
- When E5 fires: `Read packages/cli/templates/skills/fabric-archive/ref/e5-cron-recap.md` (or `.claude/skills/fabric-archive/ref/e5-cron-recap.md` post-install) for `/loop` vs OS cron tradeoffs + the `今日复盘` magic-phrase parse contract.
136
+ - **Worked examples** (3 end-to-end fab_extract_knowledge calls: decision/team, pitfall/team, guideline/personal): `Read ref/worked-examples.md`
137
+ - **E5 Scheduled Daily Recap** (only when entry_point=E5_cron — OS cron, `/loop`, or scheduled trigger): `Read ref/e5-cron-recap.md`
138
+ - **Dry-run Scope** (authoritative catalogue of all writes suspended by `--dry-run`): `Read ref/dry-run-scope.md`