@fenglimg/fabric-cli 2.0.0-rc.10 → 2.0.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MT3R57VG.js → chunk-5MQ52F42.js} +1 -0
- package/dist/index.js +3 -3
- package/dist/{init-SAVH4SKE.js → init-C56PWHID.js} +46 -9
- package/dist/{scan-ELSNCSKS.js → scan-66EKMNAY.js} +3 -1
- package/package.json +3 -3
- package/templates/skills/fabric-archive/SKILL.md +184 -30
- package/templates/skills/fabric-import/SKILL.md +319 -29
- package/templates/skills/fabric-review/SKILL.md +358 -23
|
@@ -18,14 +18,19 @@ This skill is invoked when one of the following holds:
|
|
|
18
18
|
- The user explicitly mentioned this skill by name (`fabric-import`)
|
|
19
19
|
- A `fabric import` CLI command was run (rc.4 wires this if shipped; otherwise treat as user prompt)
|
|
20
20
|
|
|
21
|
-
If none of the above hold, stop the skill immediately and tell the user
|
|
21
|
+
If none of the above hold, stop the skill immediately and tell the user:
|
|
22
|
+
|
|
23
|
+
- zh-CN: `没有触发 import 信号;如需手动 import 请显式调用 fabric-import`
|
|
24
|
+
- en: `No import signal detected; to manually import, explicitly invoke fabric-import`
|
|
25
|
+
|
|
26
|
+
(Render per `knowledge_language` resolved in Phase 0.5 Config Load below — class 2 of the UX i18n Policy.)
|
|
22
27
|
|
|
23
28
|
> **Recommendation source (rc.8+)**: 过去版本的 `.fabric/.import-requested` sentinel 机制已下线;推荐由 SessionStart hook 的 underseed 自检触发(`templates/hooks/knowledge-hint-broad.cjs` 的 `shouldRecommendImport()`:`agents.meta.json` 存在 + canonical 节点数 < `underseed_node_threshold` + `.import-state.json` 缺失三条件齐备时一次性提示)。本 skill 不再读写 sentinel 文件,也不需要在 Phase 3 完成时手动清理它。
|
|
24
29
|
|
|
25
30
|
This skill SHOULD be skipped (warn the user, do not proceed) when:
|
|
26
31
|
|
|
27
32
|
- `.fabric/` does not exist — direct the user to run `fabric init` first; `fabric-import` is NOT a substitute for the deterministic init-scan
|
|
28
|
-
- `.fabric/knowledge/` already holds
|
|
33
|
+
- `.fabric/knowledge/` already holds **>`import_skip_canonical_threshold` canonical entries (config-resolved, default 50)** across all types — the project is mature; use `fabric-archive` (per-session capture) and `fabric-review` (lifecycle review) instead; bulk import would just create dup churn
|
|
29
34
|
- `.fabric/.import-state.json` exists with `phase: "complete"` and `last_checkpoint_at` is **<24h ago** — the user just ran import; surface the prior result rather than re-running
|
|
30
35
|
|
|
31
36
|
Required preconditions before any MCP call:
|
|
@@ -36,6 +41,145 @@ Required preconditions before any MCP call:
|
|
|
36
41
|
- `mcp__fabric__fab_extract_knowledge` AND `mcp__fabric__fab_review` MCP tools are registered and reachable
|
|
37
42
|
- Working tree is reasonably clean (large uncommitted churn pollutes git-log mining; warn but allow)
|
|
38
43
|
|
|
44
|
+
### Phase 0 — Init & .tmp Residue Scan
|
|
45
|
+
|
|
46
|
+
Before reading `.fabric/.import-state.json`, scan for residue left by a
|
|
47
|
+
prior crashed run. Skill state writes use a 2-step atomic pattern (Write
|
|
48
|
+
`.tmp` then `Bash mv`); a crash between Step A and Step B leaves a
|
|
49
|
+
`.fabric/.import-state.json.tmp` sidecar that the next invocation MUST
|
|
50
|
+
triage.
|
|
51
|
+
|
|
52
|
+
1. Does `.fabric/.import-state.json.tmp` exist? (`Bash: ls .fabric/.import-state.json.tmp 2>/dev/null`)
|
|
53
|
+
- **Does not exist** → proceed normally to Phase 0.1 (no residue work).
|
|
54
|
+
- **Exists** → triage:
|
|
55
|
+
1. `Read` the `.tmp` file; try `JSON.parse` on the content.
|
|
56
|
+
2. Compare `mtime` of `.tmp` vs `.fabric/.import-state.json` via `Bash: stat`.
|
|
57
|
+
- **Parse OK + .tmp mtime newer than main file** → rescue:
|
|
58
|
+
`Bash: mv .fabric/.import-state.json.tmp .fabric/.import-state.json`
|
|
59
|
+
(commits the last incomplete write atomically).
|
|
60
|
+
- **Parse OK + .tmp mtime older than main file** → stale residue
|
|
61
|
+
from an earlier run that subsequently completed; delete it:
|
|
62
|
+
`Bash: rm .fabric/.import-state.json.tmp`.
|
|
63
|
+
- **Parse fails** (syntax error / unterminated structure / truncated
|
|
64
|
+
mid-write) → half-written, unrecoverable; delete it:
|
|
65
|
+
`Bash: rm .fabric/.import-state.json.tmp`.
|
|
66
|
+
3. After triage, proceed to Phase 0.1.
|
|
67
|
+
|
|
68
|
+
The 5-minute mtime heuristic (treat any `.tmp` older than 5 minutes as
|
|
69
|
+
stale regardless of parse result) is an acceptable conservative simplification:
|
|
70
|
+
no legitimate atomic write window stays open that long; anything older
|
|
71
|
+
than 5 minutes is definitely crash residue. Implementations MAY use either
|
|
72
|
+
the mtime-comparison rule above OR the 5-minute staleness rule.
|
|
73
|
+
|
|
74
|
+
### Phase 0.1 — State Corruption Recovery
|
|
75
|
+
|
|
76
|
+
After residue triage, `Read` `.fabric/.import-state.json`. Detect
|
|
77
|
+
corruption if ANY of the following hold:
|
|
78
|
+
|
|
79
|
+
- `JSON.parse` throws (syntax error / unterminated structure / truncated)
|
|
80
|
+
- Missing required field: `phase` OR `started_at` OR `last_checkpoint_at`
|
|
81
|
+
- `phase` value not in the enum `{P1-done, P2-done, complete}`
|
|
82
|
+
|
|
83
|
+
On corruption (any condition above):
|
|
84
|
+
|
|
85
|
+
1. `Bash: mv .fabric/.import-state.json .fabric/.import-state.json.corrupt-<ISO8601>`
|
|
86
|
+
(preserve the corrupt file for postmortem; do NOT silently overwrite).
|
|
87
|
+
2. Phase 1 restarts from scratch (Phase 1 produces no MCP calls, so re-run
|
|
88
|
+
is safe — re-globbing `.fabric/knowledge/team/**/*.md` is cheap and
|
|
89
|
+
idempotent; the `p1_baseline_titles` array is regenerated).
|
|
90
|
+
3. DO NOT attempt automatic partial recovery; corrupt state is a signal
|
|
91
|
+
that something serious happened (disk-full, kill -9 mid-write, fs
|
|
92
|
+
error). Discard-and-restart is the only safe path.
|
|
93
|
+
|
|
94
|
+
ENOENT (state file absent) is NOT corruption — it is the normal
|
|
95
|
+
first-run state. Proceed to Phase 0.5.
|
|
96
|
+
|
|
97
|
+
### Phase 0.5 — Config Load
|
|
98
|
+
|
|
99
|
+
Before any Phase 1 work, the skill MUST read `.fabric/fabric-config.json`
|
|
100
|
+
to resolve the following tunables (with documented defaults if absent):
|
|
101
|
+
|
|
102
|
+
| Config field | Default | Used by |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `import_window_first_run_months` | 60 | Step 2.1 (--since arg construction, first-run window) |
|
|
105
|
+
| `import_window_rerun_months` | 2 | Step 2.1 (--since arg construction, re-run window) |
|
|
106
|
+
| `import_max_pending_per_run` | 10 | Step 2.1/2.2 hard cap on new pending entries per run |
|
|
107
|
+
| `import_max_commits_scan` | 500 | Step 2.1 `-n` arg (commit-scan budget) |
|
|
108
|
+
| `import_skip_canonical_threshold` | 50 | Precondition skip gate (canonical-entry maturity check) |
|
|
109
|
+
|
|
110
|
+
If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
|
|
111
|
+
Whether the run is "first-run" vs "re-run" is decided by inspecting
|
|
112
|
+
`.fabric/.import-state.json`: ENOENT (or any state with `phase != "complete"`
|
|
113
|
+
and `final_summary.proposed == 0`) → first-run window; otherwise re-run window.
|
|
114
|
+
|
|
115
|
+
### UX i18n Policy (5-class bilingualization)
|
|
116
|
+
|
|
117
|
+
The skill consults `knowledge_language` from `.fabric/fabric-config.json`
|
|
118
|
+
(固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
|
|
119
|
+
CJK signal is detected in README + docs/). All user-facing text in the
|
|
120
|
+
following 5 categories MUST be rendered in the resolved language:
|
|
121
|
+
|
|
122
|
+
1. **Roll-up templates** — final summary blocks (`# Import Summary — phase=...`,
|
|
123
|
+
`## Phase 2 — Mining`, `## Phase 3 — Dedup`, etc.). zh-CN ↔ en mirror.
|
|
124
|
+
2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
|
|
125
|
+
"请先运行 fabric init 完成基线扫描…" / "Please run fabric init first…").
|
|
126
|
+
zh-CN ↔ en mirror.
|
|
127
|
+
3. **Confirmation prompts** — re-run-within-24h prompt, reset prompts, etc.
|
|
128
|
+
zh-CN ↔ en mirror.
|
|
129
|
+
4. **Dry-run table headers** — `# Import Dry Run — would propose N pending
|
|
130
|
+
entries…` + the `| # | Source | Type | Slug | Scope | Summary |` header row.
|
|
131
|
+
zh-CN ↔ en mirror.
|
|
132
|
+
5. **AskUserQuestion** — `header` + `question` fields (NOT `options[]`).
|
|
133
|
+
zh-CN ↔ en mirror. fabric-import itself does not surface AskUserQuestion
|
|
134
|
+
in the current contract (the rare re-run prompt is free-text), but if a
|
|
135
|
+
future version adds one, this rule applies.
|
|
136
|
+
|
|
137
|
+
Rendering rule:
|
|
138
|
+
|
|
139
|
+
- `knowledge_language === "zh-CN"` → emit the zh-CN variant.
|
|
140
|
+
- `knowledge_language === "en"` (or any other value) → emit the en variant.
|
|
141
|
+
- The Skill MUST NOT mix languages inside a single user-facing block
|
|
142
|
+
(no "Chinglish" partial translation); each block is either fully zh-CN
|
|
143
|
+
or fully en.
|
|
144
|
+
|
|
145
|
+
Protected tokens (`fab_extract_knowledge`, `fab_review`, `relevance_scope`,
|
|
146
|
+
`relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`,
|
|
147
|
+
`session_context`, `pending_path`, `layer`, `team`, `personal`,
|
|
148
|
+
`knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, etc.)
|
|
149
|
+
are NEVER translated — they appear verbatim in both language variants.
|
|
150
|
+
The bilingualization scope is prose ONLY.
|
|
151
|
+
|
|
152
|
+
### AskUserQuestion i18n Policy (value vs label)
|
|
153
|
+
|
|
154
|
+
When a skill (this one or any sibling skill the user is composing with)
|
|
155
|
+
issues an `AskUserQuestion`, the `header` and `question` strings are
|
|
156
|
+
user-facing prose → translated per `knowledge_language`. The `options[]`
|
|
157
|
+
array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
|
|
158
|
+
fabric-review) are **routing keys** consumed by the skill state machine —
|
|
159
|
+
they MUST remain English regardless of `knowledge_language`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// EN (knowledge_language === "en")
|
|
163
|
+
AskUserQuestion({
|
|
164
|
+
header: "Review pending entry",
|
|
165
|
+
question: "What action for '{title}'?",
|
|
166
|
+
options: ["approve", "reject", "modify", "defer", "skip"]
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// zh-CN (knowledge_language === "zh-CN")
|
|
170
|
+
AskUserQuestion({
|
|
171
|
+
header: "审核 pending 条目",
|
|
172
|
+
question: "对 '{title}' 执行什么操作?",
|
|
173
|
+
options: ["approve", "reject", "modify", "defer", "skip"] // 不翻译 — routing key
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Rationale: localizing routing keys would force every routing branch to
|
|
178
|
+
dual-string match (e.g. `if (choice === "approve" || choice === "通过")`),
|
|
179
|
+
which doubles the surface area for protected-token regressions and breaks
|
|
180
|
+
the option-list invariants that downstream tooling depends on. Keeping
|
|
181
|
+
`options[]` English-only is contract-locked across all three skills.
|
|
182
|
+
|
|
39
183
|
## 3-Phase Pipeline (P1 reference / P2 mine / P3 dedup)
|
|
40
184
|
|
|
41
185
|
The pipeline runs strictly in order. Each phase reads the prior phase's outputs and updates `.fabric/.import-state.json` after every successful sub-step (not just at phase end). The skill is `Infer-not-Ask` for which phase to run (always all three when starting fresh, or resumes from the checkpoint phase).
|
|
@@ -57,7 +201,12 @@ Phase 1 actions performed by THIS skill:
|
|
|
57
201
|
|
|
58
202
|
1. Read `.fabric/agents.meta.json` to confirm baseline counters exist (each type's `next_id` should be `>1` if init-scan landed entries; `=1` means init produced zero entries of that type — informational, not an error).
|
|
59
203
|
2. Glob `.fabric/knowledge/team/**/*.md` to enumerate baseline entry titles. Capture the list — Phase 2 uses these titles as a **negative filter** (signals already covered by init-scan should be skipped, not re-proposed).
|
|
60
|
-
3. If `.fabric/agents.meta.json` is missing OR `.fabric/knowledge/team/` is empty: STOP. Tell the user
|
|
204
|
+
3. If `.fabric/agents.meta.json` is missing OR `.fabric/knowledge/team/` is empty: STOP. Tell the user (UX i18n Policy class 2 — errors/preconditions):
|
|
205
|
+
|
|
206
|
+
- zh-CN: `请先运行 fabric init 完成基线扫描,再调用 fabric-import`
|
|
207
|
+
- en: `Please run fabric init first to complete the baseline scan, then invoke fabric-import`
|
|
208
|
+
|
|
209
|
+
…and exit.
|
|
61
210
|
4. Update `.fabric/.import-state.json`: `phase = "P1-done"`, `p1_baseline_titles = [<list>]`, `last_checkpoint_at = <ISO8601 now>`.
|
|
62
211
|
|
|
63
212
|
**Phase 1 produces no MCP calls.** It only reads the on-disk init-scan output.
|
|
@@ -120,13 +269,16 @@ This is the ONLY legal path for an imported entry to acquire `relevance_paths`.
|
|
|
120
269
|
|
|
121
270
|
#### Step 2.1 — Git Log Mining
|
|
122
271
|
|
|
123
|
-
Bash command (executed via the `Bash` tool):
|
|
272
|
+
Bash command (executed via the `Bash` tool — substitute `<window>` and `<commits-cap>` with values resolved from Phase 0.5 config load):
|
|
124
273
|
|
|
125
274
|
```bash
|
|
126
|
-
git log --since="
|
|
275
|
+
git log --since="<window> months ago" --pretty=format:"%H%n%s%n%b%n---ENDCOMMIT---" -n <commits-cap>
|
|
127
276
|
```
|
|
128
277
|
|
|
129
|
-
|
|
278
|
+
- `<window>` resolves to `import_window_first_run_months` on a first-run (default 60) or `import_window_rerun_months` on subsequent runs (default 2); first-run-vs-rerun is decided per the Phase 0.5 rule.
|
|
279
|
+
- `<commits-cap>` resolves to `import_max_commits_scan` (default 500).
|
|
280
|
+
|
|
281
|
+
Tolerate empty output (shallow clone or new repo). Cap the working set at the **`import_max_commits_scan`-most-recent commits (config-resolved)** regardless of date range to bound LLM context.
|
|
130
282
|
|
|
131
283
|
For each commit:
|
|
132
284
|
|
|
@@ -149,11 +301,10 @@ mcp__fabric__fab_extract_knowledge({
|
|
|
149
301
|
slug: "<kebab-case 2-5 words derived from commit subject + body>",
|
|
150
302
|
relevance_scope: "broad", // MANDATORY — never "narrow" from fabric-import
|
|
151
303
|
relevance_paths: [], // MANDATORY — never derived from git history
|
|
152
|
-
// v2.0.0-rc.
|
|
153
|
-
// because git-log mining surfaces newly-introduced abstractions/conventions.
|
|
304
|
+
// v2.0.0-rc.9 T6: proposed_reason inferred per Step 2.1.5 (see above).
|
|
154
305
|
// session_context cites the commit / doc origin so future-self reviewers
|
|
155
306
|
// know this is an LLM-mined entry rather than a live-session capture.
|
|
156
|
-
proposed_reason: "
|
|
307
|
+
proposed_reason: "<inferred per Step 2.1.5 — varies>",
|
|
157
308
|
session_context: "Imported from git log analysis. Origin: commit <sha7> (<subject 30 chars>). No live session — see commit body for full context."
|
|
158
309
|
})
|
|
159
310
|
```
|
|
@@ -163,8 +314,40 @@ Note: `recent_paths` continues to carry the touched-file list for **provenance d
|
|
|
163
314
|
5. On success the server returns `{pending_path, idempotency_key}`. Append to `.fabric/.import-state.json`:
|
|
164
315
|
- `p2_processed_commits[].push({sha: <full sha>, skipped: false, pending_path, type, slug})`
|
|
165
316
|
- `last_checkpoint_at = <ISO8601 now>`
|
|
166
|
-
Update is atomic
|
|
167
|
-
6. **Hard cap**: at most
|
|
317
|
+
Update is atomic via the 2-step `.tmp` + `mv` pattern documented in the **Atomic State Write** section under "Checkpoint Logic" below, so a crash between commits never corrupts the state file.
|
|
318
|
+
6. **Hard cap**: at most **`import_max_pending_per_run` new pending entries (config-resolved, default 10)** per Phase 2 run. When the cap is reached, mark `p2_cap_reached = true` and stop git-log iteration (the user can re-invoke for more — idempotent resume picks up from the next unprocessed commit).
|
|
319
|
+
|
|
320
|
+
#### Step 2.1.5 — Proposed Reason Inference (rc.7 T6)
|
|
321
|
+
|
|
322
|
+
For each non-skipped commit OR doc section, infer `proposed_reason` from
|
|
323
|
+
prefix + body signal jointly. The 6 reasons below are the full enum accepted
|
|
324
|
+
by `fab_extract_knowledge` (schema-locked):
|
|
325
|
+
|
|
326
|
+
| Source signal | Body cue | Inferred reason |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `feat(...)` commit | "vs" / "instead of" / "chose" / "rejected X for Y" | `decision-confirmation` |
|
|
329
|
+
| `feat(...)` commit | Announces new dep/lib/abstraction, no alternative cited | `new-dependency-or-pattern` |
|
|
330
|
+
| `fix(...)` commit | Cites wrong direction tried + reverted | `wrong-turn-revert` |
|
|
331
|
+
| `fix(...)` commit | Cites long diagnostic chain → root cause | `diagnostic-then-fix` |
|
|
332
|
+
| `refactor(...)` commit | Cites structural rationale (without "vs" alternatives) | `decision-confirmation` |
|
|
333
|
+
| `docs(...)` commit | Announces convention ("always X" / "never Y") | `explicit-user-mark` |
|
|
334
|
+
| Any commit | Body explicitly rejects an approach + states why | `dismissal-with-reason` |
|
|
335
|
+
| Doc section | "Why we chose X over Y" heading | `decision-confirmation` |
|
|
336
|
+
| Doc section | "Don't do Y because..." section | `dismissal-with-reason` |
|
|
337
|
+
| Doc section | "Always" / "Never" guidelines | `explicit-user-mark` |
|
|
338
|
+
| Doc section | Architecture/design narrative (descriptive, no choice rationale) | `new-dependency-or-pattern` |
|
|
339
|
+
|
|
340
|
+
**Edge cases**:
|
|
341
|
+
|
|
342
|
+
- `chore(` / `test(` / `ci(` should already be skipped per the Skip Decision
|
|
343
|
+
Tree below; if they slip through, default to `new-dependency-or-pattern`.
|
|
344
|
+
- Ambiguous signals: prefer the reason matching **body content** over
|
|
345
|
+
**prefix** (a `feat(` with strong revert-language is `wrong-turn-revert`,
|
|
346
|
+
not `new-dependency-or-pattern`).
|
|
347
|
+
|
|
348
|
+
**Fallback**: when no row clearly applies, use `new-dependency-or-pattern`
|
|
349
|
+
(the broadest "noticed something new" semantic — preserves prior rc.7
|
|
350
|
+
behavior and matches the schema's default-import slot).
|
|
168
351
|
|
|
169
352
|
#### Step 2.2 — Docs Mining
|
|
170
353
|
|
|
@@ -186,7 +369,7 @@ For each Markdown file:
|
|
|
186
369
|
3. For each observation, classify type / propose slug / draft summary. Call `fab_extract_knowledge` with the same shape as Step 2.1 (including the **mandatory `relevance_scope: "broad"` + `relevance_paths: []`**), replacing `recent_paths` with `[<this doc path>]` and citing `src=<doc-relative-path>` in the summary. The mined doc's own path goes into `recent_paths` for provenance display ONLY — it is NOT lifted into `relevance_paths`.
|
|
187
370
|
4. Append to `.fabric/.import-state.json`:
|
|
188
371
|
- `p2_processed_docs[].push({path: <doc path>, observations_proposed: <count>, pending_paths: [...]})`
|
|
189
|
-
5. **Hard cap shared with Step 2.1**: total new pending entries across git + docs is capped at 10 per Phase 2 run.
|
|
372
|
+
5. **Hard cap shared with Step 2.1**: total new pending entries across git + docs is capped at `import_max_pending_per_run` (config-resolved, default 10) per Phase 2 run.
|
|
190
373
|
|
|
191
374
|
#### Skip Decision Tree (when to NOT propose)
|
|
192
375
|
|
|
@@ -209,20 +392,44 @@ After Step 2.2 completes (or hits the cap), update `.fabric/.import-state.json`:
|
|
|
209
392
|
|
|
210
393
|
#### Dry-Run Mode
|
|
211
394
|
|
|
212
|
-
When the user invocation includes `dry-run` / `预览` / `--dry-run` keywords, Phase 2 runs WITHOUT calling `fab_extract_knowledge`. Instead it prints a table:
|
|
395
|
+
When the user invocation includes `dry-run` / `预览` / `--dry-run` keywords, Phase 2 runs WITHOUT calling `fab_extract_knowledge`. Instead it prints a table. UX i18n Policy class 4 — dry-run table headers; the header + column titles are bilingualized; row content (slug / commit sha / doc path) is data and is NOT translated. The protected tokens `broad`, `relevance_scope`, `relevance_paths` appear verbatim in both variants:
|
|
396
|
+
|
|
397
|
+
zh-CN variant (`knowledge_language === "zh-CN"`):
|
|
213
398
|
|
|
214
399
|
```md
|
|
215
|
-
# Import
|
|
400
|
+
# Import 预览 — 将提议 N 条 pending 条目(全部 relevance_scope=broad, relevance_paths=[])
|
|
216
401
|
|
|
217
|
-
| # |
|
|
218
|
-
|
|
402
|
+
| # | 来源 | 类型 | Slug | 作用域 | 摘要 (zh-CN) |
|
|
403
|
+
|---|-----------------------|-----------|-------------------------------|--------|-------------------------------------------------------------|
|
|
219
404
|
| 1 | git c0a351d | decisions | layer-flip-id-mutation | broad+[] | layer 切换是唯一合法的 stable_id 变更途径,绑定原子事务。 |
|
|
220
405
|
| 2 | docs/architecture.md | decisions | monolith-over-microservices | broad+[] | 决定保留单体架构,三人团队不值微服务运维成本。 |
|
|
221
406
|
| 3 | git 50367b5 | pitfalls | thundering-herd-no-backoff | broad+[] | 重试无指数回退导致雪崩;必须 jittered exponential backoff。|
|
|
222
407
|
```
|
|
223
408
|
|
|
409
|
+
en variant (`knowledge_language === "en"`):
|
|
410
|
+
|
|
411
|
+
```md
|
|
412
|
+
# Import Dry Run — would propose N pending entries (all relevance_scope=broad, relevance_paths=[])
|
|
413
|
+
|
|
414
|
+
| # | Source | Type | Slug | Scope | Summary |
|
|
415
|
+
|---|-----------------------|-----------|-------------------------------|----------|---------------------------------------------------------------|
|
|
416
|
+
| 1 | git c0a351d | decisions | layer-flip-id-mutation | broad+[] | Layer change is the only legal stable_id mutation path; atomic txn. |
|
|
417
|
+
| 2 | docs/architecture.md | decisions | monolith-over-microservices | broad+[] | Keep the monolith — 3-engineer team can't justify microservice ops. |
|
|
418
|
+
| 3 | git 50367b5 | pitfalls | thundering-herd-no-backoff | broad+[] | Retries without exponential backoff caused a thundering herd outage. |
|
|
419
|
+
```
|
|
420
|
+
|
|
224
421
|
Every dry-run row MUST show `broad+[]` in the Scope column (it is a constant for fabric-import). A row showing anything else is a skill bug — refuse to proceed and surface the violation. Dry-run output is informational only. The state file is NOT written to in dry-run mode (so a real run later starts clean). Phase 3 is also skipped in dry-run.
|
|
225
422
|
|
|
423
|
+
#### Idempotency Note — T5 array form
|
|
424
|
+
|
|
425
|
+
The server derives `idempotency_key = sha256({source_session, type, slug})` for every `fab_extract_knowledge` call. Re-invoking 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 is why `fabric-import` resume after Ctrl-C / crash never produces duplicate pending entries for already-processed commits.
|
|
426
|
+
|
|
427
|
+
**T5 array-form note (rc.7+)**: when `source_sessions` is passed as an array (the rc.7 T5 contract), only `source_sessions[0]` participates in the server-side idempotency hash. The actual server formula at `packages/server/src/services/extract-knowledge.ts:78` is `sha256(JSON.stringify({source_session: sourceSessions[0], type, slug}))`. Implications for fabric-import:
|
|
428
|
+
|
|
429
|
+
- Every Phase 2 call uses `source_sessions: ["fabric-import-<ISO8601-date>"]` (single-element array, stable per import run). The first-element-only rule means re-runs on the same date produce the same idempotency key per `(type, slug)` → resume-safe by construction.
|
|
430
|
+
- If a future enhancement adds a trailing element (e.g. `["fabric-import-<date>", "<commit-sha>"]`), only the first element will participate in the hash — the commit-sha tail would NOT change the idempotency key for the same `(type, slug)`. Plan accordingly.
|
|
431
|
+
- The formula is intentionally stable across the rc.5 → rc.7 migration; adding or removing tail entries does NOT change the idempotency key, preserving rc.5 single-session compat.
|
|
432
|
+
|
|
226
433
|
### Phase 3 — LLM-Driven Dedup vs Canonical
|
|
227
434
|
|
|
228
435
|
For each pending entry created in Phase 2 (read from `p2_processed_commits[].pending_path` and `p2_processed_docs[].pending_paths`), check if it duplicates / contradicts / is subsumed by an existing canonical entry. **Semantic comparison is the LLM's job — `fab_review` does not compare meaning.**
|
|
@@ -245,7 +452,7 @@ The server returns ranked `items[]` of CANONICAL entries (not pending) of the sa
|
|
|
245
452
|
|
|
246
453
|
For each `(pending, canonical)` pair the LLM judges:
|
|
247
454
|
|
|
248
|
-
- **Duplicate** — same essential claim.
|
|
455
|
+
- **Duplicate** — same essential claim. LLM 主观判断:标题与摘要表达同一核心结论,新 pending 未提供新证据。具体阈值不可量化。Action: **reject** the new pending.
|
|
249
456
|
- **Subsumption** (pending narrower) — canonical fully covers the pending plus more. Action: **reject** the new pending (canonical already serves).
|
|
250
457
|
- **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.
|
|
251
458
|
- **Contradiction** — opposing claims about the same scope. Action: leave pending; flag for user via roll-up. The user must decide via `fabric-review` later — `fabric-import` does NOT auto-resolve contradictions.
|
|
@@ -299,7 +506,48 @@ The user MAY manually delete `.fabric/.import-state.json` to reset, or the skill
|
|
|
299
506
|
|
|
300
507
|
## Checkpoint Logic — `.fabric/.import-state.json`
|
|
301
508
|
|
|
302
|
-
The state file lives at `.fabric/.import-state.json` and is the single source of resumability for fabric-import. It is written via
|
|
509
|
+
The state file lives at `.fabric/.import-state.json` and is the single source of resumability for fabric-import. It is written via the explicit 2-step atomic pattern documented below so a crash between phases / between sub-steps never corrupts it.
|
|
510
|
+
|
|
511
|
+
### Atomic State Write (2-step pattern)
|
|
512
|
+
|
|
513
|
+
**Every** update to `.fabric/.import-state.json` MUST use the following two-step pattern, executed by the skill itself (not delegated to an external helper):
|
|
514
|
+
|
|
515
|
+
- **Step A**: `Write` tool → `.fabric/.import-state.json.tmp` (full JSON content; never partial / never appended).
|
|
516
|
+
- **Step B**: `Bash` → `mv .fabric/.import-state.json.tmp .fabric/.import-state.json`.
|
|
517
|
+
|
|
518
|
+
This 2-step pattern is mandatory for every state file update. `mv` is atomic on POSIX (`rename(2)` on the same filesystem guarantees the target either points to the old or new inode, never to a half-written file). `Write` alone is NOT atomic — the open + truncate + write sequence opens a window in which a crash leaves a zero-length or partially-written file on disk, which Phase 0.1 then has to discard. The `.tmp` + `mv` pattern eliminates that window.
|
|
519
|
+
|
|
520
|
+
Crash safety expectations:
|
|
521
|
+
|
|
522
|
+
- Crash between Step A and Step B → leaves `.fabric/.import-state.json.tmp`. Phase 0 residue scan (above) triages it on next invocation.
|
|
523
|
+
- 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.
|
|
524
|
+
- Crash before Step A → no state mutation occurred; prior state file is unchanged.
|
|
525
|
+
|
|
526
|
+
The legacy phrasing `atomicWriteJson` / `write-temp-then-rename` used in earlier drafts of this skill refers to this exact 2-step pattern; the explicit Step A / Step B description above is the canonical form.
|
|
527
|
+
|
|
528
|
+
### events.jsonl Constraint Note
|
|
529
|
+
|
|
530
|
+
Event lines appended to `.fabric/events.jsonl` are subject to POSIX
|
|
531
|
+
single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed
|
|
532
|
+
atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk
|
|
533
|
+
interleaved corruption under concurrent skill + server writes to the
|
|
534
|
+
same ledger.
|
|
535
|
+
|
|
536
|
+
Skills MUST ensure:
|
|
537
|
+
|
|
538
|
+
- Each event JSON line is a **single line** (no embedded newlines;
|
|
539
|
+
escape `\n` in any string value).
|
|
540
|
+
- `session_context` and other free-form text fields **self-truncate** to
|
|
541
|
+
keep the entire serialized line under 4KB. Suggested per-field caps:
|
|
542
|
+
`session_context` first 500 chars; `source_sessions` cap at 5
|
|
543
|
+
entries; `recent_paths` cap at 20 entries; `user_messages_summary`
|
|
544
|
+
first 500 chars.
|
|
545
|
+
- If approaching the 4KB ceiling after the per-field caps, drop optional
|
|
546
|
+
fields (e.g. tags / extra metadata) **before** truncating semantic
|
|
547
|
+
content (the summary / context that carries the actual observation).
|
|
548
|
+
- This constraint applies to any event the skill itself appends (e.g.
|
|
549
|
+
abort signals); MCP-server-side appends (via `appendEventLedgerEvent`)
|
|
550
|
+
are already line-length-bounded server-side.
|
|
303
551
|
|
|
304
552
|
### Schema (all fields)
|
|
305
553
|
|
|
@@ -341,10 +589,15 @@ On every skill invocation, BEFORE Phase 1 starts:
|
|
|
341
589
|
|
|
342
590
|
1. Read `.fabric/.import-state.json`. ENOENT → fresh run, initialize state with `phase: "P1-done"` after Phase 1 completes (state file is created at end of Phase 1, not at start).
|
|
343
591
|
2. If `phase === "complete"` AND `last_checkpoint_at < 24h ago` → SKIP this invocation (precondition warning above) unless user explicitly typed `re-run import` or `reset import`.
|
|
344
|
-
3. If `phase === "complete"` AND `last_checkpoint_at ≥ 24h ago` → ask the user (free-text prompt, NOT AskUserQuestion since this is rare)
|
|
592
|
+
3. If `phase === "complete"` AND `last_checkpoint_at ≥ 24h ago` → ask the user (free-text prompt, NOT AskUserQuestion since this is rare). UX i18n Policy class 3 — confirmation prompts:
|
|
593
|
+
|
|
594
|
+
- zh-CN: `上次 import 已完成 (<N> 天前)。重新运行将基于当前 canonical 重做 P2/P3。继续?(y/n)`
|
|
595
|
+
- en: `Last import completed (<N> days ago). Re-running will redo P2/P3 against the current canonical set. Continue? (y/n)`
|
|
596
|
+
|
|
597
|
+
If `n`, exit.
|
|
345
598
|
4. If `phase === "P1-done"` → skip Phase 1; resume from Phase 2 Step 2.1; iterate git log skipping any sha already in `p2_processed_commits[]`.
|
|
346
599
|
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[]`.
|
|
347
|
-
6. After every successful sub-step (one commit processed, one doc processed, one dedup pair resolved),
|
|
600
|
+
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 (see "Atomic State Write" above). Failures append to `errors[]` and proceed (or halt with prompt if cumulative errors `>5`).
|
|
348
601
|
|
|
349
602
|
The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, network blip on MCP) MUST NOT propose duplicates of already-proposed entries and MUST NOT redo already-completed dedup decisions.
|
|
350
603
|
|
|
@@ -355,8 +608,10 @@ The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, n
|
|
|
355
608
|
| Layer for new entries | `team` | User explicit instruction ("import these as personal") |
|
|
356
609
|
| `relevance_scope` for new entries | `broad` | NONE — contract-locked; narrowing deferred to `fab_review.modify` post-import |
|
|
357
610
|
| `relevance_paths` for new entries | `[]` | NONE — contract-locked; populating deferred to `fab_review.modify` post-import |
|
|
358
|
-
| Max new pending entries per P2 run |
|
|
359
|
-
| Git log window |
|
|
611
|
+
| Max new pending entries per P2 run | config-resolved (default 10, max 50) | `import_max_pending_per_run` in `.fabric/fabric-config.json`; user explicit ("import up to 25") |
|
|
612
|
+
| Git log window | config-resolved (default 60 first-run / 2 re-run, months) | `import_window_first_run_months` / `import_window_rerun_months`; user explicit ("import the full year") |
|
|
613
|
+
| Git log commit-scan cap | config-resolved (default 500) | `import_max_commits_scan` in `.fabric/fabric-config.json` |
|
|
614
|
+
| Skip-import canonical threshold | config-resolved (default 50) | `import_skip_canonical_threshold` in `.fabric/fabric-config.json` |
|
|
360
615
|
| Docs scan depth | `3` | User explicit ("scan docs/ recursively") |
|
|
361
616
|
| Dry-run mode | OFF | User keyword `dry-run` / `预览` / `--dry-run` |
|
|
362
617
|
| Re-run within 24h of complete | BLOCKED | User keyword `re-run import` / `reset import` |
|
|
@@ -383,17 +638,19 @@ The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, n
|
|
|
383
638
|
- NEVER call `fab_review action="approve"` from this skill — promotion of pending → canonical is `fabric-review`'s concern, not import's. Imported entries land in `pending/` and wait for normal review flow.
|
|
384
639
|
- NEVER call `git mv` directly — layer flips during Phase 3 dedup go through `fab_review action="modify"` with `changes.layer`, which is a server-side transaction.
|
|
385
640
|
- NEVER infer a layer-flip target without explicit user instruction — fabric-import defaults `team`; if the user later wants `personal` for an entry, that's a `fabric-review` modify call, not an import-time decision.
|
|
386
|
-
- NEVER overwrite `.fabric/.import-state.json` non-atomically — use `
|
|
641
|
+
- NEVER overwrite `.fabric/.import-state.json` non-atomically — use the 2-step `.tmp` + `mv` pattern documented in "Atomic State Write" under Checkpoint Logic (Step A: `Write` to `.fabric/.import-state.json.tmp`; Step B: `Bash: mv` to commit).
|
|
387
642
|
- NEVER exceed the 10-entry-per-run hard cap without explicit user override.
|
|
388
643
|
- NEVER pass `relevance_scope = "narrow"` to `fab_extract_knowledge` — every call from this skill MUST use `relevance_scope: "broad"`. No heuristic, no Agent judgment, no per-candidate override (see "Mandatory Scope Rule" in Phase 2).
|
|
389
644
|
- NEVER populate `relevance_paths` with a non-empty array on import — every call from this skill MUST pass `relevance_paths: []`. Do not derive paths from `git log --name-only`, `git show --stat`, commit subjects/bodies, or the path of a mined Markdown file.
|
|
390
645
|
- NEVER copy fabric-archive's Phase 1.5 scope-decision logic (narrow-vs-broad rules, public-prefix generalization, glob blacklist) into this skill — that logic requires a live `edit_paths` signal from an active session, which fabric-import does not have.
|
|
391
646
|
- Narrowing of imported entries happens out-of-band through `fab_review action="modify"` (issued by user via `fabric-review`), NOT inside this skill.
|
|
392
|
-
- MUST preserve protected tokens exactly: `stable_id`, `pending_path`, `layer`, `team`, `personal`, `knowledge_proposed`, `fab_extract_knowledge`, `fab_review`, `MUST`, `NEVER`, `phase`, `.import-state.json`, `relevance_scope`, `relevance_paths`, `broad`, `narrow`.
|
|
647
|
+
- MUST preserve protected tokens exactly: `stable_id`, `pending_path`, `layer`, `team`, `personal`, `knowledge_proposed`, `fab_extract_knowledge`, `fab_review`, `MUST`, `NEVER`, `phase`, `.import-state.json`, `relevance_scope`, `relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`, `session_context`.
|
|
393
648
|
|
|
394
649
|
## Output Contract
|
|
395
650
|
|
|
396
|
-
After Phase 3 completes (or on any phase exit due to cap / error / interrupt), the skill MUST produce a roll-up
|
|
651
|
+
After Phase 3 completes (or on any phase exit due to cap / error / interrupt), the skill MUST produce a roll-up. UX i18n Policy class 1 — render either the en variant or the zh-CN variant per `knowledge_language`; the protected tokens (`relevance_scope`, `relevance_paths`, `broad`, `pending_path`, `layer`, `team`, `personal`, `fab_review`, `.fabric/.import-state.json`, etc.) appear verbatim in BOTH variants.
|
|
652
|
+
|
|
653
|
+
en variant (`knowledge_language === "en"`):
|
|
397
654
|
|
|
398
655
|
```md
|
|
399
656
|
# Import Summary — phase=<P1-done | P2-done | complete>
|
|
@@ -421,6 +678,34 @@ After Phase 3 completes (or on any phase exit due to cap / error / interrupt), t
|
|
|
421
678
|
- If any kept entry is actually narrow-scoped, narrow it via `fab_review action="modify"` with `changes.relevance_scope="narrow"` + `changes.relevance_paths=[...]` (this skill cannot narrow — see Mandatory Scope Rule in Phase 2).
|
|
422
679
|
```
|
|
423
680
|
|
|
681
|
+
zh-CN variant (`knowledge_language === "zh-CN"`):
|
|
682
|
+
|
|
683
|
+
```md
|
|
684
|
+
# Import 汇总 — phase=<P1-done | P2-done | complete>
|
|
685
|
+
|
|
686
|
+
## Phase 2 — 挖掘
|
|
687
|
+
- 扫描 commit 数: <N> (跳过: <S> — cosmetic/metadata/与 baseline 重叠)
|
|
688
|
+
- 扫描文档数: <D> (跳过: <DS> — README/CHANGELOG/样板文件)
|
|
689
|
+
- 提议 pending: <P> (cap_reached: <true|false>)
|
|
690
|
+
- 作用域: 全部 <P> 条提议使用 relevance_scope=broad, relevance_paths=[] (fabric-import 契约)。
|
|
691
|
+
|
|
692
|
+
## Phase 3 — 去重
|
|
693
|
+
- 保留 (新知识): <K>
|
|
694
|
+
- 已驳回 (重复): <RD>
|
|
695
|
+
- 修改后驳回: <MR> (被合入 evidence 的 canonical 条目: <stable_ids 列表>)
|
|
696
|
+
- 已标记冲突: <C> (需手动通过 fabric-review 解决)
|
|
697
|
+
|
|
698
|
+
## 状态
|
|
699
|
+
- .fabric/.import-state.json phase: <phase>
|
|
700
|
+
- last_checkpoint_at: <ISO8601>
|
|
701
|
+
- 若 phase != complete, 重新调用以继续。
|
|
702
|
+
|
|
703
|
+
## 后续操作
|
|
704
|
+
- 运行 `fabric-review` 审批 <K> 条保留 pending。
|
|
705
|
+
- 若有冲突 (<C>), 请手动处理。
|
|
706
|
+
- 若某条 pending 实际是 narrow 作用域,通过 `fab_review action="modify"` 配合 `changes.relevance_scope="narrow"` + `changes.relevance_paths=[...]` 收紧 (本 skill 不会自动收紧 — 见 Phase 2 的 Mandatory Scope Rule)。
|
|
707
|
+
```
|
|
708
|
+
|
|
424
709
|
Also surface a one-line `git status` of `.fabric/knowledge/` so the user sees the new pending files appear (and any canonical files modified by dedup-merge).
|
|
425
710
|
|
|
426
711
|
## Worked Examples
|
|
@@ -442,7 +727,7 @@ mcp__fabric__fab_extract_knowledge({
|
|
|
442
727
|
slug: "retry-without-backoff-thundering-herd",
|
|
443
728
|
relevance_scope: "broad", // MANDATORY
|
|
444
729
|
relevance_paths: [], // MANDATORY — do NOT infer ["packages/server/src/lib/retry.ts"]
|
|
445
|
-
proposed_reason: "
|
|
730
|
+
proposed_reason: "diagnostic-then-fix", // Step 2.1.5: body describes long diagnostic chain (no-backoff → thundering-herd outage → root cause) → root-cause fix; this overrides the `feat(` prefix per the "body content wins over prefix" ambiguity rule.
|
|
446
731
|
session_context: "Imported from git log analysis. Origin: commit 50367b5 (feat(server): add custom retry logic). No live session — see commit body for full context."
|
|
447
732
|
})
|
|
448
733
|
```
|
|
@@ -553,8 +838,13 @@ Key invariants of this flow:
|
|
|
553
838
|
|
|
554
839
|
- **Phase 2 mid-run failure** (e.g. `fab_extract_knowledge` errors on commit 5 of 10): state already records commits 1–4; rerun resumes at commit 5 by skipping any sha in `p2_processed_commits[]`. Error appended to `errors[]`.
|
|
555
840
|
- **Phase 3 mid-run failure** (e.g. `fab_review action="search"` MCP timeout on dedup pair 3 of 7): state records pairs 1–2 in `p3_dedup_completed[]`; rerun resumes at pair 3.
|
|
556
|
-
- **Cumulative `errors[].length > 5`**: skill halts; asks free-text
|
|
557
|
-
-
|
|
558
|
-
-
|
|
841
|
+
- **Cumulative `errors[].length > 5`**: skill halts; asks free-text confirmation (UX i18n Policy class 3 — confirmation prompts):
|
|
842
|
+
- zh-CN: `继续 (y) / 中止并保留 state (n)`
|
|
843
|
+
- en: `Continue (y) / Abort and keep state (n)`
|
|
844
|
+
- **State file corruption**: handled by Phase 0.1 — Phase 0.1 detects corruption (JSON parse error / missing required fields / phase enum violation), renames the file to `.fabric/.import-state.json.corrupt-<ISO8601>`, and restarts from Phase 1. See "Phase 0.1 — State Corruption Recovery" above.
|
|
845
|
+
- **MCP tool unreachable**: skill halts before any work; surfaces (UX i18n Policy class 2 — errors/preconditions):
|
|
846
|
+
- zh-CN: `MCP 工具未注册;请检查 fabric server 是否运行`
|
|
847
|
+
- en: `MCP tool not registered; please check that the fabric server is running`
|
|
848
|
+
…and exits without writing state.
|
|
559
849
|
|
|
560
850
|
The skill is `Check-not-Ask` for recovery: it inspects state and resumes deterministically; it does NOT ask the user where to resume from.
|