@fenglimg/fabric-cli 2.2.0 → 2.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/audit-PURSJJFH.js +734 -0
  3. package/dist/{chunk-QPAW6IYT.js → chunk-7V4XMLQ2.js} +3 -3
  4. package/dist/{chunk-7ZDXBOOU.js → chunk-ACSMNX3V.js} +44 -128
  5. package/dist/{chunk-PTGQAZEW.js → chunk-GGDVZCD6.js} +2 -4
  6. package/dist/{chunk-EOT63RDH.js → chunk-I5F5BHWI.js} +9 -0
  7. package/dist/chunk-PP7QVRXH.js +565 -0
  8. package/dist/chunk-SL77FXX7.js +54 -0
  9. package/dist/{chunk-3D7B2UAZ.js → chunk-VQKXTMWH.js} +44 -4
  10. package/dist/doctor-S6KPGS35.js +27 -0
  11. package/dist/index.js +90 -80
  12. package/dist/{info-7FKBTMVO.js → info-NJEY26H6.js} +91 -46
  13. package/dist/{context-UJCGYOT6.js → inspect-5YZMJPFM.js} +10 -10
  14. package/dist/{install-v2-3KJX3YRO.js → install-v2-KGIDII4H.js} +163 -364
  15. package/dist/{store-HOCORVL3.js → store-GF4SFBMJ.js} +155 -57
  16. package/dist/{sync-DT5UJMMR.js → sync-3XCIRDPK.js} +3 -4
  17. package/dist/{uninstall-IFN2KYBK.js → uninstall-BG4ML4FC.js} +39 -10
  18. package/package.json +3 -7
  19. package/templates/hooks/cite-policy-evict.cjs +1 -1
  20. package/templates/hooks/configs/claude-code.json +1 -5
  21. package/templates/hooks/configs/codex-hooks.json +1 -5
  22. package/templates/hooks/fabric-hint.cjs +67 -41
  23. package/templates/hooks/knowledge-hint-broad.cjs +82 -24
  24. package/templates/hooks/knowledge-hint-narrow.cjs +3 -3
  25. package/templates/hooks/knowledge-pretooluse.cjs +111 -0
  26. package/templates/hooks/lib/banner-i18n.cjs +12 -11
  27. package/templates/hooks/lib/bindings-snapshot-reader.cjs +1 -1
  28. package/templates/hooks/lib/event-writer.cjs +79 -0
  29. package/templates/hooks/lib/nudge-policy.cjs +11 -0
  30. package/templates/hooks/lib/theme.cjs +62 -0
  31. package/templates/hooks/post-tooluse-mutation.cjs +28 -39
  32. package/templates/skills/fabric-archive/SKILL.md +29 -12
  33. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  34. package/templates/skills/fabric-archive/ref/i18n-policy.md +1 -1
  35. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +5 -5
  36. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +2 -2
  37. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  38. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +1 -1
  39. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +1 -1
  40. package/templates/skills/fabric-archive/ref/phase-3-classify.md +1 -1
  41. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +1 -1
  42. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +6 -5
  43. package/templates/skills/{fabric-import/ref/checkpoint-state.md → fabric-archive/ref/source-checkpoint.md} +3 -3
  44. package/templates/skills/{fabric-import/ref/phase-3-dedup.md → fabric-archive/ref/source-dedup.md} +4 -4
  45. package/templates/skills/{fabric-import/ref/phase-2-mining.md → fabric-archive/ref/source-mining.md} +20 -20
  46. package/templates/skills/{fabric-import/ref/output-contract.md → fabric-archive/ref/source-output-contract.md} +3 -3
  47. package/templates/skills/{fabric-import/ref/state-recovery.md → fabric-archive/ref/source-state-recovery.md} +2 -2
  48. package/templates/skills/{fabric-import/ref/worked-examples.md → fabric-archive/ref/source-worked-examples.md} +10 -10
  49. package/templates/skills/fabric-archive/ref/worked-examples.md +3 -3
  50. package/templates/skills/fabric-review/SKILL.md +19 -15
  51. package/templates/skills/fabric-review/ref/cite-contract.md +2 -2
  52. package/templates/skills/fabric-review/ref/modify-flow.md +13 -1
  53. package/templates/skills/fabric-review/ref/per-mode-flows.md +5 -5
  54. package/templates/skills/fabric-review/ref/relate-mode.md +33 -0
  55. package/templates/skills/fabric-review/ref/retire-mode.md +47 -0
  56. package/templates/skills/fabric-review/ref/semantic-check.md +1 -1
  57. package/templates/skills/fabric-review/ref/worked-examples.md +5 -5
  58. package/templates/skills/fabric-store/SKILL.md +12 -27
  59. package/templates/skills/fabric-sync/SKILL.md +16 -35
  60. package/templates/skills/lib/shared-policy.md +6 -4
  61. package/dist/chunk-27HK6H5Y.js +0 -69
  62. package/dist/chunk-E7HJUU34.js +0 -1096
  63. package/dist/chunk-NLNH64A3.js +0 -43
  64. package/dist/chunk-QFIVFZRH.js +0 -13
  65. package/dist/doctor-MDTZWKBK.js +0 -24
  66. package/dist/metrics-HMFH4YHK.js +0 -135
  67. package/dist/scope-explain-HLJZ2M33.js +0 -48
  68. package/dist/status-4R3TM4FJ.js +0 -37
  69. package/dist/whoami-ITGEFWH4.js +0 -49
  70. package/templates/skills/fabric/SKILL.md +0 -100
  71. package/templates/skills/fabric-audit/SKILL.md +0 -63
  72. package/templates/skills/fabric-connect/SKILL.md +0 -48
  73. package/templates/skills/fabric-import/SKILL.md +0 -151
  74. package/templates/skills/fabric-import/ref/i18n-policy.md +0 -78
