@fenglimg/fabric-cli 2.0.0-rc.22 → 2.0.0-rc.23

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,18 @@
2
2
  "events": {
3
3
  "Stop": [
4
4
  {
5
- "command": ".codex/hooks/fabric-hint.cjs"
5
+ "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs\""
6
6
  }
7
7
  ],
8
8
  "SessionStart": [
9
9
  {
10
- "command": ".codex/hooks/knowledge-hint-broad.cjs"
10
+ "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs\""
11
11
  }
12
12
  ],
13
13
  "PreToolUse": [
14
14
  {
15
15
  "matcher": "Edit|Write|MultiEdit",
16
- "command": ".codex/hooks/knowledge-hint-narrow.cjs"
16
+ "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs\""
17
17
  }
18
18
  ]
19
19
  }
@@ -1291,8 +1291,13 @@ function summarizeTranscript(transcriptPath) {
1291
1291
  }
1292
1292
  }
1293
1293
  if (firstNonEmpty.length > 0) {
1294
- // KB: none (case-insensitive on the literal `none`).
1295
- const noneMatch = firstNonEmpty.match(/^KB:\s*none\s*$/i);
1294
+ // KB: none with optional `[<sentinel>]` tail per rc.23 T8.
1295
+ // Accepts bare `KB: none` (legacy → unspecified) AND
1296
+ // `KB: none [no-relevant]` / `KB: none [not-applicable]`. The sentinel
1297
+ // tail stays in `kb_line_raw` for doctor's downstream histogram parse;
1298
+ // the cite_tags vocab still emits the bare `none` token (schema
1299
+ // enum-bound).
1300
+ const noneMatch = firstNonEmpty.match(/^KB:\s*none\b\s*(?:\[[^\]]*\])?\s*$/i);
1296
1301
  const kbMatch = firstNonEmpty.match(/^KB:\s+(.+)$/);
