@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
@@ -0,0 +1,16 @@
1
+ # Dry-run Scope (unified)
2
+
3
+ `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.
4
+
5
+ | Write operation | Normal mode | Dry-run mode |
6
+ |---|---|---|
7
+ | `fab_extract_knowledge` MCP call (Phase 4) | One call per confirmed candidate, writes to `.fabric/knowledge/pending/<slug>.md` | SKIPPED. Phase 4 renders "would write N pending entries" preview table instead. |
8
+ | `session_archive_attempted` event (Phase 4.5) | Appended to `.fabric/events.jsonl` for every session in scope | SKIPPED entirely. No ledger entry. |
9
+ | `fab_review reject` (Phase 3 user-dismissed branch) | Invoked when user types `撤销` / `reject` after self-archive proposal | SKIPPED. The dismissal is rendered to console but no MCP write occurs. |
10
+ | `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. |
11
+ | `.fabric/.cache/session-digests/<session_id>.md` reads | Read freely (read-side, safe) | Read freely — same as normal. |
12
+ | Stop-hook / archive-hint stdin/stdout | Read-only inspection of `.fabric/events.jsonl` | Same — no change. |
13
+
14
+ All user-facing output in dry-run mode MUST prefix `[DRY-RUN]` at the start of each Phase header (e.g. `[DRY-RUN] Phase 3 — Batch Review`). Exit message: `[DRY-RUN complete] would have written N entry/entries; no .fabric/ files were modified. Re-invoke without --dry-run to commit.`
15
+
16
+ Cross-reference: Phase 4.5 `Dry-run override` section in SKILL.md holds the rationale. When adding a new write side-effect to any phase, update BOTH the phase section AND this table.
@@ -1,12 +1,12 @@
1
1
  # E5 Scheduled Daily Recap — full reference
2
2
 
3
- > **Loaded on demand.** Only relevant when invocation context = `cron` / `/loop` (E5 entry). SKILL.md's Phase -0.5 already gates this — if the user just typed `/fabric-archive`, none of the below applies.
3
+ > **Loaded on demand.** Only relevant when invocation context = `cron` / `/loop` (E5 entry). SKILL.md's Phase 0 already gates this — if the user just typed `/fabric-archive`, none of the below applies.
4
4
 
5
5
  ## E5 周期触发 (Scheduled Daily Recap)
6
6
 
7
7
  ## Overview
8
8
 
9
- `今日复盘` = E5 entry point. Default scope = today. Falls back to historical scan if today yields no candidates (silent-skip per Phase 2.5).
9
+ `今日复盘` = E5 entry point. Default scope = today. Falls back to historical scan if today yields no candidates (silent-skip per Phase 4.5).
10
10
 
11
11
  E5 是 5 入口模型中唯一由 OS 调度器或 Claude Code `/loop` 周期触发的入口形态。fab 端**零代码**——不提供 `fab schedule` 子命令,亦不内嵌 daemon。用户基于自己的执行环境二选一接入: `/loop`(Claude Code 原生,推荐) 或 OS cron(跨平台 fallback)。
12
12
 
@@ -36,9 +36,9 @@ macOS 用户可改用 `launchd` plist;Linux 用户直接 `crontab -e`。命令
36
36
 
37
37
  当用户或 cron 以 `今日复盘` / `daily recap` 字面短语触发 fabric-archive 时,skill 应按以下契约处理:
38
38
 
39
- - **Phase -0.5 Range Resolution**: 识别 `今日复盘` / `daily recap` 为 magic phrase, 直接设置 `time_window = today` (00:00 local timezone → current ts), 无需 AskUserQuestion 兜底。
40
- - **Phase 0.4 Onboard Coverage**: 跳过 (entry_point = E5_cron, 非 E2_explicit, 不弹 onboard 弹问)。
41
- - **Phase 2.5 Persist Archive Attempt**: 始终写入 `session_archive_attempted` event。当今日无 archive 信号触发 viability gate FAIL 时,走 silent-skip 路径(outcome = `skipped_no_signal`),skill 静默退出,cron 日志为空。
39
+ - **Phase 0 Range Resolution**: 识别 `今日复盘` / `daily recap` 为 magic phrase, 直接设置 `time_window = today` (00:00 local timezone → current ts), 无需 AskUserQuestion 兜底。
40
+ - **Phase 1.5 Onboard Coverage**: 跳过 (entry_point = E5_cron, 非 E2_explicit, 不弹 onboard 弹问)。
41
+ - **Phase 4.5 Persist Archive Attempt**: 始终写入 `session_archive_attempted` event。当今日无 archive 信号触发 viability gate FAIL 时,走 silent-skip 路径(outcome = `skipped_no_signal`),skill 静默退出,cron 日志为空。
42
42
 
43
43
  ### Trade-off table (/loop vs OS cron)
44
44
 
@@ -12,7 +12,7 @@ following 5 categories MUST be rendered in the resolved language:
12
12
 
13
13
  1. **Roll-up templates** — the `# Archive Review — N candidates` batch
14
14
  review block (one per candidate) AND any final session summary the
15
- skill emits after Phase 2 completes. zh-CN ↔ en mirror.
15
+ skill emits after Phase 4 completes. zh-CN ↔ en mirror.
16
16
  2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
17
17
  the "没有触发归档信号…" trigger-miss and the "本次会话为常规执行…"
18
18
  viability-gate-FAIL message). zh-CN ↔ en mirror.
