@fenglimg/fabric-cli 2.0.0-rc.11 → 2.0.0-rc.13

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.
@@ -2,18 +2,16 @@
2
2
  import {
3
3
  detectClientSupports,
4
4
  resolveClients
5
- } from "./chunk-HQLEHH4O.js";
5
+ } from "./chunk-OHWQNSLH.js";
6
6
  import {
7
7
  FABRIC_HOOK_COMMAND_PATHS,
8
+ FABRIC_SECTION_REGEX,
8
9
  HOOK_CONFIG_ARRAY_PATHS,
9
10
  HOOK_CONFIG_TARGETS,
10
11
  HOOK_SCRIPT_DESTINATIONS,
11
- IMPORT_POINTER_LINE,
12
- POINTER_LINE,
13
- POINTER_TARGETS,
14
- REVIEW_POINTER_LINE,
12
+ SECTION_TARGETS,
15
13
  SKILL_DESTINATIONS
16
- } from "./chunk-AW3G7ZH5.js";
14
+ } from "./chunk-X7QPY5KH.js";
17
15
  import {
18
16
  paint
19
17
  } from "./chunk-WWNXR34K.js";
@@ -115,12 +113,12 @@ async function unmergeCursorHookConfig(projectRoot, opts = {}) {
115
113
  cleanEmpties: opts.cleanEmpties === true
116
114
  });
117
115
  }