@@ -0,0 +1,79 @@
1
+ // ux-w2-9: the SINGLE guarded events.jsonl write path for .cjs hooks.
2
+ //
3
+ // Before this, every hook (knowledge-hint-broad / fabric-hint / post-tooluse-
4
+ // mutation) hand-stamped the event envelope (kind/id/ts/schema_version) and
5
+ // called appendLockedLine directly. A forgotten field or a non-string event_type
6
+ // produced a row the doctor's event-ledger Zod read (event_ledger_schema_compat)
7
+ // would reject. This module centralizes the stamp + guard so every cjs-written
8
+ // row satisfies the same envelope contract the TS appendEventLedgerEvent enforces
9
+ // via Zod — without pulling Zod into the no-build .cjs runtime.
10
+ //
11
+ // Contract (mirrors eventLedgerEventSchema's envelope):
12
+ // - event_type MUST be a non-empty string (the discriminator). Missing/blank →
13
+ // the event is REJECTED (returns false), never written.
14
+ // - kind / schema_version are FORCED to the ledger constants.
15
+ // - id / ts are stamped only when absent (caller may pre-stamp for determinism).
16
+ // - Never throws — hooks must never block; a write failure returns false.
17
+
18
+ const { join } = require("node:path");
19
+ const { appendLockedLine } = require("./injection-log.cjs");
20
+
21
+ const EVENT_KIND = "fabric-event";
22
+ const SCHEMA_VERSION = 1;
23
+
24
+ function safeUuid() {
25
+ try {
26
+ // eslint-disable-next-line global-require
27
+ return require("node:crypto").randomUUID();
28
+ } catch {
29
+ // crypto unavailable (exotic runtime) → time+rand fallback, still unique enough.
30
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
31
+ }
32
+ }
33
+
34
+ // Stamp the canonical envelope onto a caller event, or return null when the event
35
+ // fails the guard (not an object / missing event_type). Pure (no I/O).
36
+ function stampEvent(event) {
37
+ if (!event || typeof event !== "object" || Array.isArray(event)) return null;
38
+ if (typeof event.event_type !== "string" || event.event_type.length === 0) return null;
39
+ return {
40
+ ...event,
41
+ kind: EVENT_KIND,
42
+ schema_version: SCHEMA_VERSION,
43
+ id: typeof event.id === "string" && event.id.length > 0 ? event.id : `event:${safeUuid()}`,
44
+ ts: typeof event.ts === "number" ? event.ts : Date.now(),
45
+ };
46
+ }
47
+
48
+ // Append ONE guarded event line to <fabricDir>/events.jsonl. Returns true on a
49
+ // successful write, false if the event failed the guard or the append errored.
50
+ function appendEvent(fabricDir, event) {
51
+ const stamped = stampEvent(event);
52
+ if (stamped === null) return false;
53
+ try {
54
+ appendLockedLine(join(fabricDir, "events.jsonl"), JSON.stringify(stamped) + "\n");
55
+ return true;
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ // Append MANY guarded events in a SINGLE locked write (preserves the batched
62
+ // append the post-tooluse hook relied on). Invalid events are dropped; returns
63
+ // the count actually written. A zero-valid batch performs no write.
64
+ function appendEvents(fabricDir, events) {
65
+ if (!Array.isArray(events)) return 0;
66
+ const stamped = events.map(stampEvent).filter((e) => e !== null);
67
+ if (stamped.length === 0) return 0;
68
+ try {
69
+ appendLockedLine(
70
+ join(fabricDir, "events.jsonl"),
71
+ stamped.map((e) => JSON.stringify(e)).join("\n") + "\n",
72
+ );
73
+ return stamped.length;
74
+ } catch {
75
+ return 0;
76
+ }
77
+ }
78
+
79
+ module.exports = { appendEvent, appendEvents, stampEvent };
@@ -103,6 +103,17 @@ function resolveHumanSink(projectRoot, event, gate) {
103
103
  // 4. global human-channel mute.
104
104
  if (mode === "silent") return { emitHuman: false, verbosity, mode };
105
105
 
106
+ // 4b. v2.2 C1 (W5): the `stop` human nudge (archive cadence) defaults to QUIET.
107
+ // The edit-count / session signal already lives in the events.jsonl ledger as
108
+ // queryable telemetry (KT-DEC-0030), so the Stop hook should NOT carry a
109
+ // real-time human-observation UI that interrupts execution flow — find the
110
+ // specific session after the fact instead (user directive 2026-06-22). It stays
111
+ // OBSERVE-only by default; opt back in explicitly via observe.stop=true (handled
112
+ // at step 3) or the verbose preset. SessionStart / pre_tool_use are unaffected.
113
+ if (event === "stop" && mode !== "verbose") {
114
+ return { emitHuman: false, verbosity, mode };
115
+ }
116
+
106
117
  // 5. preset default — emit at the preset's verbosity.
107
118
  return { emitHuman: true, verbosity, mode };
108
119
  }
@@ -0,0 +1,62 @@
1
+ // ux-w2-5: the .cjs mirror of packages/shared/src/theme.ts — the hook-side half
2
+ // of Fabric's single terminal theme. Byte-locked to the TS source by
3
+ // theme-parity.test.ts (G-THEME): the PALETTE / ANSI tables and the paint/symbol
4
+ // render primitives MUST produce byte-identical output here and in the CLI, so a
5
+ // SessionStart hint and `fabric install` paint the same colours. No deps (no Ink,
6
+ // no picocolors) — pure ANSI, matching the Ink-exit direction (W3-A).
7
+
8
+ const ANSI = {
9
+ reset: "\x1b[0m",
10
+ bold: "\x1b[1m",
11
+ dim: "\x1b[2m",
12
+ };
13
+
14
+ const PALETTE = {
15
+ success: "\x1b[38;2;46;204;113m", // emerald
16
+ warn: "\x1b[38;2;241;196;15m", // amber
17
+ error: "\x1b[38;2;231;76;60m", // alizarin
18
+ drift: "\x1b[38;2;155;89;182m", // amethyst
19
+ ai: "\x1b[38;2;52;152;219m", // peter-river blue
20
+ human: "\x1b[38;2;26;188;156m", // turquoise
21
+ accent: "\x1b[38;2;155;89;182m", // amethyst
22
+ muted: ANSI.dim,
23
+ };
24
+
25
+ function isColorEnabled(env, isTTY) {
26
+ const e = env || process.env;
27
+ if (e.NO_COLOR) return false;
28
+ const force = e.FORCE_COLOR;
29
+ if (force !== undefined) return force !== "0" && force.toLowerCase() !== "false";
30
+ return Boolean(isTTY === undefined ? process.stdout.isTTY : isTTY);
31
+ }
32
+
33
+ function paint(token, text, colorOn) {
34
+ const on = colorOn === undefined ? isColorEnabled() : colorOn;
35
+ if (!on) return text;
36
+ return `${PALETTE[token]}${text}${ANSI.reset}`;
37
+ }
38
+
39
+ const SYMBOL_ASCII = { ok: "[ok]", warn: "[warn]", error: "[error]" };
40
+ const SYMBOL_GLYPH = { ok: "[ok] ✓", warn: "[warn] !", error: "[error] x" };
41
+ const SYMBOL_TOKEN = { ok: "success", warn: "warn", error: "error" };
42
+
43
+ function symbol(kind, colorOn) {
44
+ const on = colorOn === undefined ? isColorEnabled() : colorOn;
45
+ return on ? paint(SYMBOL_TOKEN[kind], SYMBOL_GLYPH[kind], true) : SYMBOL_ASCII[kind];
46
+ }
47
+
48
+ // W3-B structural primitives — HUD-shared layer (C-003), byte-mirror of the TS
49
+ // source (packages/shared/src/theme.ts), pinned by theme-parity.test.ts.
50
+ function sectionBar(title, colorOn) {
51
+ const on = colorOn === undefined ? isColorEnabled() : colorOn;
52
+ return on ? `${ANSI.bold}${PALETTE.accent}▌ ${title}${ANSI.reset}` : `# ${title}`;
53
+ }
54
+
55
+ const SCOPE_BADGE_TOKEN = { team: "drift", project: "ai", personal: "human" };
56
+ function scopeBadge(scope, colorOn) {
57
+ const on = colorOn === undefined ? isColorEnabled() : colorOn;
58
+ const text = `[${scope}]`;
59
+ return on ? paint(SCOPE_BADGE_TOKEN[scope], text, true) : text;
60
+ }
61
+
62
+ module.exports = { ANSI, PALETTE, isColorEnabled, paint, symbol, SYMBOL_ASCII, sectionBar, scopeBadge };
@@ -41,9 +41,10 @@ const { isAbsolute, join, relative } = require("node:path");
41
41
 
42
42
  // W1-01 (ISS-011) parity: route every shared-ledger append through the
43
43
  // advisory-lock primitive so concurrent PostToolUse fires (multi-window /
44
- // parallel edits) never interleave a partial line. Best-effort, drop-on-
45
- // contention same primitive the narrow/broad hooks use.
46
- const { appendLockedLine } = require("./lib/injection-log.cjs");
44
+ // parallel edits) never interleave a partial line. ux-w2-9: route batched event
45
+ // writes through the single guarded event-writer (envelope stamp + event_type
46
+ // guard + advisory-lock append) so every row satisfies the event-ledger schema.
47
+ const { appendEvents } = require("./lib/event-writer.cjs");
47
48
 
48
49
  const FABRIC_DIR_REL = ".fabric";
49
50
  const EVENTS_LEDGER_FILE = "events.jsonl";
@@ -223,23 +224,17 @@ function appendFileMutated(projectRoot, now, paths, toolCallId, toolName, sessio
223
224
  typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
224
225
  const validToolName =
225
226
  typeof toolName === "string" && toolName.length > 0 ? toolName : null;
226
- const lines =
227
- pathList
228
- .map((p) =>
229
- JSON.stringify({
230
- kind: "fabric-event",
231
- id: `event:${randomUUID()}`,
232
- ts: tsMs,
233
- schema_version: 1,
234
- ...(validSessionId ? { session_id: validSessionId } : {}),
235
- event_type: "file_mutated",
236
- path: p,
237
- tool_call_id: callId,
238
- ...(validToolName ? { tool_name: validToolName } : {}),
239
- }),
240
- )
241
- .join("\n") + "\n";
242
- appendLockedLine(join(fabricDir, EVENTS_LEDGER_FILE), lines);
227
+ appendEvents(
228
+ fabricDir,
229
+ pathList.map((p) => ({
230
+ ts: tsMs,
231
+ ...(validSessionId ? { session_id: validSessionId } : {}),
232
+ event_type: "file_mutated",
233
+ path: p,
234
+ tool_call_id: callId,
235
+ ...(validToolName ? { tool_name: validToolName } : {}),
236
+ })),
237
+ );
243
238
  } catch {
244
239
  // Silent — marker failure must never block the tool pipeline.
245
240
  }
@@ -290,25 +285,19 @@ function appendKnowledgeBodyRead(projectRoot, now, paths, toolCallId, toolName,
290
285
  typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
291
286
  const validToolName =
292
287
  typeof toolName === "string" && toolName.length > 0 ? toolName : null;
293
- const lines =
294
- reads
295
- .map((r) =>
296
- JSON.stringify({
297
- kind: "fabric-event",
298
- id: `event:${randomUUID()}`,
299
- ts: tsMs,
300
- schema_version: 1,
301
- ...(validSessionId ? { session_id: validSessionId } : {}),
302
- event_type: "knowledge_body_read",
303
- stable_id: r.stable_id,
304
- ...(r.store ? { store: r.store } : {}),
305
- path: r.path,
306
- tool_call_id: callId,
307
- ...(validToolName ? { tool_name: validToolName } : {}),
308
- }),
309
- )
310
- .join("\n") + "\n";
311
- appendLockedLine(join(fabricDir, EVENTS_LEDGER_FILE), lines);
288
+ appendEvents(
289
+ fabricDir,
290
+ reads.map((r) => ({
291
+ ts: tsMs,
292
+ ...(validSessionId ? { session_id: validSessionId } : {}),
293
+ event_type: "knowledge_body_read",
294
+ stable_id: r.stable_id,
295
+ ...(r.store ? { store: r.store } : {}),
296
+ path: r.path,
297
+ tool_call_id: callId,
298
+ ...(validToolName ? { tool_name: validToolName } : {}),
299
+ })),
300
+ );
312
301
  } catch {
313
302
  // Silent — marker failure must never block the tool pipeline.
314
303
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: fabric-archive
3
- description: 归档对话洞察到 active write store 的 pending knowledge (NOT code review). Triggers 以后/always/never/下次/记一下;wrong-turn-revert;decision-confirm;dismissal-reason;/fabric-archive.
4
- allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_extract_knowledge
3
+ description: 归档对话洞察(default)+ 冷启动从 git log/docs 回灌(source mode)到 active write store 的 pending knowledge (NOT code review). Triggers 归档/记一下/always·never/wrong-turn-revert;source mode bootstrap fabric/import 历史/mine commit.
4
+ allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_archive_scan, mcp__fabric__fab_propose, mcp__fabric__fab_pending, mcp__fabric__fab_review
5
5
  ---
6
6
 
7
7
  > **Surface**: Skill (LLM judgment over session digests). See [`docs/surfaces.md`](https://github.com/fenglimg/fabric/blob/main/docs/surfaces.md).
@@ -29,10 +29,27 @@ rc.37 NEW-9 collapsed the flow to **3 macro-phases**; the legacy fine-grained ph
29
29
 
30
30
  - **GATHER** = Phase 0 (range) → 0.5 (config) → 1 (ledger scan via `fab_archive_scan`) → [1.5 onboard] → 2 (candidates).
31
31
  - **REVIEW** = Phase 2.5 (viability gate) → 3 (classify / layer / slug + batch review) → 3.5 (relevance scope + relevance_paths) → 3.7 (semantic scope / audience axis). The single user review round lives here.
32
- - **PERSIST** = Phase 4 (`fab_extract_knowledge`, one call per candidate) → 4.5 (archive-attempt ledger).
32
+ - **PERSIST** = Phase 4 (`fab_propose`, one call per candidate) → 4.5 (archive-attempt ledger).
33
33
 
34
34
  Sub-step chain: `0 → 0.5 → 1 → [1.5] → 2 → 2.5 → 3 → 3.5 → 3.7 → 4 → 4.5`. Each below is a navigator stub — full procedure, decision tables, and worked examples live in `ref/`.
35
35
 
36
+ ## Source Mode — cold-start bootstrap (W3-C: 吸收原 fabric-import)
37
+
38
+ **Default mode** GATHERs candidates from the cross-session digest ledger (Phase 1, `fab_archive_scan`). **Source mode** swaps ONLY the GATHER source: it mines `git log` + `docs/*.md` for one-time per-project cold-start — the REVIEW (classify / layer / scope / semantic_scope) and PERSIST (`fab_propose`) pipeline below is **shared, byte-for-byte the same contract**. No new write path: mined entries land as `team`-layer pending via the same `fab_propose`, then dedupe vs canonical via `fab_review`.
39
+
40
+ **Enter source mode when** the user explicitly asks to bootstrap / import history / mine commits, OR the SessionStart hook fired `shouldRecommendImport()`. Else stay in default mode. **SKIP** when `.fabric/` missing (→ `fabric install`), canonical count > `import_skip_canonical_threshold` (default 50), or checkpoint `phase=complete` + `last_checkpoint_at <24h`.
41
+
42
+ Source-mode pipeline (replaces GATHER; REVIEW+PERSIST unchanged):
43
+
44
+ 1. **Init / checkpoint** — read/init `.fabric/.import-state.json` (single resumability source; atomic `Write .tmp` → `Bash mv`). Corruption → `Read ref/source-state-recovery.md`. Full state schema + 6-step resume → `Read ref/source-checkpoint.md`.
45
+ 2. **Init-scan reference (NO re-implement)** — `fabric onboard-coverage --json` + `fab_pending action="search"` to learn existing canonical titles for the negative filter. `fabric install` already produced the baseline; source mode references it, never redoes it.
46
+ 3. **Mine (git + docs)** — `git log --since="<window> months ago"` (conventional prefix → type signal) + `docs/*.md`; classify into the 5 types; `fab_propose` per candidate. **Source-mode scope lock (NON-NEGOTIABLE): every mined entry `relevance_scope="broad"` + `relevance_paths=[]`** — LLM-inferred narrow lies about applicability; narrowing is deferred to `fab_review.modify` post-import. Cap `import_max_pending_per_run` (default 10). Full mining procedure, conventional-prefix table, `--dry-run` template → `Read ref/source-mining.md`.
47
+ 4. **Dedupe vs canonical** — for each pending, `fab_pending action="search"` (top 5 by type), classify duplicate / subsumption / subsumption-with-novelty / contradiction / genuinely-new, then `fab_review` reject / modify. `fab_pending` does NOT compare meaning — semantic compare is the LLM's job. Full 5-way classification → `Read ref/source-dedup.md`.
48
+
49
+ Source-mode config knobs (read from `.fabric/fabric-config.json`, defaults if absent): `import_window_first_run_months` (60), `import_window_rerun_months` (2), `import_max_pending_per_run` (10), `import_max_commits_scan` (500), `import_skip_canonical_threshold` (50).
50
+
51
+ Source-mode output roll-up + worked examples → `Read ref/source-output-contract.md` / `ref/source-worked-examples.md`. Source mode requires an **explicit target store** (E7) — never auto-route mined entries; resolve writable candidates via `fabric info scope team` and `AskUserQuestion` for the alias when more than one exists.
52
+
36
53
  ### Phase 0 — Range Resolution
37
54
 
38
55
  Parse user's prompt for time-window (`今日` / `last week`), topic keyword (`rc.20`), or literal `session_id` reference; emit `session_id[]` OR `"all"` sentinel that constrains Phase 1 collection. LLM-as-parser contract — no parser code.
@@ -45,7 +62,7 @@ Read `.fabric/fabric-config.json`; resolve `archive_max_candidates_per_batch` (d
45
62
 
46
63
  ### Phase 0.6 — Store routing (v2.1 multi-store)
47
64
 
48
- Archives land in the **active write store** for the entry's scope — NEVER pick a store yourself. Run `fabric scope-explain team` (or the relevant scope) to get the resolved `writeTarget`; that is where `fab_extract_knowledge` persists. Single-store → the lone store (back-compat). After persisting, **echo the target store alias** (`归档到 store '<alias>'`). Personal-scope entries route to the personal store (never the shared team store, R5#3). Do NOT read `~/.fabric` store trees directly — go through `scope-explain` / the MCP write path.
65
+ Archives land in the **active write store** for the entry's scope — NEVER pick a store yourself. Run `fabric info scope team` (or the relevant scope) to get the resolved `writeTarget`; that is where `fab_propose` persists. Single-store → the lone store (back-compat). After persisting, **echo the target store alias** (`归档到 store '<alias>'`). Personal-scope entries route to the personal store (never the shared team store, R5#3). Do NOT read `~/.fabric` store trees directly — go through `fabric info scope` / the MCP write path.
49
66
 
50
67
  ### UX i18n Policy
51
68
 
@@ -95,7 +112,7 @@ Gather raw evidence: tail `.fabric/events.jsonl` since last `knowledge_proposed`
95
112
 
96
113
  Coarse viability check. **PASS**: user_explicit_invoke OR ≥1 archive signal hit. rc.37 NEW-4 folds the legacy 8 signals into **3 categories**: (1) **User-driven knowledge expression** (normative language `always`/`never`/`以后`/`记一下`/`永远不要`, OR decision-with-rationale, OR dismissal-with-reason); (2) **Reflective discovery** (wrong-turn-and-revert, OR long diagnostic loop, OR a named reusable pattern); (3) **Concrete artifact change** (new dependency diff, OR a formalized multi-step procedure).
97
114
 
98
- Pre-PASS HARD gate (rc.37 NEW-4): per candidate, run `fab_review action="search"` against the mounted read-set; duplicate canonical → drop the candidate (anti-signal #4). Silently writing a near-duplicate is the highest-noise failure mode.
115
+ Pre-PASS HARD gate (rc.37 NEW-4): per candidate, run `fab_pending action="search"` against the mounted read-set; duplicate canonical → drop the candidate (anti-signal #4). Silently writing a near-duplicate is the highest-noise failure mode.
99
116
 
100
117
  **FAIL → branch**: E1/E3/E5 silent-skip (`outcome='skipped_no_signal'`); E2/E4 render gate-FAIL (`outcome='viability_failed'`) and MUST include the force-archive escape hatch (zh-CN: `如需强制归档,请显式调用 fabric-archive` / en: `To force-archive, explicitly invoke fabric-archive`).
101
118
 
@@ -137,7 +154,7 @@ Sets the entry's **audience** `semantic_scope` (orthogonal to `layer`=store and
137
154
 
138
155
  ### Phase 4 — Persist via MCP
139
156
 
140
- For each user-confirmed candidate, call `fab_extract_knowledge` ONCE (NEVER batch). Required: `source_sessions[]`, `recent_paths[]` (cap 20), `user_messages_summary`, `type` (plural form: decisions/pitfalls/guidelines/models/processes), `slug`, `layer`, `relevance_scope`, `relevance_paths[]`, `proposed_reason` (enum), `session_context` (3-5 line narrative). One OPTIONAL audience field: `semantic_scope` — pass `team` ONLY when Phase 3.7 classified a team candidate as team-wide (cross-project); OMIT otherwise so the engine auto-derives `project:<active_project>` (or `team` when the repo has no `active_project`). Four OPTIONAL rc.23 triage fields (`intent_clues`, `tech_stack`, `impact`, `must_read_if`) — populate when clean, **omit rather than guess**.
157
+ For each user-confirmed candidate, call `fab_propose` ONCE (NEVER batch). Required: `source_sessions[]`, `recent_paths[]` (cap 20), `user_messages_summary`, `type` (plural form: decisions/pitfalls/guidelines/models/processes), `slug`, `layer`, `relevance_scope`, `relevance_paths[]`, `proposed_reason` (enum), `session_context` (3-5 line narrative). One OPTIONAL audience field: `semantic_scope` — pass `team` ONLY when Phase 3.7 classified a team candidate as team-wide (cross-project); OMIT otherwise so the engine auto-derives `project:<active_project>` (or `team` when the repo has no `active_project`). Four OPTIONAL rc.23 triage fields (`intent_clues`, `tech_stack`, `impact`, `must_read_if`) — populate when clean, **omit rather than guess**.
141
158
 
142
159
  Server returns `{ pending_path, idempotency_key }`. Display `pending_path` for the user. `idempotency_key = sha256({source_session, type, slug})` — repeated calls SAFE (server merges evidence).
143
160
 
@@ -163,25 +180,25 @@ MANDATORY closing step on EVERY invocation (Phase 4 success path + every early-e
163
180
  - WHEN `active_project` is set AND `layer=team`, MUST present `[semantic_scope=...]` (`team` for team-wide, or `project:<active_project>` for this-project-only) plus a one-line `Audience reasoning:` citing this-project-only vs team-wide (Phase 3.7).
164
181
  - MUST classify against the canonical singular nouns: model / decision / guideline / pitfall / process. NEVER invent new types.
165
182
  - MUST cap the batch at `archive_max_candidates_per_batch` candidates (config-resolved, default 8); drop weaker ones over the cap.
166
- - MUST display the resolved `pending_path` returned by `fab_extract_knowledge` so the user can verify.
183
+ - MUST display the resolved `pending_path` returned by `fab_propose` so the user can verify.
167
184
  - MUST treat user inline edits to type/layer/slug/relevance_scope/relevance_paths/semantic_scope as authoritative replacements before Phase 2.
168
185
  - MUST skip rather than guess when an observation does not fit any of the 5 types.
169
186
 
170
187
  ### WRITE Rules
171
188
 
172
- - NEVER write a knowledge entry directly to the filesystem; the only legal write path is `mcp__fabric__fab_extract_knowledge`.
173
- - NEVER infer or glob a project-local pending directory — persist through `fab_extract_knowledge` and use the returned store-resolved `pending_path`; promotion to canonical knowledge is fab_review concern, NOT this skill.
189
+ - NEVER write a knowledge entry directly to the filesystem; the only legal write path is `mcp__fabric__fab_propose`.
190
+ - NEVER infer or glob a project-local pending directory — persist through `fab_propose` and use the returned store-resolved `pending_path`; promotion to canonical knowledge is fab_review concern, NOT this skill.
174
191
  - NEVER include an `id` field anywhere — pending entries have no id (late-bind on approve).
175
192
  - NEVER classify a candidate as `personal` when a 强 team signal applies. Default to team on ambiguity.
176
193
  - NEVER emit a non-empty `relevance_paths` when `relevance_scope=broad` — broad MUST always carry `relevance_paths=[]`.
177
194
  - NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `relevance_scope=broad` + `relevance_paths=[]`.
178
195
  - v2.0.0-rc.37 NEW-7 widened Phase 3.5: `edit_paths` ∪ `user_mentioned_paths` drives `relevance_paths`; `read_paths` flows separately to `evidence_paths` (structured frontmatter, not body markdown). NEVER lift body regex / symbol extraction into `relevance_paths` — those remain reserved for v2.1+.
179
- - NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
196
+ - NEVER batch multiple candidates into a single fab_propose call; one call per candidate.
180
197
  - NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
181
- - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `fab_extract_knowledge`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`, `pending_path`, `layer`, `semantic_scope`, `active_project`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`, `related`, `KT→KP`.
198
+ - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `fab_propose`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`, `pending_path`, `layer`, `semantic_scope`, `active_project`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`, `related`, `KT→KP`.
182
199
 
183
200
  ## Worked Examples / E5 Cron / Dry-run (ref-only)
184
201
 
185
- - **Worked examples** (3 end-to-end fab_extract_knowledge calls: decision/team, pitfall/team, guideline/personal): `Read ref/worked-examples.md`
202
+ - **Worked examples** (3 end-to-end fab_propose calls: decision/team, pitfall/team, guideline/personal): `Read ref/worked-examples.md`
186
203
  - **E5 Scheduled Daily Recap** (only when entry_point=E5_cron — OS cron, `/loop`, or scheduled trigger): `Read ref/e5-cron-recap.md`
187
204
  - **Dry-run Scope** (authoritative catalogue of all writes suspended by `--dry-run`): `Read ref/dry-run-scope.md`
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Write operation | Normal mode | Dry-run mode |
6
6
  |---|---|---|
7
- | `fab_extract_knowledge` MCP call (Phase 4) | One call per confirmed candidate, writes to the active write store and returns `pending_path` | SKIPPED. Phase 4 renders "would write N pending entries" preview table instead. |
7
+ | `fab_propose` MCP call (Phase 4) | One call per confirmed candidate, writes to the active write store and returns `pending_path` | SKIPPED. Phase 4 renders "would write N pending entries" preview table instead. |
8
8
  | `session_archive_attempted` event (Phase 4.5) | Appended to `.fabric/events.jsonl` for every session in scope | SKIPPED entirely. No ledger entry. |
9
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
10
  | `fabric onboard-coverage` slot writes (Phase 1.5 fill-all / dismiss-all) | Each `Bash("fabric config dismiss-slot <slot>")` invocation runs | SKIPPED. Slot decisions are shown as "would dismiss/propose" preview. |
@@ -43,7 +43,7 @@ Rendering rule:
43
43
  - `fabric_language === "zh-CN-hybrid"` → emit Chinese narrative prose with English technical terms preserved. Protected tokens (always EN): MCP tool names (e.g. `fab_recall`), CLI command names (e.g. `fabric install`), file paths, technical concepts (`Skill`, `SessionStart`, `hook`, `MCP`, `revision_hash`, `pending`, `proven`, `verified`, `draft`).
44
44
  - `fabric_language === "match-existing"` or any other value → emit the en variant; pure monolingual.
45
45
 
46
- Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
46
+ Protected tokens (`fab_propose`, `relevance_scope`,
47
47
  `relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
48
48
  `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`,
49
49
  `pending_path`, `layer`, `team`, `personal`,
@@ -23,7 +23,7 @@ is the canonical taxonomy for this gate.
23
23
  |-------|--------|-------------------------------------------------------|
24
24
  | **E1** | `hook_passive` | stdout JSON `{decision:'block', ...}` from `fabric-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
- | **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` per AGENTS.md self-archive policy section; `<X>` is the signal name. v2.0.0-rc.37 NEW-2 simplified the AGENTS.md taxonomy to 2 categories: `User-driven normative` / `Wrong-turn-and-revert`. Back-compat: legacy 4-state names (`Normative` / `Decision confirmation` / `Explicit dismissal`) still route correctly because the substring gate only matches the verbatim prefix and treats any text after `signal:` as the signal label.) |
26
+ | **E3** | `ai_self_trigger` | DEFAULT else-branch: the AI invoked `fabric-archive` itself, following the AGENTS.md self-archive policy (a clear archival signal `User-driven normative` or `Wrong-turn-and-revert` surfaced this turn), AND none of the affirmative non-AI entry signals are present (no E1 hook `{decision:'block'}` block, no E2/E4 explicit user invocation, no E5 cron literal). v2.2 C1 W3b retired the AI-printed routing marker E3 is now the deterministic else-branch, so routing no longer depends on the AI emitting an exact magic string (every other entry has its own non-AI affirmative detector; E3 is what remains). The signal category does not need to be conveyed to the skill — Phase 2.5's viability gate re-derives it independently. |
27
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
 
@@ -141,7 +141,7 @@ AskUserQuestion({
141
141
  })
142
142
  ```
143
143
 
144
- `fab_extract_knowledge` is called with `onboard_slot: <slot>` set so each
144
+ `fab_propose` is called with `onboard_slot: <slot>` set so each
145
145
  proposed entry counts toward coverage once approved via fab_review.
146
146
 
147
147
  | User choice | Action |
@@ -172,7 +172,7 @@ After Read-ing the slot-specific sources, classify the observation:
172
172
  - `build-system-idiom` → type=`processes`, `proposed_reason=new-dependency-or-pattern`
173
173
  - `domain-vocabulary` → type=`models`, `proposed_reason=new-dependency-or-pattern`
174
174
 
175
- Call `fab_extract_knowledge` with the inferred fields PLUS `onboard_slot:
175
+ Call `fab_propose` with the inferred fields PLUS `onboard_slot:
176
176
  <slot>`. The pending file's frontmatter will carry the slot label, and the
177
177
  next `fabric onboard-coverage` run will see the slot as filled (once approved
178
178
  via fab_review).
@@ -180,7 +180,7 @@ via fab_review).
180
180
  Example:
181
181
 
182
182
  ```ts
183
- mcp__fabric__fab_extract_knowledge({
183
+ mcp__fabric__fab_propose({
184
184
  source_sessions: ["<current-session-id>"],
185
185
  recent_paths: ["package.json", "tsconfig.json"],
186
186
  user_messages_summary: "Project uses TypeScript + pnpm workspace + Vitest. Node 20 LTS target. ESM-only.",
@@ -214,4 +214,4 @@ mcp__fabric__fab_extract_knowledge({
214
214
  - MUST emit `onboard_slot: <slot>` verbatim — the slot name is one of
215
215
  the locked S5 strings (tech-stack-decision / architecture-pattern /
216
216
  code-style-tone / build-system-idiom / domain-vocabulary). The
217
- fab_extract_knowledge schema enum will reject anything else.
217
+ fab_propose schema enum will reject anything else.
@@ -35,7 +35,7 @@ Before Step 5 builds the cross-session context, drop sessions that the outcome l
35
35
  - **(e) Never attempted (no `session_archive_attempted` event found for this `session_id`) → keep.** First-time scan; nothing to filter against.
36
36
  - **(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 (the active write store may still contain matching pending entries). 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`.
37
37
 
38
- 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.
38
+ 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_propose call, and are NOT referenced in `session_context` bodies.
39
39
 
40
40
  ### Constants (rc.25 — verbatim)
41
41
 
@@ -54,7 +54,7 @@ The resulting filtered `session_id[]` proceeds into Step 5's digest concatenatio
54
54
  Concatenate the loaded digests into a single `### Cross-session digest` block to carry into Phase 2.5 + Phase 1. Use this block to:
55
55
 
56
56
  - Detect session-spanning patterns (e.g. a discussion that started in session A and continued in session B).
57
- - Populate the `source_sessions` array on every fab_extract_knowledge call — the array form (T5) replaces the legacy `source_session` string.
57
+ - Populate the `source_sessions` array on every fab_propose call — the array form (T5) replaces the legacy `source_session` string.
58
58
  - Inform the `session_context` blob written to each pending entry's body (3-5 lines summarizing goal + key turning point, per T6).
59
59
 
60
60
  ## Graceful degradation
@@ -36,7 +36,7 @@ These force the gate to FAIL **unless** an archive signal also fires (i.e. anti-
36
36
  1. **Typo-only edits** — the entire session is whitespace / spelling / formatting changes. No semantic content to archive.
37
37
  2. **Pure refactor** — rename / move / extract with no behavior change AND no naming convention being established.
38
38
  3. **Narrow rename request** — user asked to rename one symbol / file with no rationale. Zero generalization potential.
39
- 4. **Duplicate of existing canonical** — v2.0.0-rc.37 NEW-4: this check is now **mandatory** (was "do a quick Glob before deciding"). Pre-PASS MUST step: for each candidate, call `fab_review action="search"` scoped by type/slug keywords so the MCP read path searches mounted stores. If duplicate found → drop candidate. Silently writing a near-duplicate is the highest-noise failure mode.
39
+ 4. **Duplicate of existing canonical** — v2.0.0-rc.37 NEW-4: this check is now **mandatory** (was "do a quick Glob before deciding"). Pre-PASS MUST step: for each candidate, call `fab_pending action="search"` scoped by type/slug keywords so the MCP read path searches mounted stores. If duplicate found → drop candidate. Silently writing a near-duplicate is the highest-noise failure mode.
40
40
 
41
41
  ## Gate-FAIL user messages (E2 / E4 only)
42
42
 
@@ -62,7 +62,7 @@ Step 5: SCOPE GATE
62
62
 
63
63
  Step 6: ATTACH evidence_paths to FRONTMATTER (rc.37 NEW-7 upgrade)
64
64
  Pass evidence_candidate_paths (from Step 2, post-blacklist Step 3) to
65
- fab_extract_knowledge as the `evidence_paths` input field. Server writes
65
+ fab_propose as the `evidence_paths` input field. Server writes
66
66
  them to frontmatter `evidence_paths: [...]` (NOT to body `## Evidence`).
67
67
  This makes evidence consumable by plan-context retrieval as structured
68
68
  data instead of forcing markdown re-parsing every recall. The legacy
@@ -2,7 +2,7 @@
2
2
 
3
3
  For each candidate, identify the **`related`** graph edges to other KB entries — the store-qualified `stable_id`s this entry semantically links to (the decision it supersedes, the pitfall it explains, the model it instantiates). You discovered these ids during the session via `fab_recall` / plan-context, so cite the ones you actually saw, NEVER invent stable_ids.
4
4
 
5
- Because `fab_extract_knowledge` has no dedicated `related` input, record the candidate edges as one line inside `session_context` (e.g. `related: team:KT-DEC-0007, team:KT-PIT-0011`) so they survive to approve-time frontmatter authoring (`fabric-review` writes the canonical `related: [...]` frontmatter).
5
+ Because `fab_propose` has no dedicated `related` input, record the candidate edges as one line inside `session_context` (e.g. `related: team:KT-DEC-0007, team:KT-PIT-0011`) so they survive to approve-time frontmatter authoring (`fabric-review` writes the canonical `related: [...]` frontmatter).
6
6
 
7
7
  ## §4 privacy iron law — KT→KP is FORBIDDEN
8
8
 
@@ -5,7 +5,7 @@
5
5
  ## Five Knowledge Types (verbose)
6
6
 
7
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).
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_propose schema" (no alternative was considered).
9
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
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
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).
@@ -25,7 +25,7 @@ For EACH `session_id` in the run's scope (multi-session E4 runs emit MULTIPLE ev
25
25
 
26
26
  | Skill terminal state | outcome | candidates_proposed | knowledge_proposed_ids |
27
27
  |----------------------------------------------------------------------|----------------------|---------------------|-------------------------------------------------|
28
- | Phase 4 wrote ≥ 1 pending entry | `proposed` | N (count written) | `[idempotency_key_1, idempotency_key_2, ...]` (from each fab_extract_knowledge response) |
28
+ | Phase 4 wrote ≥ 1 pending entry | `proposed` | N (count written) | `[idempotency_key_1, idempotency_key_2, ...]` (from each fab_propose response) |
29
29
  | Phase 2.5 viability_failed AND entry_point ∈ {E2_explicit, E4_user_range} AND user saw + accepted the gate-FAIL message | `viability_failed` | 0 | `[]` |
30
30
  | Phase 3 batch review — user dismissed ALL presented candidates | `user_dismissed` | 0 | `[]` |
31
31
  | Phase 1 filter dropped every session in scope OR Phase 2.5 silent-skip path (E1_hook / E3_ai_self_trigger / E5_cron) | `skipped_no_signal` | 0 | `[]` |
@@ -5,15 +5,16 @@
5
5
  ## Full MCP tool call shape
6
6
 
7
7
  ```ts
8
- mcp__fabric__fab_extract_knowledge({
8
+ mcp__fabric__fab_propose({
9
9
  source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 1)
10
10
  recent_paths: ["<path1>", "<path2>", ...], // capped at archive_max_recent_paths (config-resolved, default 20)
11
11
  user_messages_summary: "<compact prose ≤500 chars>",
12
12
  type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
13
13
  slug: "<kebab-case-2-to-5-words>",
14
- layer: "team" | "personal",
15
- relevance_scope: "narrow" | "broad", // from Phase 3.5
16
- relevance_paths: ["<glob1>", "<literal2>", ...], // narrow derived; broad []
14
+ // v2.2 C1 (W1): author-facing scope is TWO fields only — `audience` + `paths`.
15
+ // The engine derives layer / visibility_store / store / relevance_scope.
16
+ audience: "team" | "personal" | "project:<id>" | "org:<...>", // WHO it's for (open coordinate); omit engine default (project:<active> | team)
17
+ paths: ["<glob1>", "<literal2>", ...], // relevance anchors; non-empty ⇒ narrow, empty/omit ⇒ broad (relevance_scope is derived from this — no separate flag)
17
18
  // v2.0.0-rc.7 T6: required fields for future-self reviewability.
18
19
  proposed_reason:
19
20
  "explicit-user-mark" // user said "always / never / 下次注意" etc.
@@ -78,7 +79,7 @@ The server returns `{ pending_path, idempotency_key }`. Display `pending_path` t
78
79
 
79
80
  ## Idempotency Notes (T5 array-form, rc.7+)
80
81
 
81
- The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. Calling `fab_extract_knowledge` twice with the same `(source_session, type, slug)` triple is SAFE: the server appends new evidence to the existing pending file rather than overwriting or producing duplicates. The skill MAY be re-invoked on the same session without producing junk.
82
+ The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. Calling `fab_propose` twice with the same `(source_session, type, slug)` triple is SAFE: the server appends new evidence to the existing pending file rather than overwriting or producing duplicates. The skill MAY be re-invoked on the same session without producing junk.
82
83
 
83
84
  If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
84
85
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Loaded on demand.** SKILL.md hot path retains a 2-line mention of the 2-step atomic write pattern + a pointer to this file. This file holds the full Atomic State Write rationale, events.jsonl 4KB constraint, complete schema, and resume-logic state machine.
4
4
 
5
- The state file lives at `.fabric/.import-state.json` and is the single source of resumability for fabric-import. It is written via the explicit 2-step atomic pattern documented below so a crash between phases / between sub-steps never corrupts it.
5
+ The state file lives at `.fabric/.import-state.json` and is the single source of resumability for archive source mode. It is written via the explicit 2-step atomic pattern documented below so a crash between phases / between sub-steps never corrupts it.
6
6
 
7
7
  ## Atomic State Write (2-step pattern)
8
8
 
@@ -15,7 +15,7 @@ This 2-step pattern is mandatory for every state file update. `mv` is atomic on
15
15
 
16
16
  Crash safety expectations:
17
17
 
18
- - Crash between Step A and Step B → leaves `.fabric/.import-state.json.tmp`. Phase 0 residue scan (see `ref/state-recovery.md`) triages it on next invocation.
18
+ - Crash between Step A and Step B → leaves `.fabric/.import-state.json.tmp`. Phase 0 residue scan (see `ref/source-state-recovery.md`) triages it on next invocation.
19
19
  - Crash during Step B (between the `rename` syscall start and return) → POSIX `rename` is atomic; either the prior `.import-state.json` is intact, or the new one is in place. No torn state.
20
20
  - Crash before Step A → no state mutation occurred; prior state file is unchanged.
21
21
 
@@ -82,4 +82,4 @@ On every skill invocation, BEFORE Phase 1 starts:
82
82
  5. If `phase === "P2-done"` → skip Phase 1 + Phase 2; resume from Phase 3 Step 3.1; iterate Phase 2 outputs skipping any pending_path already in `p3_dedup_completed[]`.
83
83
  6. After every successful sub-step (one commit processed, one doc processed, one dedup pair resolved), write the updated state file via the 2-step `.tmp` + `mv` pattern. Failures append to `errors[]` and proceed (or halt with prompt if cumulative errors `>5`).
84
84
 
85
- The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, network blip on MCP) MUST NOT propose duplicates of already-proposed entries and MUST NOT redo already-completed dedup decisions.
85
+ The contract: re-invoking archive source mode after ANY interruption (Ctrl-C, crash, network blip on MCP) MUST NOT propose duplicates of already-proposed entries and MUST NOT redo already-completed dedup decisions.
@@ -2,14 +2,14 @@
2
2
 
3
3
  > **Loaded on demand.** SKILL.md hot path retains the Phase 3 purpose statement + 4-step outline + completion sentinel. This file holds the full Step 3.1/3.2/3.3/3.4 MCP call shapes, semantic compare 5-way classification, and the underseed sentinel rationale.
4
4
 
5
- For each pending entry created in Phase 2 (read from `p2_processed_commits[].pending_path` and `p2_processed_docs[].pending_paths`), check if it duplicates / contradicts / is subsumed by an existing canonical entry. **Semantic comparison is the LLM's job — `fab_review` does not compare meaning.**
5
+ For each pending entry created in Phase 2 (read from `p2_processed_commits[].pending_path` and `p2_processed_docs[].pending_paths`), check if it duplicates / contradicts / is subsumed by an existing canonical entry. **Semantic comparison is the LLM's job — `fab_pending` does not compare meaning.**
6
6
 
7
7
  ## Step 3.1 — Search Canonical of Same Type
8
8
 
9
9
  For each just-proposed pending entry (read its frontmatter via the `Read` tool to get type + slug + title):
10
10
 
11
11
  ```ts
12
- mcp__fabric__fab_review({
12
+ mcp__fabric__fab_pending({
13
13
  action: "search",
14
14
  query: "<title or summary keywords from the pending entry>",
15
15
  filters: { type: "<same type as pending>" }
@@ -25,7 +25,7 @@ For each `(pending, canonical)` pair the LLM judges:
25
25
  - **Duplicate** — same essential claim. LLM 主观判断:标题与摘要表达同一核心结论,新 pending 未提供新证据。具体阈值不可量化。Action: **reject** the new pending.
26
26
  - **Subsumption** (pending narrower) — canonical fully covers the pending plus more. Action: **reject** the new pending (canonical already serves).
27
27
  - **Subsumption-with-novelty** (pending adds evidence) — canonical covers the claim but the new pending brings new evidence (commit sha, file paths). Action: **modify** the canonical to merge in the new evidence; **reject** the new pending citing the modified canonical.
28
- - **Contradiction** — opposing claims about the same scope. Action: leave pending; flag for user via roll-up. The user must decide via `fabric-review` later — `fabric-import` does NOT auto-resolve contradictions.
28
+ - **Contradiction** — opposing claims about the same scope. Action: leave pending; flag for user via roll-up. The user must decide via `fabric-review` later — `archive source mode` does NOT auto-resolve contradictions.
29
29
  - **Genuinely new** — no canonical match. Action: leave pending in place (will surface in next `fabric-review` run for normal approval flow).
30
30
 
31
31
  ## Step 3.3 — Issue Dedup MCP Calls
@@ -68,7 +68,7 @@ Append to `.fabric/.import-state.json` after EACH successful MCP call:
68
68
  After all Phase 2 outputs are dedup-reviewed:
69
69
 
70
70
  - Update `.fabric/.import-state.json`: `phase = "complete"`, `last_checkpoint_at = <ISO8601 now>`, `final_summary = {proposed: N, kept: K, rejected_dup: R, merged: M, contradictions_flagged: C}`.
71
- - Render the final roll-up to the user (see Output Contract — see `ref/output-contract.md`).
71
+ - Render the final roll-up to the user (see Output Contract — see `ref/source-output-contract.md`).
72
72
 
73
73
  > Setting `phase = "complete"` in `.fabric/.import-state.json` is enough to silence the SessionStart underseed self-check banner (`shouldRecommendImport()` returns false for any non-`absent` state). 无需额外清理 sentinel 文件 — 该机制已在 rc.8 下线。
74
74