@@ -20,13 +20,13 @@ following 5 categories MUST be rendered in the resolved language:
20
20
  edit … inline, N to skip)` line in the batch review template. zh-CN
21
21
  ↔ en mirror.
22
22
  4. **Dry-run table headers** — v2.0.0-rc.27 TASK-007 added a dry-run
23
- override path (see Phase 2.5 "dry-run") so users can preview the
23
+ override path (see Phase 4.5 "dry-run") so users can preview the
24
24
  archive proposal without writing pending entries. The dry-run summary
25
25
  header and per-candidate preview labels MUST be bilingualized per
26
26
  this policy. zh-CN ↔ en mirror.
27
27
  5. **AskUserQuestion** — `header` + `question` fields (NOT `options[]`).
28
28
  zh-CN ↔ en mirror. fabric-archive itself does not surface
29
- AskUserQuestion in the current contract (Phase 1 batch review is a
29
+ AskUserQuestion in the current contract (Phase 3 batch review is a
30
30
  single markdown screen, not a structured question), but if a future
31
31
  version adds one — e.g. to confirm layer flip — this rule applies.
32
32
 
@@ -0,0 +1,156 @@
1
+ # Phase 0 — Range Resolution (ref)
2
+
3
+ > **Loaded on demand.** SKILL.md hot path retains the Phase 0 intro, the Confidence decision rule, and Step 1 (invocation context inspection). This file holds Steps 2-6 (parsing tables, session_id resolution algorithm, AskUserQuestion fallback, carry-forward contract) + worked examples. Read when entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback} AND the user prompt likely carries a range hint that needs parsing.
4
+
5
+ ## Step 2 — Time-window parsing
6
+
7
+ Match the user prompt against the following bilingual patterns (case-insensitive substring match, leftmost-longest wins). The matched span yields a `[ts_start, ts_end]` pair in Unix milliseconds. `now` = the skill invocation timestamp.
8
+
9
+ ### zh-CN pattern table
10
+
11
+ | Pattern | ts_start | ts_end |
12
+ |---|---|---|
13
+ | `今日` / `今天` | `floor(now, day)` (本地时区 00:00) | `now` |
14
+ | `上周` / `过去一周` | `now - 7d` | `now` |
15
+ | `过去 N 天` / `近 N 天` (N ∈ 1..30) | `now - N*24h` | `now` |
16
+ | `自上次归档` / `自上次 archive` | tail-scan events.jsonl → most recent `knowledge_proposed.ts` (fallback `events[0].ts`) | `now` |
17
+
18
+ ### en pattern table
19
+
20
+ | Pattern | ts_start | ts_end |
21
+ |---|---|---|
22
+ | `today` | `floor(now, day)` (local TZ 00:00) | `now` |
23
+ | `last week` / `past week` | `now - 7d` | `now` |
24
+ | `past N days` / `last N days` (N ∈ 1..30) | `now - N*24h` | `now` |
25
+ | `since last archive` / `since last archived` | tail-scan events.jsonl → most recent `knowledge_proposed.ts` (fallback `events[0].ts`) | `now` |
26
+
27
+ Notes:
28
+
29
+ - Patterns are non-exclusive — if the prompt matches multiple (e.g. "今日 cite policy"), apply time-window THEN topic-keyword as AND.
30
+ - Numeric N must parse as a positive integer ≤ 30; reject anything else as parse-miss.
31
+ - All other date phrasings (specific dates like `5月10日`, relative phrasings like `三天前下午`) are NOT handled here — emit parse-miss and let Step 5 fallback collect a structured answer.
32
+
33
+ ## Step 3 — Topic-keyword extraction
34
+
35
+ After time-window matching (or alongside it when both apply), extract content keywords from the prompt:
36
+
37
+ 1. Strip recognised time-window tokens (e.g. remove `今日` / `last week` from the residual prompt).
38
+ 2. Tokenize residual on whitespace + CJK boundary. Combine adjacent CJK characters into one token; split en words on spaces.
39
+ 3. Filter **stop-words**: skill control verbs (`archive`, `归档`, `下`, `的`), articles / particles (`the`, `a`, `an`, `了`, `吧`), pronouns (`it`, `this`, `that`, `这个`, `那个`), and 1-character en tokens.
40
+ 4. Retain **2-5 word tokens** (or 1-token CJK content words ≥ 2 chars like `rc.20`, `cite`). Cap at 8 keywords; drop weaker (later-position) ones.
41
+
42
+ The retained set is `topic_keywords[]`. Empty set = no keyword filter.
43
+
44
+ ## Step 4 — session_id resolution algorithm
45
+
46
+ Given `time_window = [ts_start, ts_end] | null` and `topic_keywords[] | []`:
47
+
48
+ ```
49
+ Step a — Read events.jsonl tail (last 500 events) via `Bash: tail -n 500
50
+ .fabric/events.jsonl`. ENOENT → empty list (no resolution possible
51
+ → emit parse-miss → Step 5 fallback).
52
+
53
+ Step b — Per distinct session_id present in the tail, compute:
54
+ ts_min = min(ts) over events with this session_id
55
+ ts_max = max(ts) over events with this session_id
56
+ digest_path = .fabric/.cache/session-digests/<session_id>.md
57
+ digest_body = Read(digest_path) if exists, else ""
58
+
59
+ Step c — TIME-WINDOW FILTER (skip when time_window is null):
60
+ Keep session_id IFF [ts_min, ts_max] intersects [ts_start, ts_end]
61
+ (i.e. ts_max >= ts_start AND ts_min <= ts_end).
62
+ Multiple time intervals are OR'd within the time-window filter
63
+ category (none currently supported; reserved for future ranges).
64
+
65
+ Step d — TOPIC-KEYWORD FILTER (skip when topic_keywords is empty):
66
+ Keep session_id IFF digest_body (case-insensitive) contains
67
+ AT LEAST ONE keyword from topic_keywords[].
68
+ Multiple keywords are OR'd within the keyword filter category.
69
+
70
+ Step e — AND across filter categories:
71
+ A session must pass BOTH filters when BOTH are present.
72
+ Pass either filter alone when only one is present.
73
+ Pass-through (all sessions) when neither is present.
74
+
75
+ Step f — Result: distinct session_id[] (preserve event-order); if empty AND
76
+ a parse hit was claimed → degrade to Step 5 fallback (user wanted a
77
+ range that resolved to zero sessions).
78
+ ```
79
+
80
+ ## Step 5 — AskUserQuestion fallback (E2 / E4 only)
81
+
82
+ When Step 2/3 emit parse-miss OR Step 4 resolves to zero sessions AND the invocation type permits prompting (E2 user-active or E4 user回溯-active — NEVER E1 hook / E3 AI-self / E5 cron), surface a structured question. UX i18n Policy class 5 applies: `header` + `question` translate per `fabric_language`; `options[]` routing keys stay English.
83
+
84
+ ```ts
85
+ AskUserQuestion({
86
+ header: "Archive range", // zh-CN: "归档范围"
87
+ question:
88
+ "Which session range should this archive cover? " +
89
+ "(today = current calendar day; last-week = past 7 days; " +
90
+ "since-last-archive = newer than last knowledge_proposed event; " +
91
+ "custom = type a free-form range)",
92
+ options: ["today", "last-week", "since-last-archive", "custom"]
93
+ })
94
+ ```
95
+
96
+ Routing:
97
+
98
+ | Choice | Action |
99
+ |---|---|
100
+ | `today` | Re-enter Step 2 with synthetic prompt `今日` / `today` (per `fabric_language`); resolve session_ids; proceed to Phase 0.5. |
101
+ | `last-week` | Re-enter Step 2 with synthetic prompt `上周` / `last week`; proceed to Phase 0.5. |
102
+ | `since-last-archive` | Re-enter Step 2 with synthetic prompt `自上次归档` / `since last archive`; proceed to Phase 0.5. |
103
+ | `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 Step 1 with the user-typed sub-prompt. Loop max 1 time — second parse-miss falls through to `range = "all"` with a warning. |
104
+
105
+ ## Step 6 — Carry-forward contract
106
+
107
+ Phase 0 produces ONE of:
108
+
109
+ - `session_id[]` (non-empty array of distinct session_ids) — passed to Phase 1 as the explicit scope filter; Phase 1 skips its own anchor-walk and uses this list directly.
110
+ - `"all"` (sentinel string) — no range hint detected; Phase 1 falls back to the legacy anchor-walk behaviour ("all distinct sessions since last `knowledge_proposed`").
111
+
112
+ NEVER pass an empty `session_id[]` forward — that case must degrade to Step 5 fallback (or, when fallback is forbidden by invocation type, to `"all"` with a one-line stderr warning).
113
+
114
+ ## Worked examples
115
+
116
+ ### Example A — time-only: `今日复盘`
117
+
118
+ ```
119
+ Step 1: prompt = "今日复盘"; user_invocation_type = E2.
120
+ Step 2: matches `今日` → time_window = [floor(now, day), now].
121
+ Step 3: residual "复盘" survives stop-word filter → topic_keywords = ["复盘"].
122
+ (Edge case: the residual content word may also filter; if 复盘 is
123
+ in the stop list it becomes []. Treat as topic-keyword empty.)
124
+ Step 4: tail-scan events.jsonl; keep sessions whose [ts_min, ts_max]
125
+ intersects today's window. Say 3 sessions match.
126
+ Step 5: skipped (resolution succeeded).
127
+ Step 6: emit session_id[] = ["sess-a", "sess-b", "sess-c"] → Phase 0.5.
128
+ ```
129
+
130
+ ### Example B — keyword-only: `rc.20 的归档下`
131
+
132
+ ```
133
+ Step 1: prompt = "rc.20 的归档下"; user_invocation_type = E2.
134
+ Step 2: no time pattern matches → time_window = null.
135
+ Step 3: strip "归档"/"下"/"的" stop-words → topic_keywords = ["rc.20"].
136
+ Step 4: tail-scan events.jsonl; for each session_id, Read its digest;
137
+ keep those whose digest body matches /rc\.20/i. Say 2 sessions
138
+ match (one was the rc.20 grilling session, one had a tangential
139
+ mention).
140
+ Step 5: skipped.
141
+ Step 6: emit session_id[] = ["sess-x", "sess-y"] → Phase 0.5.
142
+ ```
143
+
144
+ ### Example C — combined: `上周 rc.20`
145
+
146
+ ```
147
+ Step 1: prompt = "上周 rc.20"; user_invocation_type = E4.
148
+ Step 2: matches `上周` → time_window = [now - 7d, now].
149
+ Step 3: strip "上周" → topic_keywords = ["rc.20"].
150
+ Step 4: AND filter — keep sessions whose [ts_min, ts_max] intersects last
151
+ week AND whose digest matches /rc\.20/i. Say 1 session matches.
152
+ Step 5: skipped.
153
+ Step 6: emit session_id[] = ["sess-z"] → Phase 0.5.
154
+ ```
155
+
156
+ If Example C had resolved to zero sessions (e.g. user types `上周 rc.99`), Step 4 would degrade into Step 5 — surfacing AskUserQuestion since E4 permits prompting.
@@ -1,20 +1,20 @@
1
- # Phase 0.4 — First-run Onboard Phase (ref)
1
+ # Phase 1.5 — First-run Onboard Phase (ref)
2
2
 