118
- async function stripArchiveSkillPointers(projectRoot) {
116
+ async function stripFabricKnowledgeBaseSection(projectRoot) {
119
117
  const results = [];
120
- for (const rel of POINTER_TARGETS) {
118
+ for (const rel of SECTION_TARGETS) {
121
119
  const target = join(projectRoot, rel);
122
120
  if (!existsSync(target)) {
123
- results.push({ step: "pointer", path: target, status: "skipped", message: "absent" });
121
+ results.push({ step: "section", path: target, status: "skipped", message: "absent" });
124
122
  continue;
125
123
  }
126
124
  let existing;
@@ -128,30 +126,41 @@ async function stripArchiveSkillPointers(projectRoot) {
128
126
  existing = await readFile(target, "utf8");
129
127
  } catch (error) {
130
128
  results.push({
131
- step: "pointer",
129
+ step: "section",
132
130
  path: target,
133
131
  status: "error",
134
132
  message: error instanceof Error ? error.message : String(error)
135
133
  });
136
134
  continue;
137
135
  }
138
- const pointerLiterals = [POINTER_LINE, REVIEW_POINTER_LINE, IMPORT_POINTER_LINE];
139
- const filtered = existing.split("\n").filter((line) => !pointerLiterals.some((literal) => line.includes(literal))).join("\n");
136
+ const match = existing.match(FABRIC_SECTION_REGEX);
137
+ if (match === null) {
138
+ results.push({
139
+ step: "section",
140
+ path: target,
141
+ status: "skipped",
142
+ message: "no-fabric-section"
143
+ });
144
+ continue;
145
+ }
146
+ const before = existing.slice(0, match.index ?? 0);
147
+ const after = existing.slice((match.index ?? 0) + match[0].length);
148
+ const filtered = `${before}${after.replace(/^\r?\n/, "")}`;
140
149
  if (filtered === existing) {
141
150
  results.push({
142
- step: "pointer",
151
+ step: "section",
143
152
  path: target,
144
153
  status: "skipped",
145
- message: "no-fabric-pointers"
154
+ message: "no-fabric-section"
146
155
  });
147
156
  continue;
148
157
  }
149
158
  try {
150
159
  await atomicWriteText(target, filtered);
151
- results.push({ step: "pointer", path: target, status: "removed" });
160
+ results.push({ step: "section", path: target, status: "removed" });
152
161
  } catch (error) {
153
162
  results.push({
154
- step: "pointer",
163
+ step: "section",
155
164
  path: target,
156
165
  status: "error",
157
166
  message: error instanceof Error ? error.message : String(error)
@@ -164,9 +173,9 @@ async function uninstallBootstrapStage(projectRoot, opts = {}) {
164
173
  const results = [];
165
174
  await runAndCollect(
166
175
  results,
167
- "pointer",
176
+ "section",
168
177
  projectRoot,
169
- () => stripArchiveSkillPointers(projectRoot)
178
+ () => stripFabricKnowledgeBaseSection(projectRoot)
170
179
  );
171
180
  await runAndCollectOne(
172
181
  results,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.0.0-rc.11",
3
+ "version": "2.0.0-rc.13",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "fab": "dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "tree-sitter-javascript": "^0.25.0",
21
21
  "tree-sitter-typescript": "^0.23.2",
22
22
  "web-tree-sitter": "^0.26.8",
23
- "@fenglimg/fabric-server": "2.0.0-rc.11",
24
- "@fenglimg/fabric-shared": "2.0.0-rc.11"
23
+ "@fenglimg/fabric-shared": "2.0.0-rc.13",
24
+ "@fenglimg/fabric-server": "2.0.0-rc.13"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.15.0",
@@ -1,6 +1,6 @@
1
1
  # Client hook config templates
2
2
 
3
- These JSON files are **fragment templates** consumed by `fabric init` and
3
+ These JSON files are **fragment templates** consumed by `fabric install` and
4
4
  `fabric hooks install`. They are not standalone client config files.
5
5
 
6
6
  The supported clients are pinned by `packages/shared/src/schemas/fabric-config.ts`
@@ -49,41 +49,22 @@
49
49
  const { spawnSync } = require("node:child_process");
50
50
  const {
51
51
  existsSync,
52
- mkdirSync,
53
52
  readdirSync,
54
53
  readFileSync,
55
- writeFileSync,
56
54
  } = require("node:fs");
57
- const { dirname, join } = require("node:path");
55
+ const { join } = require("node:path");
58
56
 
59
57
  // -----------------------------------------------------------------------------
60
- // rc.7 T8: SessionStart revision_hash gating.
61
- //
62
- // Q-14 problem: every SessionStart re-dumped the full broad knowledge list,
63
- // causing banner blindness. Solution: hash-of-canonical-graph gating — record
64
- // the last-emitted `payload.revision_hash` to a sidecar; on subsequent
65
- // SessionStart fires, compare. Match silent exit 0 (no re-dump). Mismatch
66
- // (canonical/ corpus changed planContext bumps revision_hash) emit AND
67
- // update sidecar.
68
- //
69
- // The revision_hash is supplied by `fabric plan-context-hint --all`'s JSON
70
- // payload (carried in payload.revision_hash since rc.5). Reusing the existing
71
- // hash primitive keeps the gating predicate exactly aligned with the "is the
72
- // knowledge graph different from last time?" question — no second hashing
73
- // scheme to maintain. computeRevisionHash() is not needed at this layer; we
74
- // compare the strings the CLI hands us.
75
- //
76
- // rc.8 underseed self-check: the retired `.fabric/.import-requested` sentinel
77
- // mechanism is replaced by a deterministic three-condition probe in
78
- // shouldRecommendImport(). When the probe says "recommend", a one-line
79
- // `/fabric-import` banner is appended to the broad-injection output and
80
- // the revision_hash gate is bypassed FOR THE BANNER ONLY (the broad-summary
81
- // body itself remains hash-gated). See shouldRecommendImport() below for
82
- // the full truth table.
58
+ // rc.12: SessionStart broad-menu is now unconditionally emitted on every
59
+ // SessionStart fire (matching Skill-style progressive disclosure). Prior
60
+ // versions (rc.5-rc.11) wrote `.fabric/.cache/sessionstart-last-hash` as a
61
+ // revision_hash cooldown sidecar to suppress re-emission on unchanged
62
+ // knowledge graphs; that gate was removed in rc.12. Orphaned sidecar files
63
+ // on existing dogfood repos are harmless dead state and are intentionally
64
+ // NOT cleaned up (zero-user clean-slate no migration logic needed).
83
65
  // -----------------------------------------------------------------------------
84
66
 
85
67
  const FABRIC_DIR_REL = ".fabric";
86
- const SESSIONSTART_HASH_CACHE_FILE = join(".fabric", ".cache", "sessionstart-last-hash");
87
68
 
88
69
  // rc.8 underseed self-check constants (mirror fabric-hint.cjs ~line 76 / 83).
89
70
  // Intentionally duplicated inline — hooks are independent .cjs files and
@@ -102,41 +83,6 @@ const KNOWLEDGE_CANONICAL_TYPES = [
102
83
  ];
103
84
  const DEFAULT_UNDERSEED_NODE_THRESHOLD = 10;
104
85
 
105
- /**
106
- * Read the previously-emitted revision_hash from
107
- * `.fabric/.cache/sessionstart-last-hash`. Missing file / read failure /
108
- * empty file → null (treat as "no prior emit", forces re-emit).
109
- *
110
- * NEVER throws — best-effort read.
111
- */
112
- function readSessionStartLastHash(projectRoot) {
113
- try {
114
- const p = join(projectRoot, SESSIONSTART_HASH_CACHE_FILE);
115
- if (!existsSync(p)) return null;
116
- const raw = readFileSync(p, "utf8").trim();
117
- return raw.length > 0 ? raw : null;
118
- } catch {
119
- return null;
120
- }
121
- }
122
-
123
- /**
124
- * Write `hash` to `.fabric/.cache/sessionstart-last-hash` so subsequent
125
- * SessionStart fires can compare. Creates the directory if missing.
126
- * Best-effort: any write failure is swallowed so a read-only .fabric/
127
- * never blocks session start.
128
- */
129
- function writeSessionStartLastHash(projectRoot, hash) {
130
- try {
131
- if (typeof hash !== "string" || hash.length === 0) return;
132
- const p = join(projectRoot, SESSIONSTART_HASH_CACHE_FILE);
133
- mkdirSync(dirname(p), { recursive: true });
134
- writeFileSync(p, hash, "utf8");
135
- } catch {
136
- // Silent — sidecar failure must never block session start.
137
- }
138
- }
139
-
140
86
  // -----------------------------------------------------------------------------
141
87
  // rc.8 underseed self-check helpers.
142
88
  //
@@ -488,15 +434,21 @@ function renderTruncated(narrow) {
488
434
  * (empty narrow set) so callers know to stay silent.
489
435
  */
490
436
  function renderSummary(payload) {
491
- const narrow = Array.isArray(payload && payload.narrow) ? payload.narrow : [];
492
- if (narrow.length === 0) return [];
493
-
494
- const truncated = narrow.length > TRUNCATION_THRESHOLD;
437
+ // Local rebind: `payload.narrow` in `--all` mode degenerates to the full
438
+ // shared index (every broad-scoped entry), so the field name `narrow` is
439
+ // misleading at this rendering layer. We rename the local variable to
440
+ // `entries` to avoid name confusion when reading renderSummary in isolation.
441
+ // The CLI protocol field name (`payload.narrow`) is unchanged — a wire-shape
442
+ // rename is a deferred independent task.
443
+ const entries = Array.isArray(payload && payload.narrow) ? payload.narrow : [];
444
+ if (entries.length === 0) return [];
445
+
446
+ const truncated = entries.length > TRUNCATION_THRESHOLD;
495
447
  const banner = truncated
496
- ? `[fabric] Session start — ${narrow.length} broad-scoped knowledge entries available (truncated):`
497
- : `[fabric] Session start — ${narrow.length} broad-scoped knowledge entries available:`;
448
+ ? `[fabric] Session start — ${entries.length} broad-scoped knowledge entries available (truncated):`
449
+ : `[fabric] Session start — ${entries.length} broad-scoped knowledge entries available:`;
498
450
 
499
- const body = truncated ? renderTruncated(narrow) : renderFull(narrow);
451
+ const body = truncated ? renderTruncated(entries) : renderFull(entries);
500
452
 
501
453
  const lines = [banner, ...body];
502
454
  const revHash = typeof payload.revision_hash === "string" ? payload.revision_hash : null;
@@ -524,32 +476,15 @@ function main(env, stdio) {
524
476
  if (payload === null || payload === undefined) return; // silent
525
477
 
526
478
  // rc.8 underseed self-check: decide whether to surface the one-line
527
- // `/fabric-import` recommendation. The decision is taken BEFORE the
528
- // revision_hash gate so the banner can bypass it (an unchanged
529
- // knowledge graph would otherwise hide the recommendation forever).
530
- // The broad-summary BODY itself remains hash-gated below — only the
531
- // banner line is unconditionally emitted when the probe says so.
479
+ // `/fabric-import` recommendation banner alongside the broad summary.
532
480
  const recommendImport = shouldRecommendImport(cwd);
533
481
 
534
- // rc.7 T8: revision_hash gate. If the CLI payload carries a stable
535
- // revision_hash and it matches the previously-emitted hash recorded in
536
- // the sidecar, the knowledge graph is unchanged since last session →
537
- // suppress the broad-summary body. The import-recommendation banner
538
- // (when applicable) is still emitted below regardless of this gate.
539
- const currentHash =
540
- typeof payload.revision_hash === "string" ? payload.revision_hash : "";
541
- let bodySuppressed = false;
542
- if (currentHash.length > 0) {
543
- const lastHash = readSessionStartLastHash(cwd);
544
- if (lastHash !== null && lastHash === currentHash) {
545
- bodySuppressed = true;
546
- }
547
- }
548
-
549
- // Build emitted lines. When the body is hash-suppressed we skip the
550
- // broad summary entirely; only the import banner (if applicable) goes
551
- // to stderr in that case.
552
- const lines = bodySuppressed ? [] : renderSummary(payload);
482
+ // rc.12: broad-summary body is unconditionally rendered on every
483
+ // SessionStart fire (Skill-style progressive disclosure). The prior
484
+ // revision_hash cooldown gate (rc.7 T8 rc.11) was removed because
485
+ // compact/clear-triggered SessionStart re-fires must re-inject the menu
486
+ // for the agent's working memory.
487
+ const lines = renderSummary(payload);
553
488
 
554
489
  if (recommendImport) {
555
490
  lines.push(IMPORT_RECOMMENDATION_BANNER);
@@ -560,16 +495,6 @@ function main(env, stdio) {
560
495
  for (const line of lines) {
561
496
  err.write(`${line}\n`);
562
497
  }
563
-
564
- // Update sidecar AFTER successful emit. We only persist the hash when
565
- // the broad-summary body actually went out (i.e. the gate let the body
566
- // through). If the body was suppressed but the banner emitted on its
567
- // own, we deliberately do NOT bump the sidecar — the next session
568
- // should still get to compare against the prior canonical-graph hash
569
- // and re-emit the body when the graph actually changes.
570
- if (!bodySuppressed && currentHash.length > 0) {
571
- writeSessionStartLastHash(cwd, currentHash);
572
- }
573
498
  } catch {
574
499
  // Silent — never block session start on hook failure.
575
500
  }
@@ -583,9 +508,6 @@ module.exports = {
583
508
  renderTruncated,
584
509
  renderSummary,
585
510
  truncateSummary,
586
- // rc.7 T8: revision_hash gating sidecar helpers (exported for unit testing).
587
- readSessionStartLastHash,
588
- writeSessionStartLastHash,
589
511
  // rc.8 underseed self-check helpers (exported for unit testing).
590
512
  countCanonicalNodes,
591
513
  readUnderseedThreshold,
@@ -599,7 +521,6 @@ module.exports = {
599
521
  MATURITY_PROVEN,
600
522
  MATURITY_VERIFIED,
601
523
  MATURITY_DRAFT,
602
- SESSIONSTART_HASH_CACHE_FILE,
603
524
  DEFAULT_UNDERSEED_NODE_THRESHOLD,
604
525
  KNOWLEDGE_CANONICAL_TYPES,
605
526
  IMPORT_RECOMMENDATION_BANNER,
@@ -19,7 +19,7 @@ If none of the above hold, stop the skill immediately and tell the user (UX i18n
19
19
  - zh-CN: `没有触发归档信号;如需手动归档请显式调用 fabric-archive`
20
20
  - en: `No archive signal detected; to manually archive, explicitly invoke fabric-archive`
21
21
 
22
- (Render per `knowledge_language` resolved in Phase 0.6 Config Load below.)
22
+ (Render per `fabric_language` resolved in Phase 0.6 Config Load below.)
23
23
 
24
24
  This skill is `Check-not-Ask`, not a preference interview:
25
25
 
@@ -47,9 +47,10 @@ If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
47
47
 
48
48
  ### UX i18n Policy (5-class bilingualization)
49
49
 
50
- The skill consults `knowledge_language` from `.fabric/fabric-config.json`
50
+ The skill consults `fabric_language` from `.fabric/fabric-config.json`
51
51
  (固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
52
- CJK signal is detected in README + docs/). All user-facing text in the
52
+ CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
53
+ `"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
53
54
  following 5 categories MUST be rendered in the resolved language:
54
55
 
55
56
  1. **Roll-up templates** — the `# Archive Review — N candidates` batch
@@ -73,11 +74,10 @@ following 5 categories MUST be rendered in the resolved language:
73
74
 
74
75
  Rendering rule:
75
76
 
76
- - `knowledge_language === "zh-CN"` → emit the zh-CN variant.
77
- - `knowledge_language === "en"` (or any other value) → emit the en variant.
78
- - The Skill MUST NOT mix languages inside a single user-facing block
79
- (no "Chinglish" partial translation); each block is either fully zh-CN
80
- or fully en.
77
+ - `fabric_language === "zh-CN"` → emit the zh-CN variant; pure monolingual, no language mixing inside a single user-facing block.
78
+ - `fabric_language === "en"` → emit the en variant; pure monolingual, no language mixing inside a single user-facing block.
79
+ - `fabric_language === "zh-CN-hybrid"` → emit Chinese narrative prose with English technical terms preserved. Protected tokens (always EN): MCP tool names (e.g. `fab_get_knowledge_sections`), CLI command names (e.g. `fab install`), file paths, technical concepts (`Skill`, `SessionStart`, `hook`, `MCP`, `revision_hash`, `pending`, `proven`, `verified`, `draft`).
80
+ - `fabric_language === "match-existing"` or any other value emit the en variant; pure monolingual.
81
81
 
82
82
  Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
83
83
  `relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
@@ -91,21 +91,21 @@ bilingualization scope is prose ONLY.
91
91
 
92
92
  When a skill (this one or any sibling skill the user is composing with)
93
93
  issues an `AskUserQuestion`, the `header` and `question` strings are
94
- user-facing prose → translated per `knowledge_language`. The `options[]`
94
+ user-facing prose → translated per `fabric_language`. The `options[]`
95
95
  array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
96
96
  fabric-review, or `["team", "personal"]` for a layer-flip target) are
97
97
  **routing keys** consumed by the skill state machine — they MUST remain
98
- English regardless of `knowledge_language`.
98
+ English regardless of `fabric_language`.
99
99
 
100
100
  ```ts
101
- // EN (knowledge_language === "en")
101
+ // EN (fabric_language === "en")
102
102
  AskUserQuestion({
103
103
  header: "Layer-flip target",
104
104
  question: "Move '{title}' to which layer? (current: {current_layer})",
105
105
  options: ["team", "personal"]
106
106
  })
107
107
 
108
- // zh-CN (knowledge_language === "zh-CN")
108
+ // zh-CN (fabric_language === "zh-CN")
109
109
  AskUserQuestion({
110
110
  header: "Layer 切换目标",
111
111
  question: "将 '{title}' 切换到哪一层?(当前: {current_layer})",
@@ -214,7 +214,7 @@ ELSE:
214
214
 
215
215
  #### On gate FAIL
216
216
 
217
- Stop the skill with the gate-FAIL message (UX i18n Policy class 2 — errors/preconditions; render per `knowledge_language`):
217
+ Stop the skill with the gate-FAIL message (UX i18n Policy class 2 — errors/preconditions; render per `fabric_language`):
218
218
 
219
219
  zh-CN variant:
220
220
 
@@ -317,7 +317,7 @@ Recent session contains an observation worth keeping?
317
317
 
318
318
  Present all candidates in a single screen. UX i18n Policy classes 1 + 3 — the roll-up structure AND the per-candidate `Confirm?` prompt are bilingualized; protected tokens (`relevance_scope`, `relevance_paths`, `narrow`, `broad`, `layer`, `team`, `personal`, `pending_path`, etc.) appear verbatim in BOTH variants. Field VALUES (slugs, file paths, type/layer enum strings like `decision` / `team`) are data and are NOT translated.
319
319
 
320
- en variant (`knowledge_language === "en"`):
320
+ en variant (`fabric_language === "en"`):
321
321
 
322
322
  ```md
323
323
  # Archive Review — N candidates
@@ -337,7 +337,7 @@ relevance_paths: []
337
337
  Confirm? ...
338
338
  ```
339
339
 
340
- zh-CN variant (`knowledge_language === "zh-CN"`):
340
+ zh-CN variant (`fabric_language === "zh-CN"`):
341
341
 
342
342
  ```md
343
343
  # 归档 Review — N 条候选
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fabric-import
3
- description: Use this skill for cold-start enrichment of `.fabric/knowledge/` from existing project artifacts — mines `git log` and `docs/*.md` for candidate observations, proposes pending entries via `fab_extract_knowledge`, then deduplicates against canonical entries via `fab_review action: search` (rejecting obvious duplicates, modifying-to-merge marginal duplicates). Triggered by user prompts like "import knowledge from git history" / "bootstrap fabric for this repo" or by an explicit fabric-import skill mention. Default layer: team (project artifacts are team-shared). The 3-phase pipeline is resumable via `.fabric/.import-state.json`.
3
+ description: Use this skill for cold-start enrichment of `.fabric/knowledge/` from existing project artifacts — mines `git log` and `docs/*.md` for candidate observations, proposes pending entries via `fab_extract_knowledge`, then deduplicates against canonical entries via `fab_review action=search` (rejecting obvious duplicates, modifying-to-merge marginal duplicates). Triggered by user prompts like "import knowledge from git history" / "bootstrap fabric for this repo" or by an explicit fabric-import skill mention. Default layer is `team` (project artifacts are team-shared). The 3-phase pipeline is resumable via `.fabric/.import-state.json`.
4
4
  allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_extract_knowledge, mcp__fabric__fab_review
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ allowed-tools: Read, Glob, Grep, Bash, mcp__fabric__fab_extract_knowledge, mcp__
8
8
 
9
9
  ## Purpose
10
10
 
11
- `fabric-import` is a one-time (per project) cold-start skill that lifts existing project artifacts — git commit history and Markdown documentation — into the knowledge layer as pending entries. It is the bridge between a brand-new Fabric installation (which only has the 4–7 baseline entries produced by `fabric init`'s deterministic scan) and a useful corpus that reflects accumulated team thinking. Run it once when adopting Fabric on an existing repo, or after a major refactor that invalidates large chunks of canonical knowledge. Default layer is `team`: project artifacts in git/docs are team-shared by definition; the user can later layer-flip individual entries to `personal` via `fabric-review` modify.
11
+ `fabric-import` is a one-time (per project) cold-start skill that lifts existing project artifacts — git commit history and Markdown documentation — into the knowledge layer as pending entries. It is the bridge between a brand-new Fabric installation (which only has the 4–7 baseline entries produced by `fabric install`'s deterministic scan) and a useful corpus that reflects accumulated team thinking. Run it once when adopting Fabric on an existing repo, or after a major refactor that invalidates large chunks of canonical knowledge. Default layer is `team`: project artifacts in git/docs are team-shared by definition; the user can later layer-flip individual entries to `personal` via `fabric-review` modify.
12
12
 
13
13
  ## Precondition
14
14
 
@@ -23,13 +23,13 @@ If none of the above hold, stop the skill immediately and tell the user:
23
23
  - zh-CN: `没有触发 import 信号;如需手动 import 请显式调用 fabric-import`
24
24
  - en: `No import signal detected; to manually import, explicitly invoke fabric-import`
25
25
 
26
- (Render per `knowledge_language` resolved in Phase 0.5 Config Load below — class 2 of the UX i18n Policy.)
26
+ (Render per `fabric_language` resolved in Phase 0.5 Config Load below — class 2 of the UX i18n Policy.)
27
27
 
28
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 完成时手动清理它。
29
29
 
30
30
  This skill SHOULD be skipped (warn the user, do not proceed) when:
31
31
 
32
- - `.fabric/` does not exist — direct the user to run `fabric init` first; `fabric-import` is NOT a substitute for the deterministic init-scan
32
+ - `.fabric/` does not exist — direct the user to run `fabric install` first; `fabric-import` is NOT a substitute for the deterministic install-scan
33
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
34
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
35
35
 
@@ -114,15 +114,16 @@ and `final_summary.proposed == 0`) → first-run window; otherwise re-run window
114
114
 
115
115
  ### UX i18n Policy (5-class bilingualization)
116
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
117
+ The skill consults `fabric_language` from `.fabric/fabric-config.json`
118
+ (固化于 install 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
119
+ CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
120
+ `"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
120
121
  following 5 categories MUST be rendered in the resolved language:
121
122
 
122
123
  1. **Roll-up templates** — final summary blocks (`# Import Summary — phase=...`,
123
124
  `## Phase 2 — Mining`, `## Phase 3 — Dedup`, etc.). zh-CN ↔ en mirror.
124
125
  2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
125
- "请先运行 fabric init 完成基线扫描…" / "Please run fabric init first…").
126
+ "请先运行 fabric install 完成基线扫描…" / "Please run fabric install first…").
126
127
  zh-CN ↔ en mirror.
127
128
  3. **Confirmation prompts** — re-run-within-24h prompt, reset prompts, etc.
128
129
  zh-CN ↔ en mirror.
@@ -136,11 +137,10 @@ following 5 categories MUST be rendered in the resolved language:
136
137
 
137
138
  Rendering rule:
138
139
 
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.
140
+ - `fabric_language === "zh-CN"` → emit the zh-CN variant; pure monolingual, no language mixing inside a single user-facing block.
141
+ - `fabric_language === "en"` → emit the en variant; pure monolingual, no language mixing inside a single user-facing block.
142
+ - `fabric_language === "zh-CN-hybrid"` → emit Chinese narrative prose with English technical terms preserved. Protected tokens (always EN): MCP tool names (e.g. `fab_get_knowledge_sections`), CLI command names (e.g. `fab install`), file paths, technical concepts (`Skill`, `SessionStart`, `hook`, `MCP`, `revision_hash`, `pending`, `proven`, `verified`, `draft`).
143
+ - `fabric_language === "match-existing"` or any other value emit the en variant; pure monolingual.
144
144
 
145
145
  Protected tokens (`fab_extract_knowledge`, `fab_review`, `relevance_scope`,
146
146
  `relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`,
@@ -153,20 +153,20 @@ The bilingualization scope is prose ONLY.
153
153
 
154
154
  When a skill (this one or any sibling skill the user is composing with)
155
155
  issues an `AskUserQuestion`, the `header` and `question` strings are
156
- user-facing prose → translated per `knowledge_language`. The `options[]`
156
+ user-facing prose → translated per `fabric_language`. The `options[]`
157
157
  array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
158
158
  fabric-review) are **routing keys** consumed by the skill state machine —
159
- they MUST remain English regardless of `knowledge_language`.
159
+ they MUST remain English regardless of `fabric_language`.
160
160
 
161
161
  ```ts
162
- // EN (knowledge_language === "en")
162
+ // EN (fabric_language === "en")
163
163
  AskUserQuestion({
164
164
  header: "Review pending entry",
165
165
  question: "What action for '{title}'?",
166
166
  options: ["approve", "reject", "modify", "defer", "skip"]
167
167
  })
168
168
 
169
- // zh-CN (knowledge_language === "zh-CN")
169
+ // zh-CN (fabric_language === "zh-CN")
170
170
  AskUserQuestion({
171
171
  header: "审核 pending 条目",
172
172
  question: "对 '{title}' 执行什么操作?",
@@ -186,7 +186,7 @@ The pipeline runs strictly in order. Each phase reads the prior phase's outputs
186
186
 
187
187
  ### Phase 1 — Init-Scan Reference (NO RE-IMPLEMENTATION)
188
188
 
189
- > Verbatim boundary: `fabric init` (rc.1, deterministic CLI) already produces the baseline scan. Phase 1 of this skill **REFERENCES** that output. It does NOT redo the scan.
189
+ > Verbatim boundary: `fabric install` (v2.0+, deterministic CLI) already produces the baseline scan. Phase 1 of this skill **REFERENCES** that output. It does NOT redo the scan.
190
190
 
191
191
  The deterministic init-scan has already populated `.fabric/knowledge/team/` with 4–7 baseline entries derived from:
192
192
 
@@ -203,8 +203,8 @@ Phase 1 actions performed by THIS skill:
203
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).
204
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
205
 
206
- - zh-CN: `请先运行 fabric init 完成基线扫描,再调用 fabric-import`
207
- - en: `Please run fabric init first to complete the baseline scan, then invoke fabric-import`
206
+ - zh-CN: `请先运行 fabric install 完成基线扫描,再调用 fabric-import`
207
+ - en: `Please run fabric install first to complete the baseline scan, then invoke fabric-import`
208
208
 
209
209
  …and exit.
210
210
  4. Update `.fabric/.import-state.json`: `phase = "P1-done"`, `p1_baseline_titles = [<list>]`, `last_checkpoint_at = <ISO8601 now>`.
@@ -288,7 +288,7 @@ For each commit:
288
288
  - `refactor(...)` with body → likely **decision** (architectural choice was made)
289
289
  - `docs(...)` → usually a **guideline** if the body announces a convention; skip if it's just typo/reformat
290
290
  - `chore(...)`, `test(...)`, `ci(...)` → almost always skip (mechanical; no reusable insight)
291
- 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 knowledge_language; mirror fabric-archive M3 style).
291
+ 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).
292
292
  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.
293
293
  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):
294
294
 
@@ -394,7 +394,7 @@ After Step 2.2 completes (or hits the cap), update `.fabric/.import-state.json`:
394
394
 
395
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
396
 
397
- zh-CN variant (`knowledge_language === "zh-CN"`):
397
+ zh-CN variant (`fabric_language === "zh-CN"`):
398
398
 
399
399
  ```md
400
400
  # Import 预览 — 将提议 N 条 pending 条目(全部 relevance_scope=broad, relevance_paths=[])
@@ -406,7 +406,7 @@ zh-CN variant (`knowledge_language === "zh-CN"`):
406
406
  | 3 | git 50367b5 | pitfalls | thundering-herd-no-backoff | broad+[] | 重试无指数回退导致雪崩;必须 jittered exponential backoff。|
407
407
  ```
408
408
 
409
- en variant (`knowledge_language === "en"`):
409
+ en variant (`fabric_language === "en"`):
410
410
 
411
411
  ```md
412
412
  # Import Dry Run — would propose N pending entries (all relevance_scope=broad, relevance_paths=[])
@@ -648,9 +648,9 @@ The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, n
648
648
 
649
649
  ## Output Contract
650
650
 
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.
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 `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.
652
652
 
653
- en variant (`knowledge_language === "en"`):
653
+ en variant (`fabric_language === "en"`):
654
654
 
655
655
  ```md
656
656
  # Import Summary — phase=<P1-done | P2-done | complete>
@@ -678,7 +678,7 @@ en variant (`knowledge_language === "en"`):
678
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).
679
679
  ```
680
680
 
681
- zh-CN variant (`knowledge_language === "zh-CN"`):
681
+ zh-CN variant (`fabric_language === "zh-CN"`):
682
682
 
683
683
  ```md
684
684
  # Import 汇总 — phase=<P1-done | P2-done | complete>