@fenglimg/fabric-cli 1.8.0-rc.3 → 2.0.0
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 +6 -6
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/chunk-74SZWYPH.js +658 -0
- package/dist/chunk-EYIDD2YS.js +1000 -0
- package/dist/{chunk-QPCRBQ5Y.js → chunk-OBQU6NHO.js} +1 -52
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-T7JWODKG.js +282 -0
- package/dist/hooks-Y74Y5LQS.js +12 -0
- package/dist/index.js +7 -5
- package/dist/{init-7EYGUJNJ.js → init-BIRSIOXO.js} +312 -1022
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/scan-LMK3UCWL.js +22 -0
- package/dist/{serve-466QXQ5Q.js → serve-H554BHLG.js} +8 -4
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +55 -17
- package/templates/bootstrap/CLAUDE.md +1 -1
- package/templates/bootstrap/codex-AGENTS-header.md +1 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
- package/templates/hooks/configs/README.md +73 -0
- package/templates/hooks/configs/claude-code.json +37 -0
- package/templates/hooks/configs/codex-hooks.json +20 -0
- package/templates/hooks/configs/cursor-hooks.json +20 -0
- package/templates/hooks/fabric-hint.cjs +1307 -0
- package/templates/hooks/knowledge-hint-broad.cjs +464 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
- package/templates/hooks/lib/session-digest-writer.cjs +172 -0
- package/templates/skills/fabric-archive/SKILL.md +486 -0
- package/templates/skills/fabric-import/SKILL.md +588 -0
- package/templates/skills/fabric-review/SKILL.md +382 -0
- package/dist/chunk-NMMUETVK.js +0 -216
- package/dist/doctor-F52XWWZC.js +0 -98
- package/dist/scan-NNBNGIZG.js +0 -12
- package/templates/agents-md/variants/cocos.md +0 -20
- package/templates/agents-md/variants/next.md +0 -20
- package/templates/agents-md/variants/vite.md +0 -20
- package/templates/bootstrap/GEMINI.md +0 -8
- package/templates/bootstrap/roo-fabric.md +0 -5
- package/templates/bootstrap/windsurf-fabric.md +0 -5
- package/templates/claude-hooks/fabric-init-reminder.cjs +0 -18
- package/templates/claude-skills/fabric-init/SKILL.md +0 -163
- package/templates/codex-hooks/fabric-session-start.cjs +0 -19
- package/templates/codex-hooks/fabric-stop-reminder.cjs +0 -18
- package/templates/codex-skills/fabric-init/SKILL.md +0 -162
- package/templates/husky/pre-commit +0 -9
- package/templates/skill-source/fabric-init/SOURCE.md +0 -157
- package/templates/skill-source/fabric-init/clients.json +0 -17
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.0.0-rc.7 T5: session-digest writer.
|
|
3
|
+
*
|
|
4
|
+
* Persist a per-session ~5KB digest under
|
|
5
|
+
* `<projectRoot>/.fabric/.cache/session-digests/<session_id>.md` so the
|
|
6
|
+
* fabric-archive Skill (Phase 0.0) can stitch together cross-session
|
|
7
|
+
* context when it runs after Signal A fires.
|
|
8
|
+
*
|
|
9
|
+
* Contract (non-blocking, best-effort):
|
|
10
|
+
* - writeDigest({ projectRoot, session_id, user_messages, edit_paths, title })
|
|
11
|
+
* - returns { written: boolean, path: string | null } — never throws
|
|
12
|
+
* - silently no-ops on ENOENT, EPERM, malformed inputs
|
|
13
|
+
* - caps file at SIZE_CAP_BYTES (5120 = 5KB) by truncating user_messages
|
|
14
|
+
* bullets from the tail (oldest preserved)
|
|
15
|
+
* - atomic write: temp file + rename
|
|
16
|
+
*
|
|
17
|
+
* Digest shape (markdown):
|
|
18
|
+
*
|
|
19
|
+
* # <title or fallback>
|
|
20
|
+
*
|
|
21
|
+
* _Session: <session_id> · written: <iso>_
|
|
22
|
+
*
|
|
23
|
+
* ## User messages (top 10)
|
|
24
|
+
*
|
|
25
|
+
* - <msg 1, trimmed to MAX_MSG_CHARS>
|
|
26
|
+
* - <msg 2, ...>
|
|
27
|
+
*
|
|
28
|
+
* ## Edits
|
|
29
|
+
*
|
|
30
|
+
* - <path 1>
|
|
31
|
+
* - <path 2>
|
|
32
|
+
*/
|
|
33
|
+
"use strict";
|
|
34
|
+
|
|
35
|
+
const { existsSync, mkdirSync, renameSync, writeFileSync, unlinkSync } = require("node:fs");
|
|
36
|
+
const { dirname, join } = require("node:path");
|
|
37
|
+
|
|
38
|
+
const FABRIC_DIR = ".fabric";
|
|
39
|
+
const CACHE_REL = join(FABRIC_DIR, ".cache", "session-digests");
|
|
40
|
+
const SIZE_CAP_BYTES = 5120; // ~5KB
|
|
41
|
+
const MAX_USER_MESSAGES = 10;
|
|
42
|
+
const MAX_MSG_CHARS = 500;
|
|
43
|
+
const MAX_EDIT_PATHS = 60;
|
|
44
|
+
|
|
45
|
+
function sanitizeSessionId(id) {
|
|
46
|
+
if (typeof id !== "string") return "";
|
|
47
|
+
// Allow alphanumeric, dash, underscore. Strip everything else to avoid
|
|
48
|
+
// path traversal / weird filename characters. Hard-cap 64 chars.
|
|
49
|
+
const cleaned = id.replace(/[^A-Za-z0-9_\-]/g, "_").slice(0, 64);
|
|
50
|
+
return cleaned;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function truncateString(s, max) {
|
|
54
|
+
if (typeof s !== "string") return "";
|
|
55
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
56
|
+
if (t.length <= max) return t;
|
|
57
|
+
return `${t.slice(0, max - 1)}…`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderDigest({ session_id, title, user_messages, edit_paths }) {
|
|
61
|
+
const safeTitle =
|
|
62
|
+
typeof title === "string" && title.trim().length > 0
|
|
63
|
+
? truncateString(title, 120)
|
|
64
|
+
: "(untitled session)";
|
|
65
|
+
const safeSessionId = sanitizeSessionId(session_id) || "(unknown)";
|
|
66
|
+
const userMsgs = Array.isArray(user_messages) ? user_messages : [];
|
|
67
|
+
const edits = Array.isArray(edit_paths) ? edit_paths : [];
|
|
68
|
+
|
|
69
|
+
const messageBullets = userMsgs
|
|
70
|
+
.slice(0, MAX_USER_MESSAGES)
|
|
71
|
+
.map((m) => `- ${truncateString(String(m ?? ""), MAX_MSG_CHARS)}`)
|
|
72
|
+
.filter((line) => line.length > 2);
|
|
73
|
+
|
|
74
|
+
const editBullets = edits
|
|
75
|
+
.slice(0, MAX_EDIT_PATHS)
|
|
76
|
+
.map((p) => `- ${truncateString(String(p ?? ""), 200)}`)
|
|
77
|
+
.filter((line) => line.length > 2);
|
|
78
|
+
|
|
79
|
+
const messagesSection =
|
|
80
|
+
messageBullets.length > 0
|
|
81
|
+
? messageBullets.join("\n")
|
|
82
|
+
: "_(no user messages captured)_";
|
|
83
|
+
const editsSection =
|
|
84
|
+
editBullets.length > 0 ? editBullets.join("\n") : "_(no edits captured)_";
|
|
85
|
+
|
|
86
|
+
return [
|
|
87
|
+
`# ${safeTitle}`,
|
|
88
|
+
"",
|
|
89
|
+
`_Session: ${safeSessionId} · written: ${new Date().toISOString()}_`,
|
|
90
|
+
"",
|
|
91
|
+
"## User messages (top 10)",
|
|
92
|
+
"",
|
|
93
|
+
messagesSection,
|
|
94
|
+
"",
|
|
95
|
+
"## Edits",
|
|
96
|
+
"",
|
|
97
|
+
editsSection,
|
|
98
|
+
"",
|
|
99
|
+
].join("\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Soft-cap the rendered digest to SIZE_CAP_BYTES. If the rendered text exceeds
|
|
104
|
+
* the cap we drop user message bullets from the tail (keeping the earliest /
|
|
105
|
+
* oldest entries first since those usually frame the session goal) until the
|
|
106
|
+
* size fits OR we run out of trimmable content.
|
|
107
|
+
*/
|
|
108
|
+
function capSize(text, original) {
|
|
109
|
+
if (Buffer.byteLength(text, "utf8") <= SIZE_CAP_BYTES) return text;
|
|
110
|
+
let userMessages = Array.isArray(original.user_messages)
|
|
111
|
+
? [...original.user_messages]
|
|
112
|
+
: [];
|
|
113
|
+
let attempt = text;
|
|
114
|
+
while (
|
|
115
|
+
Buffer.byteLength(attempt, "utf8") > SIZE_CAP_BYTES &&
|
|
116
|
+
userMessages.length > 0
|
|
117
|
+
) {
|
|
118
|
+
userMessages.pop();
|
|
119
|
+
attempt = renderDigest({ ...original, user_messages: userMessages });
|
|
120
|
+
}
|
|
121
|
+
if (Buffer.byteLength(attempt, "utf8") <= SIZE_CAP_BYTES) return attempt;
|
|
122
|
+
// Last resort: hard-truncate.
|
|
123
|
+
const buf = Buffer.from(attempt, "utf8").slice(0, SIZE_CAP_BYTES);
|
|
124
|
+
return buf.toString("utf8");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function writeDigest(opts) {
|
|
128
|
+
if (opts === null || typeof opts !== "object") {
|
|
129
|
+
return { written: false, path: null };
|
|
130
|
+
}
|
|
131
|
+
const projectRoot = typeof opts.projectRoot === "string" ? opts.projectRoot : "";
|
|
132
|
+
const safeSessionId = sanitizeSessionId(opts.session_id);
|
|
133
|
+
if (projectRoot.length === 0 || safeSessionId.length === 0) {
|
|
134
|
+
return { written: false, path: null };
|
|
135
|
+
}
|
|
136
|
+
const cacheDir = join(projectRoot, CACHE_REL);
|
|
137
|
+
const target = join(cacheDir, `${safeSessionId}.md`);
|
|
138
|
+
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const rendered = renderDigest(opts);
|
|
142
|
+
const capped = capSize(rendered, opts);
|
|
143
|
+
if (!existsSync(cacheDir)) {
|
|
144
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
145
|
+
}
|
|
146
|
+
writeFileSync(tmp, capped, "utf8");
|
|
147
|
+
renameSync(tmp, target);
|
|
148
|
+
return { written: true, path: target };
|
|
149
|
+
} catch {
|
|
150
|
+
// Best-effort. Never let digest write failure propagate to the Stop hook.
|
|
151
|
+
try {
|
|
152
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
153
|
+
} catch {
|
|
154
|
+
// ignore
|
|
155
|
+
}
|
|
156
|
+
return { written: false, path: null };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
writeDigest,
|
|
162
|
+
// exposed for tests
|
|
163
|
+
renderDigest,
|
|
164
|
+
capSize,
|
|
165
|
+
CONSTANTS: {
|
|
166
|
+
CACHE_REL,
|
|
167
|
+
SIZE_CAP_BYTES,
|
|
168
|
+
MAX_USER_MESSAGES,
|
|
169
|
+
MAX_MSG_CHARS,
|
|
170
|
+
MAX_EDIT_PATHS,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fabric-archive
|
|
3
|
+
description: Use this skill when the Stop-hook signals an archive opportunity (events.jsonl shows ≥5 plan_context entries since the last knowledge_proposed event, or ≥24h elapsed since the last archive), OR when the user explicitly invokes archival. The skill classifies recent session candidates into one of five knowledge types (model/decision/guideline/pitfall/process), assigns a layer (team/personal) via the verbatim heuristic, proposes a slug, presents one batch review, and persists confirmed entries through the fab_extract_knowledge MCP tool to .fabric/knowledge/pending/.
|
|
4
|
+
allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_extract_knowledge
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> **Surface**: This is a Skill (AI-driven, LLM judgment over session digests). See [`docs/surfaces.md`](https://github.com/fenglimg/fabric/blob/main/docs/surfaces.md) for the CLI / Skill / MCP boundary.
|
|
8
|
+
|
|
9
|
+
## Precondition
|
|
10
|
+
|
|
11
|
+
This skill is invoked when one of the following holds:
|
|
12
|
+
|
|
13
|
+
- The Stop-hook printed a stdout JSON pointer of shape `{"decision":"block","reason":"..."}` mentioning fabric-archive
|
|
14
|
+
- The user typed an explicit archive request (e.g. "archive what we just did", "fabric archive")
|
|
15
|
+
- A task wrap-up moment where the agent itself判定 a worth-keeping insight has surfaced
|
|
16
|
+
|
|
17
|
+
If none of the above hold, stop the skill immediately and tell the user `没有触发归档信号;如需手动归档请显式调用 fabric-archive`.
|
|
18
|
+
|
|
19
|
+
This skill is `Check-not-Ask`, not a preference interview:
|
|
20
|
+
|
|
21
|
+
- Phase 0 proactively gathers candidate evidence from the session
|
|
22
|
+
- Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
|
|
23
|
+
- Phase 1 classifies / layers / slugs each candidate and presents one batch review for user correction
|
|
24
|
+
- Phase 1.5 assigns `scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
|
|
25
|
+
- Phase 2 calls `fab_extract_knowledge` once per confirmed candidate
|
|
26
|
+
|
|
27
|
+
## 执行流程 (5 Phase / 1 User Review Round)
|
|
28
|
+
|
|
29
|
+
### Phase 0.0 — Collect Cross-Session Digests (v2.0.0-rc.7 T5)
|
|
30
|
+
|
|
31
|
+
Before any single-session collection or viability gating, stitch together
|
|
32
|
+
context from every session that has accumulated since the last
|
|
33
|
+
`knowledge_proposed` event. The rc.7 Stop hook writes a per-session digest to
|
|
34
|
+
`.fabric/.cache/session-digests/<session_id>.md` (≤5KB, contains top 10 user
|
|
35
|
+
messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
|
|
36
|
+
|
|
37
|
+
1. **Read events.jsonl tail.** Use `Bash` with
|
|
38
|
+
`tail -n 200 .fabric/events.jsonl` (tolerate ENOENT — empty ledger is a
|
|
39
|
+
normal first-run state).
|
|
40
|
+
2. **Find the anchor.** Walk the tail backwards to locate the most recent
|
|
41
|
+
`knowledge_proposed` event (`event_type === "knowledge_proposed"`). The
|
|
42
|
+
anchor's `ts` becomes the lower bound for digest selection. If NO anchor
|
|
43
|
+
exists, treat all digests in the cache as in-scope.
|
|
44
|
+
3. **Collect session_ids since anchor.** Scan the tail forward from the
|
|
45
|
+
anchor and collect every distinct `session_id` field that appears on any
|
|
46
|
+
event newer than the anchor. Distinct ordering preserved.
|
|
47
|
+
4. **Load digests.** For each collected `session_id`, read
|
|
48
|
+
`.fabric/.cache/session-digests/<session_id>.md`. Missing digest files
|
|
49
|
+
degrade silently (the digest write was best-effort, so a Stop hook crash
|
|
50
|
+
can produce a session_id without a digest). Cap the loaded digest set at
|
|
51
|
+
10 most-recent sessions to bound LLM context (~50KB worst-case).
|
|
52
|
+
5. **Build cross-session context.** Concatenate the loaded digests into a
|
|
53
|
+
single `### Cross-session digest` block to carry into Phase 0.5 + Phase 1.
|
|
54
|
+
Use this block to:
|
|
55
|
+
- Detect session-spanning patterns (e.g. a discussion that started in
|
|
56
|
+
session A and continued in session B).
|
|
57
|
+
- Populate the `source_sessions` array on every fab_extract_knowledge
|
|
58
|
+
call — the array form (T5) replaces the legacy `source_session` string.
|
|
59
|
+
- Inform the `session_context` blob written to each pending entry's body
|
|
60
|
+
(3-5 lines summarizing goal + key turning point, per T6).
|
|
61
|
+
|
|
62
|
+
Graceful degradation: if `.fabric/.cache/session-digests/` is missing
|
|
63
|
+
entirely, this phase reports an empty context and Phase 0 falls back to the
|
|
64
|
+
single-session behaviour. Tests that synthesize events.jsonl without
|
|
65
|
+
populating the digest cache continue to work.
|
|
66
|
+
|
|
67
|
+
### Phase 0 — Collect Candidates
|
|
68
|
+
|
|
69
|
+
Gather raw evidence from the recent session before any classification:
|
|
70
|
+
|
|
71
|
+
1. Read the tail of `.fabric/events.jsonl` since the last `knowledge_proposed` event.
|
|
72
|
+
- Use `Bash` with `tail -n 200 .fabric/events.jsonl` if the file is large.
|
|
73
|
+
- Tolerate ENOENT — empty ledger is a normal first-run state.
|
|
74
|
+
2. Enumerate `recent_paths`: workspace files touched by Read/Edit/Write in the current session. Cap at 20 most-recent paths.
|
|
75
|
+
3. Distill `user_messages_summary`: a compact (≤500 char) prose summary of what the user asked for and what was decided. NOT a verbatim transcript.
|
|
76
|
+
4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
|
|
77
|
+
|
|
78
|
+
Hard budget: 8 candidates max per Phase 1 batch. If more surface, keep the 8 with strongest worth-archiving signals (see Phase 1 type definitions) and drop the rest.
|
|
79
|
+
|
|
80
|
+
### Phase 0.5 — Viability Gate (Anti-Archive Guard)
|
|
81
|
+
|
|
82
|
+
Before producing any candidate output, run a coarse viability check on the session as a whole. The goal is to short-circuit obvious no-archive sessions (routine execution, typo fixes, narrow renames) so that Phase 1 batch review is never spent on noise.
|
|
83
|
+
|
|
84
|
+
#### Archive signals (≥ 1 hit ⇒ gate PASSES, proceed to Phase 1)
|
|
85
|
+
|
|
86
|
+
Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 0:
|
|
87
|
+
|
|
88
|
+
1. Explicit normative language: user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`.
|
|
89
|
+
2. Wrong-turn-and-revert: a path was edited, then reverted (or partially undone) after diagnosis — indicates a pitfall worth recording.
|
|
90
|
+
3. Long diagnostic loop: an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution.
|
|
91
|
+
4. New dependency adoption: a new package / library / external tool was introduced (e.g. `package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
|
|
92
|
+
5. New pattern emergence: a reusable abstraction or naming convention was named ("the X phase", "the Y pattern", "let's call this Z").
|
|
93
|
+
6. Decision confirmation: ≥ 2 alternatives were weighed AND a rationale was given before settling.
|
|
94
|
+
7. Explicit dismissal-with-reason: user rejected an approach AND stated why (the why is the archivable knowledge, not the dismissal itself).
|
|
95
|
+
8. Process formalization: a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
|
|
96
|
+
|
|
97
|
+
#### Anti-archive signals (forces gate to FAIL unless an archive signal also fires)
|
|
98
|
+
|
|
99
|
+
1. Typo-only edits: the entire session is whitespace / spelling / formatting changes.
|
|
100
|
+
2. Pure refactor: rename / move / extract with no behavior change AND no naming convention being established.
|
|
101
|
+
3. Narrow rename request: user asked to rename one symbol / file with no rationale.
|
|
102
|
+
4. Duplicate of existing canonical: the observation is already covered by an existing entry under `.fabric/knowledge/<type>/` (do a quick Glob before deciding).
|
|
103
|
+
|
|
104
|
+
#### Gate decision
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
archive_signals_hit = count of archive signals fired
|
|
108
|
+
anti_signals_hit = count of anti-archive signals fired
|
|
109
|
+
user_explicit_invoke = user typed "archive what we just did" / "fabric archive" / similar
|
|
110
|
+
|
|
111
|
+
IF user_explicit_invoke:
|
|
112
|
+
gate = PASS # explicit invocation bypasses all gates
|
|
113
|
+
ELIF archive_signals_hit == 0:
|
|
114
|
+
gate = FAIL (reason="no_signal")
|
|
115
|
+
ELIF anti_signals_hit > 0 AND archive_signals_hit == 0:
|
|
116
|
+
gate = FAIL (reason="anti_signal_dominates")
|
|
117
|
+
ELSE:
|
|
118
|
+
gate = PASS
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### On gate FAIL
|
|
122
|
+
|
|
123
|
+
Stop the skill with the exact user-facing message:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Optionally append a one-line event to `.fabric/events.jsonl` of shape `{"ts":"...","kind":"knowledge_archive_aborted","reason":"<reason>","session":"<id>"}` if the events ledger is writable; otherwise just log to stderr. Do NOT proceed to Phase 1, do NOT call any MCP tool.
|
|
130
|
+
|
|
131
|
+
#### On gate PASS
|
|
132
|
+
|
|
133
|
+
Proceed to Phase 1 with the candidates carried over from Phase 0.
|
|
134
|
+
|
|
135
|
+
### Phase 1 — Classify, Layer, Slug, Review
|
|
136
|
+
|
|
137
|
+
For each candidate, the skill proposes:
|
|
138
|
+
|
|
139
|
+
- **type** ∈ {model, decision, guideline, pitfall, process}
|
|
140
|
+
- **layer** ∈ {team, personal} via the verbatim heuristic below
|
|
141
|
+
- **slug** per the 5-rule naming guideline below
|
|
142
|
+
- **summary** (1-2 sentences, will become the entry body's lead paragraph)
|
|
143
|
+
|
|
144
|
+
#### Five Knowledge Types (singular noun = type concept)
|
|
145
|
+
|
|
146
|
+
- **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).
|
|
147
|
+
- **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).
|
|
148
|
+
- **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).
|
|
149
|
+
- **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).
|
|
150
|
+
- **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).
|
|
151
|
+
|
|
152
|
+
#### Layer Classification Heuristic (强 team 信号 / 强 personal 信号 / 默认 team)
|
|
153
|
+
|
|
154
|
+
> - **强 team**: 引用本项目代码、团队共识用语("we decided")、fabric-import 路径产物、业务领域、绑定本项目代码的 pitfall
|
|
155
|
+
> - **强 personal**: 第一人称偏好、跨项目通用、工具/编辑器偏好、个人工作流
|
|
156
|
+
> - **默认 team**: 安全偏置——错标 team 在 PR review 中会被发现,错标 personal 静默丢失
|
|
157
|
+
|
|
158
|
+
Resolution order: check 强 team signals first; only assign personal if 强 personal signals dominate AND no 强 team signal applies; otherwise default to team.
|
|
159
|
+
|
|
160
|
+
#### Slug Naming Guideline (5 Rules)
|
|
161
|
+
|
|
162
|
+
1. kebab-case (lowercase letters, digits, hyphens only — no underscores, no CamelCase)
|
|
163
|
+
2. 2-5 words separated by hyphens
|
|
164
|
+
3. 20-40 characters total length
|
|
165
|
+
4. semantic core only (drop articles "the/a", drop generic suffixes "stuff/thing")
|
|
166
|
+
5. unique within its (type, layer) bucket — if collision, the LLM must add a discriminating word, NOT a counter
|
|
167
|
+
|
|
168
|
+
Examples passing: `wave-1-parallel-task-dag` (4 words, 24 chars), `deepmerge-array-replace-trap` (4 words, 28 chars). Examples failing: `the_solution` (underscore + article), `fix` (1 word, too short), `how-we-decided-to-handle-the-merge-conflict-in-stop-hook-config` (overlong).
|
|
169
|
+
|
|
170
|
+
#### Decision Tree (是否值得归档)
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Recent session contains an observation worth keeping?
|
|
174
|
+
├─ NO → skip (do nothing, no MCP call)
|
|
175
|
+
└─ YES → does it fit one of {model, decision, guideline, pitfall, process}?
|
|
176
|
+
├─ NO → skip (not classifiable = not yet ripe)
|
|
177
|
+
└─ YES → assign type
|
|
178
|
+
↓
|
|
179
|
+
Apply layer heuristic
|
|
180
|
+
↓
|
|
181
|
+
Propose slug per 5 rules
|
|
182
|
+
↓
|
|
183
|
+
Present in batch review
|
|
184
|
+
↓
|
|
185
|
+
User confirms / corrects / rejects
|
|
186
|
+
↓
|
|
187
|
+
Phase 2: call fab_extract_knowledge once per confirmed candidate
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Batch Review Template
|
|
191
|
+
|
|
192
|
+
Present all candidates in a single screen using this exact structure:
|
|
193
|
+
|
|
194
|
+
```md
|
|
195
|
+
# Archive Review — N candidates
|
|
196
|
+
|
|
197
|
+
## C1 [type=decision] [layer=team] [scope=narrow] slug=wave-1-parallel-task-dag
|
|
198
|
+
Summary: <1-2 sentences capturing the observation>
|
|
199
|
+
Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
|
|
200
|
+
Scope reasoning: <why narrow or broad — see Phase 1.5>
|
|
201
|
+
relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
|
|
202
|
+
Confirm? (Y to accept, edit type/layer/slug/scope/relevance_paths inline, N to skip)
|
|
203
|
+
|
|
204
|
+
## C2 [type=pitfall] [layer=team] [scope=broad] slug=deepmerge-array-replace-trap
|
|
205
|
+
Summary: ...
|
|
206
|
+
Layer reasoning: ...
|
|
207
|
+
Scope reasoning: ...
|
|
208
|
+
relevance_paths: []
|
|
209
|
+
Confirm? ...
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The user MAY edit type/layer/slug/scope/relevance_paths inline before confirming. The user MAY skip individual candidates without rejecting the whole batch. Inline-editing `[scope=...]` triggers a re-derivation of `relevance_paths` per the Phase 1.5 rules (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).
|
|
213
|
+
|
|
214
|
+
### Phase 1.5 — Scope Decision + relevance_paths Derivation
|
|
215
|
+
|
|
216
|
+
After classify/layer/slug but BEFORE batch review output, assign a `scope` to each candidate and derive its `relevance_paths` array. These two fields drive rc.6 hint injection: narrow knowledge is gated by working in matching paths, broad knowledge is project-wide.
|
|
217
|
+
|
|
218
|
+
#### Scope decision (narrow vs broad)
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
scope =
|
|
222
|
+
narrow IF the candidate is tied to a specific module / file / subsystem
|
|
223
|
+
AND there is explicit single-module evidence in edit_paths
|
|
224
|
+
(i.e. all worth-keeping edits in this session concentrated in one
|
|
225
|
+
module tree, OR the candidate explicitly references that module)
|
|
226
|
+
|
|
227
|
+
broad IF the candidate is cross-cutting / methodological / general
|
|
228
|
+
(applies regardless of which path the agent is working in)
|
|
229
|
+
|
|
230
|
+
broad (default, on uncertainty — safe偏置 per Q-1 in handoff)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Special case — Personal layer ALWAYS resolves to `scope=broad` with `relevance_paths=[]`. Rationale: personal knowledge crosses projects; paths from one project do not generalize. If `layer=personal` and a narrow scope was tentatively chosen, auto-flip to `broad` and clear `relevance_paths`.
|
|
234
|
+
|
|
235
|
+
##### Examples
|
|
236
|
+
|
|
237
|
+
- `decision: single-cjs-hook-script` → `narrow` (tied to `templates/claude-hooks/` + `packages/cli/src/commands/hooks.ts`)
|
|
238
|
+
- `pitfall: deepmerge-array-replace-trap` → `broad` (cross-cutting JSON merge gotcha, applies anywhere deepMerge is used)
|
|
239
|
+
- `guideline: slug-naming-rules` → `broad` (methodology, no specific module)
|
|
240
|
+
- `model: wave-1-parallel-task-dag` → `narrow` (tied to `packages/cli/src/commands/plan.ts`)
|
|
241
|
+
- `guideline: indent-style-by-language` (personal layer) → `broad + []` (personal forces broad)
|
|
242
|
+
|
|
243
|
+
#### relevance_paths derivation algorithm (rc.5 single-signal: edit_paths only)
|
|
244
|
+
|
|
245
|
+
rc.5 uses ONLY the `edit_paths` signal — list of paths modified by `Edit` / `Write` / `MultiEdit` tool calls in the current session. Multi-signal (read_paths + body regex + symbols) is explicitly deferred to rc.7 per design decision.
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
Step 1: COLLECT
|
|
249
|
+
edit_paths = []
|
|
250
|
+
Scan session transcript for tool_use entries where
|
|
251
|
+
tool_use.name ∈ {Edit, Write, MultiEdit}
|
|
252
|
+
Extract the file_path argument from each, push into edit_paths.
|
|
253
|
+
|
|
254
|
+
Step 2: DEDUPE
|
|
255
|
+
edit_paths = unique(edit_paths)
|
|
256
|
+
|
|
257
|
+
Step 3: BLACKLIST FILTER
|
|
258
|
+
Drop paths matching any of:
|
|
259
|
+
- **/*.<ext> where <ext> is a single trivial extension on a single file
|
|
260
|
+
(i.e. avoid emitting bare **/*.md as a relevance pattern)
|
|
261
|
+
- Repo-root single files: README.md, package.json, package-lock.json,
|
|
262
|
+
pnpm-lock.yaml, tsconfig.json, .gitignore, LICENSE, CHANGELOG.md
|
|
263
|
+
- Read-only paths (never modified) — those go to ## Evidence, not relevance_paths
|
|
264
|
+
|
|
265
|
+
Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
|
|
266
|
+
Group remaining paths by common prefix.
|
|
267
|
+
For each group of ≥ 2 sibling paths sharing a prefix:
|
|
268
|
+
- Compute longest common directory prefix
|
|
269
|
+
- Limit generalization depth: at most 2 levels below the common prefix
|
|
270
|
+
- Emit glob: <common-prefix>/**/*.<ext> (or <common-prefix>/**/<filename>)
|
|
271
|
+
Singleton paths (group size = 1) are kept as-is (literal path, no glob).
|
|
272
|
+
|
|
273
|
+
Step 5: SCOPE GATE
|
|
274
|
+
IF scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
|
|
275
|
+
IF scope == narrow → relevance_paths = result of Step 4
|
|
276
|
+
|
|
277
|
+
Step 6: ATTACH READ-ONLY EVIDENCE
|
|
278
|
+
Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
|
|
279
|
+
block in the pending entry body — NOT in relevance_paths. They document
|
|
280
|
+
what the agent consulted without making them part of the activation gate.
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
##### Worked generalization example
|
|
284
|
+
|
|
285
|
+
Edit history during session:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
packages/server/src/services/extract.ts
|
|
289
|
+
packages/server/src/services/review.ts
|
|
290
|
+
packages/server/src/services/promote.ts
|
|
291
|
+
packages/cli/src/commands/plan.ts
|
|
292
|
+
README.md
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Step 1-2 (collect + dedupe): all 5 unique.
|
|
296
|
+
Step 3 (blacklist): drop `README.md` (repo-root single file).
|
|
297
|
+
Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
|
|
298
|
+
- `packages/server/src/services/{extract,review,promote}.ts` → group size 3 ≥ 2, common prefix `packages/server/src/services/`, glob: `packages/server/src/services/**/*.ts`
|
|
299
|
+
- `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
|
|
300
|
+
|
|
301
|
+
Step 5 (assume `scope=narrow`):
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
"relevance_paths": [
|
|
305
|
+
"packages/server/src/services/**/*.ts",
|
|
306
|
+
"packages/cli/src/commands/plan.ts"
|
|
307
|
+
]
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
If `scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
|
|
311
|
+
|
|
312
|
+
#### Inline-edit support during batch review
|
|
313
|
+
|
|
314
|
+
The user MAY inline-edit `[scope=...]` in the batch review. When this happens:
|
|
315
|
+
|
|
316
|
+
- Edit changes `narrow → broad`: clear `relevance_paths` to `[]`.
|
|
317
|
+
- Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
|
|
318
|
+
- The user MAY also directly inline-edit `relevance_paths` to a custom array; treat this as authoritative and skip auto-derivation.
|
|
319
|
+
|
|
320
|
+
### Phase 2 — Persist via MCP
|
|
321
|
+
|
|
322
|
+
For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT batch multiple candidates into one call.
|
|
323
|
+
|
|
324
|
+
#### Output Contract (MCP tool call shape)
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
mcp__fabric__fab_extract_knowledge({
|
|
328
|
+
source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 0.0)
|
|
329
|
+
recent_paths: ["<path1>", "<path2>", ...], // capped at 20
|
|
330
|
+
user_messages_summary: "<compact prose ≤500 chars>",
|
|
331
|
+
type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
|
|
332
|
+
slug: "<kebab-case-2-to-5-words>",
|
|
333
|
+
layer: "team" | "personal",
|
|
334
|
+
scope: "narrow" | "broad", // from Phase 1.5
|
|
335
|
+
relevance_paths: ["<glob1>", "<literal2>", ...], // narrow ⇒ derived; broad ⇒ []
|
|
336
|
+
// v2.0.0-rc.7 T6: required fields for future-self reviewability.
|
|
337
|
+
proposed_reason:
|
|
338
|
+
"explicit-user-mark" // user said "always / never / 下次注意" etc.
|
|
339
|
+
| "diagnostic-then-fix" // long debug loop surfaced a new pattern/pitfall
|
|
340
|
+
| "decision-confirmation" // ≥2 options weighed AND rationale stated → decision/model
|
|
341
|
+
| "wrong-turn-revert" // tried path X, reverted → pitfall
|
|
342
|
+
| "new-dependency-or-pattern" // new dep/lib/abstraction introduced
|
|
343
|
+
| "dismissal-with-reason", // user rejected approach AND said why
|
|
344
|
+
session_context: "<3-5 line markdown: session goal + key turning point>",
|
|
345
|
+
// tags? — NOT in current schema; reserved for future
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The Skill infers `proposed_reason` from the classification + viability-gate
|
|
350
|
+
signal that fired:
|
|
351
|
+
|
|
352
|
+
| Signal fired (Phase 0.5) | Classification | Default proposed_reason |
|
|
353
|
+
|--------------------------------|----------------|-----------------------------|
|
|
354
|
+
| Explicit normative language | guideline | `explicit-user-mark` |
|
|
355
|
+
| Wrong-turn-and-revert | pitfall | `wrong-turn-revert` |
|
|
356
|
+
| Long diagnostic loop | pitfall/model | `diagnostic-then-fix` |
|
|
357
|
+
| New dependency adoption | decision/model | `new-dependency-or-pattern` |
|
|
358
|
+
| New pattern emergence | model | `new-dependency-or-pattern` |
|
|
359
|
+
| Decision confirmation | decision | `decision-confirmation` |
|
|
360
|
+
| Explicit dismissal-with-reason | decision | `dismissal-with-reason` |
|
|
361
|
+
| Process formalization | process | `new-dependency-or-pattern` |
|
|
362
|
+
|
|
363
|
+
The `session_context` is a 3-5 line summary distilled from the Phase 0.0
|
|
364
|
+
cross-session digest (see Phase 0.0 below for digest source). Format:
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
Session goal: <one-line of what the user was trying to accomplish>
|
|
368
|
+
Turning point: <one-line of the key moment that produced the worth-archive observation>
|
|
369
|
+
[optional 1-3 more lines of supporting context]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Future-self reviewing the pending entry MUST be able to understand WHY this
|
|
373
|
+
entry was proposed without conversation transcript access — proposed_reason
|
|
374
|
+
is the structured why, session_context is the narrative why.
|
|
375
|
+
|
|
376
|
+
Note on type plurality: the MCP enum uses plural directory-form (decisions / pitfalls / guidelines / models / processes), while the conceptual classification above uses singular nouns (decision / pitfall / guideline / model / process) for natural English. They map 1:1.
|
|
377
|
+
|
|
378
|
+
The server returns `{ pending_path, idempotency_key }`. Display `pending_path` to the user so they can `Read` the persisted entry if they wish.
|
|
379
|
+
|
|
380
|
+
#### Idempotency Note
|
|
381
|
+
|
|
382
|
+
The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. Calling fab_extract_knowledge twice with the same `(source_session, type, slug)` triple is SAFE: the server appends new evidence to the existing pending file rather than overwriting or producing duplicates. This means the skill MAY be re-invoked on the same session without producing junk.
|
|
383
|
+
|
|
384
|
+
If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
|
|
385
|
+
|
|
386
|
+
## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
|
|
387
|
+
|
|
388
|
+
### DISPLAY Rules
|
|
389
|
+
|
|
390
|
+
- MUST complete Phase 0 AND Phase 0.5 viability gate before any batch-review output.
|
|
391
|
+
- MUST abort with the gate-FAIL message (no MCP call) when the viability gate fails AND the user did not explicitly invoke fabric-archive.
|
|
392
|
+
- MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
|
|
393
|
+
- MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
|
|
394
|
+
- MUST include a one-line `Scope reasoning:` for each candidate citing why narrow or broad was chosen (or that personal forced broad).
|
|
395
|
+
- MUST classify against the canonical singular nouns: model / decision / guideline / pitfall / process. NEVER invent new types.
|
|
396
|
+
- MUST cap the batch at 8 candidates; drop weaker ones over the cap.
|
|
397
|
+
- MUST display the resolved `pending_path` returned by `fab_extract_knowledge` so the user can verify.
|
|
398
|
+
- MUST treat user inline edits to type/layer/slug/scope/relevance_paths as authoritative replacements before Phase 2.
|
|
399
|
+
- MUST skip rather than guess when an observation does not fit any of the 5 types.
|
|
400
|
+
|
|
401
|
+
### WRITE Rules
|
|
402
|
+
|
|
403
|
+
- NEVER write a knowledge entry directly to the filesystem; the only legal write path is `mcp__fabric__fab_extract_knowledge`.
|
|
404
|
+
- NEVER write outside `.fabric/knowledge/pending/` — promotion to `.fabric/knowledge/<type>/` is rc.3 fab_review concern, NOT this skill.
|
|
405
|
+
- NEVER include an `id` field anywhere — pending entries have no id (late-bind on approve).
|
|
406
|
+
- NEVER classify a candidate as `personal` when a 强 team signal applies. Default to team on ambiguity.
|
|
407
|
+
- NEVER emit a non-empty `relevance_paths` when `scope=broad` — broad MUST always carry `relevance_paths=[]`.
|
|
408
|
+
- NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `scope=broad` + `relevance_paths=[]`.
|
|
409
|
+
- NEVER use multi-signal sources for relevance_paths in rc.5 — `edit_paths` is the SOLE source. `read_paths`, body regex, and symbol extraction are reserved for rc.7+.
|
|
410
|
+
- NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
|
|
411
|
+
- NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
|
|
412
|
+
- MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `scope`, `narrow`, `broad`, `edit_paths`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
|
|
413
|
+
|
|
414
|
+
## Worked Examples
|
|
415
|
+
|
|
416
|
+
### Example 1 — decision (team)
|
|
417
|
+
|
|
418
|
+
Session: User and agent debated whether the Stop-hook should be one .cjs script or three per-client scripts. Settled on one because stdout JSON shape `{"decision":"block","reason"}` is identical across Claude / Codex.
|
|
419
|
+
|
|
420
|
+
Skill output:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
mcp__fabric__fab_extract_knowledge({
|
|
424
|
+
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
425
|
+
recent_paths: ["templates/claude-hooks/", "packages/cli/src/commands/hooks.ts"],
|
|
426
|
+
user_messages_summary: "User pushed back on three-script proposal; agreed single .cjs because stdout JSON shape is universal across Claude Code and Codex CLI.",
|
|
427
|
+
type: "decisions",
|
|
428
|
+
slug: "single-cjs-hook-script",
|
|
429
|
+
layer: "team",
|
|
430
|
+
scope: "narrow",
|
|
431
|
+
relevance_paths: [
|
|
432
|
+
"templates/claude-hooks/**/*.cjs",
|
|
433
|
+
"packages/cli/src/commands/hooks.ts"
|
|
434
|
+
],
|
|
435
|
+
proposed_reason: "decision-confirmation",
|
|
436
|
+
session_context: "Session goal: ship Stop-hook for v2 release.\nTurning point: user rejected 3-script proposal after seeing identical stdout JSON across Claude / Codex.\nResult: single .cjs path locked in."
|
|
437
|
+
})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Layer = team (引用本项目代码 + fabric-import 路径产物 signals). Scope = narrow (tied to hook templates + hooks command module; single-module evidence in edit_paths).
|
|
441
|
+
|
|
442
|
+
### Example 2 — pitfall (team)
|
|
443
|
+
|
|
444
|
+
Session: deepMerge silently replaced the existing `hooks.Stop[]` array in `.claude/settings.json` instead of appending. Cost ~30 min to diagnose.
|
|
445
|
+
|
|
446
|
+
Skill output:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
mcp__fabric__fab_extract_knowledge({
|
|
450
|
+
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
451
|
+
recent_paths: ["packages/cli/src/config/json.ts"],
|
|
452
|
+
user_messages_summary: "deepMerge default behavior REPLACES arrays. hooks.Stop[] needs an array-append-with-dedupe special case keyed on .command string match.",
|
|
453
|
+
type: "pitfalls",
|
|
454
|
+
slug: "deepmerge-array-replace-trap",
|
|
455
|
+
layer: "team",
|
|
456
|
+
scope: "broad",
|
|
457
|
+
relevance_paths: [],
|
|
458
|
+
proposed_reason: "diagnostic-then-fix",
|
|
459
|
+
session_context: "Session goal: wire hook installer for v2.\nTurning point: spent ~30 min chasing why prior Stop[] entries vanished — root cause was deepMerge replacing arrays silently.\nResult: array-append-with-dedupe special case added."
|
|
460
|
+
})
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Layer = team (绑定本项目代码的 pitfall signal). Scope = broad (deepMerge gotcha is cross-cutting — applies anywhere JSON merge is used, not just `json.ts`).
|
|
464
|
+
|
|
465
|
+
### Example 3 — guideline (personal)
|
|
466
|
+
|
|
467
|
+
Session: User mentioned across three projects that they prefer 2-space indent in TypeScript and 4-space in Python.
|
|
468
|
+
|
|
469
|
+
Skill output:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
mcp__fabric__fab_extract_knowledge({
|
|
473
|
+
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
474
|
+
recent_paths: [".editorconfig"],
|
|
475
|
+
user_messages_summary: "Personal indent preference: 2-space TS / 4-space Py. Stable across multiple projects, not project-specific.",
|
|
476
|
+
type: "guidelines",
|
|
477
|
+
slug: "indent-style-by-language",
|
|
478
|
+
layer: "personal",
|
|
479
|
+
scope: "broad",
|
|
480
|
+
relevance_paths: [],
|
|
481
|
+
proposed_reason: "explicit-user-mark",
|
|
482
|
+
session_context: "Session goal: align editor config.\nTurning point: user said '一直 prefer 2-space TS / 4-space Py,across projects'.\nResult: personal-layer guideline; not bound to this project."
|
|
483
|
+
})
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Layer = personal (跨项目通用 + 工具/编辑器偏好 signals dominate; no 强 team signal applies). Scope = broad with `relevance_paths=[]` (personal layer ALWAYS forces broad — paths don't generalize across projects per Phase 1.5 special case).
|