3
3
  > **Loaded on demand.** SKILL.md hot path only runs this when entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback} AND `fab onboard-coverage --json` reports `missing.length > 0`. For E1/E3/E5 entries OR fully-covered workspaces, this entire phase is skipped — no reason to load.
4
4
 
5
- ## Phase 0.4 — First-run Onboard Phase
5
+ ## Phase 1.5 — First-run Onboard Phase
6
6
 
7
- #### Phase 0.4 Trigger Gate (rc.25 — entry-context aware)
7
+ #### Phase 1.5 Trigger Gate (rc.25 — entry-context aware)
8
8
 
9
9
  Before running ANY of the onboard coverage steps below, evaluate the
10
10
  **entry-context gate**. Onboard slot collection is an interactive,
11
11
  one-time project-tone capture flow that REQUIRES live user dialogue.
12
12
  Non-user-active entries (hook / AI self-trigger / cron) either interrupt
13
13
  the user mid-work or run unattended where dialogue is impossible, so
14
- they MUST skip Phase 0.4 entirely and fall through to Phase 0.
14
+ they MUST skip Phase 1.5 entirely and fall through to Phase 0.
15
15
 
16
- Read `context.entry_point` — already determined in **Phase -0.5 Range
17
- Resolution** (see TASK-04 / Phase -0.5 section above). The 5-entry model
16
+ Read `context.entry_point` — already determined in **Phase 0 Range
17
+ Resolution** (see TASK-04 / Phase 0 section above). The 5-entry model
18
18
  is the canonical taxonomy for this gate.
19
19
 
20
20
  ##### Entry-context detection rules
@@ -24,7 +24,7 @@ is the canonical taxonomy for this gate.
24
24
  | **E1** | `hook_passive` | stdout JSON `{decision:'block', ...}` from `archive-hint.cjs` detected at skill entry (the Stop-hook reminder path). |
25
25
  | **E2** | `explicit_user_invoke` | User prompt is a direct invocation: `fabric archive` / `/fabric-archive` / `archive what we just did` / `归档一下` / similar imperative. |
26
26
  | **E3** | `ai_self_trigger` | AI internal marker `self-archive policy triggered by signal: <X>` present (substring match on the verbatim prefix `self-archive policy triggered by signal`; `<X>` is one of the 4 self-trigger signals from AGENTS.md E3 section: `Normative` / `Wrong-turn-and-revert` / `Decision confirmation` / `Explicit dismissal`). |
27
- | **E4** | `user_range_rollback` | Prompt contains a **range hint** (parsed in Phase -0.5 — e.g. `今日` / `上周` / `rc.20`) AND the user is invoking. Sub-mode of E2. |
27
+ | **E4** | `user_range_rollback` | Prompt contains a **range hint** (parsed in Phase 0 — e.g. `今日` / `上周` / `rc.20`) AND the user is invoking. Sub-mode of E2. |
28
28
  | **E5** | `cron` | Prompt contains literal `今日复盘` / `daily recap` / `daily-archive` AND no human is present (running under `/loop`, OS cron, or scheduled trigger). |
29
29
 
30
30
  ##### Gate decision
@@ -35,8 +35,8 @@ IF context.entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback}:
35
35
  → continue to Step 1 (Check coverage) below
36
36
  ELSE (E1_hook_passive | E3_ai_self_trigger | E5_cron):
37
37
  → gate = SKIP # no live user, onboard prompting would misfire
38
- → emit one-line log: "Phase 0.4 skipped (entry=<E1|E3|E5>, no live user)"
39
- → proceed directly to Phase 0
38
+ → emit one-line log: "Phase 1.5 skipped (entry=<E1|E3|E5>, no live user)"
39
+ → proceed directly to Phase 2
40
40
  ```
41
41
 
42
42
  ##### Rationale
@@ -44,7 +44,7 @@ ELSE (E1_hook_passive | E3_ai_self_trigger | E5_cron):
44
44
  Onboard slot collection is a one-time project-tone capture flow that
45
45
  requires user dialogue. Non-user-active entries (hook / AI / cron)
46
46
  interrupt the user mid-work or run unattended where dialogue is
47
- impossible, so they MUST skip Phase 0.4. The S5 slot semantics
47
+ impossible, so they MUST skip Phase 1.5. The S5 slot semantics
48
48
  (`tech-stack-decision`, `architecture-pattern`, ...) are user-validated
49
49
  baselines — populating them from a hook fire-and-forget or a cron daily
50
50
  recap would defeat the purpose of capturing _user-confirmed_ project
@@ -63,11 +63,11 @@ baseline.
63
63
  ```
64
64
  $ /loop 24h /fabric-archive 今日复盘
65
65
  → cron context, no live user
66
- → Phase -0.5 detects literal "今日复盘" + no-human marker
66
+ → Phase 0 detects literal "今日复盘" + no-human marker
67
67
  → context.entry_point = E5_cron
68
- → Phase 0.4 Trigger Gate evaluates: E5 ∉ {E2, E4} → SKIP
69
- → emit log "Phase 0.4 skipped (entry=E5, no live user)"
70
- → proceed directly to Phase 0 (collect candidates for daily window)
68
+ → Phase 1.5 Trigger Gate evaluates: E5 ∉ {E2, E4} → SKIP
69
+ → emit log "Phase 1.5 skipped (entry=E5, no live user)"
70
+ → proceed directly to Phase 2 (collect candidates for daily window)
71
71
  ```
72
72
 
73
73
  Contrast with E2:
@@ -75,8 +75,8 @@ Contrast with E2:
75
75
  ```
