@fenglimg/fabric-cli 2.0.0-rc.27 → 2.0.0-rc.28
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-XEGXQOOJ.js → chunk-PNRWNUFX.js} +63 -0
- package/dist/index.js +3 -3
- package/dist/{install-UJOFZUYF.js → install-MPSTI654.js} +2 -2
- package/dist/{uninstall-O3PXESM2.js → uninstall-VLLJG7JT.js} +1 -1
- package/package.json +3 -3
- package/templates/skills/fabric-archive/SKILL.md +15 -410
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
- package/templates/skills/fabric-archive/ref/i18n-policy.md +80 -0
- package/templates/skills/fabric-archive/ref/phase-0-4-onboard.md +218 -0
- package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
- package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
- package/templates/skills/fabric-import/SKILL.md +7 -117
- package/templates/skills/fabric-import/ref/i18n-policy.md +73 -0
- package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
- package/templates/skills/fabric-review/SKILL.md +6 -187
- package/templates/skills/fabric-review/ref/i18n-policy.md +105 -0
- package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
|
@@ -148,6 +148,7 @@ async function installFabricArchiveSkill(projectRoot, _options = {}) {
|
|
|
148
148
|
for (const target of targets) {
|
|
149
149
|
results.push(await copyTextIdempotent("skill", source, target));
|
|
150
150
|
}
|
|
151
|
+
results.push(...await installSkillRefFiles(projectRoot, "fabric-archive"));
|
|
151
152
|
return results;
|
|
152
153
|
}
|
|
153
154
|
async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
@@ -157,6 +158,7 @@ async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
|
157
158
|
for (const target of targets) {
|
|
158
159
|
results.push(await copyTextIdempotent("skill-review", source, target));
|
|
159
160
|
}
|
|
161
|
+
results.push(...await installSkillRefFiles(projectRoot, "fabric-review"));
|
|
160
162
|
return results;
|
|
161
163
|
}
|
|
162
164
|
async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
@@ -166,6 +168,67 @@ async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
|
166
168
|
for (const target of targets) {
|
|
167
169
|
results.push(await copyTextIdempotent("skill-import", source, target));
|
|
168
170
|
}
|
|
171
|
+
results.push(...await installSkillRefFiles(projectRoot, "fabric-import"));
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
async function installSkillRefFiles(projectRoot, skillSlug) {
|
|
175
|
+
let refTemplateDir;
|
|
176
|
+
try {
|
|
177
|
+
refTemplateDir = findTemplatePath(`skills/${skillSlug}/ref`);
|
|
178
|
+
} catch {
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
step: "skill-ref",
|
|
182
|
+
path: `skills/${skillSlug}/ref`,
|
|
183
|
+
status: "skipped",
|
|
184
|
+
message: `no-ref-dir: ${skillSlug}`
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
let refFiles;
|
|
189
|
+
try {
|
|
190
|
+
refFiles = readdirSync(refTemplateDir).filter((name) => name.endsWith(".md"));
|
|
191
|
+
} catch {
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
step: "skill-ref",
|
|
195
|
+
path: refTemplateDir,
|
|
196
|
+
status: "skipped",
|
|
197
|
+
message: `no-ref-files: ${skillSlug}`
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
if (refFiles.length === 0) {
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
step: "skill-ref",
|
|
205
|
+
path: refTemplateDir,
|
|
206
|
+
status: "skipped",
|
|
207
|
+
message: `no-ref-files: ${skillSlug}`
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
const clientPrefixes = [".claude", ".codex"];
|
|
212
|
+
const results = [];
|
|
213
|
+
for (const refFile of refFiles) {
|
|
214
|
+
const sourcePath = join2(refTemplateDir, refFile);
|
|
215
|
+
let source;
|
|
216
|
+
try {
|
|
217
|
+
source = readFileSync2(sourcePath, "utf8");
|
|
218
|
+
} catch (error) {
|
|
219
|
+
results.push({
|
|
220
|
+
step: "skill-ref",
|
|
221
|
+
path: sourcePath,
|
|
222
|
+
status: "error",
|
|
223
|
+
message: error instanceof Error ? error.message : String(error)
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
for (const prefix of clientPrefixes) {
|
|
228
|
+
const target = join2(projectRoot, prefix, "skills", skillSlug, "ref", refFile);
|
|
229
|
+
results.push(await copyTextIdempotent("skill-ref", source, target));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
169
232
|
return results;
|
|
170
233
|
}
|
|
171
234
|
async function installArchiveHintHook(projectRoot, _options = {}) {
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,10 @@ import { defineCommand, runMain } from "citty";
|
|
|
11
11
|
|
|
12
12
|
// src/commands/index.ts
|
|
13
13
|
var allCommands = {
|
|
14
|
-
install: () => import("./install-
|
|
14
|
+
install: () => import("./install-MPSTI654.js").then((module) => module.default),
|
|
15
15
|
doctor: () => import("./doctor-ZIQXN2T2.js").then((module) => module.default),
|
|
16
16
|
serve: () => import("./serve-U3TPWDOB.js").then((module) => module.default),
|
|
17
|
-
uninstall: () => import("./uninstall-
|
|
17
|
+
uninstall: () => import("./uninstall-VLLJG7JT.js").then((module) => module.default),
|
|
18
18
|
config: () => import("./config-5CH4EJQ2.js").then((module) => module.default),
|
|
19
19
|
"plan-context-hint": () => import("./plan-context-hint-CXTLNVSV.js").then((module) => module.default),
|
|
20
20
|
// v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
|
|
@@ -26,7 +26,7 @@ var allCommands = {
|
|
|
26
26
|
var main = defineCommand({
|
|
27
27
|
meta: {
|
|
28
28
|
name: "fabric",
|
|
29
|
-
version: "2.0.0-rc.
|
|
29
|
+
version: "2.0.0-rc.28",
|
|
30
30
|
description: t("cli.main.description")
|
|
31
31
|
},
|
|
32
32
|
subCommands: allCommands
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
writeCodexBootstrapManagedBlock,
|
|
19
19
|
writeCursorBootstrapManagedBlock,
|
|
20
20
|
writeFabricAgentsSnapshot
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-PNRWNUFX.js";
|
|
22
22
|
import {
|
|
23
23
|
detectClientSupports
|
|
24
24
|
} from "./chunk-MF3OTILQ.js";
|
|
@@ -1348,7 +1348,7 @@ function readProjectName(target) {
|
|
|
1348
1348
|
return basename(target);
|
|
1349
1349
|
}
|
|
1350
1350
|
function getCliVersion() {
|
|
1351
|
-
return true ? "2.0.0-rc.
|
|
1351
|
+
return true ? "2.0.0-rc.28" : "unknown";
|
|
1352
1352
|
}
|
|
1353
1353
|
function sortRecord(record) {
|
|
1354
1354
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-cli",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.28",
|
|
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.
|
|
24
|
-
"@fenglimg/fabric-shared": "2.0.0-rc.
|
|
23
|
+
"@fenglimg/fabric-server": "2.0.0-rc.28",
|
|
24
|
+
"@fenglimg/fabric-shared": "2.0.0-rc.28"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22.15.0",
|
|
@@ -32,7 +32,7 @@ This skill is `Check-not-Ask`, not a preference interview:
|
|
|
32
32
|
|
|
33
33
|
## 执行流程 (6 Phase / 1 User Review Round)
|
|
34
34
|
|
|
35
|
-
### Phase -0.5 — Range Resolution
|
|
35
|
+
### Phase -0.5 — Range Resolution
|
|
36
36
|
|
|
37
37
|
When the skill is invoked, the user's prompt may carry an explicit range hint —
|
|
38
38
|
a time window (`今日` / `last week`), a topic keyword (`rc.20`, `cite policy`),
|
|
@@ -252,83 +252,16 @@ defaults if absent):
|
|
|
252
252
|
|
|
253
253
|
If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
|
|
254
254
|
|
|
255
|
-
### UX i18n Policy
|
|
256
|
-
|
|
257
|
-
The skill consults `fabric_language` from `.fabric/fabric-config.json`
|
|
258
|
-
(固化于 init 时,via `lib/detect-language.ts:detectExistingLanguage`; default `"en"` when no
|
|
259
|
-
CJK signal is detected in README + docs/; may resolve to `"match-existing"`,
|
|
260
|
-
`"zh-CN"`, `"en"`, or `"zh-CN-hybrid"`). All user-facing text in the
|
|
261
|
-
following 5 categories MUST be rendered in the resolved language:
|
|
262
|
-
|
|
263
|
-
1. **Roll-up templates** — the `# Archive Review — N candidates` batch
|
|
264
|
-
review block (one per candidate) AND any final session summary the
|
|
265
|
-
skill emits after Phase 2 completes. zh-CN ↔ en mirror.
|
|
266
|
-
2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
|
|
267
|
-
the "没有触发归档信号…" trigger-miss and the "本次会话为常规执行…"
|
|
268
|
-
viability-gate-FAIL message). zh-CN ↔ en mirror.
|
|
269
|
-
3. **Confirmation prompts** — the per-candidate `Confirm? (Y to accept,
|
|
270
|
-
edit … inline, N to skip)` line in the batch review template. zh-CN
|
|
271
|
-
↔ en mirror.
|
|
272
|
-
4. **Dry-run table headers** — v2.0.0-rc.27 TASK-007 added a dry-run
|
|
273
|
-
override path (see Phase 2.5 "dry-run") so users can preview the
|
|
274
|
-
archive proposal without writing pending entries. The dry-run summary
|
|
275
|
-
header and per-candidate preview labels MUST be bilingualized per
|
|
276
|
-
this policy. zh-CN ↔ en mirror.
|
|
277
|
-
5. **AskUserQuestion** — `header` + `question` fields (NOT `options[]`).
|
|
278
|
-
zh-CN ↔ en mirror. fabric-archive itself does not surface
|
|
279
|
-
AskUserQuestion in the current contract (Phase 1 batch review is a
|
|
280
|
-
single markdown screen, not a structured question), but if a future
|
|
281
|
-
version adds one — e.g. to confirm layer flip — this rule applies.
|
|
282
|
-
|
|
283
|
-
Rendering rule:
|
|
284
|
-
|
|
285
|
-
- `fabric_language === "zh-CN"` → emit the zh-CN variant; pure monolingual, no language mixing inside a single user-facing block.
|
|
286
|
-
- `fabric_language === "en"` → emit the en variant; pure monolingual, no language mixing inside a single user-facing block.
|
|
287
|
-
- `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`).
|
|
288
|
-
- `fabric_language === "match-existing"` or any other value → emit the en variant; pure monolingual.
|
|
289
|
-
|
|
290
|
-
Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
|
|
291
|
-
`relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
|
|
292
|
-
`session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`,
|
|
293
|
-
`pending_path`, `layer`, `team`, `personal`,
|
|
294
|
-
`knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, the verbatim
|
|
295
|
-
`强 team` / `强 personal` / `默认 team` heuristic block, etc.) are NEVER
|
|
296
|
-
translated — they appear verbatim in both language variants. The
|
|
297
|
-
bilingualization scope is prose ONLY.
|
|
298
|
-
|
|
299
|
-
### AskUserQuestion i18n Policy (value vs label)
|
|
300
|
-
|
|
301
|
-
When a skill (this one or any sibling skill the user is composing with)
|
|
302
|
-
issues an `AskUserQuestion`, the `header` and `question` strings are
|
|
303
|
-
user-facing prose → translated per `fabric_language`. The `options[]`
|
|
304
|
-
array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
|
|
305
|
-
fabric-review, or `["team", "personal"]` for a layer-flip target) are
|
|
306
|
-
**routing keys** consumed by the skill state machine — they MUST remain
|
|
307
|
-
English regardless of `fabric_language`.
|
|
255
|
+
### UX i18n Policy
|
|
308
256
|
|
|
309
|
-
|
|
310
|
-
// EN (fabric_language === "en")
|
|
311
|
-
AskUserQuestion({
|
|
312
|
-
header: "Layer-flip target",
|
|
313
|
-
question: "Move '{title}' to which layer? (current: {current_layer})",
|
|
314
|
-
options: ["team", "personal"]
|
|
315
|
-
})
|
|
257
|
+
Read `.fabric/fabric-config.json` → `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`). Emit all user-facing prose in the resolved variant. Protected tokens (MCP tool names like `fab_extract_knowledge`, schema fields like `relevance_scope`, the verbatim `强 team` / `强 personal` / `默认 team` heuristic block) are NEVER translated.
|
|
316
258
|
|
|
317
|
-
|
|
318
|
-
AskUserQuestion({
|
|
319
|
-
header: "Layer 切换目标",
|
|
320
|
-
question: "将 '{title}' 切换到哪一层?(当前: {current_layer})",
|
|
321
|
-
options: ["team", "personal"] // 不翻译 — routing key
|
|
322
|
-
})
|
|
323
|
-
```
|
|
259
|
+
`AskUserQuestion` policy: `header` + `question` translate; `options[]` are routing keys — stay English regardless of locale.
|
|
324
260
|
|
|
325
|
-
|
|
326
|
-
dual-string match (e.g. `if (choice === "team" || choice === "团队")`),
|
|
327
|
-
which doubles the surface area for protected-token regressions and breaks
|
|
328
|
-
the option-list invariants that downstream tooling depends on. Keeping
|
|
329
|
-
`options[]` English-only is contract-locked across all three skills.
|
|
261
|
+
**For the full 5-class taxonomy + edge cases:** `Read packages/cli/templates/skills/fabric-archive/ref/i18n-policy.md` (or `.claude/skills/fabric-archive/ref/i18n-policy.md` post-install).
|
|
330
262
|
|
|
331
|
-
|
|
263
|
+
|
|
264
|
+
### Phase 0.0 — Collect Cross-Session Digests
|
|
332
265
|
|
|
333
266
|
Before any single-session collection or viability gating, stitch together
|
|
334
267
|
context from every session that has accumulated since the last
|
|
@@ -459,219 +392,11 @@ events), treat all sessions as never-attempted (current default behavior) —
|
|
|
459
392
|
step 4.5 rule (e) applies uniformly, so the filter degrades to the legacy
|
|
460
393
|
"scan everything since anchor" semantics without raising errors.
|
|
461
394
|
|
|
462
|
-
### Phase 0.4 — First-run Onboard
|
|
463
|
-
|
|
464
|
-
#### Phase 0.4 Trigger Gate (rc.25 — entry-context aware)
|
|
465
|
-
|
|
466
|
-
Before running ANY of the onboard coverage steps below, evaluate the
|
|
467
|
-
**entry-context gate**. Onboard slot collection is an interactive,
|
|
468
|
-
one-time project-tone capture flow that REQUIRES live user dialogue.
|
|
469
|
-
Non-user-active entries (hook / AI self-trigger / cron) either interrupt
|
|
470
|
-
the user mid-work or run unattended where dialogue is impossible, so
|
|
471
|
-
they MUST skip Phase 0.4 entirely and fall through to Phase 0.
|
|
472
|
-
|
|
473
|
-
Read `context.entry_point` — already determined in **Phase -0.5 Range
|
|
474
|
-
Resolution** (see TASK-04 / Phase -0.5 section above). The 5-entry model
|
|
475
|
-
is the canonical taxonomy for this gate.
|
|
476
|
-
|
|
477
|
-
##### Entry-context detection rules
|
|
478
|
-
|
|
479
|
-
| Entry | Symbol | Detection rule (LLM-native, evaluated at skill entry) |
|
|
480
|
-
|-------|--------|-------------------------------------------------------|
|
|
481
|
-
| **E1** | `hook_passive` | stdout JSON `{decision:'block', ...}` from `archive-hint.cjs` detected at skill entry (the Stop-hook reminder path). |
|
|
482
|
-
| **E2** | `explicit_user_invoke` | User prompt is a direct invocation: `fabric archive` / `/fabric-archive` / `archive what we just did` / `归档一下` / similar imperative. |
|
|
483
|
-
| **E3** | `ai_self_trigger` | AI internal marker `self-archive policy triggered by signal: <X>` present (substring match on the verbatim prefix `self-archive policy triggered by signal`; `<X>` is one of the 4 self-trigger signals from AGENTS.md E3 section: `Normative` / `Wrong-turn-and-revert` / `Decision confirmation` / `Explicit dismissal`). |
|
|
484
|
-
| **E4** | `user_range_rollback` | Prompt contains a **range hint** (parsed in Phase -0.5 — e.g. `今日` / `上周` / `rc.20`) AND the user is invoking. Sub-mode of E2. |
|
|
485
|
-
| **E5** | `cron` | Prompt contains literal `今日复盘` / `daily recap` / `daily-archive` AND no human is present (running under `/loop`, OS cron, or scheduled trigger). |
|
|
486
|
-
|
|
487
|
-
##### Gate decision
|
|
488
|
-
|
|
489
|
-
```
|
|
490
|
-
IF context.entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback}:
|
|
491
|
-
→ gate = PROCEED # user is live, dialogue is possible
|
|
492
|
-
→ continue to Step 1 (Check coverage) below
|
|
493
|
-
ELSE (E1_hook_passive | E3_ai_self_trigger | E5_cron):
|
|
494
|
-
→ gate = SKIP # no live user, onboard prompting would misfire
|
|
495
|
-
→ emit one-line log: "Phase 0.4 skipped (entry=<E1|E3|E5>, no live user)"
|
|
496
|
-
→ proceed directly to Phase 0
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
##### Rationale
|
|
500
|
-
|
|
501
|
-
Onboard slot collection is a one-time project-tone capture flow that
|
|
502
|
-
requires user dialogue. Non-user-active entries (hook / AI / cron)
|
|
503
|
-
interrupt the user mid-work or run unattended where dialogue is
|
|
504
|
-
impossible, so they MUST skip Phase 0.4. The S5 slot semantics
|
|
505
|
-
(`tech-stack-decision`, `architecture-pattern`, ...) are user-validated
|
|
506
|
-
baselines — populating them from a hook fire-and-forget or a cron daily
|
|
507
|
-
recap would defeat the purpose of capturing _user-confirmed_ project
|
|
508
|
-
tone.
|
|
509
|
-
|
|
510
|
-
##### Tradeoff (documented in CHANGELOG)
|
|
511
|
-
|
|
512
|
-
A first-time user whose ONLY invocations ever come via hook (never an
|
|
513
|
-
explicit `/fabric-archive`) will not see the onboard prompt; the 5
|
|
514
|
-
onboard slots remain empty. Mitigation: documentation tells users to
|
|
515
|
-
run an explicit `fab archive` at least once to populate the onboard
|
|
516
|
-
baseline.
|
|
517
|
-
|
|
518
|
-
##### Worked example
|
|
519
|
-
|
|
520
|
-
```
|
|
521
|
-
$ /loop 24h /fabric-archive 今日复盘
|
|
522
|
-
→ cron context, no live user
|
|
523
|
-
→ Phase -0.5 detects literal "今日复盘" + no-human marker
|
|
524
|
-
→ context.entry_point = E5_cron
|
|
525
|
-
→ Phase 0.4 Trigger Gate evaluates: E5 ∉ {E2, E4} → SKIP
|
|
526
|
-
→ emit log "Phase 0.4 skipped (entry=E5, no live user)"
|
|
527
|
-
→ proceed directly to Phase 0 (collect candidates for daily window)
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
Contrast with E2:
|
|
531
|
-
|
|
532
|
-
```
|
|
533
|
-
$ /fabric-archive
|
|
534
|
-
→ user typed explicit invocation
|
|
535
|
-
→ Phase -0.5: context.entry_point = E2_explicit_user_invoke
|
|
536
|
-
→ Phase 0.4 Trigger Gate evaluates: E2 ∈ {E2, E4} → PROCEED
|
|
537
|
-
→ run Step 1 (Check coverage) below
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
After F8a removed the auto-`fab scan` baseline pipeline, a freshly installed
|
|
543
|
-
Fabric workspace ships with an EMPTY `.fabric/knowledge/` tree. Five fixed
|
|
544
|
-
**S5 onboard slots** capture the "project tone" baseline that the AI needs
|
|
545
|
-
for high-quality plan_context retrieval from day one:
|
|
546
|
-
|
|
547
|
-
- `tech-stack-decision` — primary languages / frameworks / runtime stack
|
|
548
|
-
- `architecture-pattern` — module layout, service boundaries, layering rules
|
|
549
|
-
- `code-style-tone` — naming / formatting / idiom conventions the project enforces
|
|
550
|
-
- `build-system-idiom` — build tool quirks, scripts, deploy pipeline shape
|
|
551
|
-
- `domain-vocabulary` — business / product terminology that names code entities
|
|
552
|
-
|
|
553
|
-
This phase runs ONCE per archive-skill invocation, BEFORE Phase 0 evidence
|
|
554
|
-
gathering, so coverage state is fresh for the session.
|
|
555
|
-
|
|
556
|
-
#### Step 1 — Check coverage
|
|
557
|
-
|
|
558
|
-
Invoke `fab onboard-coverage --json` and parse the JSON payload:
|
|
559
|
-
|
|
560
|
-
```bash
|
|
561
|
-
fab onboard-coverage --json
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
Expected shape:
|
|
565
|
-
|
|
566
|
-
```json
|
|
567
|
-
{
|
|
568
|
-
"filled": { "tech-stack-decision": ["KT-DEC-0012"], ... },
|
|
569
|
-
"missing": ["architecture-pattern", "code-style-tone"],
|
|
570
|
-
"opted_out": ["domain-vocabulary"],
|
|
571
|
-
"total": 5
|
|
572
|
-
}
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
#### Step 2 — Decide
|
|
576
|
-
|
|
577
|
-
```
|
|
578
|
-
IF missing.length === 0:
|
|
579
|
-
→ skip Phase 0.4 entirely; proceed to Phase 0.
|
|
580
|
-
ELSE:
|
|
581
|
-
→ ask the user how to handle the missing slots (Step 3).
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
#### Step 3 — Prompt user
|
|
585
|
-
|
|
586
|
-
Present a single roll-up listing each missing slot. UX i18n Policy class 5
|
|
587
|
-
applies: the `header` + `question` strings are translated per
|
|
588
|
-
`fabric_language`; the `options[]` routing keys stay English.
|
|
589
|
-
|
|
590
|
-
```ts
|
|
591
|
-
AskUserQuestion({
|
|
592
|
-
header: "Onboard coverage", // zh-CN: "首装基调覆盖"
|
|
593
|
-
question:
|
|
594
|
-
"KB is missing the following project-tone slots: " +
|
|
595
|
-
missing.join(", ") +
|
|
596
|
-
". Tour the project and propose pending entries for each?",
|
|
597
|
-
options: ["fill-all", "fill-each", "dismiss-all", "skip"]
|
|
598
|
-
})
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
`fab_extract_knowledge` is called with `onboard_slot: <slot>` set so each
|
|
602
|
-
proposed entry counts toward coverage once approved via fab_review.
|
|
603
|
-
|
|
604
|
-
| User choice | Action |
|
|
605
|
-
|----------------|--------|
|
|
606
|
-
| `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). |
|
|
607
|
-
| `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. |
|
|
608
|
-
| `dismiss-all` | For EACH slot in `missing`, invoke `Bash("fab config dismiss-slot <slot>")`. Print a one-line confirmation each. Skip to Phase 0. |
|
|
609
|
-
| `skip` | No-op. Slots remain in `missing` for the next archive run. Skip to Phase 0. |
|
|
395
|
+
### Phase 0.4 — First-run Onboard (ref-only)
|
|
610
396
|
|
|
611
|
-
|
|
397
|
+
**SKIP this phase entirely unless** entry_point ∈ {E2_explicit_user_invoke, E4_user_range_rollback} AND a fresh `fab onboard-coverage --json` reports `missing.length > 0`. For E1 (hook), E3 (AI self-trigger), and E5 (cron), onboard is non-applicable — silently fall through to Phase 0.
|
|
612
398
|
|
|
613
|
-
|
|
614
|
-
from the project (no user prompt — this is a Read-only tour):
|
|
615
|
-
|
|
616
|
-
| Slot | Source files (LLM should Read these) |
|
|
617
|
-
|--------------------------|---------------------------------------|
|
|
618
|
-
| `tech-stack-decision` | `package.json` (+ lockfile), `pyproject.toml` / `Cargo.toml` / `go.mod`, `tsconfig.json`, root README |
|
|
619
|
-
| `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`) |
|
|
620
|
-
| `code-style-tone` | `.editorconfig`, `prettier.config.*`, `eslint.config.*`, `biome.*`, `.prettierrc*`, framework lint config, 2-3 representative source files for naming-pattern inference |
|
|
621
|
-
| `build-system-idiom` | `package.json` `scripts` block, `Makefile`, `taskfile.yaml`, CI yml (`.github/workflows/*.yml`), Dockerfile if present |
|
|
622
|
-
| `domain-vocabulary` | README, `docs/*.md`, top-level `src/` directory names (often domain-aligned), public API entry types |
|
|
623
|
-
|
|
624
|
-
After Read-ing the slot-specific sources, classify the observation:
|
|
625
|
-
|
|
626
|
-
- `tech-stack-decision` → type=`decisions`, `proposed_reason=decision-confirmation`
|
|
627
|
-
- `architecture-pattern` → type=`models`, `proposed_reason=new-dependency-or-pattern`
|
|
628
|
-
- `code-style-tone` → type=`guidelines`, `proposed_reason=explicit-user-mark` (the project ITSELF is the mark)
|
|
629
|
-
- `build-system-idiom` → type=`processes`, `proposed_reason=new-dependency-or-pattern`
|
|
630
|
-
- `domain-vocabulary` → type=`models`, `proposed_reason=new-dependency-or-pattern`
|
|
631
|
-
|
|
632
|
-
Call `fab_extract_knowledge` with the inferred fields PLUS `onboard_slot:
|
|
633
|
-
<slot>`. The pending file's frontmatter will carry the slot label, and the
|
|
634
|
-
next `fab onboard-coverage` run will see the slot as filled (once approved
|
|
635
|
-
via fab_review).
|
|
636
|
-
|
|
637
|
-
Example:
|
|
638
|
-
|
|
639
|
-
```ts
|
|
640
|
-
mcp__fabric__fab_extract_knowledge({
|
|
641
|
-
source_sessions: ["<current-session-id>"],
|
|
642
|
-
recent_paths: ["package.json", "tsconfig.json"],
|
|
643
|
-
user_messages_summary: "Project uses TypeScript + pnpm workspace + Vitest. Node 20 LTS target. ESM-only.",
|
|
644
|
-
type: "decisions",
|
|
645
|
-
slug: "primary-tech-stack",
|
|
646
|
-
layer: "team",
|
|
647
|
-
relevance_scope: "broad", // tech stack applies everywhere
|
|
648
|
-
relevance_paths: [],
|
|
649
|
-
proposed_reason: "decision-confirmation",
|
|
650
|
-
session_context:
|
|
651
|
-
"Session goal: capture onboard tech-stack baseline.\nTurning point: read package.json + tsconfig.json + pnpm-workspace.yaml; stack confirmed.",
|
|
652
|
-
onboard_slot: "tech-stack-decision", // ← claims the slot
|
|
653
|
-
tech_stack: ["typescript", "nodejs", "pnpm", "vitest"]
|
|
654
|
-
})
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
#### Onboard phase constraints (DO NOT TRANSLATE)
|
|
658
|
-
|
|
659
|
-
- MUST run BEFORE Phase 0 evidence gathering — onboard is a separate flow,
|
|
660
|
-
not interleaved with session-archive candidates.
|
|
661
|
-
- MUST call `fab onboard-coverage --json` before deciding; never assume
|
|
662
|
-
coverage state.
|
|
663
|
-
- NEVER fill a slot that is in `opted_out` — `fab onboard-coverage` already
|
|
664
|
-
excludes those from `missing`, but the Skill MUST NOT re-propose them
|
|
665
|
-
even if the user asks "fill all of them" — the dismiss is intentional.
|
|
666
|
-
- NEVER prompt the user when `missing.length === 0` — silent skip.
|
|
667
|
-
- NEVER set `onboard_slot` on a regular session-archive candidate in
|
|
668
|
-
Phase 2 — that field is RESERVED for the onboard phase. Mixing the
|
|
669
|
-
two would let session-archive proposals masquerade as onboard
|
|
670
|
-
coverage and let any random pending file claim a slot.
|
|
671
|
-
- MUST emit `onboard_slot: <slot>` verbatim — the slot name is one of
|
|
672
|
-
the locked S5 strings (tech-stack-decision / architecture-pattern /
|
|
673
|
-
code-style-tone / build-system-idiom / domain-vocabulary). The
|
|
674
|
-
fab_extract_knowledge schema enum will reject anything else.
|
|
399
|
+
When the gate above does fire (live user + missing slots), `Read packages/cli/templates/skills/fabric-archive/ref/phase-0-4-onboard.md` (or `.claude/skills/fabric-archive/ref/phase-0-4-onboard.md` post-install) for the full Step 1-4 (coverage check → user prompt → tour-and-propose) procedure.
|
|
675
400
|
|
|
676
401
|
### Phase 0 — Collect Candidates
|
|
677
402
|
|
|
@@ -1213,132 +938,12 @@ Next day's cron rescan: Phase 0.0 sees `covered_through_ts < max(ts of session's
|
|
|
1213
938
|
- NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
|
|
1214
939
|
- 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`.
|
|
1215
940
|
|
|
1216
|
-
## Worked Examples
|
|
1217
|
-
|
|
1218
|
-
### Example 1 — decision (team)
|
|
1219
|
-
|
|
1220
|
-
Session: User and agent debated whether the Stop-hook should be one .cjs script or three per-client scripts. Settled on one because stdout JSON shape `{"decision":"block","reason"}` is identical across Claude / Codex.
|
|
1221
|
-
|
|
1222
|
-
Skill output:
|
|
1223
|
-
|
|
1224
|
-
```ts
|
|
1225
|
-
mcp__fabric__fab_extract_knowledge({
|
|
1226
|
-
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
1227
|
-
recent_paths: ["templates/claude-hooks/", "packages/cli/src/commands/hooks.ts"],
|
|
1228
|
-
user_messages_summary: "User pushed back on three-script proposal; agreed single .cjs because stdout JSON shape is universal across Claude Code and Codex CLI.",
|
|
1229
|
-
type: "decisions",
|
|
1230
|
-
slug: "single-cjs-hook-script",
|
|
1231
|
-
layer: "team",
|
|
1232
|
-
relevance_scope: "narrow",
|
|
1233
|
-
relevance_paths: [
|
|
1234
|
-
"templates/claude-hooks/**/*.cjs",
|
|
1235
|
-
"packages/cli/src/commands/hooks.ts"
|
|
1236
|
-
],
|
|
1237
|
-
proposed_reason: "decision-confirmation",
|
|
1238
|
-
session_context: "Session goal: ship Stop-hook for v2 release.\nTurning point: user rejected 3-script proposal after seeing identical stdout JSON across Claude / Codex.\nResult: single .cjs path locked in."
|
|
1239
|
-
})
|
|
1240
|
-
```
|
|
1241
|
-
|
|
1242
|
-
Layer = team (引用本项目代码 + fabric-import 路径产物 signals). Scope = narrow (tied to hook templates + hooks command module; single-module evidence in edit_paths).
|
|
1243
|
-
|
|
1244
|
-
### Example 2 — pitfall (team)
|
|
1245
|
-
|
|
1246
|
-
Session: deepMerge silently replaced the existing `hooks.Stop[]` array in `.claude/settings.json` instead of appending. Cost ~30 min to diagnose.
|
|
1247
|
-
|
|
1248
|
-
Skill output:
|
|
1249
|
-
|
|
1250
|
-
```ts
|
|
1251
|
-
mcp__fabric__fab_extract_knowledge({
|
|
1252
|
-
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
1253
|
-
recent_paths: ["packages/cli/src/config/json.ts"],
|
|
1254
|
-
user_messages_summary: "deepMerge default behavior REPLACES arrays. hooks.Stop[] needs an array-append-with-dedupe special case keyed on .command string match.",
|
|
1255
|
-
type: "pitfalls",
|
|
1256
|
-
slug: "deepmerge-array-replace-trap",
|
|
1257
|
-
layer: "team",
|
|
1258
|
-
relevance_scope: "broad",
|
|
1259
|
-
relevance_paths: [],
|
|
1260
|
-
proposed_reason: "diagnostic-then-fix",
|
|
1261
|
-
session_context: "Session goal: wire hook installer for v2.\nTurning point: spent ~30 min chasing why prior Stop[] entries vanished — root cause was deepMerge replacing arrays silently.\nResult: array-append-with-dedupe special case added."
|
|
1262
|
-
})
|
|
1263
|
-
```
|
|
1264
|
-
|
|
1265
|
-
Layer = team (绑定本项目代码的 pitfall signal). Scope = broad (deepMerge gotcha is cross-cutting — applies anywhere JSON merge is used, not just `json.ts`).
|
|
1266
|
-
|
|
1267
|
-
### Example 3 — guideline (personal)
|
|
1268
|
-
|
|
1269
|
-
Session: User mentioned across three projects that they prefer 2-space indent in TypeScript and 4-space in Python.
|
|
1270
|
-
|
|
1271
|
-
Skill output:
|
|
1272
|
-
|
|
1273
|
-
```ts
|
|
1274
|
-
mcp__fabric__fab_extract_knowledge({
|
|
1275
|
-
source_sessions: ["WFS-2026-05-10-rc2"],
|
|
1276
|
-
recent_paths: [".editorconfig"],
|
|
1277
|
-
user_messages_summary: "Personal indent preference: 2-space TS / 4-space Py. Stable across multiple projects, not project-specific.",
|
|
1278
|
-
type: "guidelines",
|
|
1279
|
-
slug: "indent-style-by-language",
|
|
1280
|
-
layer: "personal",
|
|
1281
|
-
relevance_scope: "broad",
|
|
1282
|
-
relevance_paths: [],
|
|
1283
|
-
proposed_reason: "explicit-user-mark",
|
|
1284
|
-
session_context: "Session goal: align editor config.\nTurning point: user said '一直 prefer 2-space TS / 4-space Py,across projects'.\nResult: personal-layer guideline; not bound to this project."
|
|
1285
|
-
})
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
Layer = personal (跨项目通用 + 工具/编辑器偏好 signals dominate; no 强 team signal applies). Scope = broad with `relevance_paths=[]` (personal layer ALWAYS forces broad — paths don't generalize across projects per Phase 1.5 special case).
|
|
1289
|
-
|
|
1290
|
-
## E5 周期触发 (Scheduled Daily Recap)
|
|
1291
|
-
|
|
1292
|
-
### Overview
|
|
1293
|
-
|
|
1294
|
-
`今日复盘` = E5 entry point. Default scope = today. Falls back to historical scan if today yields no candidates (silent-skip per Phase 2.5).
|
|
1295
|
-
|
|
1296
|
-
E5 是 5 入口模型中唯一由 OS 调度器或 Claude Code `/loop` 周期触发的入口形态。fab 端**零代码**——不提供 `fab schedule` 子命令,亦不内嵌 daemon。用户基于自己的执行环境二选一接入: `/loop`(Claude Code 原生,推荐) 或 OS cron(跨平台 fallback)。
|
|
1297
|
-
|
|
1298
|
-
### /loop sample (primary path for Claude Code)
|
|
1299
|
-
|
|
1300
|
-
```
|
|
1301
|
-
/loop /fabric-archive 今日复盘 --cron "0 23 * * *"
|
|
1302
|
-
```
|
|
1303
|
-
|
|
1304
|
-
每晚 23:00 在当前 Claude Code session 中触发 fabric-archive skill,scope = today。`/loop` 复用现有 Claude session 鉴权,无需独立 token。
|
|
1305
|
-
|
|
1306
|
-
### OS cron sample (cross-platform alternative)
|
|
1307
|
-
|
|
1308
|
-
```
|
|
1309
|
-
# crontab -e
|
|
1310
|
-
0 23 * * * cd /path/to/project && claude code -p "/fabric-archive 今日复盘" 2>&1 >> /var/log/fabric-daily-recap.log
|
|
1311
|
-
```
|
|
1312
|
-
|
|
1313
|
-
适用于:
|
|
1314
|
-
- 非 Claude Code 环境(纯 server / CI 节点)
|
|
1315
|
-
- 希望脱离 /loop session 生命周期独立运行的场景
|
|
1316
|
-
- 已有 cron / launchd 调度基础设施的团队
|
|
1317
|
-
|
|
1318
|
-
macOS 用户可改用 `launchd` plist;Linux 用户直接 `crontab -e`。命令需自行确保 `claude code` CLI 已安装且鉴权可用。
|
|
1319
|
-
|
|
1320
|
-
### E5 prompt parse contract
|
|
1321
|
-
|
|
1322
|
-
当用户或 cron 以 `今日复盘` / `daily recap` 字面短语触发 fabric-archive 时,skill 应按以下契约处理:
|
|
1323
|
-
|
|
1324
|
-
- **Phase -0.5 Range Resolution**: 识别 `今日复盘` / `daily recap` 为 magic phrase, 直接设置 `time_window = today` (00:00 local timezone → current ts), 无需 AskUserQuestion 兜底。
|
|
1325
|
-
- **Phase 0.4 Onboard Coverage**: 跳过 (entry_point = E5_cron, 非 E2_explicit, 不弹 onboard 弹问)。
|
|
1326
|
-
- **Phase 2.5 Persist Archive Attempt**: 始终写入 `session_archive_attempted` event。当今日无 archive 信号触发 viability gate FAIL 时,走 silent-skip 路径(outcome = `skipped_no_signal`),skill 静默退出,cron 日志为空。
|
|
1327
|
-
|
|
1328
|
-
### Trade-off table (/loop vs OS cron)
|
|
1329
|
-
|
|
1330
|
-
| 维度 | /loop | OS cron |
|
|
1331
|
-
|---|---|---|
|
|
1332
|
-
| 鉴权 | 复用 Claude session | 独立 token / login |
|
|
1333
|
-
| 跨平台 | Claude Code 全平台一致 | macOS launchd / Linux cron 不同 |
|
|
1334
|
-
| Token 成本 | 累积 (长 session) | 每 tick fresh, 无累积 |
|
|
1335
|
-
| 调试 | Claude UI 可见 | 日志文件 |
|
|
941
|
+
## Worked Examples (ref-only)
|
|
1336
942
|
|
|
1337
|
-
|
|
943
|
+
Three end-to-end fab_extract_knowledge call examples (decision/team, pitfall/team, guideline/personal) live in `packages/cli/templates/skills/fabric-archive/ref/worked-examples.md` (or `.claude/skills/fabric-archive/ref/worked-examples.md` post-install). Load when you want to see all required + optional fields populated together in a realistic shape.
|
|
1338
944
|
|
|
1339
|
-
|
|
1340
|
-
- **OS cron**: 自带恢复(下一个 tick 重新启动),但需独立 `claude code` CLI 安装与鉴权;鉴权 token 过期时 cron job 会静默失败,需人工 `claude login` 重置。
|
|
945
|
+
## E5 Scheduled Daily Recap (ref-only)
|
|
1341
946
|
|
|
1342
|
-
|
|
947
|
+
Only relevant when entry_point=E5_cron (OS cron, `/loop`, or scheduled trigger). For interactive invocations, Phase -0.5 has already routed past this — nothing to load.
|
|
1343
948
|
|
|
1344
|
-
|
|
949
|
+
When E5 fires: `Read packages/cli/templates/skills/fabric-archive/ref/e5-cron-recap.md` (or `.claude/skills/fabric-archive/ref/e5-cron-recap.md` post-install) for `/loop` vs OS cron tradeoffs + the `今日复盘` magic-phrase parse contract.
|