@fenglimg/fabric-cli 2.0.1 → 2.1.0-rc.2
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-D25XJ4BC.js → chunk-F46ORPOA.js} +23 -0
- package/dist/chunk-HFQVXY6P.js +86 -0
- package/dist/chunk-L4Q55UC4.js +52 -0
- package/dist/chunk-LFIKMVY7.js +27 -0
- package/dist/chunk-RYAFBNES.js +33 -0
- package/dist/chunk-T5RPGCCM.js +40 -0
- package/dist/chunk-WU6GAPKH.js +36 -0
- package/dist/{doctor-EJDSEJSS.js → doctor-QVNPHLJK.js} +111 -1
- package/dist/index.js +12 -4
- package/dist/{install-EKWMFLUU.js → install-2HDO5FTQ.js} +242 -51
- package/dist/scope-explain-2F2R5URO.js +33 -0
- package/dist/status-GLQWLWH6.js +23 -0
- package/dist/store-XTSE5TY6.js +105 -0
- package/dist/sync-BJCWDPNC.js +245 -0
- package/dist/{uninstall-MH7ZIB6M.js → uninstall-TAXSUSKH.js} +14 -5
- package/dist/whoami-B6AEMSEV.js +31 -0
- package/package.json +3 -3
- package/templates/hooks/fabric-hint.cjs +40 -0
- package/templates/hooks/knowledge-hint-broad.cjs +40 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +39 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
- package/templates/hooks/lib/cite-contract-reminder.cjs +15 -9
- package/templates/hooks/lib/cite-line-parser.cjs +48 -26
- package/templates/skills/fabric-archive/SKILL.md +4 -0
- package/templates/skills/fabric-import/SKILL.md +4 -0
- package/templates/skills/fabric-review/SKILL.md +4 -0
- package/templates/skills/fabric-sync/SKILL.md +46 -0
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
// auto-ships to cc/codex/cursor with no install pipeline change.
|
|
19
19
|
//
|
|
20
20
|
// Vocabulary contract (mirrored 1:1 with the TS source):
|
|
21
|
-
// - cite_tags enum:
|
|
21
|
+
// - cite_tags enum: applied | dismissed | none (rc.37 NEW-1 2-state vocab).
|
|
22
|
+
// v2.1.0-rc.1 (ADJ-P4-1, full remap): legacy rc≤36 tags (planned / recalled
|
|
23
|
+
// / chained-from) are REMAPPED to `applied` here — accepted as input but no
|
|
24
|
+
// longer emitted verbatim, so the TS source and this twin stay in lockstep.
|
|
22
25
|
// - operator kinds: edit | not_edit | require | forbid
|
|
23
26
|
// (source token `!edit:` → schema kind `not_edit`)
|
|
24
27
|
// - skip:<reason> captures everything after the first colon, so
|
|
@@ -29,31 +32,44 @@
|
|
|
29
32
|
const ID_RE = /^K[TP]-[A-Z]+-\d+$/;
|
|
30
33
|
const SENTINEL_RE = /^KB:\s*none\b\s*(?:\[[^\]]*\])?\s*$/i;
|
|
31
34
|
// v2.0.0-rc.27 TASK-003 (audit §2.18): multi-id citations supported via
|
|
32
|
-
// comma-separated ID group.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
// comma-separated ID group. v2.1.0-rc.1 P4 (F3/S62): each id may carry an
|
|
36
|
+
// optional `<store>:` prefix. Mirrors packages/shared/src/cite-line-parser.ts.
|
|
37
|
+
const QUALIFIED_ID = "(?:[^\\s,:]+:)?K[TP]-[A-Z]+-\\d+";
|
|
38
|
+
const FULL_RE = new RegExp(
|
|
39
|
+
"^KB:\\s+(" +
|
|
40
|
+
QUALIFIED_ID +
|
|
41
|
+
"(?:\\s*,\\s*" +
|
|
42
|
+
QUALIFIED_ID +
|
|
43
|
+
")*)(?:\\s+\\(([^)]*)\\))?(?:\\s+\\[([^\\]]+)\\])?(?:\\s+→\\s*(.+))?\\s*$",
|
|
44
|
+
);
|
|
35
45
|
const CHAINED_FROM_ID_RE = /chained-from\s+(K[TP]-[A-Z]+-\d+)/i;
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Split `<store>:<id>` into qualifier + local id; bare id → null qualifier.
|
|
48
|
+
function splitStorePrefix(token) {
|
|
49
|
+
const colon = token.lastIndexOf(":");
|
|
50
|
+
return colon === -1
|
|
51
|
+
? { store: null, id: token }
|
|
52
|
+
: { store: token.slice(0, colon), id: token.slice(colon + 1) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// v2.1.0-rc.1 (ADJ-P4-1, full remap): legacy rc≤36 tags collapse to `applied`.
|
|
56
|
+
// Mirrors LEGACY_CITE_TAG_REMAP / normalizeCiteTag in the TS source — accepted
|
|
57
|
+
// as input but emitted as the 2-state vocab so cite-coverage never undercounts.
|
|
58
|
+
const LEGACY_CITE_TAG_REMAP = {
|
|
59
|
+
planned: "applied",
|
|
60
|
+
recalled: "applied",
|
|
61
|
+
"chained-from": "applied",
|
|
62
|
+
};
|
|
50
63
|
|
|
51
64
|
function parseTag(rawTag) {
|
|
52
65
|
if (!rawTag) return "none";
|
|
53
66
|
// Tags may carry tails like `chained-from KT-DEC-0001` or
|
|
54
67
|
// `dismissed:scope-mismatch`; head token (whitespace/colon-bounded) wins.
|
|
55
68
|
const head = rawTag.trim().split(/[\s:]+/)[0].toLowerCase();
|
|
56
|
-
|
|
69
|
+
if (head === "applied" || head === "dismissed" || head === "none") {
|
|
70
|
+
return head;
|
|
71
|
+
}
|
|
72
|
+
return LEGACY_CITE_TAG_REMAP[head] || "none";
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
function parseContractTail(tail) {
|
|
@@ -89,17 +105,21 @@ function parseLine(line) {
|
|
|
89
105
|
const trimmed = line.trim();
|
|
90
106
|
if (trimmed.length === 0) return null;
|
|
91
107
|
if (SENTINEL_RE.test(trimmed)) {
|
|
92
|
-
return { ids: [], tag: "none", commitment: null };
|
|
108
|
+
return { ids: [], stores: [], tag: "none", commitment: null };
|
|
93
109
|
}
|
|
94
110
|
const fullMatch = trimmed.match(FULL_RE);
|
|
95
111
|
if (fullMatch) {
|
|
96
112
|
// v2.0.0-rc.27 TASK-003 (audit §2.18): split + revalidate each id;
|
|
97
|
-
// capture chained-from tail id when present.
|
|
98
|
-
|
|
113
|
+
// capture chained-from tail id when present. v2.1.0-rc.1 P4 (F3): strip +
|
|
114
|
+
// surface any `<store>:` prefix into a parallel stores array.
|
|
115
|
+
const split = fullMatch[1]
|
|
99
116
|
.split(",")
|
|
100
117
|
.map((part) => part.trim())
|
|
101
|
-
.filter((part) => part.length > 0)
|
|
102
|
-
|
|
118
|
+
.filter((part) => part.length > 0)
|
|
119
|
+
.map(splitStorePrefix);
|
|
120
|
+
if (split.some((entry) => !ID_RE.test(entry.id))) return null;
|
|
121
|
+
const primaryIds = split.map((entry) => entry.id);
|
|
122
|
+
const primaryStores = split.map((entry) => entry.store);
|
|
103
123
|
|
|
104
124
|
const rawTag = fullMatch[3];
|
|
105
125
|
const tag = parseTag(rawTag);
|
|
@@ -114,6 +134,7 @@ function parseLine(line) {
|
|
|
114
134
|
|
|
115
135
|
return {
|
|
116
136
|
ids: primaryIds.concat(chainedIds),
|
|
137
|
+
stores: primaryStores.concat(chainedIds.map(() => null)),
|
|
117
138
|
tag,
|
|
118
139
|
commitment: parseContractTail(fullMatch[4]),
|
|
119
140
|
};
|
|
@@ -131,14 +152,15 @@ function parseLine(line) {
|
|
|
131
152
|
* embedded id as an additional cite_id. cite_tags carries one tag per LINE.
|
|
132
153
|
*/
|
|
133
154
|
function parseCiteLine(raw) {
|
|
134
|
-
const result = { cite_ids: [], cite_tags: [], cite_commitments: [] };
|
|
155
|
+
const result = { cite_ids: [], cite_tags: [], cite_commitments: [], cite_stores: [] };
|
|
135
156
|
if (typeof raw !== "string") return result;
|
|
136
157
|
for (const line of raw.split(/\r?\n/)) {
|
|
137
158
|
const parsed = parseLine(line);
|
|
138
159
|
if (!parsed) continue;
|
|
139
160
|
result.cite_tags.push(parsed.tag);
|
|
140
|
-
for (
|
|
141
|
-
result.cite_ids.push(
|
|
161
|
+
for (let i = 0; i < parsed.ids.length; i += 1) {
|
|
162
|
+
result.cite_ids.push(parsed.ids[i]);
|
|
163
|
+
result.cite_stores.push(parsed.stores[i] == null ? null : parsed.stores[i]);
|
|
142
164
|
}
|
|
143
165
|
if (parsed.commitment !== null) {
|
|
144
166
|
// v2.0.0-rc.27.1 (Codex review fix): cite_commitments MUST be index-
|
|
@@ -43,6 +43,10 @@ Parse user's prompt for time-window (`今日` / `last week`), topic keyword (`rc
|
|
|
43
43
|
|
|
44
44
|
Read `.fabric/fabric-config.json`; resolve `archive_max_candidates_per_batch` (default 8), `archive_max_recent_paths` (default 20), `archive_digest_max_sessions` (default 10). Missing file → defaults silently.
|
|
45
45
|
|
|
46
|
+
### Phase 0.6 — Store routing (v2.1 multi-store)
|
|
47
|
+
|
|
48
|
+
Archives land in the **active write store** for the entry's scope — NEVER pick a store yourself. Run `fabric scope-explain team` (or the relevant scope) to get the resolved `writeTarget` (alias + UUID); that is where `fab_extract_knowledge` persists. Single-store / no global config → the lone store (back-compat). After persisting, **echo the target store alias** to the user (`归档到 store '<alias>'`). Personal-scope entries route to the personal store (never the shared team store, R5#3). Do NOT read `~/.fabric` store trees directly — go through `scope-explain` / the MCP write path.
|
|
49
|
+
|
|
46
50
|
### UX i18n Policy
|
|
47
51
|
|
|
48
52
|
Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`); emit user-facing prose in resolved variant. Protected tokens (MCP tool names, schema fields, the verbatim `强 team` / `强 personal` / `默认 team` heuristic) NEVER translated. `AskUserQuestion` policy: `header` + `question` translate; `options[]` stay English (routing keys).
|
|
@@ -40,6 +40,10 @@ Read `.fabric/fabric-config.json` for tunables (defaults if absent):
|
|
|
40
40
|
|
|
41
41
|
First-run vs re-run by state file (ENOENT or `phase != complete && proposed == 0` → first-run window).
|
|
42
42
|
|
|
43
|
+
### Store routing (v2.1 multi-store)
|
|
44
|
+
|
|
45
|
+
Import requires an **explicit target store** (E7) — mined entries are NOT auto-routed. Resolve candidates via `fabric scope-explain team` (writable stores in the read-set); if more than one writable store exists, `AskUserQuestion` for the target store alias before persisting (header/question translate, the alias options stay English routing keys). Single writable store → use it. Persist through `fab_extract_knowledge` with the chosen store; echo the target alias. Never write to a store the project did not declare (read-set bound).
|
|
46
|
+
|
|
43
47
|
## UX i18n Policy
|
|
44
48
|
|
|
45
49
|
Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`). Emit prose per variant. Protected tokens NEVER translate (`fab_extract_knowledge`, `fab_review`, `.fabric/.import-state.json`, all enum strings, `MUST`/`NEVER`). Full 5-class taxonomy → `Read .../ref/i18n-policy.md`.
|
|
@@ -40,6 +40,10 @@ Read `.fabric/fabric-config.json`; resolve:
|
|
|
40
40
|
|
|
41
41
|
Missing or unreadable → defaults silently.
|
|
42
42
|
|
|
43
|
+
### Store routing (v2.1 multi-store)
|
|
44
|
+
|
|
45
|
+
Review iterates **per-store** — the read-set may span multiple stores (`fabric scope-explain team` → resolved `readSet.stores`). Pending/backlog is reported per-store (NOT aggregated into one undifferentiated pile); each candidate's provenance store is surfaced in cites as `KB: <store-alias>:<id>`. Promotion (draft → verified/proven) is a normal edit + git commit **inside that store's own repo** — no cross-store move. A `dismissed`/`modify` that flips layer between team and personal still goes through `AskUserQuestion`. Never read `~/.fabric` store trees directly; go through the MCP recall path / `scope-explain`.
|
|
46
|
+
|
|
43
47
|
### UX i18n Policy
|
|
44
48
|
|
|
45
49
|
Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`); emit user-facing prose in resolved variant. Protected tokens (`fab_review`, `fab_extract_knowledge`, `relevance_scope`, layer/scope enums, `stable_id`, the verbatim `强 team` / `强 personal` / `默认 team` block) NEVER translated. `AskUserQuestion` policy: `header` + `question` translate; `options[]` stay English (routing keys).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fabric-sync
|
|
3
|
+
description: 多 store git 同步辅助 — 遍历挂载的知识 store, pull --rebase + push, AI 辅助解冲突。Triggers 同步知识库/sync stores/fabric-sync/解决 store 冲突/rebase 冲突.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fabric-sync
|
|
7
|
+
|
|
8
|
+
跨多个挂载知识 store 的 git 同步辅助 (v2.1, S46)。CLI `fabric sync` 是事务/状态机引擎;本 skill 是它的 AI 辅助外层:遍历 store、解释每个 store 的同步结果、在 rebase 冲突时辅助用户决断 continue/abort。
|
|
9
|
+
|
|
10
|
+
## Precondition
|
|
11
|
+
|
|
12
|
+
- 已 `fabric install --global` (存在 `~/.fabric` + 全局 store registry)。无全局配置 → 提示先装,停止。
|
|
13
|
+
- 本 skill 不直接读 `~/.fabric` store 树;所有 store 状态经 `fabric sync` / `fabric store list` / `fabric scope-explain` 的 JSON 输出获取 (hook/skill 不自解析 store)。
|
|
14
|
+
|
|
15
|
+
## Phase 0 — Enumerate stores
|
|
16
|
+
|
|
17
|
+
`fabric store list` 拿到挂载的 store (alias / uuid / remote)。仅 remote-backed store 参与同步;local-only store 跳过 (无可推/拉),但提示「local-only — 加 remote 备份」(R5#5)。
|
|
18
|
+
|
|
19
|
+
## Phase 1 — Run sync
|
|
20
|
+
|
|
21
|
+
执行 `fabric sync`。逐 store 渲染结果 (NOT 聚合成一坨):
|
|
22
|
+
- `synced` — 干净 rebase + push 完成。
|
|
23
|
+
- `offline` — 网络不可达;本地已提交,push 已 defer (S17 offline-first),下次 online 重试。**不报错**。
|
|
24
|
+
- `conflict` — rebase 冲突,sync 暂停并持久化 session。进入 Phase 2。
|
|
25
|
+
|
|
26
|
+
## Phase 2 — AI-assisted conflict resolution (仅冲突时)
|
|
27
|
+
|
|
28
|
+
冲突 store 的工作区停在 rebase 中途。辅助用户:
|
|
29
|
+
1. 展示冲突文件 (store 知识树 `.fabric/knowledge/<type>/*.md` 中的 `<<<<<<<`/`=======`/`>>>>>>>` 段)。
|
|
30
|
+
2. 对每个冲突,MUST 解释两侧 (ours = 本地草稿/晋升, theirs = 远端协作者) 并给出**合并建议**(知识条目通常可并存或取更成熟 maturity)。NEVER 擅自丢弃任一侧未经用户确认。
|
|
31
|
+
3. 用户解决后 → `fabric sync --continue` (git rebase --continue + 恢复遍历剩余 store)。
|
|
32
|
+
4. 用户选择放弃该 store → `fabric sync --abort` (git rebase --abort,该 store 留未同步,继续遍历其余)。
|
|
33
|
+
|
|
34
|
+
## Phase 3 — Settle
|
|
35
|
+
|
|
36
|
+
所有 store settled (无 pending/conflict) 后:CLI 自动清 session 并重生 `~/.fabric/state/bindings/<id>_resolved.json` 快照 (P3→P4 链)。汇报:已同步 store 数、deferred (offline) store 数、aborted store 数。
|
|
37
|
+
|
|
38
|
+
## UX i18n Policy
|
|
39
|
+
|
|
40
|
+
按 `.fabric/fabric-config.json` 的 `fabric_language` 渲染用户可见文案。Protected tokens (`fabric sync`, `--continue`, `--abort`, `git rebase`, store alias, enum) NEVER translate。
|
|
41
|
+
|
|
42
|
+
## Constraints
|
|
43
|
+
|
|
44
|
+
- Hook/skill **绝不**直接解析 store 或执行 store 内任何文件 (S65 RCE 防线:store 是数据-only)。
|
|
45
|
+
- 冲突合并建议是辅助,最终由用户拍板;不静默丢弃知识。
|
|
46
|
+
- promotion/CR 经普通 git commit,不跨 store 搬运条目。
|