76
76
  $ /fabric-archive
77
77
  → user typed explicit invocation
78
- → Phase -0.5: context.entry_point = E2_explicit_user_invoke
79
- → Phase 0.4 Trigger Gate evaluates: E2 ∈ {E2, E4} → PROCEED
78
+ → Phase 0: context.entry_point = E2_explicit_user_invoke
79
+ → Phase 1.5 Trigger Gate evaluates: E2 ∈ {E2, E4} → PROCEED
80
80
  → run Step 1 (Check coverage) below
81
81
  ```
82
82
 
@@ -93,7 +93,7 @@ for high-quality plan_context retrieval from day one:
93
93
  - `build-system-idiom` — build tool quirks, scripts, deploy pipeline shape
94
94
  - `domain-vocabulary` — business / product terminology that names code entities
95
95
 
96
- This phase runs ONCE per archive-skill invocation, BEFORE Phase 0 evidence
96
+ This phase runs ONCE per archive-skill invocation, BEFORE Phase 2 evidence
97
97
  gathering, so coverage state is fresh for the session.
98
98
 
99
99
  #### Step 1 — Check coverage
@@ -119,7 +119,7 @@ Expected shape:
119
119
 
120
120
  ```
121
121
  IF missing.length === 0:
122
- → skip Phase 0.4 entirely; proceed to Phase 0.
122
+ → skip Phase 1.5 entirely; proceed to Phase 0.
123
123
  ELSE:
124
124
  → ask the user how to handle the missing slots (Step 3).
