@fenglimg/fabric-cli 2.0.0-rc.30 → 2.0.0-rc.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PNRWNUFX.js → chunk-5N3KXIVI.js} +73 -4
- package/dist/{doctor-TTDTKOFJ.js → doctor-E26YO67D.js} +8 -2
- package/dist/index.js +4 -4
- package/dist/{install-OEBNSCS5.js → install-XCRX34CX.js} +4 -2
- package/dist/{uninstall-VLLJG7JT.js → uninstall-Q7V55BXH.js} +1 -1
- package/package.json +3 -3
- package/templates/hooks/cite-policy-evict.cjs +242 -0
- package/templates/hooks/configs/claude-code.json +11 -0
- package/templates/hooks/fabric-hint.cjs +11 -1
- package/templates/hooks/knowledge-hint-broad.cjs +276 -21
- package/templates/hooks/knowledge-hint-narrow.cjs +466 -14
- package/templates/skills/fabric-archive/SKILL.md +53 -864
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +5 -5
- package/templates/skills/fabric-archive/ref/i18n-policy.md +3 -3
- package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
- package/templates/skills/fabric-archive/ref/{phase-0-4-onboard.md → phase-1-5-onboard.md} +21 -21
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +60 -0
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +54 -0
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +80 -0
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
- package/templates/skills/fabric-archive/ref/rc-history.md +6 -6
- package/templates/skills/fabric-archive/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-import/SKILL.md +29 -556
- package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
- package/templates/skills/fabric-import/ref/output-contract.md +61 -0
- package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
- package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
- package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
- package/templates/skills/fabric-review/SKILL.md +56 -414
- package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
- package/templates/skills/fabric-review/ref/modify-flow.md +95 -0
- package/templates/skills/fabric-review/ref/output-contract.md +58 -0
- package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
- package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Checkpoint Logic — `.fabric/.import-state.json` (ref)
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
The state file lives at `.fabric/.import-state.json` and is the single source of resumability for fabric-import. It is written via the explicit 2-step atomic pattern documented below so a crash between phases / between sub-steps never corrupts it.
|
|
6
|
+
|
|
7
|
+
## Atomic State Write (2-step pattern)
|
|
8
|
+
|
|
9
|
+
**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):
|
|
10
|
+
|
|
11
|
+
- **Step A**: `Write` tool → `.fabric/.import-state.json.tmp` (full JSON content; never partial / never appended).
|
|
12
|
+
- **Step B**: `Bash` → `mv .fabric/.import-state.json.tmp .fabric/.import-state.json`.
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
Crash safety expectations:
|
|
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.
|
|
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
|
+
- Crash before Step A → no state mutation occurred; prior state file is unchanged.
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
## events.jsonl Constraint Note
|
|
25
|
+
|
|
26
|
+
Event lines appended to `.fabric/events.jsonl` are subject to POSIX single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk interleaved corruption under concurrent skill + server writes to the same ledger.
|
|
27
|
+
|
|
28
|
+
Skills MUST ensure:
|
|
29
|
+
|
|
30
|
+
- Each event JSON line is a **single line** (no embedded newlines; escape `\n` in any string value).
|
|
31
|
+
- `session_context` and other free-form text fields **self-truncate** to keep the entire serialized line under 4KB. Suggested per-field caps: `session_context` first 500 chars; `source_sessions` cap at 5 entries; `recent_paths` cap at 20 entries; `user_messages_summary` first 500 chars.
|
|
32
|
+
- If approaching the 4KB ceiling after the per-field caps, drop optional fields (e.g. tags / extra metadata) **before** truncating semantic content.
|
|
33
|
+
- This constraint applies to any event the skill itself appends; MCP-server-side appends (via `appendEventLedgerEvent`) are already line-length-bounded server-side.
|
|
34
|
+
|
|
35
|
+
## Schema (all fields)
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"phase": "P1-done | P2-done | complete",
|
|
40
|
+
"started_at": "<ISO8601 first invocation>",
|
|
41
|
+
"last_checkpoint_at": "<ISO8601 most recent successful sub-step>",
|
|
42
|
+
"p1_baseline_titles": ["<title1>", "<title2>"],
|
|
43
|
+
"p2_processed_commits": [
|
|
44
|
+
{ "sha": "<full sha>", "skipped": true,
|
|
45
|
+
"skip_reason": "cosmetic | metadata-only | already-in-baseline | unclassifiable | overlong-slug" },
|
|
46
|
+
{ "sha": "<full sha>", "skipped": false,
|
|
47
|
+
"pending_path": "knowledge/pending/<type>/<slug>.md",
|
|
48
|
+
"type": "<one of 5>", "slug": "<kebab-case-slug>" }
|
|
49
|
+
],
|
|
50
|
+
"p2_processed_docs": [
|
|
51
|
+
{ "path": "docs/<file>.md", "observations_proposed": 2,
|
|
52
|
+
"pending_paths": ["<path1>", "<path2>"] }
|
|
53
|
+
],
|
|
54
|
+
"p2_cap_reached": false,
|
|
55
|
+
"p3_dedup_completed": [
|
|
56
|
+
{ "pending_path": "<new pending path>",
|
|
57
|
+
"action": "reject | modify-then-reject | kept",
|
|
58
|
+
"canonical_ref": "<stable_id or null>" }
|
|
59
|
+
],
|
|
60
|
+
"errors": [
|
|
61
|
+
{ "step": "P2.git", "ref": "<commit sha or doc path>", "error": "<message>" }
|
|
62
|
+
],
|
|
63
|
+
"final_summary": {
|
|
64
|
+
"proposed": 0, "kept": 0, "rejected_dup": 0, "merged": 0, "contradictions_flagged": 0
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Resume Logic (Idempotent Re-Invocation)
|
|
70
|
+
|
|
71
|
+
On every skill invocation, BEFORE Phase 1 starts:
|
|
72
|
+
|
|
73
|
+
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).
|
|
74
|
+
2. If `phase === "complete"` AND `last_checkpoint_at < 24h ago` → SKIP this invocation (precondition warning) unless user explicitly typed `re-run import` or `reset import`.
|
|
75
|
+
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:
|
|
76
|
+
|
|
77
|
+
- zh-CN: `上次 import 已完成 (<N> 天前)。重新运行将基于当前 canonical 重做 P2/P3。继续?(y/n)`
|
|
78
|
+
- en: `Last import completed (<N> days ago). Re-running will redo P2/P3 against the current canonical set. Continue? (y/n)`
|
|
79
|
+
|
|
80
|
+
If `n`, exit.
|
|
81
|
+
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[]`.
|
|
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
|
+
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
|
+
|
|
85
|
+
The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, network blip on MCP) MUST NOT propose duplicates of already-proposed entries and MUST NOT redo already-completed dedup decisions.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Output Contract (ref)
|
|
2
|
+
|
|
3
|
+
> **Loaded on demand.** SKILL.md hot path mentions the roll-up requirement + section names. This file holds the full bilingual roll-up templates rendered after Phase 3 completes (or on any phase exit due to cap / error / interrupt).
|
|
4
|
+
|
|
5
|
+
UX i18n Policy class 1 — render either the en variant or the zh-CN variant per `fabric_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.
|
|
6
|
+
|
|
7
|
+
## en variant (`fabric_language === "en"`)
|
|
8
|
+
|
|
9
|
+
```md
|
|
10
|
+
# Import Summary — phase=<P1-done | P2-done | complete>
|
|
11
|
+
|
|
12
|
+
## Phase 2 — Mining
|
|
13
|
+
- Commits scanned: <N> (skipped: <S> — cosmetic/metadata/baseline-overlap)
|
|
14
|
+
- Docs scanned: <D> (skipped: <DS> — README/CHANGELOG/boilerplate)
|
|
15
|
+
- Pending proposed: <P> (cap_reached: <true|false>)
|
|
16
|
+
- Scope: all <P> proposed entries use relevance_scope=broad, relevance_paths=[] (fabric-import contract).
|
|
17
|
+
|
|
18
|
+
## Phase 3 — Dedup
|
|
19
|
+
- Kept (genuinely new): <K>
|
|
20
|
+
- Rejected (duplicate): <RD>
|
|
21
|
+
- Modified-then-rejected: <MR> (canonical entries enriched: <list of stable_ids>)
|
|
22
|
+
- Contradictions flagged: <C> (require manual fabric-review)
|
|
23
|
+
|
|
24
|
+
## State
|
|
25
|
+
- .fabric/.import-state.json phase: <phase>
|
|
26
|
+
- last_checkpoint_at: <ISO8601>
|
|
27
|
+
- Re-invoke to continue if phase != complete.
|
|
28
|
+
|
|
29
|
+
## Next Steps
|
|
30
|
+
- Run `fabric-review` to approve the <K> kept pending entries.
|
|
31
|
+
- Resolve <C> contradictions manually if any.
|
|
32
|
+
- 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).
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## zh-CN variant (`fabric_language === "zh-CN"`)
|
|
36
|
+
|
|
37
|
+
```md
|
|
38
|
+
# Import 汇总 — phase=<P1-done | P2-done | complete>
|
|
39
|
+
|
|
40
|
+
## Phase 2 — 挖掘
|
|
41
|
+
- 扫描 commit 数: <N> (跳过: <S> — cosmetic/metadata/与 baseline 重叠)
|
|
42
|
+
- 扫描文档数: <D> (跳过: <DS> — README/CHANGELOG/样板文件)
|
|
43
|
+
- 提议 pending: <P> (cap_reached: <true|false>)
|
|
44
|
+
- 作用域: 全部 <P> 条提议使用 relevance_scope=broad, relevance_paths=[] (fabric-import 契约)。
|
|
45
|
+
|
|
46
|
+
## Phase 3 — 去重
|
|
47
|
+
- 保留 (新知识): <K>
|
|
48
|
+
- 已驳回 (重复): <RD>
|
|
49
|
+
- 修改后驳回: <MR> (被合入 evidence 的 canonical 条目: <stable_ids 列表>)
|
|
50
|
+
- 已标记冲突: <C> (需手动通过 fabric-review 解决)
|
|
51
|
+
|
|
52
|
+
## 状态
|
|
53
|
+
- .fabric/.import-state.json phase: <phase>
|
|
54
|
+
- last_checkpoint_at: <ISO8601>
|
|
55
|
+
- 如 phase != complete, 请重新调用 fabric-import 续作。
|
|
56
|
+
|
|
57
|
+
## 下一步
|
|
58
|
+
- 运行 `fabric-review` 审批 <K> 条新 pending。
|
|
59
|
+
- 手动解决 <C> 条 contradictions 标记 (如有)。
|
|
60
|
+
- 若某条 kept 条目实际是 narrow-scoped, 通过 `fab_review action="modify"` 配 `changes.relevance_scope="narrow"` + `changes.relevance_paths=[...]` 收窄 (本 skill 无法收窄 — 见 Phase 2 Mandatory Scope Rule)。
|
|
61
|
+
```
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Phase 2 — LLM-Driven Git + Doc Mining (ref)
|
|
2
|
+
|
|
3
|
+
> **Loaded on demand.** SKILL.md hot path retains the broad+[] contract one-liner, brief Step 2.1/2.2 summaries, and a pointer to this file. This file holds: full Mandatory Scope Rule rationale + strict prohibitions, Step 2.1 git mining call shape + 6 conventional-commit signals, Step 2.1.5 Proposed Reason Inference table, Step 2.2 docs mining filter list + call shape, Skip Decision Tree, bilingual dry-run preview templates, and T5 array-form idempotency notes.
|
|
4
|
+
|
|
5
|
+
## Mandatory Scope Rule — Always Broad + Empty Paths (Q-1 Resolution)
|
|
6
|
+
|
|
7
|
+
**EVERY `fab_extract_knowledge` call issued from this skill MUST set:**
|
|
8
|
+
|
|
9
|
+
- `relevance_scope = "broad"`
|
|
10
|
+
- `relevance_paths = []`
|
|
11
|
+
|
|
12
|
+
This is non-negotiable and applies to BOTH Step 2.1 (git mining) AND Step 2.2 (docs mining). No exceptions, no per-candidate override, no Agent judgment.
|
|
13
|
+
|
|
14
|
+
**Rationale — why fabric-import cannot bind paths from git history:**
|
|
15
|
+
|
|
16
|
+
1. `fabric-import` is LLM-driven (mines git log + docs), not session-driven (no live `edit_paths` signal).
|
|
17
|
+
2. `git diff --stat` lists files touched by a commit, but those files are the commit's **effect surface**, not the **applicability surface** of the underlying observation. A pitfall surfaced by a fix in `packages/server/src/retry.ts` may apply to every retry call-site in the repo, not just that one file.
|
|
18
|
+
3. LLM-inferred `relevance_paths` from historical commit metadata produces false-narrow bindings — entries get filtered out for paths they actually govern. False-narrow is worse than broad because it silently hides knowledge during plan-context filtering.
|
|
19
|
+
4. Doc-mined observations are usually architectural / cross-cutting (a `docs/architecture.md` "Why a monolith?" decision applies to the whole codebase, not just to `docs/`).
|
|
20
|
+
|
|
21
|
+
**Strict prohibitions — DO NOT attempt any of the following:**
|
|
22
|
+
|
|
23
|
+
- DO NOT derive `relevance_paths` from `git log --name-only` / `git show --stat` / `git diff` file lists.
|
|
24
|
+
- DO NOT derive `relevance_paths` from the path of a mined Markdown file (e.g. do NOT bind a `docs/architecture.md` observation to `["docs/**"]`).
|
|
25
|
+
- DO NOT extract path-shaped tokens from commit subjects / bodies / doc text and lift them into `relevance_paths`.
|
|
26
|
+
- DO NOT classify a candidate as `relevance_scope = "narrow"` under ANY heuristic.
|
|
27
|
+
- DO NOT copy the public-prefix-generalization logic from fabric-archive Phase 3.5 — that logic is valid only when bound to a real-time `edit_paths` signal from an active session, which fabric-import lacks.
|
|
28
|
+
|
|
29
|
+
**Cross-reference — fabric-import vs fabric-archive scope handling:**
|
|
30
|
+
|
|
31
|
+
| Skill | Scope decision | Why |
|
|
32
|
+
|------------------|--------------------|-----------------------------------------------------------------------|
|
|
33
|
+
| `fabric-archive` | narrow OR broad, case-by-case per Phase 3.5 rules | Has live `edit_paths` from the active session — the actual applicability surface. |
|
|
34
|
+
| `fabric-import` | ALWAYS broad + `[]` (this skill) | LLM-only, no live session signal; git-history paths are effect-surface, not applicability-surface. |
|
|
35
|
+
|
|
36
|
+
`fabric-archive`'s Phase 3.5 scope decision (narrow-vs-broad rules + public-prefix generalization + glob blacklist) is INTENTIONALLY MORE PERMISSIVE than fabric-import because archive has the data to bind safely. fabric-import is the STRICTER case.
|
|
37
|
+
|
|
38
|
+
**Post-import narrowing path — deferred to user, via `fab_review.modify`:**
|
|
39
|
+
|
|
40
|
+
After import completes, the user reviews each kept pending entry via `fabric-review`. When the user judges that an imported entry is actually narrow-scoped, they (or the reviewing Agent on their explicit instruction) issue:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
mcp__fabric__fab_review({
|
|
44
|
+
action: "modify",
|
|
45
|
+
pending_path: "<the imported pending or its post-approval canonical path>",
|
|
46
|
+
changes: {
|
|
47
|
+
relevance_scope: "narrow",
|
|
48
|
+
relevance_paths: ["packages/server/src/retry/**", "packages/server/src/lib/retry.ts"]
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This is the ONLY legal path for an imported entry to acquire `relevance_paths`. The narrowing decision is the user's, informed by the actual `relevance_paths` candidates they propose — not the skill's, inferred from git metadata.
|
|
54
|
+
|
|
55
|
+
**Lint backstop:** doctor lint #23 (`narrow_no_paths`) warns on any `relevance_scope=narrow` entry with empty `relevance_paths`. If this skill ever deviates from the broad+[] rule and writes narrow without paths, lint #23 catches the mistake post-hoc.
|
|
56
|
+
|
|
57
|
+
## Step 2.1 — Git Log Mining
|
|
58
|
+
|
|
59
|
+
Bash command (executed via the `Bash` tool — substitute `<window>` and `<commits-cap>` with values resolved from Phase 0.5 config load):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git log --since="<window> months ago" --pretty=format:"%H%n%s%n%b%n---ENDCOMMIT---" -n <commits-cap>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `<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.
|
|
66
|
+
- `<commits-cap>` resolves to `import_max_commits_scan` (default 500).
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
For each commit:
|
|
71
|
+
|
|
72
|
+
1. Inspect the conventional-commit prefix in the subject line. Strong signals:
|
|
73
|
+
- `feat(...)` with a non-empty body → likely **decision** or **model** (a new capability landed; the body usually explains why)
|
|
74
|
+
- `fix(...)` with body length >100 chars → likely **pitfall** (a bug worth diagnosing was non-trivial)
|
|
75
|
+
- `refactor(...)` with body → likely **decision** (architectural choice was made)
|
|
76
|
+
- `docs(...)` → usually a **guideline** if the body announces a convention; skip if it's just typo/reformat
|
|
77
|
+
- `chore(...)`, `test(...)`, `ci(...)` → almost always skip (mechanical; no reusable insight)
|
|
78
|
+
2. Read the commit body. Extract the LLM-judged "core observation" — what would a future engineer want to know about this commit beyond the diff? Aim for 1–2 sentences in zh-CN (project fabric_language; mirror fabric-archive M3 style).
|
|
79
|
+
3. Apply the **Skip Decision Tree** below. If the commit is skip-worthy, record it in `p2_processed_commits[]` with `skipped: true` and move on.
|
|
80
|
+
4. For non-skipped commits, classify type / propose slug / draft summary. Then call `fab_extract_knowledge` with the **mandatory broad + [] scope** (see "Mandatory Scope Rule" above):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
mcp__fabric__fab_extract_knowledge({
|
|
84
|
+
source_sessions: ["fabric-import-<ISO8601-date>"], // T5: array form; stable per import run
|
|
85
|
+
recent_paths: ["<files touched by this commit, capped at 20>"], // provenance only, NOT a path-binding signal
|
|
86
|
+
user_messages_summary: "<zh-CN 1-2 sentence summary of the commit's core observation; cite the commit sha as 'src=<sha7>'>",
|
|
87
|
+
type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
|
|
88
|
+
slug: "<kebab-case 2-5 words derived from commit subject + body>",
|
|
89
|
+
relevance_scope: "broad", // MANDATORY — never "narrow" from fabric-import
|
|
90
|
+
relevance_paths: [], // MANDATORY — never derived from git history
|
|
91
|
+
proposed_reason: "<inferred per Step 2.1.5 — varies>",
|
|
92
|
+
session_context: "Imported from git log analysis. Origin: commit <sha7> (<subject 30 chars>). No live session — see commit body for full context.",
|
|
93
|
+
// rc.23 TASK-006 four OPTIONAL triage fields — populate from commit body when clean, omit otherwise.
|
|
94
|
+
intent_clues: ["<inferred trigger if commit body suggests one>"],
|
|
95
|
+
tech_stack: ["<lang/framework from recent_paths extensions>"],
|
|
96
|
+
impact: ["<consequence stated in commit body / doc>"],
|
|
97
|
+
must_read_if: "<one-line strongest trigger from commit's touched-path family>"
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Note: `recent_paths` carries the touched-file list for **provenance display** only. It is NOT lifted into `relevance_paths` — those two fields serve different purposes and the prohibition on path inference from git history applies.
|
|
102
|
+
|
|
103
|
+
5. On success the server returns `{pending_path, idempotency_key}`. Append to `.fabric/.import-state.json`:
|
|
104
|
+
- `p2_processed_commits[].push({sha: <full sha>, skipped: false, pending_path, type, slug})`
|
|
105
|
+
- `last_checkpoint_at = <ISO8601 now>`
|
|
106
|
+
Update is atomic via the 2-step `.tmp` + `mv` pattern documented in the **Atomic State Write** section under "Checkpoint Logic" below.
|
|
107
|
+
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.
|
|
108
|
+
|
|
109
|
+
## Step 2.1.5 — Proposed Reason Inference (rc.7 T6)
|
|
110
|
+
|
|
111
|
+
For each non-skipped commit OR doc section, infer `proposed_reason` from prefix + body signal jointly. The 6 reasons below are the full enum accepted by `fab_extract_knowledge` (schema-locked):
|
|
112
|
+
|
|
113
|
+
| Source signal | Body cue | Inferred reason |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `feat(...)` commit | "vs" / "instead of" / "chose" / "rejected X for Y" | `decision-confirmation` |
|
|
116
|
+
| `feat(...)` commit | Announces new dep/lib/abstraction, no alternative cited | `new-dependency-or-pattern` |
|
|
117
|
+
| `fix(...)` commit | Cites wrong direction tried + reverted | `wrong-turn-revert` |
|
|
118
|
+
| `fix(...)` commit | Cites long diagnostic chain → root cause | `diagnostic-then-fix` |
|
|
119
|
+
| `refactor(...)` commit | Cites structural rationale (without "vs" alternatives) | `decision-confirmation` |
|
|
120
|
+
| `docs(...)` commit | Announces convention ("always X" / "never Y") | `explicit-user-mark` |
|
|
121
|
+
| Any commit | Body explicitly rejects an approach + states why | `dismissal-with-reason` |
|
|
122
|
+
| Doc section | "Why we chose X over Y" heading | `decision-confirmation` |
|
|
123
|
+
| Doc section | "Don't do Y because..." section | `dismissal-with-reason` |
|
|
124
|
+
| Doc section | "Always" / "Never" guidelines | `explicit-user-mark` |
|
|
125
|
+
| Doc section | Architecture/design narrative (descriptive, no choice rationale) | `new-dependency-or-pattern` |
|
|
126
|
+
|
|
127
|
+
**Edge cases:**
|
|
128
|
+
|
|
129
|
+
- `chore(` / `test(` / `ci(` should already be skipped per the Skip Decision Tree below; if they slip through, default to `new-dependency-or-pattern`.
|
|
130
|
+
- Ambiguous signals: prefer the reason matching **body content** over **prefix** (a `feat(` with strong revert-language is `wrong-turn-revert`, not `new-dependency-or-pattern`).
|
|
131
|
+
|
|
132
|
+
**Fallback**: when no row clearly applies, use `new-dependency-or-pattern` (the broadest "noticed something new" semantic).
|
|
133
|
+
|
|
134
|
+
## Step 2.2 — Docs Mining
|
|
135
|
+
|
|
136
|
+
Bash command:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
find docs/ -maxdepth 3 -name '*.md' -type f 2>/dev/null
|
|
140
|
+
ls -1 *.md 2>/dev/null # root-level architectural docs
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For each Markdown file:
|
|
144
|
+
|
|
145
|
+
1. **Skip filter**:
|
|
146
|
+
- `README.md` → skip (its first paragraph already lives in init-scan; body too generic for fine-grained classification)
|
|
147
|
+
- `CHANGELOG.md` → skip (rendered from commit log; mining commits already covers it)
|
|
148
|
+
- `LICENSE.md`, `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md` → skip (boilerplate)
|
|
149
|
+
- Files <300 bytes → skip (too thin to extract meaningful observations)
|
|
150
|
+
2. Read the file. Identify candidate observations: section headings that read like decisions ("we chose X over Y"), guidelines ("always do X"), pitfalls ("don't do Y because..."), or process steps ("the deploy procedure is..."). Architecture diagrams in fenced code blocks are strong **model** signals.
|
|
151
|
+
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.
|
|
152
|
+
4. Append to `.fabric/.import-state.json`:
|
|
153
|
+
- `p2_processed_docs[].push({path: <doc path>, observations_proposed: <count>, pending_paths: [...]})`
|
|
154
|
+
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.
|
|
155
|
+
|
|
156
|
+
## Skip Decision Tree
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
A candidate signal surfaces (commit body or doc section).
|
|
160
|
+
├─ Is it cosmetic only? ("fix typo", whitespace, formatting)
|
|
161
|
+
│ └─ YES → skip
|
|
162
|
+
├─ Is the body just metadata? (Co-Authored-By, Signed-off-by, no prose)
|
|
163
|
+
│ └─ YES → skip
|
|
164
|
+
├─ Is the same observation already covered by an init-scan baseline title (Phase 1 list)?
|
|
165
|
+
│ └─ YES → skip (don't re-propose what init already captured)
|
|
166
|
+
├─ Does the observation fit one of {decisions, pitfalls, guidelines, models, processes}?
|
|
167
|
+
│ └─ NO → skip (not classifiable = not yet ripe)
|
|
168
|
+
├─ Is the slug derivable as 2-5 kebab-case words?
|
|
169
|
+
│ └─ NO → skip (signal too vague for stable identifier)
|
|
170
|
+
└─ Else → propose via fab_extract_knowledge
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
After Step 2.2 completes (or hits the cap), update `.fabric/.import-state.json`: `phase = "P2-done"`, `last_checkpoint_at = <ISO8601 now>`.
|
|
174
|
+
|
|
175
|
+
## Dry-Run Mode
|
|
176
|
+
|
|
177
|
+
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 — header + column titles bilingualized; row content (slug / commit sha / doc path) NOT translated. Protected tokens `broad`, `relevance_scope`, `relevance_paths` appear verbatim:
|
|
178
|
+
|
|
179
|
+
### zh-CN variant (`fabric_language === "zh-CN"`)
|
|
180
|
+
|
|
181
|
+
```md
|
|
182
|
+
# Import 预览 — 将提议 N 条 pending 条目(全部 relevance_scope=broad, relevance_paths=[])
|
|
183
|
+
|
|
184
|
+
| # | 来源 | 类型 | Slug | 作用域 | 摘要 (zh-CN) |
|
|
185
|
+
|---|-----------------------|-----------|-------------------------------|--------|-------------------------------------------------------------|
|
|
186
|
+
| 1 | git c0a351d | decisions | layer-flip-id-mutation | broad+[] | layer 切换是唯一合法的 stable_id 变更途径,绑定原子事务。 |
|
|
187
|
+
| 2 | docs/architecture.md | decisions | monolith-over-microservices | broad+[] | 决定保留单体架构,三人团队不值微服务运维成本。 |
|
|
188
|
+
| 3 | git 50367b5 | pitfalls | thundering-herd-no-backoff | broad+[] | 重试无指数回退导致雪崩;必须 jittered exponential backoff。|
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### en variant (`fabric_language === "en"`)
|
|
192
|
+
|
|
193
|
+
```md
|
|
194
|
+
# Import Dry Run — would propose N pending entries (all relevance_scope=broad, relevance_paths=[])
|
|
195
|
+
|
|
196
|
+
| # | Source | Type | Slug | Scope | Summary |
|
|
197
|
+
|---|-----------------------|-----------|-------------------------------|----------|---------------------------------------------------------------|
|
|
198
|
+
| 1 | git c0a351d | decisions | layer-flip-id-mutation | broad+[] | Layer change is the only legal stable_id mutation path; atomic txn. |
|
|
199
|
+
| 2 | docs/architecture.md | decisions | monolith-over-microservices | broad+[] | Keep the monolith — 3-engineer team can't justify microservice ops. |
|
|
200
|
+
| 3 | git 50367b5 | pitfalls | thundering-herd-no-backoff | broad+[] | Retries without exponential backoff caused a thundering herd outage. |
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Every dry-run row MUST show `broad+[]` in the Scope column (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.
|
|
204
|
+
|
|
205
|
+
## Idempotency Note — T5 array form
|
|
206
|
+
|
|
207
|
+
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.
|
|
208
|
+
|
|
209
|
+
**T5 array-form note (rc.7+)**: when `source_sessions` is passed as an array (rc.7 T5 contract), only `source_sessions[0]` participates in the server-side idempotency hash. 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:
|
|
210
|
+
|
|
211
|
+
- Every Phase 2 call uses `source_sessions: ["fabric-import-<ISO8601-date>"]` (single-element array, stable per import run). First-element-only rule means re-runs on the same date produce the same idempotency key per `(type, slug)` → resume-safe by construction.
|
|
212
|
+
- If a future enhancement adds a trailing element (e.g. `["fabric-import-<date>", "<commit-sha>"]`), only the first element participates in the hash — the commit-sha tail would NOT change the idempotency key for the same `(type, slug)`. Plan accordingly.
|
|
213
|
+
- 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.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Phase 3 — LLM-Driven Dedup vs Canonical (ref)
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
For each pending entry created in Phase 2 (read from `p2_processed_commits[].pending_path` and `p2_processed_docs[].pending_paths`), check if it duplicates / contradicts / is subsumed by an existing canonical entry. **Semantic comparison is the LLM's job — `fab_review` does not compare meaning.**
|
|
6
|
+
|
|
7
|
+
## Step 3.1 — Search Canonical of Same Type
|
|
8
|
+
|
|
9
|
+
For each just-proposed pending entry (read its frontmatter via the `Read` tool to get type + slug + title):
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
mcp__fabric__fab_review({
|
|
13
|
+
action: "search",
|
|
14
|
+
query: "<title or summary keywords from the pending entry>",
|
|
15
|
+
filters: { type: "<same type as pending>" }
|
|
16
|
+
})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The server returns ranked `items[]` of CANONICAL entries (not pending) of the same type. Cap the comparison set at the top 5 results.
|
|
20
|
+
|
|
21
|
+
## Step 3.2 — Semantic Compare
|
|
22
|
+
|
|
23
|
+
For each `(pending, canonical)` pair the LLM judges:
|
|
24
|
+
|
|
25
|
+
- **Duplicate** — same essential claim. LLM 主观判断:标题与摘要表达同一核心结论,新 pending 未提供新证据。具体阈值不可量化。Action: **reject** the new pending.
|
|
26
|
+
- **Subsumption** (pending narrower) — canonical fully covers the pending plus more. Action: **reject** the new pending (canonical already serves).
|
|
27
|
+
- **Subsumption-with-novelty** (pending adds evidence) — canonical covers the claim but the new pending brings new evidence (commit sha, file paths). Action: **modify** the canonical to merge in the new evidence; **reject** the new pending citing the modified canonical.
|
|
28
|
+
- **Contradiction** — opposing claims about the same scope. Action: leave pending; flag for user via roll-up. The user must decide via `fabric-review` later — `fabric-import` does NOT auto-resolve contradictions.
|
|
29
|
+
- **Genuinely new** — no canonical match. Action: leave pending in place (will surface in next `fabric-review` run for normal approval flow).
|
|
30
|
+
|
|
31
|
+
## Step 3.3 — Issue Dedup MCP Calls
|
|
32
|
+
|
|
33
|
+
For each `reject`-classified pending:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
mcp__fabric__fab_review({
|
|
37
|
+
action: "reject",
|
|
38
|
+
pending_paths: ["<the new pending path>"],
|
|
39
|
+
reason: "duplicate of <stable_id of canonical>" // OR "subsumed by <stable_id>"
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For each `subsumption-with-novelty` case (modify canonical, then reject pending):
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// Step A: merge new evidence into canonical
|
|
47
|
+
mcp__fabric__fab_review({
|
|
48
|
+
action: "modify",
|
|
49
|
+
pending_path: "<canonical's pending_path-style relative path>",
|
|
50
|
+
changes: { summary: "<merged summary; original + new evidence cite>" }
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Step B: reject the now-superseded pending
|
|
54
|
+
mcp__fabric__fab_review({
|
|
55
|
+
action: "reject",
|
|
56
|
+
pending_paths: ["<the new pending path>"],
|
|
57
|
+
reason: "merged into <stable_id of modified canonical>"
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Append to `.fabric/.import-state.json` after EACH successful MCP call:
|
|
62
|
+
|
|
63
|
+
- `p3_dedup_completed[].push({pending_path: <new pending>, action: "reject" | "modify-then-reject" | "kept", canonical_ref: "<stable_id>" | null})`
|
|
64
|
+
- `last_checkpoint_at = <ISO8601 now>`
|
|
65
|
+
|
|
66
|
+
## Step 3.4 — Phase 3 Completion
|
|
67
|
+
|
|
68
|
+
After all Phase 2 outputs are dedup-reviewed:
|
|
69
|
+
|
|
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`).
|
|
72
|
+
|
|
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
|
+
|
|
75
|
+
The user MAY manually delete `.fabric/.import-state.json` to reset, or the skill MAY offer a one-line "reset state and re-run from scratch?" prompt the next time it is invoked with `phase="complete"` already present.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Worked Examples (ref)
|
|
2
|
+
|
|
3
|
+
> **Loaded on demand.** SKILL.md hot path points here for concrete end-to-end traces. These 4 examples illustrate Phase 2 mining (git + docs), Phase 3 dedup classification, and the out-of-band narrowing path that is NOT performed by this skill.
|
|
4
|
+
|
|
5
|
+
## Example A — Phase 2 git mining: feat commit → pitfall entry
|
|
6
|
+
|
|
7
|
+
Source signal: `git log` surfaces commit `50367b5` with subject `feat(server): add custom retry logic` and body explaining that initial implementation retried without exponential backoff, causing a thundering-herd outage during a brief upstream hiccup; the fix was jittered exponential backoff with a 30s ceiling.
|
|
8
|
+
|
|
9
|
+
LLM analysis: this is a **pitfall** (a non-obvious trap that wasted time and is repeatable across services). The body itself documents the trap. Slug candidates: `retry-without-backoff-thundering-herd` (5 words, 38 chars — passes 5 rules).
|
|
10
|
+
|
|
11
|
+
Skill output (note `relevance_scope: "broad"` + `relevance_paths: []` — mandatory for fabric-import):
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
mcp__fabric__fab_extract_knowledge({
|
|
15
|
+
source_sessions: ["fabric-import-2026-05-10"],
|
|
16
|
+
recent_paths: ["packages/server/src/lib/retry.ts"], // provenance only
|
|
17
|
+
user_messages_summary: "重试无指数退避会在短暂上游故障下放大成雪崩。修正:jittered exponential backoff,30 秒上限。src=50367b5",
|
|
18
|
+
type: "pitfalls",
|
|
19
|
+
slug: "retry-without-backoff-thundering-herd",
|
|
20
|
+
relevance_scope: "broad", // MANDATORY
|
|
21
|
+
relevance_paths: [], // MANDATORY — do NOT infer ["packages/server/src/lib/retry.ts"]
|
|
22
|
+
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.
|
|
23
|
+
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."
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Counter-example — DO NOT do this:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// WRONG — this skill must never produce narrow + paths from git metadata.
|
|
31
|
+
// The retry pitfall applies to every retry site, not just the file touched by 50367b5.
|
|
32
|
+
mcp__fabric__fab_extract_knowledge({
|
|
33
|
+
// ...
|
|
34
|
+
relevance_scope: "narrow", // VIOLATION
|
|
35
|
+
relevance_paths: ["packages/server/src/lib/retry.ts"] // VIOLATION
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the user later judges this pitfall to be narrow-scoped, they (via `fabric-review`) issue `fab_review action="modify"` with `changes.relevance_scope` + `changes.relevance_paths` — that is the legal narrowing path.
|
|
40
|
+
|
|
41
|
+
State file delta:
|
|
42
|
+
```json
|
|
43
|
+
{ "p2_processed_commits": [
|
|
44
|
+
{ "sha": "50367b5...", "skipped": false,
|
|
45
|
+
"pending_path": "knowledge/pending/pitfalls/retry-without-backoff-thundering-herd.md",
|
|
46
|
+
"type": "pitfalls", "slug": "retry-without-backoff-thundering-herd" }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Example B — Phase 2 doc mining: architecture.md → decision entry
|
|
52
|
+
|
|
53
|
+
Source signal: `docs/architecture.md` contains a section heading "## Why a monolith?" with body explaining the team chose monolith over microservices because the 3-engineer team couldn't justify the operational cost of multi-service deploys, and the dominant performance constraint (DB throughput) doesn't benefit from horizontal split.
|
|
54
|
+
|
|
55
|
+
LLM analysis: this is a **decision** (≥2 alternatives weighed — monolith vs microservices — with explicit rationale). Slug candidates: `monolith-over-microservices-small-team` (5 words, 38 chars — passes 5 rules).
|
|
56
|
+
|
|
57
|
+
Skill output (broad+[] mandatory; the doc's own path stays in `recent_paths` for provenance, NOT in `relevance_paths`):
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
mcp__fabric__fab_extract_knowledge({
|
|
61
|
+
source_sessions: ["fabric-import-2026-05-10"],
|
|
62
|
+
recent_paths: ["docs/architecture.md"], // provenance only
|
|
63
|
+
user_messages_summary: "选择单体架构而非微服务:3 人团队无法承担多服务运维成本,且主要性能瓶颈在 DB 吞吐而非应用层水平扩展。src=docs/architecture.md",
|
|
64
|
+
type: "decisions",
|
|
65
|
+
slug: "monolith-over-microservices-small-team",
|
|
66
|
+
relevance_scope: "broad", // MANDATORY
|
|
67
|
+
relevance_paths: [] // MANDATORY — a monolith-vs-microservices decision applies repo-wide, not only to docs/
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Example C — Phase 3 dedup finds duplicate, rejects
|
|
72
|
+
|
|
73
|
+
After Example A's pending entry (`retry-without-backoff-thundering-herd`) is proposed, Phase 3 runs:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
mcp__fabric__fab_review({
|
|
77
|
+
action: "search",
|
|
78
|
+
query: "retry backoff thundering herd",
|
|
79
|
+
filters: { type: "pitfalls" }
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Server returns 1 canonical match: `KT-P-0007--retry-no-jitter-amplification.md` with summary "重试缺少 jitter 在并发场景放大原始故障峰值". LLM judgment: the existing canonical asserts the same essential claim (retry without jitter amplifies failures) — this is a **duplicate**, not subsumption-with-novelty (the new pending offers no new evidence beyond restating the trap).
|
|
84
|
+
|
|
85
|
+
Skill output:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
mcp__fabric__fab_review({
|
|
89
|
+
action: "reject",
|
|
90
|
+
pending_paths: ["knowledge/pending/pitfalls/retry-without-backoff-thundering-herd.md"],
|
|
91
|
+
reason: "duplicate of KT-P-0007"
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
State file delta:
|
|
96
|
+
```json
|
|
97
|
+
{ "p3_dedup_completed": [
|
|
98
|
+
{ "pending_path": "knowledge/pending/pitfalls/retry-without-backoff-thundering-herd.md",
|
|
99
|
+
"action": "reject", "canonical_ref": "KT-P-0007" }
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Final roll-up to user reflects: 1 proposed, 0 kept, 1 rejected_dup, 0 merged, 0 contradictions.
|
|
105
|
+
|
|
106
|
+
## Example D — Post-import narrowing (out-of-band, NOT this skill)
|
|
107
|
+
|
|
108
|
+
This example documents the legal narrowing path; it is NOT performed by `fabric-import` itself. After Example B's `monolith-over-microservices-small-team` decision is imported (with `relevance_scope=broad`, `relevance_paths=[]`) and later approved into canonical via `fabric-review`, the user decides the decision is actually narrow to the server package's deploy tooling.
|
|
109
|
+
|
|
110
|
+
The user issues (via `fabric-review`, NOT via this skill):
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
mcp__fabric__fab_review({
|
|
114
|
+
action: "modify",
|
|
115
|
+
pending_path: "knowledge/team/decisions/monolith-over-microservices-small-team.md",
|
|
116
|
+
changes: {
|
|
117
|
+
relevance_scope: "narrow",
|
|
118
|
+
relevance_paths: ["packages/server/**", "scripts/deploy/**"]
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Key invariants of this flow:
|
|
124
|
+
|
|
125
|
+
- The narrowing decision originates from the **user**, informed by the actual paths they propose — not from `fabric-import` inferring paths from git metadata.
|
|
126
|
+
- The modify call goes through `fab_review`, not `fab_extract_knowledge`, because the entry already exists (post-import or post-approval).
|
|
127
|
+
- If the user later flips the entry's layer from `team` to `personal`, server-side auto-degrades scope back to `broad` and clears `relevance_paths` (see rc.5 C3 acceptance criterion; personal knowledge crosses projects so paths don't generalize). This is the only legal way for `relevance_paths` to be re-cleared.
|