1297
1302
  if (noneMatch) {
1298
1303
  kbLineRaw = firstNonEmpty;
@@ -23,6 +23,7 @@ If none of the above hold, stop the skill immediately and tell the user (UX i18n
23
23
 
24
24
  This skill is `Check-not-Ask`, not a preference interview:
25
25
 
26
+ - **Phase 0.4 (rc.23 F8c) first-run onboard phase** — checks S5 onboard-slot coverage; if unclaimed slots remain, prompts user to fill / dismiss / skip before proceeding to normal archive flow
26
27
  - Phase 0 proactively gathers candidate evidence from the session
27
28
  - Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
28
29
  - Phase 1 classifies / layers / slugs each candidate and presents one batch review for user correction
@@ -48,7 +49,7 @@ If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
48
49
  ### UX i18n Policy (5-class bilingualization)
49
50
 
50
51
  The skill consults `fabric_language` from `.fabric/fabric-config.json`
51
- (固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
52
+ (固化于 init 时,via `lib/detect-language.ts:detectExistingLanguage`; default `"en"` when no
52
53
  CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
53
54
  `"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
54
55
  following 5 categories MUST be rendered in the resolved language:
@@ -81,7 +82,8 @@ Rendering rule:
81
82
 
82
83
  Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
83
84
  `relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
84
- `session_context`, `pending_path`, `layer`, `team`, `personal`,
85
+ `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`,
86
+ `pending_path`, `layer`, `team`, `personal`,
85
87
  `knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, the verbatim
86
88
  `强 team` / `强 personal` / `默认 team` heuristic block, etc.) are NEVER
87
89
  translated — they appear verbatim in both language variants. The
@@ -158,6 +160,142 @@ entirely, this phase reports an empty context and Phase 0 falls back to the
158
160
  single-session behaviour. Tests that synthesize events.jsonl without
159
161
  populating the digest cache continue to work.
160
162
 
163
+ ### Phase 0.4 — First-run Onboard Phase (rc.23 F8c)
164
+
165
+ After F8a removed the auto-`fab scan` baseline pipeline, a freshly installed
166
+ Fabric workspace ships with an EMPTY `.fabric/knowledge/` tree. Five fixed
167
+ **S5 onboard slots** capture the "project tone" baseline that the AI needs
168
+ for high-quality plan_context retrieval from day one:
169
+
170
+ - `tech-stack-decision` — primary languages / frameworks / runtime stack
171
+ - `architecture-pattern` — module layout, service boundaries, layering rules
172
+ - `code-style-tone` — naming / formatting / idiom conventions the project enforces
173
+ - `build-system-idiom` — build tool quirks, scripts, deploy pipeline shape
174
+ - `domain-vocabulary` — business / product terminology that names code entities
175
+
176
+ This phase runs ONCE per archive-skill invocation, BEFORE Phase 0 evidence
177
+ gathering, so coverage state is fresh for the session.
178
+
179
+ #### Step 1 — Check coverage
180
+
181
+ Invoke `fab onboard-coverage --json` and parse the JSON payload:
182
+
183
+ ```bash
184
+ fab onboard-coverage --json
185
+ ```
186
+
187
+ Expected shape:
188
+
189
+ ```json
190
+ {
191
+ "filled": { "tech-stack-decision": ["KT-DEC-0012"], ... },
192
+ "missing": ["architecture-pattern", "code-style-tone"],
193
+ "opted_out": ["domain-vocabulary"],
194
+ "total": 5
195
+ }
196
+ ```
197
+
198
+ #### Step 2 — Decide
199
+
200
+ ```
201
+ IF missing.length === 0:
202
+ → skip Phase 0.4 entirely; proceed to Phase 0.
203
+ ELSE:
204
+ → ask the user how to handle the missing slots (Step 3).
205
+ ```
206
+
207
+ #### Step 3 — Prompt user
208
+
209
+ Present a single roll-up listing each missing slot. UX i18n Policy class 5
210
+ applies: the `header` + `question` strings are translated per
211
+ `fabric_language`; the `options[]` routing keys stay English.
212
+
213
+ ```ts
214
+ AskUserQuestion({
215
+ header: "Onboard coverage", // zh-CN: "首装基调覆盖"
216
+ question:
217
+ "KB is missing the following project-tone slots: " +
218
+ missing.join(", ") +
219
+ ". Tour the project and propose pending entries for each?",
220
+ options: ["fill-all", "fill-each", "dismiss-all", "skip"]
221
+ })
222
+ ```
223
+
224
+ `fab_extract_knowledge` is called with `onboard_slot: <slot>` set so each
225
+ proposed entry counts toward coverage once approved via fab_review.
226
+
227
+ | User choice | Action |
228
+ |----------------|--------|
229
+ | `fill-all` | For EACH slot in `missing`, run Step 4 (Tour-and-propose). All proposals share session_id; one batch review at the end (Phase 1). |
230
+ | `fill-each` | Loop slot-by-slot through `missing`. Per slot: ask user `confirm | dismiss | skip` (per-slot AskUserQuestion); `confirm` → run Step 4; `dismiss` → `fab config dismiss-slot <slot>`; `skip` → leave for next archive run. |
231
+ | `dismiss-all` | For EACH slot in `missing`, invoke `Bash("fab config dismiss-slot <slot>")`. Print a one-line confirmation each. Skip to Phase 0. |
232
+ | `skip` | No-op. Slots remain in `missing` for the next archive run. Skip to Phase 0. |
233
+
234
+ #### Step 4 — Tour-and-propose (per-slot)
235
+
236
+ For each slot to fill, the LLM independently sources slot-specific evidence
237
+ from the project (no user prompt — this is a Read-only tour):
238
+
239
+ | Slot | Source files (LLM should Read these) |
240
+ |--------------------------|---------------------------------------|
241
+ | `tech-stack-decision` | `package.json` (+ lockfile), `pyproject.toml` / `Cargo.toml` / `go.mod`, `tsconfig.json`, root README |
242
+ | `architecture-pattern` | Top-level dir tree (`ls -F`), 1-2 entry-point files (`src/index.ts`, `main.go`, etc.), framework-config files (`next.config`, `vite.config`, `astro.config`) |
243
+ | `code-style-tone` | `.editorconfig`, `prettier.config.*`, `eslint.config.*`, `biome.*`, `.prettierrc*`, framework lint config, 2-3 representative source files for naming-pattern inference |
244
+ | `build-system-idiom` | `package.json` `scripts` block, `Makefile`, `taskfile.yaml`, CI yml (`.github/workflows/*.yml`), Dockerfile if present |
245
+ | `domain-vocabulary` | README, `docs/*.md`, top-level `src/` directory names (often domain-aligned), public API entry types |
246
+
247
+ After Read-ing the slot-specific sources, classify the observation:
248
+
249
+ - `tech-stack-decision` → type=`decisions`, `proposed_reason=decision-confirmation`
250
+ - `architecture-pattern` → type=`models`, `proposed_reason=new-dependency-or-pattern`
251
+ - `code-style-tone` → type=`guidelines`, `proposed_reason=explicit-user-mark` (the project ITSELF is the mark)
252
+ - `build-system-idiom` → type=`processes`, `proposed_reason=new-dependency-or-pattern`
253
+ - `domain-vocabulary` → type=`models`, `proposed_reason=new-dependency-or-pattern`
254
+
255
+ Call `fab_extract_knowledge` with the inferred fields PLUS `onboard_slot:
256
+ <slot>`. The pending file's frontmatter will carry the slot label, and the
257
+ next `fab onboard-coverage` run will see the slot as filled (once approved
258
+ via fab_review).
259
+
260
+ Example:
261
+
262
+ ```ts
263
+ mcp__fabric__fab_extract_knowledge({
264
+ source_sessions: ["<current-session-id>"],
265
+ recent_paths: ["package.json", "tsconfig.json"],
266
+ user_messages_summary: "Project uses TypeScript + pnpm workspace + Vitest. Node 20 LTS target. ESM-only.",
267
+ type: "decisions",
268
+ slug: "primary-tech-stack",
269
+ layer: "team",
270
+ relevance_scope: "broad", // tech stack applies everywhere
271
+ relevance_paths: [],
272
+ proposed_reason: "decision-confirmation",
273
+ session_context:
274
+ "Session goal: capture onboard tech-stack baseline.\nTurning point: read package.json + tsconfig.json + pnpm-workspace.yaml; stack confirmed.",
275
+ onboard_slot: "tech-stack-decision", // ← claims the slot
276
+ tech_stack: ["typescript", "nodejs", "pnpm", "vitest"]
277
+ })
278
+ ```
279
+
280
+ #### Onboard phase constraints (DO NOT TRANSLATE)
281
+
282
+ - MUST run BEFORE Phase 0 evidence gathering — onboard is a separate flow,
283
+ not interleaved with session-archive candidates.
284
+ - MUST call `fab onboard-coverage --json` before deciding; never assume
285
+ coverage state.
286
+ - NEVER fill a slot that is in `opted_out` — `fab onboard-coverage` already
287
+ excludes those from `missing`, but the Skill MUST NOT re-propose them
288
+ even if the user asks "fill all of them" — the dismiss is intentional.
289
+ - NEVER prompt the user when `missing.length === 0` — silent skip.
290
+ - NEVER set `onboard_slot` on a regular session-archive candidate in
291
+ Phase 2 — that field is RESERVED for the onboard phase. Mixing the
292
+ two would let session-archive proposals masquerade as onboard
293
+ coverage and let any random pending file claim a slot.
294
+ - MUST emit `onboard_slot: <slot>` verbatim — the slot name is one of
295
+ the locked S5 strings (tech-stack-decision / architecture-pattern /
296
+ code-style-tone / build-system-idiom / domain-vocabulary). The
297
+ fab_extract_knowledge schema enum will reject anything else.
298
+
161
299
  ### Phase 0 — Collect Candidates
162
300
 
163
301
  Gather raw evidence from the recent session before any classification:
@@ -490,10 +628,30 @@ mcp__fabric__fab_extract_knowledge({
490
628
  | "new-dependency-or-pattern" // new dep/lib/abstraction introduced
491
629
  | "dismissal-with-reason", // user rejected approach AND said why
492
630
  session_context: "<3-5 line markdown: session goal + key turning point>",
631
+ // v2.0.0-rc.23 TASK-006 (a-C1): four OPTIONAL structured triage fields.
632
+ // Lift implicit signals out of `## Session context` prose so future-self
633
+ // reviewers / plan-context retrievers can triage relevance from
634
+ // frontmatter alone, without re-reading the body. Omit any field the
635
+ // skill cannot infer cleanly — guessing is worse than omitting.
636
+ intent_clues: ["<short trigger>", "<negative trigger e.g. 'NOT for X'>"], // when this rule applies / when NOT
637
+ tech_stack: ["<lang/framework>", "..."], // inferred from recent_paths (see table below)
638
+ impact: ["<consequence of ignoring>"], // why future-self should care
639
+ must_read_if: "<one-line strong trigger>" // single condition; if it holds, the entry is required reading
493
640
  // tags? — NOT in current schema; reserved for future
494
641
  })
495
642
  ```
496
643
 
644
+ ##### C1 triage-field inference table
645
+
646
+ | Field | Inference source | Skip when |
647
+ |----------------|----------------------------------------------------------------------------------|------------------------------------|
648
+ | `intent_clues` | Pull from `session_context` turning point + negative phrasing in the transcript ("not for", "don't do X when") | No clear trigger phrasing surfaced |
649
+ | `tech_stack` | Map `recent_paths` extensions: `.ts`→`typescript`, `.tsx`→`typescript`+`react`, `.go`→`go`, `package.json`→`nodejs`, `pyproject.toml`→`python`, `Cargo.toml`→`rust`. Add framework markers from path heuristics (`cocos`→`cocos-creator`, `next.config`→`nextjs`) | Rule is stack-agnostic |
650
+ | `impact` | Pull from the diagnostic-loop body — "wasted 30 min", "production outage", "silent data loss" | No observable consequence stated |
651
+ | `must_read_if` | Strongest single trigger from the worth-archive signal: a file path, a routine, a recurring condition; ≤160 chars | No single dominant trigger fits |
652
+
653
+ All four fields are STRICTLY OPTIONAL. The schema accepts the call without any of them — omit rather than guess. None of the four participate in the idempotency_key hash (server formula at `extract-knowledge.ts:100-106` is frozen to `{source_session, type, slug}`), so partial-vs-full fill of these fields on the same triple is safe.
654
+
497
655
  The Skill infers `proposed_reason` from the classification + viability-gate
498
656
  signal that fired:
499
657
 
@@ -563,7 +721,7 @@ If the skill needs to record a genuinely separate observation in the same sessio
563
721
  - NEVER use multi-signal sources for relevance_paths in rc.5 — `edit_paths` is the SOLE source. `read_paths`, body regex, and symbol extraction are reserved for rc.7+.
564
722
  - NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
565
723
  - NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
566
- - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `pending_path`, `layer`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
724
+ - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`, `pending_path`, `layer`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
567
725
 
568
726
  ## Worked Examples
569
727
 
@@ -144,7 +144,8 @@ Rendering rule:
144
144
 
145
145
  Protected tokens (`fab_extract_knowledge`, `fab_review`, `relevance_scope`,
146
146
  `relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`,
147
- `session_context`, `pending_path`, `layer`, `team`, `personal`,
147
+ `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`,
148
+ `pending_path`, `layer`, `team`, `personal`,
148
149
  `knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, etc.)
149
150
  are NEVER translated — they appear verbatim in both language variants.
150
151
  The bilingualization scope is prose ONLY.
@@ -305,7 +306,28 @@ mcp__fabric__fab_extract_knowledge({
305
306
  // session_context cites the commit / doc origin so future-self reviewers
306
307
  // know this is an LLM-mined entry rather than a live-session capture.
307
308
  proposed_reason: "<inferred per Step 2.1.5 — varies>",
308
- session_context: "Imported from git log analysis. Origin: commit <sha7> (<subject 30 chars>). No live session — see commit body for full context."
309
+ session_context: "Imported from git log analysis. Origin: commit <sha7> (<subject 30 chars>). No live session — see commit body for full context.",
310
+ // v2.0.0-rc.23 TASK-006 (a-C1): four OPTIONAL structured triage fields.
311
+ // Inference for the import path (no live session, only commit/doc evidence):
312
+ // intent_clues: pull from commit subject/body — when is this rule worth
313
+ // consulting? (e.g. ["editing retry/backoff logic"]). Omit if unclear.
314
+ // tech_stack: derived from extensions in `recent_paths` (.ts→typescript;
315
+ // package.json→nodejs; pyproject.toml→python; etc.). Omit if mixed.
316
+ // impact: quote the commit body's "fixes …" / "prevents …" clause
317
+ // when present (e.g. ["thundering-herd outage on retry"]). Omit if
318
+ // the body has no impact statement.
319
+ // must_read_if: ONE strong trigger, ≤160 chars, from the commit's
320
+ // primary touched-path family (e.g. "touching retry / backoff logic
321
+ // in packages/server/"). Omit if no single path family dominates.
322
+ // ALL FOUR ARE OPTIONAL — omit any field that cannot be inferred cleanly
323
+ // from commit/doc text alone. None participate in the idempotency_key hash
324
+ // (server formula: sha256({source_session, type, slug})), so subsequent
325
+ // imports with refined inference do NOT split a single pending entry into
326
+ // duplicates.
327
+ intent_clues: ["<inferred trigger if commit body suggests one>"],
328
+ tech_stack: ["<lang/framework from recent_paths extensions>"],
329
+ impact: ["<consequence stated in commit body / doc>"],
330
+ must_read_if: "<one-line strongest trigger from commit's touched-path family>"
309
331
  })
310
332
  ```
311
333
 
@@ -644,7 +666,7 @@ The contract: re-invoking fabric-import after ANY interruption (Ctrl-C, crash, n
644
666
  - NEVER populate `relevance_paths` with a non-empty array on import — every call from this skill MUST pass `relevance_paths: []`. Do not derive paths from `git log --name-only`, `git show --stat`, commit subjects/bodies, or the path of a mined Markdown file.
645
667
  - NEVER copy fabric-archive's Phase 1.5 scope-decision logic (narrow-vs-broad rules, public-prefix generalization, glob blacklist) into this skill — that logic requires a live `edit_paths` signal from an active session, which fabric-import does not have.
646
668
  - Narrowing of imported entries happens out-of-band through `fab_review action="modify"` (issued by user via `fabric-review`), NOT inside this skill.
647
- - MUST preserve protected tokens exactly: `stable_id`, `pending_path`, `layer`, `team`, `personal`, `knowledge_proposed`, `fab_extract_knowledge`, `fab_review`, `MUST`, `NEVER`, `phase`, `.import-state.json`, `relevance_scope`, `relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`, `session_context`.
669
+ - MUST preserve protected tokens exactly: `stable_id`, `pending_path`, `layer`, `team`, `personal`, `knowledge_proposed`, `fab_extract_knowledge`, `fab_review`, `MUST`, `NEVER`, `phase`, `.import-state.json`, `relevance_scope`, `relevance_paths`, `broad`, `narrow`, `source_sessions`, `proposed_reason`, `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`.
648
670
 
649
671
  ## Output Contract
650
672
 
@@ -766,7 +788,7 @@ Skill output (broad+[] mandatory; the doc's own path stays in `recent_paths` for
766
788
 
767
789
  ```ts
768
790
  mcp__fabric__fab_extract_knowledge({
769
- source_session: "fabric-import-2026-05-10",
791
+ source_sessions: ["fabric-import-2026-05-10"],
770
792
  recent_paths: ["docs/architecture.md"], // provenance only
771
793
  user_messages_summary: "选择单体架构而非微服务:3 人团队无法承担多服务运维成本,且主要性能瓶颈在 DB 吞吐而非应用层水平扩展。src=docs/architecture.md",
772
794
  type: "decisions",
@@ -52,7 +52,7 @@ If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
52
52
  ### UX i18n Policy (5-class bilingualization)
53
53
 
54
54
  The skill consults `fabric_language` from `.fabric/fabric-config.json`
55
- (固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
55
+ (固化于 init 时,via `lib/detect-language.ts:detectExistingLanguage`; default `"en"` when no
56
56
  CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
57
57
  `"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
58
58
  following 5 categories MUST be rendered in the resolved language: