@fenglimg/fabric-cli 2.0.0-rc.30 → 2.0.0-rc.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{doctor-TTDTKOFJ.js → doctor-E26YO67D.js} +8 -2
- package/dist/index.js +3 -3
- package/dist/{install-OEBNSCS5.js → install-YSFVNY3T.js} +1 -1
- package/package.json +3 -3
- package/templates/hooks/knowledge-hint-broad.cjs +268 -21
- package/templates/hooks/knowledge-hint-narrow.cjs +466 -14
- package/templates/skills/fabric-archive/SKILL.md +144 -738
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +5 -5
- package/templates/skills/fabric-archive/ref/i18n-policy.md +3 -3
- package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
- package/templates/skills/fabric-archive/ref/{phase-0-4-onboard.md → phase-1-5-onboard.md} +21 -21
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +60 -0
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +54 -0
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +80 -0
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
- package/templates/skills/fabric-archive/ref/rc-history.md +6 -6
- package/templates/skills/fabric-archive/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-import/SKILL.md +29 -556
- package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
- package/templates/skills/fabric-import/ref/output-contract.md +61 -0
- package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
- package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
- package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
- package/templates/skills/fabric-review/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fabric-archive
|
|
3
|
-
description:
|
|
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
|
|
|
@@ -19,27 +19,27 @@ If none of the above hold, stop the skill immediately and tell the user (UX i18n
|
|
|
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.
|
|
22
|
+
(Render per `fabric_language` resolved in Phase 0.5 Config Load below.)
|
|
23
23
|
|
|
24
24
|
This skill is `Check-not-Ask`, not a preference interview:
|
|
25
25
|
|
|
26
|
-
- **Phase
|
|
27
|
-
- Phase
|
|
28
|
-
- Phase
|
|
29
|
-
- Phase
|
|
30
|
-
- Phase
|
|
31
|
-
- Phase
|
|
26
|
+
- **Phase 1.5 (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 2 proactively gathers candidate evidence from the session
|
|
28
|
+
- Phase 2.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
|
|
29
|
+
- Phase 3 classifies / layers / slugs each candidate and presents one batch review for user correction
|
|
30
|
+
- Phase 3.5 assigns `relevance_scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
|
|
31
|
+
- Phase 4 calls `fab_extract_knowledge` once per confirmed candidate
|
|
32
32
|
|
|
33
|
-
## 执行流程 (
|
|
33
|
+
## 执行流程 (1 User Review Round)
|
|
34
34
|
|
|
35
|
-
### Phase
|
|
35
|
+
### Phase 0 — Range Resolution
|
|
36
36
|
|
|
37
37
|
When the skill is invoked, the user's prompt may carry an explicit range hint —
|
|
38
38
|
a time window (`今日` / `last week`), a topic keyword (`rc.20`, `cite policy`),
|
|
39
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
|
|
40
|
+
them to a concrete `session_id[]` set that constrains Phase 1 cross-session
|
|
41
41
|
digest collection. **Falls through silently** when no hint is detected — Phase
|
|
42
|
-
|
|
42
|
+
1 then sees the legacy "all distinct sessions since last anchor" behaviour.
|
|
43
43
|
|
|
44
44
|
This is the foundation of the **E4 (user-language range selection) entry
|
|
45
45
|
point** per rc.25 Q3.3. AI (Claude/Codex) interprets the rules below at runtime
|
|
@@ -47,6 +47,23 @@ point** per rc.25 Q3.3. AI (Claude/Codex) interprets the rules below at runtime
|
|
|
47
47
|
keyword extraction are LLM-native tasks; an `AskUserQuestion` fallback covers
|
|
48
48
|
the low-confidence case.
|
|
49
49
|
|
|
50
|
+
#### Confidence decision rule (rc.33 — explicit LLM-as-parser contract)
|
|
51
|
+
|
|
52
|
+
LLM-as-parser commits a result OR falls back to `AskUserQuestion` per these
|
|
53
|
+
deterministic criteria. **Confidence is not a vibe** — it is a checklist.
|
|
54
|
+
|
|
55
|
+
| Confidence | Conditions (ALL must hold) | Action |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **HIGH (commit)** | (a) Time-window matches a row in the Step 2 bilingual pattern table verbatim (modulo case); AND (b) Topic-keywords are nouns from Step 3 retention rules (no ambiguous referents like `it`, `这个`); AND (c) No conflicting hints (e.g. `今日` + `上周` co-present) | Commit `time_window` + `topic_keywords[]` → Step 4 |
|
|
58
|
+
| **MEDIUM (commit with audit log)** | Exactly ONE category present (only time-window OR only keywords) AND remaining categories empty (not contradictory) | Commit with single-category filter; emit `low_confidence_parse=false` field |
|
|
59
|
+
| **LOW (AskUserQuestion)** | ANY of: (i) multiple competing time-windows; (ii) numeric N in `过去 N 天` parses outside 1..30; (iii) keyword set after Step 3 stop-word filter is empty BUT prompt clearly carried non-time intent (≥3 residual content words); (iv) literal `session_id=` substring present but malformed (not matching `[a-f0-9-]{36}` UUID); (v) entry = E2/E4 AND patterns yield BOTH time + keywords but they appear in unconnected clauses (LLM-judged separation) | Step 5 fallback (`AskUserQuestion` with structured options) |
|
|
60
|
+
| **PARSE-MISS (silent skip)** | None of above match AND entry ∈ {E1, E3, E5} | Fall through with `range = "all"` sentinel; no user prompt |
|
|
61
|
+
|
|
62
|
+
Implementation note: the LLM's evaluation MUST proceed top-to-bottom — HIGH
|
|
63
|
+
checks first, then MEDIUM, then LOW. The first match wins. Do not skip
|
|
64
|
+
categories or pick LOW preemptively to avoid commitment — that defeats the
|
|
65
|
+
deterministic-parser contract and reintroduces the rc.32 audit P0 (T4) issue.
|
|
66
|
+
|
|
50
67
|
#### Step 1 — Invocation context inspection
|
|
51
68
|
|
|
52
69
|
Read three sources to determine whether a range hint is present:
|
|
@@ -58,187 +75,16 @@ Read three sources to determine whether a range hint is present:
|
|
|
58
75
|
| 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
76
|
|
|
60
77
|
If NONE of the three yields a usable hint AND `user_invocation_type ∉ {E2, E4}`,
|
|
61
|
-
fall through directly to Phase 0.
|
|
78
|
+
fall through directly to Phase 0.5 with `range = "all"` sentinel (legacy
|
|
62
79
|
behaviour). E2 / E4 with no hint → proceed to Step 5 fallback.
|
|
63
80
|
|
|
64
|
-
####
|
|
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 的归档下`**
|
|
81
|
+
#### Steps 2-6 (ref-only)
|
|
212
82
|
|
|
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
|
-
```
|
|
83
|
+
For the full **Step 2** bilingual time-window pattern tables (zh-CN + en), **Step 3** topic-keyword extraction algorithm, **Step 4** session_id resolution pseudocode, **Step 5** AskUserQuestion fallback (E2/E4 only), **Step 6** carry-forward contract (`session_id[] | "all"`), and three worked examples (time-only / keyword-only / combined), `Read packages/cli/templates/skills/fabric-archive/ref/phase-0-range-resolution.md` (or `.claude/skills/fabric-archive/ref/phase-0-range-resolution.md` post-install).
|
|
236
84
|
|
|
237
|
-
|
|
238
|
-
Step 4 would degrade into Step 5 — surfacing AskUserQuestion since E4 permits
|
|
239
|
-
prompting.
|
|
85
|
+
Brief output contract: Phase 0 emits ONE of `session_id[]` (non-empty distinct list, scope filter for Phase 1) OR `"all"` sentinel (legacy anchor-walk). Never empty array.
|
|
240
86
|
|
|
241
|
-
### Phase 0.
|
|
87
|
+
### Phase 0.5 — Config Load
|
|
242
88
|
|
|
243
89
|
Before any candidate-gathering work, the skill MUST read
|
|
244
90
|
`.fabric/fabric-config.json` to resolve the following tunables (with documented
|
|
@@ -246,9 +92,9 @@ defaults if absent):
|
|
|
246
92
|
|
|
247
93
|
| Config field | Default | Used by |
|
|
248
94
|
|---|---|---|
|
|
249
|
-
| `archive_max_candidates_per_batch` | 8 | Phase
|
|
250
|
-
| `archive_max_recent_paths` | 20 | Phase
|
|
251
|
-
| `archive_digest_max_sessions` | 10 | Phase
|
|
95
|
+
| `archive_max_candidates_per_batch` | 8 | Phase 2 hard budget on candidates per Phase 3 batch |
|
|
96
|
+
| `archive_max_recent_paths` | 20 | Phase 2 cap on `recent_paths` enumeration |
|
|
97
|
+
| `archive_digest_max_sessions` | 10 | Phase 1 cap on cross-session digest load |
|
|
252
98
|
|
|
253
99
|
If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
|
|
254
100
|
|
|
@@ -261,144 +107,30 @@ Read `.fabric/fabric-config.json` → `fabric_language` (`zh-CN` / `en` / `zh-CN
|
|
|
261
107
|
**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
108
|
|
|
263
109
|
|
|
264
|
-
### Phase
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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)
|
|
110
|
+
### Phase 1 — Collect Cross-Session Digests
|
|
111
|
+
|
|
112
|
+
Stitch together context from every session that has accumulated since the last `knowledge_proposed` event. The rc.7 Stop hook writes a per-session digest to `.fabric/.cache/session-digests/<session_id>.md` (≤5KB, contains top 10 user messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
|
|
113
|
+
|
|
114
|
+
**5-step summary:**
|
|
115
|
+
|
|
116
|
+
1. Read `.fabric/events.jsonl` tail (last 200 lines). Tolerate ENOENT.
|
|
117
|
+
2. Walk backward → find most recent `knowledge_proposed` event as anchor (ts lower bound).
|
|
118
|
+
3. Forward-scan from anchor → collect distinct `session_id`s.
|
|
119
|
+
4. Load each `<session_id>.md` digest. Cap at `archive_digest_max_sessions` (default 10).
|
|
120
|
+
4.5. **(rc.25 TASK-05) Apply ledger filter state machine** — drop sessions per outcome ledger: `user_dismissed` (permanent), within 12h cooldown, or no high-value signal past `covered_through_ts`. Cross-session pending dedupe also gates Phase 3 candidate emission.
|
|
121
|
+
5. Concatenate digests into `### Cross-session digest` block; populate `source_sessions[]` + `session_context` for Phase 4.
|
|
122
|
+
|
|
123
|
+
For the full Step 4.5 ledger 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), `Read packages/cli/templates/skills/fabric-archive/ref/phase-1-cross-session.md` (or `.claude/skills/fabric-archive/ref/phase-1-cross-session.md` post-install).
|
|
124
|
+
|
|
125
|
+
Graceful degradation: missing digest cache → empty context, Phase 2 single-session fallback. Missing `session_archive_attempted` events (pre-rc.25) → Step 4.5 rule (e) applies uniformly (legacy "scan everything since anchor").
|
|
126
|
+
|
|
127
|
+
### Phase 1.5 — First-run Onboard (ref-only)
|
|
396
128
|
|
|
397
129
|
**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
130
|
|
|
399
|
-
When the gate above does fire (live user + missing slots), `Read packages/cli/templates/skills/fabric-archive/ref/phase-
|
|
131
|
+
When the gate above does fire (live user + missing slots), `Read packages/cli/templates/skills/fabric-archive/ref/phase-1-5-onboard.md` (or `.claude/skills/fabric-archive/ref/phase-1-5-onboard.md` post-install) for the full Step 1-4 (coverage check → user prompt → tour-and-propose) procedure.
|
|
400
132
|
|
|
401
|
-
### Phase
|
|
133
|
+
### Phase 2 — Collect Candidates
|
|
402
134
|
|
|
403
135
|
Gather raw evidence from the recent session before any classification:
|
|
404
136
|
|
|
@@ -409,116 +141,43 @@ Gather raw evidence from the recent session before any classification:
|
|
|
409
141
|
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
142
|
4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
|
|
411
143
|
|
|
412
|
-
Hard budget: `archive_max_candidates_per_batch` candidates max per Phase
|
|
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.
|
|
144
|
+
Hard budget: `archive_max_candidates_per_batch` candidates max per Phase 3 batch (config-resolved, default 8). If more surface, keep the configured-N with strongest worth-archiving signals (see Phase 3 type definitions) and drop the rest.
|
|
417
145
|
|
|
418
|
-
|
|
146
|
+
### Phase 2.5 — Viability Gate (Anti-Archive Guard)
|
|
419
147
|
|
|
420
|
-
|
|
148
|
+
Coarse viability check before Phase 3 batch review. Goal: short-circuit obvious no-archive sessions (routine execution, typo fixes, narrow renames).
|
|
421
149
|
|
|
422
|
-
|
|
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.
|
|
150
|
+
**Archive signals (≥ 1 hit ⇒ gate PASS):**
|
|
430
151
|
|
|
431
|
-
|
|
152
|
+
1. Explicit normative language (`always`/`never`/`以后`/`下次注意`/`记一下`/`from now on`/`永远不要`).
|
|
153
|
+
2. Wrong-turn-and-revert (edit-then-undo with diagnosis).
|
|
154
|
+
3. Long diagnostic loop (> 15 min or > ~10 tool turns).
|
|
155
|
+
4. New dependency adoption (package.json / pyproject.toml diff adds dep).
|
|
156
|
+
5. New pattern emergence (named abstraction/convention).
|
|
157
|
+
6. Decision confirmation (≥ 2 alternatives + rationale).
|
|
158
|
+
7. Explicit dismissal-with-reason.
|
|
159
|
+
8. Process formalization (load-bearing step order).
|
|
432
160
|
|
|
433
|
-
1
|
|
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).
|
|
161
|
+
**Anti-archive signals (force FAIL unless ≥ 1 archive signal also fires):** typo-only / pure refactor / narrow rename / duplicate-of-existing-canonical.
|
|
437
162
|
|
|
438
|
-
|
|
163
|
+
**Gate decision:**
|
|
439
164
|
|
|
440
165
|
```
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
166
|
+
IF user_explicit_invoke: gate = PASS
|
|
167
|
+
ELIF archive_signals_hit == 0: gate = FAIL (reason="no_signal")
|
|
168
|
+
ELSE: gate = PASS # any archive signal overrides anti-archive
|
|
453
169
|
```
|
|
454
170
|
|
|
455
|
-
|
|
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
|
|
494
|
-
|
|
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.
|
|
500
|
-
|
|
501
|
-
Skills MUST ensure:
|
|
171
|
+
**On gate FAIL — branch by entry_point:**
|
|
502
172
|
|
|
503
|
-
-
|
|
504
|
-
|
|
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.
|
|
173
|
+
- `E1_hook` / `E3_ai_self_trigger` / `E5_cron` → SILENT-SKIP path. No message, no AskUserQuestion. Still emit Phase 4.5 `session_archive_attempted` event with `outcome='skipped_no_signal'`. Exit silently.
|
|
174
|
+
- `E2_explicit` / `E4_user_range` → User-active path. Render gate-FAIL message (i18n class 2, see ref). Emit Phase 4.5 event with `outcome='viability_failed'`. Exit.
|
|
516
175
|
|
|
517
|
-
|
|
176
|
+
**On gate PASS:** proceed to Phase 3 with carried-over candidates.
|
|
518
177
|
|
|
519
|
-
|
|
178
|
+
For verbose signal explanations, zh-CN/en gate-FAIL message bodies, and the events.jsonl 4KB POSIX atomicity constraint note (single-line + self-truncate rules), `Read packages/cli/templates/skills/fabric-archive/ref/phase-2-5-viability.md` (or `.claude/skills/fabric-archive/ref/phase-2-5-viability.md` post-install).
|
|
520
179
|
|
|
521
|
-
### Phase
|
|
180
|
+
### Phase 3 — Classify, Layer, Slug, Review
|
|
522
181
|
|
|
523
182
|
For each candidate, the skill proposes:
|
|
524
183
|
|
|
@@ -527,13 +186,11 @@ For each candidate, the skill proposes:
|
|
|
527
186
|
- **slug** per the 5-rule naming guideline below
|
|
528
187
|
- **summary** (1-2 sentences, will become the entry body's lead paragraph)
|
|
529
188
|
|
|
530
|
-
#### Five Knowledge Types
|
|
189
|
+
#### Five Knowledge Types
|
|
531
190
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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).
|
|
191
|
+
`{model, decision, guideline, pitfall, process}` — singular noun = type concept. Pick one type per candidate; skip if none fits (not yet ripe is also a valid outcome).
|
|
192
|
+
|
|
193
|
+
For verbose worth-archive vs skip-it signals per type with positive/negative examples, see `ref/phase-3-classify.md`.
|
|
537
194
|
|
|
538
195
|
#### Layer Classification Heuristic (强 team 信号 / 强 personal 信号 / 默认 team)
|
|
539
196
|
|
|
@@ -543,83 +200,36 @@ For each candidate, the skill proposes:
|
|
|
543
200
|
|
|
544
201
|
Resolution order: check 强 team signals first; only assign personal if 强 personal signals dominate AND no 强 team signal applies; otherwise default to team.
|
|
545
202
|
|
|
546
|
-
#### Slug Naming
|
|
203
|
+
#### Slug Naming (5 rules)
|
|
547
204
|
|
|
548
|
-
1. kebab-case (lowercase letters, digits, hyphens only
|
|
549
|
-
2. 2-5 words
|
|
550
|
-
3. 20-40
|
|
551
|
-
4.
|
|
552
|
-
5.
|
|
205
|
+
1. kebab-case (lowercase letters, digits, hyphens only).
|
|
206
|
+
2. 2-5 words.
|
|
207
|
+
3. 20-40 chars total.
|
|
208
|
+
4. Semantic core only (drop articles/generics).
|
|
209
|
+
5. Unique within (type, layer) bucket — collisions → add discriminator, NOT counter.
|
|
553
210
|
|
|
554
|
-
|
|
211
|
+
Pass/fail examples → see `ref/phase-3-classify.md`.
|
|
555
212
|
|
|
556
|
-
#### Decision Tree
|
|
213
|
+
#### Decision Tree
|
|
557
214
|
|
|
558
215
|
```
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
216
|
+
Observation worth keeping?
|
|
217
|
+
NO → skip
|
|
218
|
+
YES → fits {model, decision, guideline, pitfall, process}?
|
|
219
|
+
NO → skip (not yet ripe)
|
|
220
|
+
YES → assign type → apply layer heuristic → propose slug
|
|
221
|
+
→ batch review → user confirm → Phase 4: fab_extract_knowledge per candidate
|
|
574
222
|
```
|
|
575
223
|
|
|
576
|
-
#### Batch Review
|
|
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"`):
|
|
224
|
+
#### Batch Review
|
|
581
225
|
|
|
582
|
-
|
|
583
|
-
# Archive Review — N candidates
|
|
226
|
+
Single-screen presentation of all candidates. UX i18n Policy classes 1 + 3 — structure + `Confirm?` prompt bilingualized; protected tokens verbatim; data values (slugs, paths, enum strings) NOT translated.
|
|
584
227
|
|
|
585
|
-
|
|
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
|
-
```
|
|
228
|
+
For en + zh-CN batch review templates with example output, `Read packages/cli/templates/skills/fabric-archive/ref/phase-3-classify.md` (or `.claude/skills/fabric-archive/ref/phase-3-classify.md` post-install).
|
|
599
229
|
|
|
600
|
-
|
|
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
|
-
```
|
|
230
|
+
User MAY inline-edit `type` / `layer` / `slug` / `relevance_scope` / `relevance_paths` before confirming. Editing `[relevance_scope=...]` triggers re-derivation per Phase 3.5 (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).
|
|
619
231
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
### Phase 1.5 — Scope Decision + relevance_paths Derivation
|
|
232
|
+
### Phase 3.5 — Scope Decision + relevance_paths Derivation
|
|
623
233
|
|
|
624
234
|
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
235
|
|
|
@@ -648,273 +258,52 @@ Special case — Personal layer ALWAYS resolves to `relevance_scope=broad` with
|
|
|
648
258
|
- `model: wave-1-parallel-task-dag` → `narrow` (tied to `packages/cli/src/commands/plan.ts`)
|
|
649
259
|
- `guideline: indent-style-by-language` (personal layer) → `broad + []` (personal forces broad)
|
|
650
260
|
|
|
651
|
-
#### relevance_paths derivation
|
|
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.
|
|
261
|
+
#### relevance_paths derivation (rc.5 single-signal: edit_paths)
|
|
708
262
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
```json
|
|
712
|
-
"relevance_paths": [
|
|
713
|
-
"packages/server/src/services/**/*.ts",
|
|
714
|
-
"packages/cli/src/commands/plan.ts"
|
|
715
|
-
]
|
|
716
|
-
```
|
|
263
|
+
rc.5 derives `relevance_paths` exclusively from `edit_paths` (Edit/Write/MultiEdit tool calls). Multi-signal (read_paths + body regex + symbols) deferred to rc.7. The algorithm has 6 steps:
|
|
717
264
|
|
|
718
|
-
|
|
265
|
+
1. COLLECT edit_paths from session transcript.
|
|
266
|
+
2. DEDUPE.
|
|
267
|
+
3. BLACKLIST FILTER (drop repo-root single files like README.md, package.json; drop trivial `**/*.<ext>` globs; drop read-only paths).
|
|
268
|
+
4. PUBLIC-PREFIX GENERALIZE (group ≥ 2 siblings into glob, depth ≤ 2; singletons kept literal).
|
|
269
|
+
5. SCOPE GATE (broad → force `[]`; narrow → use Step 4 result).
|
|
270
|
+
6. ATTACH READ-ONLY EVIDENCE as `## Evidence` block (NOT in relevance_paths).
|
|
719
271
|
|
|
720
|
-
|
|
272
|
+
For full pseudocode, a worked generalization example (5 sample paths → glob + literal output), and inline-edit re-derivation rules (narrow↔broad transitions), `Read packages/cli/templates/skills/fabric-archive/ref/phase-3-5-scope.md` (or `.claude/skills/fabric-archive/ref/phase-3-5-scope.md` post-install).
|
|
721
273
|
|
|
722
|
-
|
|
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
|
|
274
|
+
### Phase 4 — Persist via MCP
|
|
729
275
|
|
|
730
276
|
For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT batch multiple candidates into one call.
|
|
731
277
|
|
|
732
|
-
#### Output Contract (
|
|
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` |
|
|
790
|
-
|
|
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
|
-
```
|
|
278
|
+
#### Output Contract (essentials)
|
|
799
279
|
|
|
800
|
-
|
|
801
|
-
entry was proposed without conversation transcript access — proposed_reason
|
|
802
|
-
is the structured why, session_context is the narrative why.
|
|
280
|
+
`fab_extract_knowledge` call carries: `source_sessions[]` (T5 array), `recent_paths[]` (cap 20), `user_messages_summary` (≤500 chars), `type` ∈ {decisions, pitfalls, guidelines, models, processes} (plural directory-form), `slug` (kebab-case 2-5 words), `layer` ∈ {team, personal}, `relevance_scope` ∈ {narrow, broad}, `relevance_paths[]` (narrow ⇒ derived; broad ⇒ `[]`), `proposed_reason` (enum: `explicit-user-mark` | `diagnostic-then-fix` | `decision-confirmation` | `wrong-turn-revert` | `new-dependency-or-pattern` | `dismissal-with-reason`), `session_context` (3-5 line narrative).
|
|
803
281
|
|
|
804
|
-
|
|
282
|
+
Four OPTIONAL rc.23 triage fields (`intent_clues`, `tech_stack`, `impact`, `must_read_if`) — populate when the skill can infer cleanly; **omit rather than guess**.
|
|
805
283
|
|
|
806
|
-
|
|
284
|
+
For the full TypeScript call shape, the C1 triage-field inference table, the Phase-2.5-signal → `proposed_reason` mapping table, `session_context` format, and T5 array-form idempotency notes, `Read packages/cli/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md` (or `.claude/skills/fabric-archive/ref/phase-4-mcp-persist.md` post-install).
|
|
807
285
|
|
|
808
|
-
|
|
286
|
+
Server returns `{ pending_path, idempotency_key }`. Display `pending_path` for the user. `idempotency_key = sha256({source_session, type, slug})` — calling twice with the same triple is SAFE (server merges evidence).
|
|
809
287
|
|
|
810
|
-
|
|
288
|
+
### Phase 4.5 — Persist Archive Attempt
|
|
811
289
|
|
|
812
|
-
|
|
290
|
+
MANDATORY closing step on every skill invocation — runs AFTER Phase 4 (success path) AND on every early-exit path (Phase 1 dropped-all, Phase 2.5 gate-FAIL silent-skip or user-active, Phase 3 batch user-dismissed). Drives the Q3.4 outcome state machine + cross-session digest rescan filter.
|
|
813
291
|
|
|
814
|
-
|
|
292
|
+
#### Dry-run override
|
|
815
293
|
|
|
816
|
-
-
|
|
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.
|
|
294
|
+
See the unified `## Dry-run Scope` section at the end of this file for the full catalogue of writes suspended in dry-run mode. Summary for Phase 4.5: dry-run SKIPS the `session_archive_attempted` emit entirely; the read-side machinery (Phase 1 digest, Phase 2.5 gate, Phase 3 preview) runs normally so the user sees what WOULD have been written.
|
|
819
295
|
|
|
820
|
-
|
|
296
|
+
#### Event emission summary
|
|
821
297
|
|
|
822
|
-
|
|
298
|
+
Append ONE `session_archive_attempted` line to `.fabric/events.jsonl` PER `session_id` in the run scope. Single-line JSON ≤ 4KB (POSIX atomicity — see Phase 2.5 ref). Best-effort write: append failure → log stderr only, skill still exits successfully. Outcome ∈ {`proposed` | `viability_failed` | `user_dismissed` | `skipped_no_signal`}. `covered_through_ts` = max ts of events the skill examined for that session.
|
|
823
299
|
|
|
824
|
-
|
|
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.
|
|
870
|
-
|
|
871
|
-
#### covered_through_ts watermark
|
|
872
|
-
|
|
873
|
-
```
|
|
874
|
-
covered_through_ts = max(events_in_scope[*].ts)
|
|
875
|
-
```
|
|
876
|
-
|
|
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.
|
|
878
|
-
|
|
879
|
-
#### Multi-session emission rule
|
|
880
|
-
|
|
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.
|
|
882
|
-
|
|
883
|
-
#### Append pattern (Bash echo, 4KB-safe, fail-tolerant)
|
|
884
|
-
|
|
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.
|
|
886
|
-
|
|
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
|
-
```
|
|
892
|
-
|
|
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.
|
|
894
|
-
|
|
895
|
-
#### Worked example: E5 cron silent-skip
|
|
896
|
-
|
|
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.
|
|
898
|
-
|
|
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.
|
|
910
|
-
|
|
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.
|
|
300
|
+
For the full jsonc event shape, the outcome decision matrix (4 terminal states × outcome / candidates_proposed / knowledge_proposed_ids columns), `covered_through_ts` watermark spec, multi-session emission rule, the bash echo append pattern, and an E5-cron-silent-skip worked trace, `Read packages/cli/templates/skills/fabric-archive/ref/phase-4-5-emit.md` (or `.claude/skills/fabric-archive/ref/phase-4-5-emit.md` post-install).
|
|
912
301
|
|
|
913
302
|
## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
|
|
914
303
|
|
|
915
304
|
### DISPLAY Rules
|
|
916
305
|
|
|
917
|
-
- MUST complete Phase
|
|
306
|
+
- MUST complete Phase 2 AND Phase 2.5 viability gate before any batch-review output.
|
|
918
307
|
- 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
308
|
- MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[relevance_scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
|
|
920
309
|
- MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
|
|
@@ -944,6 +333,23 @@ Three end-to-end fab_extract_knowledge call examples (decision/team, pitfall/tea
|
|
|
944
333
|
|
|
945
334
|
## E5 Scheduled Daily Recap (ref-only)
|
|
946
335
|
|
|
947
|
-
Only relevant when entry_point=E5_cron (OS cron, `/loop`, or scheduled trigger). For interactive invocations, Phase
|
|
336
|
+
Only relevant when entry_point=E5_cron (OS cron, `/loop`, or scheduled trigger). For interactive invocations, Phase 0 has already routed past this — nothing to load.
|
|
948
337
|
|
|
949
338
|
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.
|
|
339
|
+
|
|
340
|
+
## Dry-run Scope (unified)
|
|
341
|
+
|
|
342
|
+
`dry_run = true` (per Phase 4.5 detection rule — substring match on `--dry-run` | `dry-run` | `dry_run` | `预览` token) 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.
|
|
343
|
+
|
|
344
|
+
| Write operation | Normal mode | Dry-run mode |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| `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. |
|
|
347
|
+
| `session_archive_attempted` event (Phase 4.5) | Appended to `.fabric/events.jsonl` for every session in scope | SKIPPED entirely. No ledger entry. |
|
|
348
|
+
| `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. |
|
|
349
|
+
| `fab onboard-coverage` slot writes (Phase 1.5 fill-all / dismiss-all) | Each `Bash("fab config dismiss-slot <slot>")` invocation runs | SKIPPED. Slot decisions are shown as "would dismiss/propose" preview. |
|
|
350
|
+
| `.fabric/.cache/session-digests/<session_id>.md` reads | Read freely (read-side, safe) | Read freely — same as normal. |
|
|
351
|
+
| Stop-hook / archive-hint stdin/stdout | Read-only inspection of `.fabric/events.jsonl` | Same — no change. |
|
|
352
|
+
|
|
353
|
+
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.`
|
|
354
|
+
|
|
355
|
+
Cross-reference: Phase 4.5 §Dry-run override holds the rationale; this section is the authoritative catalogue of skipped writes. When adding a new write side-effect to any phase, update BOTH the phase section AND this table.
|