@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.
- package/README.md +2 -2
- package/dist/audit-PURSJJFH.js +734 -0
- package/dist/{chunk-QPAW6IYT.js → chunk-7V4XMLQ2.js} +3 -3
- package/dist/{chunk-7ZDXBOOU.js → chunk-ACSMNX3V.js} +44 -128
- package/dist/{chunk-PTGQAZEW.js → chunk-GGDVZCD6.js} +2 -4
- package/dist/{chunk-EOT63RDH.js → chunk-I5F5BHWI.js} +9 -0
- package/dist/chunk-PP7QVRXH.js +565 -0
- package/dist/chunk-SL77FXX7.js +54 -0
- package/dist/{chunk-3D7B2UAZ.js → chunk-VQKXTMWH.js} +44 -4
- package/dist/doctor-S6KPGS35.js +27 -0
- package/dist/index.js +90 -80
- package/dist/{info-7FKBTMVO.js → info-NJEY26H6.js} +91 -46
- package/dist/{context-UJCGYOT6.js → inspect-5YZMJPFM.js} +10 -10
- package/dist/{install-v2-3KJX3YRO.js → install-v2-KGIDII4H.js} +163 -364
- package/dist/{store-HOCORVL3.js → store-GF4SFBMJ.js} +155 -57
- package/dist/{sync-DT5UJMMR.js → sync-3XCIRDPK.js} +3 -4
- package/dist/{uninstall-IFN2KYBK.js → uninstall-BG4ML4FC.js} +39 -10
- package/package.json +3 -7
- package/templates/hooks/cite-policy-evict.cjs +1 -1
- package/templates/hooks/configs/claude-code.json +1 -5
- package/templates/hooks/configs/codex-hooks.json +1 -5
- package/templates/hooks/fabric-hint.cjs +67 -41
- package/templates/hooks/knowledge-hint-broad.cjs +82 -24
- package/templates/hooks/knowledge-hint-narrow.cjs +3 -3
- package/templates/hooks/knowledge-pretooluse.cjs +111 -0
- package/templates/hooks/lib/banner-i18n.cjs +12 -11
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +1 -1
- package/templates/hooks/lib/event-writer.cjs +79 -0
- package/templates/hooks/lib/nudge-policy.cjs +11 -0
- package/templates/hooks/lib/theme.cjs +62 -0
- package/templates/hooks/post-tooluse-mutation.cjs +28 -39
- package/templates/skills/fabric-archive/SKILL.md +29 -12
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/i18n-policy.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +5 -5
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +2 -2
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +6 -5
- package/templates/skills/{fabric-import/ref/checkpoint-state.md → fabric-archive/ref/source-checkpoint.md} +3 -3
- package/templates/skills/{fabric-import/ref/phase-3-dedup.md → fabric-archive/ref/source-dedup.md} +4 -4
- package/templates/skills/{fabric-import/ref/phase-2-mining.md → fabric-archive/ref/source-mining.md} +20 -20
- package/templates/skills/{fabric-import/ref/output-contract.md → fabric-archive/ref/source-output-contract.md} +3 -3
- package/templates/skills/{fabric-import/ref/state-recovery.md → fabric-archive/ref/source-state-recovery.md} +2 -2
- package/templates/skills/{fabric-import/ref/worked-examples.md → fabric-archive/ref/source-worked-examples.md} +10 -10
- package/templates/skills/fabric-archive/ref/worked-examples.md +3 -3
- package/templates/skills/fabric-review/SKILL.md +19 -15
- package/templates/skills/fabric-review/ref/cite-contract.md +2 -2
- package/templates/skills/fabric-review/ref/modify-flow.md +13 -1
- package/templates/skills/fabric-review/ref/per-mode-flows.md +5 -5
- package/templates/skills/fabric-review/ref/relate-mode.md +33 -0
- package/templates/skills/fabric-review/ref/retire-mode.md +47 -0
- package/templates/skills/fabric-review/ref/semantic-check.md +1 -1
- package/templates/skills/fabric-review/ref/worked-examples.md +5 -5
- package/templates/skills/fabric-store/SKILL.md +12 -27
- package/templates/skills/fabric-sync/SKILL.md +16 -35
- package/templates/skills/lib/shared-policy.md +6 -4
- package/dist/chunk-27HK6H5Y.js +0 -69
- package/dist/chunk-E7HJUU34.js +0 -1096
- package/dist/chunk-NLNH64A3.js +0 -43
- package/dist/chunk-QFIVFZRH.js +0 -13
- package/dist/doctor-MDTZWKBK.js +0 -24
- package/dist/metrics-HMFH4YHK.js +0 -135
- package/dist/scope-explain-HLJZ2M33.js +0 -48
- package/dist/status-4R3TM4FJ.js +0 -37
- package/dist/whoami-ITGEFWH4.js +0 -49
- package/templates/skills/fabric/SKILL.md +0 -100
- package/templates/skills/fabric-audit/SKILL.md +0 -63
- package/templates/skills/fabric-connect/SKILL.md +0 -48
- package/templates/skills/fabric-import/SKILL.md +0 -151
- 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.
|
|
45
|
-
//
|
|
46
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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:
|
|
4
|
-
allowed-tools: Read, Glob, Grep, Bash,
|
|
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 (`
|
|
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
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
173
|
-
- NEVER infer or glob a project-local pending directory — persist through `
|
|
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
|
|
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`, `
|
|
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
|
|
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
|
-
| `
|
|
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 (`
|
|
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` |
|
|
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
|
-
`
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 `
|
|
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
|
|
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
|
|
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.
|
package/templates/skills/{fabric-import/ref/phase-3-dedup.md → fabric-archive/ref/source-dedup.md}
RENAMED
|
@@ -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 — `
|
|
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
|
-
|
|
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 — `
|
|
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
|
|