125
125
  ```
@@ -146,7 +146,7 @@ proposed entry counts toward coverage once approved via fab_review.
146
146
 
147
147
  | User choice | Action |
148
148
  |----------------|--------|
149
- | `fill-all` | For EACH slot in `missing`, run Step 4 (Tour-and-propose). All proposals share session_id; one batch review at the end (Phase 1). |
149
+ | `fill-all` | For EACH slot in `missing`, run Step 4 (Tour-and-propose). All proposals share session_id; one batch review at the end (Phase 3). |
150
150
  | `fill-each` | Loop slot-by-slot through `missing`. Per slot: ask user `confirm | dismiss | skip` (per-slot AskUserQuestion); `confirm` → run Step 4; `dismiss` → `fab config dismiss-slot <slot>`; `skip` → leave for next archive run. |
151
151
  | `dismiss-all` | For EACH slot in `missing`, invoke `Bash("fab config dismiss-slot <slot>")`. Print a one-line confirmation each. Skip to Phase 0. |
152
152
  | `skip` | No-op. Slots remain in `missing` for the next archive run. Skip to Phase 0. |
@@ -199,7 +199,7 @@ mcp__fabric__fab_extract_knowledge({
199
199
 
200
200
  #### Onboard phase constraints (DO NOT TRANSLATE)
201
201
 
202
- - MUST run BEFORE Phase 0 evidence gathering — onboard is a separate flow,
202
+ - MUST run BEFORE Phase 2 evidence gathering — onboard is a separate flow,
203
203
  not interleaved with session-archive candidates.
204
204
  - MUST call `fab onboard-coverage --json` before deciding; never assume
205
205
  coverage state.
@@ -208,7 +208,7 @@ mcp__fabric__fab_extract_knowledge({
208
208
  even if the user asks "fill all of them" — the dismiss is intentional.
209
209
  - NEVER prompt the user when `missing.length === 0` — silent skip.
210
210
  - NEVER set `onboard_slot` on a regular session-archive candidate in
211
- Phase 2 — that field is RESERVED for the onboard phase. Mixing the
211
+ Phase 4 — that field is RESERVED for the onboard phase. Mixing the
212
212
  two would let session-archive proposals masquerade as onboard
213
213
  coverage and let any random pending file claim a slot.
214
214
  - MUST emit `onboard_slot: <slot>` verbatim — the slot name is one of
@@ -0,0 +1,60 @@
1
+ # Phase 1 — Collect Cross-Session Digests (ref)
2
+
3
+ > **Loaded on demand.** SKILL.md hot path retains Phase 1's purpose statement + 5-step summary + graceful-degradation note. This file holds Steps 1-5 detailed implementation (events.jsonl tail-scan, anchor-walk, digest load, rc.25 TASK-05 ledger filter algorithm + constants + worked examples, cross-session context build).
4
+
5
+ ## Step 1 — Read events.jsonl tail
6
+
7
+ Use `Bash` with `tail -n 200 .fabric/events.jsonl` (tolerate ENOENT — empty ledger is a normal first-run state).
8
+
9
+ ## Step 2 — Find the anchor
10
+
11
+ Walk the tail backwards to locate the most recent `knowledge_proposed` event (`event_type === "knowledge_proposed"`). The anchor's `ts` becomes the lower bound for digest selection. If NO anchor exists, treat all digests in the cache as in-scope.
12
+
13
+ ## Step 3 — Collect session_ids since anchor
14
+
15
+ Scan the tail forward from the anchor and collect every distinct `session_id` field that appears on any event newer than the anchor. Distinct ordering preserved.
16
+
17
+ ## Step 4 — Load digests
18
+
19
+ For each collected `session_id`, read `.fabric/.cache/session-digests/<session_id>.md`. Missing digest files degrade silently (the digest write was best-effort, so a Stop hook crash can produce a session_id without a digest). Cap the loaded digest set at `archive_digest_max_sessions` most-recent sessions (config-resolved, default 10) to bound LLM context (~50KB worst-case at default).
20
+
21
+ ## Step 4.5 — Filter via session_archive_attempted ledger (rc.25 TASK-05)
22
+
23
+ Before Step 5 builds the cross-session context, drop sessions that the outcome ledger says we should not re-scan. For each `session_id` collected in Steps 1-3, scan `.fabric/events.jsonl` for events where `event_type === "session_archive_attempted"` AND `session_id` matches, keep the most-recent one by `ts`, and apply this state machine:
24
+
25
+ - **(a) Look up the most recent `session_archive_attempted`** event for this `session_id` (none found → fall through to (e)).
26
+ - **(b) `outcome === "user_dismissed"` → drop (permanent skip).** The user explicitly rejected this session's candidates; never auto-re-scan it. Respect the dismissal forever — re-scanning would re-propose the same content the user already declined.
27
+ - **(c) `(nowMs - attempted_event.ts) < ANTI_LOOP_HOURS * 3_600_000` → drop (cooldown skip).** Anti-loop window: even if outcome is otherwise re-scannable, never re-scan a session within 12 hours of the last attempt. Aligns 心智 with the Stop-hook cooldown so a single user does not see the same session repeatedly within one work day.
28
+ - **(d) `covered_through_ts` present → check for high-value signal in `ts > covered_through_ts` events for this `session_id`.** Tail-scan `events.jsonl` for events newer than the watermark whose `session_id` matches. A session passes this gate iff at least ONE of:
29
+ - ≥1 event with `event_type ∈ HIGH_VALUE_EVENT_TYPES` (`knowledge_context_planned`, `edit_paths_recorded`), OR
30
+ - the latest `assistant_turn_observed` event body contains ≥1 of `NORMATIVE_KEYWORDS` (substring match, case-insensitive for English entries).
31
+
32
+ No high-value signal → drop (no new content worth re-scanning, even though the cooldown has expired). Has signal → keep for re-scan.
33
+ - **(e) Never attempted (no `session_archive_attempted` event found for this `session_id`) → keep.** First-time scan; nothing to filter against.
34
+ - **(f) Cross-session pending dedupe** (operates on candidate observations, not on `session_id` filter): gather all `knowledge_proposed_ids` from `session_archive_attempted` events with `outcome === "proposed"` across ALL sessions in the recent window (NOT just the current candidate session). This builds a global set of idempotency keys already proposed by prior archive runs but not yet reviewed by the user (`.fabric/knowledge/pending/` may still contain them). When classifying new observations in Phase 3, drop any candidate whose computed `idempotency_key` matches an id already in this set — it was already proposed by an earlier archive run, the user just hasn't reviewed it yet, so re-proposing would duplicate pending entries and inflate `candidates_proposed` counts. Per Phase 4.5 dedupe consumer of `knowledge_proposed_ids`.
35
+
36
+ The resulting filtered `session_id[]` proceeds into Step 5's digest concatenation. Sessions filtered out in this step do NOT contribute to `### Cross-session digest`, are NOT included in `source_sessions` on any fab_extract_knowledge call, and are NOT referenced in `session_context` bodies.
37
+
38
+ ### Constants (rc.25 — verbatim)
39
+
40
+ - `ANTI_LOOP_HOURS = 12` — cooldown window in hours between consecutive re-scans of the same `session_id`. Rationale: 心智对齐 hook cooldown (`stop_hook_cooldown_hours = 12`); identical mental model avoids user confusion when a session shows up in both hook reminders and archive re-scan candidates.
41
+ - `HIGH_VALUE_EVENT_TYPES = ['knowledge_context_planned', 'edit_paths_recorded']` — event types that count as "new substantive activity worth re-scanning" past `covered_through_ts`. Chat accumulation (`assistant_turn_observed` alone) does NOT count — it would let mere conversation noise trigger re-scans.
42
+ - `NORMATIVE_KEYWORDS = ['以后','always','never','from now on','下次','记一下','永远不要']` — substring patterns scanned against the latest `assistant_turn_observed` body for the session. Mixed CN/EN to cover bilingual users. If any keyword hits, the session is flagged as having high-value chat-only signal even without code edits.
43
+
44
+ ### Worked examples
45
+
46
+ - **Session X (user_dismissed)** — last `session_archive_attempted` ts = 3 days ago, outcome = `user_dismissed`. Rule (b) fires → permanent skip. Session X is dropped even if 50 new `knowledge_context_planned` events have accumulated since.
47
+ - **Session Y (proposed 6h ago)** — last `session_archive_attempted` ts = 6h ago, outcome = `proposed`. Rule (c) fires: 6h < 12h cooldown window → drop (cooldown skip). Y becomes eligible again after the 12h window closes, provided high-value signal accumulates by then.
48
+ - **Session Z (viability_failed 14h ago + 3 new plan_context)** — last `session_archive_attempted` ts = 14h ago, outcome = `viability_failed`, `covered_through_ts` = T₀. Rules (b)(c) pass. Rule (d) tail-scans for `session_id === Z AND ts > T₀`: finds 3 `knowledge_context_planned` events. HIGH_VALUE_EVENT_TYPES match → keep Z for re-scan. The previous viability failure does not block a re-scan once new substantive activity has accumulated.
49
+
50
+ ## Step 5 — Build cross-session context
51
+
52
+ Concatenate the loaded digests into a single `### Cross-session digest` block to carry into Phase 2.5 + Phase 1. Use this block to:
53
+
54
+ - Detect session-spanning patterns (e.g. a discussion that started in session A and continued in session B).
55
+ - Populate the `source_sessions` array on every fab_extract_knowledge call — the array form (T5) replaces the legacy `source_session` string.
56
+ - Inform the `session_context` blob written to each pending entry's body (3-5 lines summarizing goal + key turning point, per T6).
57
+
58
+ ## Graceful degradation
59
+
60
+ If `.fabric/.cache/session-digests/` is missing entirely, this phase reports an empty context and Phase 2 falls back to the single-session behaviour. Tests that synthesize events.jsonl without populating the digest cache continue to work. If `session_archive_attempted` events are missing entirely (pre-rc.25 ledger or rotation has trimmed older events), treat all sessions as never-attempted (current default behavior) — Step 4.5 rule (e) applies uniformly, so the filter degrades to the legacy "scan everything since anchor" semantics without raising errors.
@@ -0,0 +1,54 @@
1
+ # Phase 2.5 — Viability Gate (ref)
2
+
3
+ > **Loaded on demand.** SKILL.md hot path retains the signal lists, gate decision pseudocode, and entry-point branching summary. This file holds the verbose explanations for each signal, gate-FAIL message variants (zh-CN/en), the events.jsonl 4KB atomicity constraint note, and rationale.
4
+
5
+ ## Archive signals — verbose explanation
6
+
7
+ Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 2. Each signal listed in SKILL.md hot path is explained below:
8
+
9
+ 1. **Explicit normative language** — user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`. Strongest single signal — even one normative cue is sufficient.
10
+ 2. **Wrong-turn-and-revert** — a path was edited, then reverted (or partially undone) after diagnosis. Indicates a pitfall worth recording (the why-not lives in the revert).
11
+ 3. **Long diagnostic loop** — an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution. Implies a non-obvious cause worth capturing.
12
+ 4. **New dependency adoption** — a new package / library / external tool was introduced (e.g. `package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
13
+ 5. **New pattern emergence** — a reusable abstraction or naming convention was named ("the X phase", "the Y pattern", "let's call this Z").
14
+ 6. **Decision confirmation** — ≥ 2 alternatives were weighed AND a rationale was given before settling. The rationale is the archivable knowledge.
15
+ 7. **Explicit dismissal-with-reason** — user rejected an approach AND stated why. The why is the archivable knowledge, not the dismissal itself.
16
+ 8. **Process formalization** — a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
17
+
18
+ ## Anti-archive signals — verbose explanation
19
+
20
+ These force the gate to FAIL **unless** an archive signal also fires (i.e. anti-signals are dominated by any archive signal per the gate decision rules):
21
+
22
+ 1. **Typo-only edits** — the entire session is whitespace / spelling / formatting changes. No semantic content to archive.
23
+ 2. **Pure refactor** — rename / move / extract with no behavior change AND no naming convention being established.
24
+ 3. **Narrow rename request** — user asked to rename one symbol / file with no rationale. Zero generalization potential.
25
+ 4. **Duplicate of existing canonical** — the observation is already covered by an existing entry under `.fabric/knowledge/<type>/`. Do a quick Glob before deciding.
26
+
27
+ ## Gate-FAIL user messages (E2 / E4 only)
28
+
29
+ For the user-active branch, the gate-FAIL message variants are:
30
+
31
+ ### zh-CN variant
32
+
33
+ ```
34
+ 本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
35
+ ```
36
+
37
+ ### en variant
38
+
39
+ ```
40
+ Current session is routine execution; no new knowledge to archive (gate=<reason>). To force-archive, explicitly invoke fabric-archive.
41
+ ```
42
+
43
+ In BOTH branches: do NOT proceed to Phase 3, 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 4.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.
44
+
45
+ ## events.jsonl Constraint Note (POSIX 4KB atomicity)
46
+
47
+ Event lines appended to `.fabric/events.jsonl` are subject to POSIX single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk interleaved corruption under concurrent skill + server writes to the same ledger.
48
+
49
+ Skills MUST ensure:
50
+
51
+ - Each event JSON line is a **single line** (no embedded newlines; escape `\n` in any string value).
52
+ - `session_context` and other free-form text fields **self-truncate** to keep the entire serialized line under 4KB. Suggested per-field caps: `session_context` first 500 chars; `source_sessions` cap at 5 entries; `recent_paths` cap at 20 entries; `user_messages_summary` first 500 chars.
53
+ - If approaching the 4KB ceiling after the per-field caps, drop optional fields (e.g. tags / extra metadata) **before** truncating semantic content (the summary / context that carries the actual observation).
54
+ - This constraint applies to any event the skill itself appends (e.g. the abort signal above); MCP-server-side appends (via `appendEventLedgerEvent`) are already line-length-bounded server-side.
@@ -0,0 +1,80 @@
1
+ # Phase 3.5 — Scope Decision + relevance_paths Derivation (ref)
2
+
3
+ > **Loaded on demand.** SKILL.md hot path retains the scope decision pseudocode, personal-layer-forces-broad rule, and brief examples. This file holds the rc.5 single-signal `edit_paths` derivation algorithm (Steps 1-6) + worked generalization example + inline-edit re-derivation rules.
4
+
5
+ ## relevance_paths derivation algorithm (rc.5 single-signal: edit_paths only)
6
+
7
+ 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.
8
+
9
+ ```
10
+ Step 1: COLLECT
11
+ edit_paths = []
12
+ Scan session transcript for tool_use entries where
13
+ tool_use.name ∈ {Edit, Write, MultiEdit}
14
+ Extract the file_path argument from each, push into edit_paths.
15
+
16
+ Step 2: DEDUPE
17
+ edit_paths = unique(edit_paths)
18
+
19
+ Step 3: BLACKLIST FILTER
20
+ Drop paths matching any of:
21
+ - **/*.<ext> where <ext> is a single trivial extension on a single file
22
+ (i.e. avoid emitting bare **/*.md as a relevance pattern)
23
+ - Repo-root single files: README.md, package.json, package-lock.json,
24
+ pnpm-lock.yaml, tsconfig.json, .gitignore, LICENSE, CHANGELOG.md
25
+ - Read-only paths (never modified) — those go to ## Evidence, not relevance_paths
26
+
27
+ Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
28
+ Group remaining paths by common prefix.
29
+ For each group of ≥ 2 sibling paths sharing a prefix:
30
+ - Compute longest common directory prefix
31
+ - Limit generalization depth: at most 2 levels below the common prefix
32
+ - Emit glob: <common-prefix>/**/*.<ext> (or <common-prefix>/**/<filename>)
33
+ Singleton paths (group size = 1) are kept as-is (literal path, no glob).
34
+
35
+ Step 5: SCOPE GATE
36
+ IF relevance_scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
37
+ IF relevance_scope == narrow → relevance_paths = result of Step 4
38
+
39
+ Step 6: ATTACH READ-ONLY EVIDENCE
40
+ Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
41
+ block in the pending entry body — NOT in relevance_paths. They document
42
+ what the agent consulted without making them part of the activation gate.
43
+ ```
44
+
45
+ ## Worked generalization example
46
+
47
+ Edit history during session:
48
+
49
+ ```
50
+ packages/server/src/services/extract.ts
51
+ packages/server/src/services/review.ts
52
+ packages/server/src/services/promote.ts
53
+ packages/cli/src/commands/plan.ts
54
+ README.md
55
+ ```
56
+
57
+ Step 1-2 (collect + dedupe): all 5 unique.
58
+ Step 3 (blacklist): drop `README.md` (repo-root single file).
59
+ Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
60
+ - `packages/server/src/services/{extract,review,promote}.ts` → group size 3 ≥ 2, common prefix `packages/server/src/services/`, glob: `packages/server/src/services/**/*.ts`
61
+ - `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
62
+
63
+ Step 5 (assume `relevance_scope=narrow`):
64
+
65
+ ```json
66
+ "relevance_paths": [
67
+ "packages/server/src/services/**/*.ts",
68
+ "packages/cli/src/commands/plan.ts"
69
+ ]
70
+ ```
71
+
72
+ If `relevance_scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
73
+
74
+ ## Inline-edit support during batch review
75
+
76
+ The user MAY inline-edit `[relevance_scope=...]` in the batch review. When this happens:
77
+
78
+ - Edit changes `narrow → broad`: clear `relevance_paths` to `[]`.
79
+ - Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
80
+ - The user MAY also directly inline-edit `relevance_paths` to a custom array; treat this as authoritative and skip auto-derivation.
@@ -0,0 +1,63 @@
1
+ # Phase 3 — Classify, Layer, Slug, Review (ref)
2
+
3
+ > **Loaded on demand.** SKILL.md hot path retains the contract (type/layer/slug/summary fields), the verbatim layer heuristic block (protected tokens — NEVER paraphrased), and brief slug rules + decision tree. This file holds verbose 5-type explanations, slug examples, and the bilingual batch review templates.
4
+
5
+ ## Five Knowledge Types (verbose)
6
+
7
+ - **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).
8
+ - **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).
9
+ - **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).
10
+ - **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).
11
+ - **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).
12
+
13
+ ## Slug Naming — examples
14
+
15
+ Passing examples: `wave-1-parallel-task-dag` (4 words, 24 chars), `deepmerge-array-replace-trap` (4 words, 28 chars).
16
+
17
+ Failing examples: `the_solution` (underscore + article), `fix` (1 word, too short), `how-we-decided-to-handle-the-merge-conflict-in-stop-hook-config` (overlong).
18
+
19
+ ## Batch Review Template (bilingual)
20
+
21
+ 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.
22
+
23
+ ### en variant (`fabric_language === "en"`)
24
+
25
+ ```md
26
+ # Archive Review — N candidates
27
+
28
+ ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
29
+ Summary: <1-2 sentences capturing the observation>
30
+ Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
31
+ Scope reasoning: <why narrow or broad — see Phase 3.5>
32
+ relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
33
+ Confirm? (Y to accept, edit type/layer/slug/relevance_scope/relevance_paths inline, N to skip)
34
+
35
+ ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
36
+ Summary: ...
37
+ Layer reasoning: ...
38
+ Scope reasoning: ...
39
+ relevance_paths: []
40
+ Confirm? ...
41
+ ```
42
+
43
+ ### zh-CN variant (`fabric_language === "zh-CN"`)
44
+
45
+ ```md
46
+ # 归档 Review — N 条候选
47
+
48
+ ## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
49
+ 摘要: <1-2 句捕捉该观察>
50
+ Layer 判定: <命中哪条 强 team / 强 personal 信号,或默认 team>
51
+ Scope 判定: <为什么 narrow 或 broad — 见 Phase 3.5>
52
+ relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
53
+ 确认?(Y 接受 / 内联编辑 type/layer/slug/relevance_scope/relevance_paths / N 跳过)
54
+
55
+ ## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
56
+ 摘要: ...
57
+ Layer 判定: ...
58
+ Scope 判定: ...
59
+ relevance_paths: []
60
+ 确认?...
61
+ ```
62
+
63
+ 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 3.5 rules (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).