@fenglimg/fabric-cli 2.0.0-rc.10 → 2.0.0-rc.11
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-MT3R57VG.js → chunk-5MQ52F42.js} +1 -0
- package/dist/index.js +3 -3
- package/dist/{init-SAVH4SKE.js → init-C56PWHID.js} +46 -9
- package/dist/{scan-ELSNCSKS.js → scan-66EKMNAY.js} +3 -1
- package/package.json +3 -3
- package/templates/skills/fabric-archive/SKILL.md +184 -30
- package/templates/skills/fabric-import/SKILL.md +319 -29
- package/templates/skills/fabric-review/SKILL.md +358 -23
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { defineCommand, runMain } from "citty";
|
|
|
8
8
|
|
|
9
9
|
// src/commands/index.ts
|
|
10
10
|
var allCommands = {
|
|
11
|
-
init: () => import("./init-
|
|
12
|
-
scan: () => import("./scan-
|
|
11
|
+
init: () => import("./init-C56PWHID.js").then((module) => module.default),
|
|
12
|
+
scan: () => import("./scan-66EKMNAY.js").then((module) => module.default),
|
|
13
13
|
serve: () => import("./serve-NGLXHDYC.js").then((module) => module.default),
|
|
14
14
|
uninstall: () => import("./uninstall-DBAR2JBS.js").then((module) => module.default),
|
|
15
15
|
doctor: () => import("./doctor-RILCO5OG.js").then((module) => module.default),
|
|
@@ -21,7 +21,7 @@ var allCommands = {
|
|
|
21
21
|
var main = defineCommand({
|
|
22
22
|
meta: {
|
|
23
23
|
name: "fabric",
|
|
24
|
-
version: "2.0.0-rc.
|
|
24
|
+
version: "2.0.0-rc.11",
|
|
25
25
|
description: 'Initialize and manage Fabric projects. Use "fabric init" for one-shot setup.'
|
|
26
26
|
},
|
|
27
27
|
subCommands: allCommands
|
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
installHooks
|
|
5
5
|
} from "./chunk-WPTA74BY.js";
|
|
6
6
|
import {
|
|
7
|
+
detectExistingLanguage,
|
|
7
8
|
detectFramework,
|
|
8
9
|
runInitScan
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5MQ52F42.js";
|
|
10
11
|
import {
|
|
11
12
|
detectClientSupports,
|
|
12
13
|
resolveClients
|
|
@@ -1292,7 +1293,7 @@ function readProjectName(target) {
|
|
|
1292
1293
|
return basename(target);
|
|
1293
1294
|
}
|
|
1294
1295
|
function getCliVersion() {
|
|
1295
|
-
return true ? "2.0.0-rc.
|
|
1296
|
+
return true ? "2.0.0-rc.11" : "unknown";
|
|
1296
1297
|
}
|
|
1297
1298
|
function sortRecord(record) {
|
|
1298
1299
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1424,14 +1425,16 @@ async function runInitCommand(args) {
|
|
|
1424
1425
|
}
|
|
1425
1426
|
return result;
|
|
1426
1427
|
}
|
|
1427
|
-
function writeDefaultFabricConfig(fabricDir) {
|
|
1428
|
+
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1428
1429
|
const target = join2(fabricDir, "fabric-config.json");
|
|
1429
1430
|
if (existsSync3(target)) return;
|
|
1431
|
+
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1430
1432
|
const FABRIC_CONFIG_DEFAULTS = {
|
|
1431
|
-
// Scan/import language policy.
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
|
|
1433
|
+
// Scan/import language policy. Fixated at init time by probing
|
|
1434
|
+
// README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
|
|
1435
|
+
// can edit `.fabric/fabric-config.json` to override. See
|
|
1436
|
+
// packages/shared/src/schemas/fabric-config.ts for the enum.
|
|
1437
|
+
knowledge_language: detectedLanguage,
|
|
1435
1438
|
// fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
|
|
1436
1439
|
// since last knowledge_proposed event.
|
|
1437
1440
|
archive_hint_hours: 24,
|
|
@@ -1452,10 +1455,44 @@ function writeDefaultFabricConfig(fabricDir) {
|
|
|
1452
1455
|
archive_edit_threshold: 20,
|
|
1453
1456
|
// fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
|
|
1454
1457
|
// knowledge node count below this value flags an underseeded workspace.
|
|
1455
|
-
underseed_node_threshold: 10
|
|
1458
|
+
underseed_node_threshold: 10,
|
|
1459
|
+
// rc.9+ (skill-contract-fix B1): fabric-import first-run git-history
|
|
1460
|
+
// window in months. Default 60 captures the bulk of a mature repo's
|
|
1461
|
+
// signal in one pass; lower to 12-24 for fresh / small repos.
|
|
1462
|
+
import_window_first_run_months: 60,
|
|
1463
|
+
// rc.9+ (skill-contract-fix B1): fabric-import rerun window in months.
|
|
1464
|
+
// Default 2; raise to 6 if the workspace pauses imports for long stretches.
|
|
1465
|
+
import_window_rerun_months: 2,
|
|
1466
|
+
// rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
|
|
1467
|
+
// per fabric-import invocation. Default 10 matches one-sitting triage.
|
|
1468
|
+
import_max_pending_per_run: 10,
|
|
1469
|
+
// rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
|
|
1470
|
+
// fabric-import invocation. Default 500 covers ~2 months of typical churn.
|
|
1471
|
+
import_max_commits_scan: 500,
|
|
1472
|
+
// rc.9+ (skill-contract-fix B1): canonical-node count above which
|
|
1473
|
+
// fabric-import suggests review over importing more. Default 50.
|
|
1474
|
+
import_skip_canonical_threshold: 50,
|
|
1475
|
+
// rc.9+ (skill-contract-fix B1): max candidates per fabric-archive batch.
|
|
1476
|
+
// Default 8 keeps each batch reviewable in one sitting.
|
|
1477
|
+
archive_max_candidates_per_batch: 8,
|
|
1478
|
+
// rc.9+ (skill-contract-fix B1): max recently-touched paths in
|
|
1479
|
+
// fabric-archive's relevance digest. Default 20.
|
|
1480
|
+
archive_max_recent_paths: 20,
|
|
1481
|
+
// rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
|
|
1482
|
+
// summarised in the digest the skill loads on start. Default 10.
|
|
1483
|
+
archive_digest_max_sessions: 10,
|
|
1484
|
+
// rc.9+ (skill-contract-fix B1): max review results per topic cluster
|
|
1485
|
+
// in fabric-review. Default 8.
|
|
1486
|
+
review_topic_result_cap: 8,
|
|
1487
|
+
// rc.9+ (skill-contract-fix B1): age (days) above which a pending entry
|
|
1488
|
+
// is considered stale by fabric-review. Default 14.
|
|
1489
|
+
review_stale_pending_days: 14
|
|
1456
1490
|
};
|
|
1457
1491
|
mkdirSync(fabricDir, { recursive: true });
|
|
1458
1492
|
writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
1493
|
+
log.info(
|
|
1494
|
+
`Detected and fixated knowledge_language = ${detectedLanguage}; edit ${target} to override.`
|
|
1495
|
+
);
|
|
1459
1496
|
}
|
|
1460
1497
|
function resolveInitCliIntent(args, targetInput) {
|
|
1461
1498
|
const target = normalizeTarget2(targetInput);
|
|
@@ -1623,7 +1660,7 @@ async function executeInitFabricPlan(plan) {
|
|
|
1623
1660
|
rmSync(plan.fabricDir, { force: true });
|
|
1624
1661
|
}
|
|
1625
1662
|
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1626
|
-
writeDefaultFabricConfig(plan.fabricDir);
|
|
1663
|
+
writeDefaultFabricConfig(plan.fabricDir, plan.target);
|
|
1627
1664
|
if (plan.agentsMdAction === "created" && !existsSync3(plan.agentsMdPath)) {
|
|
1628
1665
|
await atomicWriteText(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1629
1666
|
}
|
|
@@ -3,11 +3,12 @@ import {
|
|
|
3
3
|
__testing__,
|
|
4
4
|
createScanReport,
|
|
5
5
|
deriveTagsFromForensic,
|
|
6
|
+
detectExistingLanguage,
|
|
6
7
|
formatKnowledgeId,
|
|
7
8
|
runInitScan,
|
|
8
9
|
scanCommand,
|
|
9
10
|
scan_default
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5MQ52F42.js";
|
|
11
12
|
import "./chunk-WWNXR34K.js";
|
|
12
13
|
import "./chunk-OBQU6NHO.js";
|
|
13
14
|
import "./chunk-6ICJICVU.js";
|
|
@@ -16,6 +17,7 @@ export {
|
|
|
16
17
|
createScanReport,
|
|
17
18
|
scan_default as default,
|
|
18
19
|
deriveTagsFromForensic,
|
|
20
|
+
detectExistingLanguage,
|
|
19
21
|
formatKnowledgeId,
|
|
20
22
|
runInitScan,
|
|
21
23
|
scanCommand
|
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.11",
|
|
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-
|
|
24
|
-
"@fenglimg/fabric-
|
|
23
|
+
"@fenglimg/fabric-server": "2.0.0-rc.11",
|
|
24
|
+
"@fenglimg/fabric-shared": "2.0.0-rc.11"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^22.15.0",
|
|
@@ -14,18 +14,111 @@ This skill is invoked when one of the following holds:
|
|
|
14
14
|
- The user typed an explicit archive request (e.g. "archive what we just did", "fabric archive")
|
|
15
15
|
- A task wrap-up moment where the agent itself判定 a worth-keeping insight has surfaced
|
|
16
16
|
|
|
17
|
-
If none of the above hold, stop the skill immediately and tell the user
|
|
17
|
+
If none of the above hold, stop the skill immediately and tell the user (UX i18n Policy class 2 — errors/preconditions):
|
|
18
|
+
|
|
19
|
+
- zh-CN: `没有触发归档信号;如需手动归档请显式调用 fabric-archive`
|
|
20
|
+
- en: `No archive signal detected; to manually archive, explicitly invoke fabric-archive`
|
|
21
|
+
|
|
22
|
+
(Render per `knowledge_language` resolved in Phase 0.6 Config Load below.)
|
|
18
23
|
|
|
19
24
|
This skill is `Check-not-Ask`, not a preference interview:
|
|
20
25
|
|
|
21
26
|
- Phase 0 proactively gathers candidate evidence from the session
|
|
22
27
|
- Phase 0.5 viability gate aborts the skill if the session lacks any archive-signal (anti-archive guard)
|
|
23
28
|
- Phase 1 classifies / layers / slugs each candidate and presents one batch review for user correction
|
|
24
|
-
- Phase 1.5 assigns `
|
|
29
|
+
- Phase 1.5 assigns `relevance_scope=narrow|broad` and derives `relevance_paths` from edit history (rc.5 single-signal source)
|
|
25
30
|
- Phase 2 calls `fab_extract_knowledge` once per confirmed candidate
|
|
26
31
|
|
|
27
32
|
## 执行流程 (5 Phase / 1 User Review Round)
|
|
28
33
|
|
|
34
|
+
### Phase 0.6 — Config Load
|
|
35
|
+
|
|
36
|
+
Before any candidate-gathering work, the skill MUST read
|
|
37
|
+
`.fabric/fabric-config.json` to resolve the following tunables (with documented
|
|
38
|
+
defaults if absent):
|
|
39
|
+
|
|
40
|
+
| Config field | Default | Used by |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `archive_max_candidates_per_batch` | 8 | Phase 0 hard budget on candidates per Phase 1 batch |
|
|
43
|
+
| `archive_max_recent_paths` | 20 | Phase 0 cap on `recent_paths` enumeration |
|
|
44
|
+
| `archive_digest_max_sessions` | 10 | Phase 0.0 cap on cross-session digest load |
|
|
45
|
+
|
|
46
|
+
If `.fabric/fabric-config.json` is missing or unreadable, use defaults silently.
|
|
47
|
+
|
|
48
|
+
### UX i18n Policy (5-class bilingualization)
|
|
49
|
+
|
|
50
|
+
The skill consults `knowledge_language` from `.fabric/fabric-config.json`
|
|
51
|
+
(固化于 init 时,via `scan.ts:detectExistingLanguage`; default `"en"` when no
|
|
52
|
+
CJK signal is detected in README + docs/). All user-facing text in the
|
|
53
|
+
following 5 categories MUST be rendered in the resolved language:
|
|
54
|
+
|
|
55
|
+
1. **Roll-up templates** — the `# Archive Review — N candidates` batch
|
|
56
|
+
review block (one per candidate) AND any final session summary the
|
|
57
|
+
skill emits after Phase 2 completes. zh-CN ↔ en mirror.
|
|
58
|
+
2. **Errors / Preconditions warnings** — abort + gate-fail messages (e.g.
|
|
59
|
+
the "没有触发归档信号…" trigger-miss and the "本次会话为常规执行…"
|
|
60
|
+
viability-gate-FAIL message). zh-CN ↔ en mirror.
|
|
61
|
+
3. **Confirmation prompts** — the per-candidate `Confirm? (Y to accept,
|
|
62
|
+
edit … inline, N to skip)` line in the batch review template. zh-CN
|
|
63
|
+
↔ en mirror.
|
|
64
|
+
4. **Dry-run table headers** — fabric-archive does not currently expose
|
|
65
|
+
a dry-run mode; this slot is reserved for parity with fabric-import.
|
|
66
|
+
IF a future revision adds dry-run, the table header MUST be
|
|
67
|
+
bilingualized per this policy. zh-CN ↔ en mirror.
|
|
68
|
+
5. **AskUserQuestion** — `header` + `question` fields (NOT `options[]`).
|
|
69
|
+
zh-CN ↔ en mirror. fabric-archive itself does not surface
|
|
70
|
+
AskUserQuestion in the current contract (Phase 1 batch review is a
|
|
71
|
+
single markdown screen, not a structured question), but if a future
|
|
72
|
+
version adds one — e.g. to confirm layer flip — this rule applies.
|
|
73
|
+
|
|
74
|
+
Rendering rule:
|
|
75
|
+
|
|
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.
|
|
81
|
+
|
|
82
|
+
Protected tokens (`fab_extract_knowledge`, `relevance_scope`,
|
|
83
|
+
`relevance_paths`, `narrow`, `broad`, `source_sessions`, `proposed_reason`,
|
|
84
|
+
`session_context`, `pending_path`, `layer`, `team`, `personal`,
|
|
85
|
+
`knowledge_scope_degraded`, `MUST`, `NEVER`, `.fabric/knowledge/`, the verbatim
|
|
86
|
+
`强 team` / `强 personal` / `默认 team` heuristic block, etc.) are NEVER
|
|
87
|
+
translated — they appear verbatim in both language variants. The
|
|
88
|
+
bilingualization scope is prose ONLY.
|
|
89
|
+
|
|
90
|
+
### AskUserQuestion i18n Policy (value vs label)
|
|
91
|
+
|
|
92
|
+
When a skill (this one or any sibling skill the user is composing with)
|
|
93
|
+
issues an `AskUserQuestion`, the `header` and `question` strings are
|
|
94
|
+
user-facing prose → translated per `knowledge_language`. The `options[]`
|
|
95
|
+
array entries (e.g. `["approve", "reject", "modify", "defer", "skip"]` in
|
|
96
|
+
fabric-review, or `["team", "personal"]` for a layer-flip target) are
|
|
97
|
+
**routing keys** consumed by the skill state machine — they MUST remain
|
|
98
|
+
English regardless of `knowledge_language`.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// EN (knowledge_language === "en")
|
|
102
|
+
AskUserQuestion({
|
|
103
|
+
header: "Layer-flip target",
|
|
104
|
+
question: "Move '{title}' to which layer? (current: {current_layer})",
|
|
105
|
+
options: ["team", "personal"]
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// zh-CN (knowledge_language === "zh-CN")
|
|
109
|
+
AskUserQuestion({
|
|
110
|
+
header: "Layer 切换目标",
|
|
111
|
+
question: "将 '{title}' 切换到哪一层?(当前: {current_layer})",
|
|
112
|
+
options: ["team", "personal"] // 不翻译 — routing key
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Rationale: localizing routing keys would force every routing branch to
|
|
117
|
+
dual-string match (e.g. `if (choice === "team" || choice === "团队")`),
|
|
118
|
+
which doubles the surface area for protected-token regressions and breaks
|
|
119
|
+
the option-list invariants that downstream tooling depends on. Keeping
|
|
120
|
+
`options[]` English-only is contract-locked across all three skills.
|
|
121
|
+
|
|
29
122
|
### Phase 0.0 — Collect Cross-Session Digests (v2.0.0-rc.7 T5)
|
|
30
123
|
|
|
31
124
|
Before any single-session collection or viability gating, stitch together
|
|
@@ -48,7 +141,8 @@ messages + edit_paths + 1-line title), so this phase is a tail-scan + read.
|
|
|
48
141
|
`.fabric/.cache/session-digests/<session_id>.md`. Missing digest files
|
|
49
142
|
degrade silently (the digest write was best-effort, so a Stop hook crash
|
|
50
143
|
can produce a session_id without a digest). Cap the loaded digest set at
|
|
51
|
-
|
|
144
|
+
`archive_digest_max_sessions` most-recent sessions (config-resolved, default
|
|
145
|
+
10) to bound LLM context (~50KB worst-case at default).
|
|
52
146
|
5. **Build cross-session context.** Concatenate the loaded digests into a
|
|
53
147
|
single `### Cross-session digest` block to carry into Phase 0.5 + Phase 1.
|
|
54
148
|
Use this block to:
|
|
@@ -71,11 +165,11 @@ Gather raw evidence from the recent session before any classification:
|
|
|
71
165
|
1. Read the tail of `.fabric/events.jsonl` since the last `knowledge_proposed` event.
|
|
72
166
|
- Use `Bash` with `tail -n 200 .fabric/events.jsonl` if the file is large.
|
|
73
167
|
- Tolerate ENOENT — empty ledger is a normal first-run state.
|
|
74
|
-
2. Enumerate `recent_paths`: workspace files touched by Read/Edit/Write in the current session. Cap at
|
|
168
|
+
2. Enumerate `recent_paths`: workspace files touched by Read/Edit/Write in the current session. Cap at `archive_max_recent_paths` most-recent paths (config-resolved, default 20).
|
|
75
169
|
3. Distill `user_messages_summary`: a compact (≤500 char) prose summary of what the user asked for and what was decided. NOT a verbatim transcript.
|
|
76
170
|
4. Build a candidate list: each candidate is one observation that MIGHT be worth archiving.
|
|
77
171
|
|
|
78
|
-
Hard budget:
|
|
172
|
+
Hard budget: `archive_max_candidates_per_batch` candidates max per Phase 1 batch (config-resolved, default 8). If more surface, keep the configured-N with strongest worth-archiving signals (see Phase 1 type definitions) and drop the rest.
|
|
79
173
|
|
|
80
174
|
### Phase 0.5 — Viability Gate (Anti-Archive Guard)
|
|
81
175
|
|
|
@@ -120,14 +214,46 @@ ELSE:
|
|
|
120
214
|
|
|
121
215
|
#### On gate FAIL
|
|
122
216
|
|
|
123
|
-
Stop the skill with the
|
|
217
|
+
Stop the skill with the gate-FAIL message (UX i18n Policy class 2 — errors/preconditions; render per `knowledge_language`):
|
|
218
|
+
|
|
219
|
+
zh-CN variant:
|
|
124
220
|
|
|
125
221
|
```
|
|
126
222
|
本次会话为常规执行,无新知识可归档(gate=<reason>)。如需强制归档,请显式调用 fabric-archive。
|
|
127
223
|
```
|
|
128
224
|
|
|
225
|
+
en variant:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
Current session is routine execution; no new knowledge to archive (gate=<reason>). To force-archive, explicitly invoke fabric-archive.
|
|
229
|
+
```
|
|
230
|
+
|
|
129
231
|
Optionally append a one-line event to `.fabric/events.jsonl` of shape `{"ts":"...","kind":"knowledge_archive_aborted","reason":"<reason>","session":"<id>"}` if the events ledger is writable; otherwise just log to stderr. Do NOT proceed to Phase 1, do NOT call any MCP tool.
|
|
130
232
|
|
|
233
|
+
##### events.jsonl Constraint Note
|
|
234
|
+
|
|
235
|
+
Event lines appended to `.fabric/events.jsonl` are subject to POSIX
|
|
236
|
+
single-write atomicity: only writes ≤ 4KB (`PIPE_BUF`) are guaranteed
|
|
237
|
+
atomic via `Bash: echo "..." >> file`. Lines exceeding 4KB risk
|
|
238
|
+
interleaved corruption under concurrent skill + server writes to the
|
|
239
|
+
same ledger.
|
|
240
|
+
|
|
241
|
+
Skills MUST ensure:
|
|
242
|
+
|
|
243
|
+
- Each event JSON line is a **single line** (no embedded newlines;
|
|
244
|
+
escape `\n` in any string value).
|
|
245
|
+
- `session_context` and other free-form text fields **self-truncate** to
|
|
246
|
+
keep the entire serialized line under 4KB. Suggested per-field caps:
|
|
247
|
+
`session_context` first 500 chars; `source_sessions` cap at 5
|
|
248
|
+
entries; `recent_paths` cap at 20 entries; `user_messages_summary`
|
|
249
|
+
first 500 chars.
|
|
250
|
+
- If approaching the 4KB ceiling after the per-field caps, drop optional
|
|
251
|
+
fields (e.g. tags / extra metadata) **before** truncating semantic
|
|
252
|
+
content (the summary / context that carries the actual observation).
|
|
253
|
+
- This constraint applies to any event the skill itself appends (e.g.
|
|
254
|
+
the abort signal above); MCP-server-side appends (via
|
|
255
|
+
`appendEventLedgerEvent`) are already line-length-bounded server-side.
|
|
256
|
+
|
|
131
257
|
#### On gate PASS
|
|
132
258
|
|
|
133
259
|
Proceed to Phase 1 with the candidates carried over from Phase 0.
|
|
@@ -189,19 +315,21 @@ Recent session contains an observation worth keeping?
|
|
|
189
315
|
|
|
190
316
|
#### Batch Review Template
|
|
191
317
|
|
|
192
|
-
Present all candidates in a single screen
|
|
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
|
+
|
|
320
|
+
en variant (`knowledge_language === "en"`):
|
|
193
321
|
|
|
194
322
|
```md
|
|
195
323
|
# Archive Review — N candidates
|
|
196
324
|
|
|
197
|
-
## C1 [type=decision] [layer=team] [
|
|
325
|
+
## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
|
|
198
326
|
Summary: <1-2 sentences capturing the observation>
|
|
199
327
|
Layer reasoning: <which 强 team / 强 personal signal applied, or default team>
|
|
200
328
|
Scope reasoning: <why narrow or broad — see Phase 1.5>
|
|
201
329
|
relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
|
|
202
|
-
Confirm? (Y to accept, edit type/layer/slug/
|
|
330
|
+
Confirm? (Y to accept, edit type/layer/slug/relevance_scope/relevance_paths inline, N to skip)
|
|
203
331
|
|
|
204
|
-
## C2 [type=pitfall] [layer=team] [
|
|
332
|
+
## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
|
|
205
333
|
Summary: ...
|
|
206
334
|
Layer reasoning: ...
|
|
207
335
|
Scope reasoning: ...
|
|
@@ -209,16 +337,36 @@ relevance_paths: []
|
|
|
209
337
|
Confirm? ...
|
|
210
338
|
```
|
|
211
339
|
|
|
212
|
-
|
|
340
|
+
zh-CN variant (`knowledge_language === "zh-CN"`):
|
|
341
|
+
|
|
342
|
+
```md
|
|
343
|
+
# 归档 Review — N 条候选
|
|
344
|
+
|
|
345
|
+
## C1 [type=decision] [layer=team] [relevance_scope=narrow] slug=wave-1-parallel-task-dag
|
|
346
|
+
摘要: <1-2 句捕捉该观察>
|
|
347
|
+
Layer 判定: <命中哪条 强 team / 强 personal 信号,或默认 team>
|
|
348
|
+
Scope 判定: <为什么 narrow 或 broad — 见 Phase 1.5>
|
|
349
|
+
relevance_paths: ["packages/cli/src/commands/plan.ts", "packages/cli/templates/**/*.md"]
|
|
350
|
+
确认?(Y 接受 / 内联编辑 type/layer/slug/relevance_scope/relevance_paths / N 跳过)
|
|
351
|
+
|
|
352
|
+
## C2 [type=pitfall] [layer=team] [relevance_scope=broad] slug=deepmerge-array-replace-trap
|
|
353
|
+
摘要: ...
|
|
354
|
+
Layer 判定: ...
|
|
355
|
+
Scope 判定: ...
|
|
356
|
+
relevance_paths: []
|
|
357
|
+
确认?...
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The user MAY edit type/layer/slug/relevance_scope/relevance_paths inline before confirming. The user MAY skip individual candidates without rejecting the whole batch. Inline-editing `[relevance_scope=...]` triggers a re-derivation of `relevance_paths` per the Phase 1.5 rules (narrow ⇒ recompute from edit_paths; broad ⇒ force `[]`).
|
|
213
361
|
|
|
214
362
|
### Phase 1.5 — Scope Decision + relevance_paths Derivation
|
|
215
363
|
|
|
216
|
-
After classify/layer/slug but BEFORE batch review output, assign a `
|
|
364
|
+
After classify/layer/slug but BEFORE batch review output, assign a `relevance_scope` to each candidate and derive its `relevance_paths` array. These two fields drive rc.6 hint injection: narrow knowledge is gated by working in matching paths, broad knowledge is project-wide.
|
|
217
365
|
|
|
218
366
|
#### Scope decision (narrow vs broad)
|
|
219
367
|
|
|
220
368
|
```
|
|
221
|
-
|
|
369
|
+
relevance_scope =
|
|
222
370
|
narrow IF the candidate is tied to a specific module / file / subsystem
|
|
223
371
|
AND there is explicit single-module evidence in edit_paths
|
|
224
372
|
(i.e. all worth-keeping edits in this session concentrated in one
|
|
@@ -230,7 +378,7 @@ scope =
|
|
|
230
378
|
broad (default, on uncertainty — safe偏置 per Q-1 in handoff)
|
|
231
379
|
```
|
|
232
380
|
|
|
233
|
-
Special case — Personal layer ALWAYS resolves to `
|
|
381
|
+
Special case — Personal layer ALWAYS resolves to `relevance_scope=broad` with `relevance_paths=[]`. Rationale: personal knowledge crosses projects; paths from one project do not generalize. If `layer=personal` and a narrow scope was tentatively chosen, auto-flip to `broad` and clear `relevance_paths`.
|
|
234
382
|
|
|
235
383
|
##### Examples
|
|
236
384
|
|
|
@@ -271,8 +419,8 @@ Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
|
|
|
271
419
|
Singleton paths (group size = 1) are kept as-is (literal path, no glob).
|
|
272
420
|
|
|
273
421
|
Step 5: SCOPE GATE
|
|
274
|
-
IF
|
|
275
|
-
IF
|
|
422
|
+
IF relevance_scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
|
|
423
|
+
IF relevance_scope == narrow → relevance_paths = result of Step 4
|
|
276
424
|
|
|
277
425
|
Step 6: ATTACH READ-ONLY EVIDENCE
|
|
278
426
|
Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
|
|
@@ -298,7 +446,7 @@ Step 4 (generalize, depth ≤ 2, minGroupSize = 2):
|
|
|
298
446
|
- `packages/server/src/services/{extract,review,promote}.ts` → group size 3 ≥ 2, common prefix `packages/server/src/services/`, glob: `packages/server/src/services/**/*.ts`
|
|
299
447
|
- `packages/cli/src/commands/plan.ts` → group size 1, kept literal.
|
|
300
448
|
|
|
301
|
-
Step 5 (assume `
|
|
449
|
+
Step 5 (assume `relevance_scope=narrow`):
|
|
302
450
|
|
|
303
451
|
```json
|
|
304
452
|
"relevance_paths": [
|
|
@@ -307,11 +455,11 @@ Step 5 (assume `scope=narrow`):
|
|
|
307
455
|
]
|
|
308
456
|
```
|
|
309
457
|
|
|
310
|
-
If `
|
|
458
|
+
If `relevance_scope=broad` had been chosen instead, `relevance_paths` would be `[]` regardless of the above.
|
|
311
459
|
|
|
312
460
|
#### Inline-edit support during batch review
|
|
313
461
|
|
|
314
|
-
The user MAY inline-edit `[
|
|
462
|
+
The user MAY inline-edit `[relevance_scope=...]` in the batch review. When this happens:
|
|
315
463
|
|
|
316
464
|
- Edit changes `narrow → broad`: clear `relevance_paths` to `[]`.
|
|
317
465
|
- Edit changes `broad → narrow`: re-run Steps 1-4 of the derivation algorithm to recompute.
|
|
@@ -326,12 +474,12 @@ For each user-confirmed candidate, call `fab_extract_knowledge` ONCE. Do NOT bat
|
|
|
326
474
|
```ts
|
|
327
475
|
mcp__fabric__fab_extract_knowledge({
|
|
328
476
|
source_sessions: ["<session id1>", "<session id2>", ...], // T5: array form (Phase 0.0)
|
|
329
|
-
recent_paths: ["<path1>", "<path2>", ...], // capped at 20
|
|
477
|
+
recent_paths: ["<path1>", "<path2>", ...], // capped at archive_max_recent_paths (config-resolved, default 20)
|
|
330
478
|
user_messages_summary: "<compact prose ≤500 chars>",
|
|
331
479
|
type: "decisions" | "pitfalls" | "guidelines" | "models" | "processes",
|
|
332
480
|
slug: "<kebab-case-2-to-5-words>",
|
|
333
481
|
layer: "team" | "personal",
|
|
334
|
-
|
|
482
|
+
relevance_scope: "narrow" | "broad", // from Phase 1.5
|
|
335
483
|
relevance_paths: ["<glob1>", "<literal2>", ...], // narrow ⇒ derived; broad ⇒ []
|
|
336
484
|
// v2.0.0-rc.7 T6: required fields for future-self reviewability.
|
|
337
485
|
proposed_reason:
|
|
@@ -383,19 +531,25 @@ The MCP tool derives `idempotency_key = sha256({source_session, type, slug})`. C
|
|
|
383
531
|
|
|
384
532
|
If the skill needs to record a genuinely separate observation in the same session+type, the slug MUST differ.
|
|
385
533
|
|
|
534
|
+
**T5 array-form note (rc.7+)**: when `source_sessions` is passed as an array (the rc.7 T5 contract), only `source_sessions[0]` participates in the server-side idempotency hash. The actual server formula at `packages/server/src/services/extract-knowledge.ts:78` is `sha256(JSON.stringify({source_session: sourceSessions[0], type, slug}))`. Implications:
|
|
535
|
+
|
|
536
|
+
- Same `(type, slug)` but a different **first** session → distinct idempotency key → produces two pending files.
|
|
537
|
+
- Same first session but different tail sessions → evidence-merge into the SAME pending file; tail `session_id`s are NOT recorded as independent evidence keys.
|
|
538
|
+
- The formula is intentionally stable across the rc.5 → rc.7 migration; adding or removing tail entries does NOT change the idempotency key, preserving rc.5 single-session compat.
|
|
539
|
+
|
|
386
540
|
## Hard Rules (DO NOT TRANSLATE) — DISPLAY / WRITE Split
|
|
387
541
|
|
|
388
542
|
### DISPLAY Rules
|
|
389
543
|
|
|
390
544
|
- MUST complete Phase 0 AND Phase 0.5 viability gate before any batch-review output.
|
|
391
545
|
- MUST abort with the gate-FAIL message (no MCP call) when the viability gate fails AND the user did not explicitly invoke fabric-archive.
|
|
392
|
-
- MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[
|
|
546
|
+
- MUST present every candidate with explicit `[type=...]`, `[layer=...]`, `[relevance_scope=...]`, and `slug=...` fields plus a `relevance_paths` line.
|
|
393
547
|
- MUST include a one-line `Layer reasoning:` for each candidate citing which 强 team / 强 personal signal applied (or default team).
|
|
394
548
|
- MUST include a one-line `Scope reasoning:` for each candidate citing why narrow or broad was chosen (or that personal forced broad).
|
|
395
549
|
- MUST classify against the canonical singular nouns: model / decision / guideline / pitfall / process. NEVER invent new types.
|
|
396
|
-
- MUST cap the batch at
|
|
550
|
+
- MUST cap the batch at `archive_max_candidates_per_batch` candidates (config-resolved, default 8); drop weaker ones over the cap.
|
|
397
551
|
- MUST display the resolved `pending_path` returned by `fab_extract_knowledge` so the user can verify.
|
|
398
|
-
- MUST treat user inline edits to type/layer/slug/
|
|
552
|
+
- MUST treat user inline edits to type/layer/slug/relevance_scope/relevance_paths as authoritative replacements before Phase 2.
|
|
399
553
|
- MUST skip rather than guess when an observation does not fit any of the 5 types.
|
|
400
554
|
|
|
401
555
|
### WRITE Rules
|
|
@@ -404,12 +558,12 @@ If the skill needs to record a genuinely separate observation in the same sessio
|
|
|
404
558
|
- NEVER write outside `.fabric/knowledge/pending/` — promotion to `.fabric/knowledge/<type>/` is rc.3 fab_review concern, NOT this skill.
|
|
405
559
|
- NEVER include an `id` field anywhere — pending entries have no id (late-bind on approve).
|
|
406
560
|
- NEVER classify a candidate as `personal` when a 强 team signal applies. Default to team on ambiguity.
|
|
407
|
-
- NEVER emit a non-empty `relevance_paths` when `
|
|
408
|
-
- NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `
|
|
561
|
+
- NEVER emit a non-empty `relevance_paths` when `relevance_scope=broad` — broad MUST always carry `relevance_paths=[]`.
|
|
562
|
+
- NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `relevance_scope=broad` + `relevance_paths=[]`.
|
|
409
563
|
- 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+.
|
|
410
564
|
- NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
|
|
411
565
|
- NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
|
|
412
|
-
- MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `
|
|
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`.
|
|
413
567
|
|
|
414
568
|
## Worked Examples
|
|
415
569
|
|
|
@@ -427,7 +581,7 @@ mcp__fabric__fab_extract_knowledge({
|
|
|
427
581
|
type: "decisions",
|
|
428
582
|
slug: "single-cjs-hook-script",
|
|
429
583
|
layer: "team",
|
|
430
|
-
|
|
584
|
+
relevance_scope: "narrow",
|
|
431
585
|
relevance_paths: [
|
|
432
586
|
"templates/claude-hooks/**/*.cjs",
|
|
433
587
|
"packages/cli/src/commands/hooks.ts"
|
|
@@ -453,7 +607,7 @@ mcp__fabric__fab_extract_knowledge({
|
|
|
453
607
|
type: "pitfalls",
|
|
454
608
|
slug: "deepmerge-array-replace-trap",
|
|
455
609
|
layer: "team",
|
|
456
|
-
|
|
610
|
+
relevance_scope: "broad",
|
|
457
611
|
relevance_paths: [],
|
|
458
612
|
proposed_reason: "diagnostic-then-fix",
|
|
459
613
|
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."
|
|
@@ -476,7 +630,7 @@ mcp__fabric__fab_extract_knowledge({
|
|
|
476
630
|
type: "guidelines",
|
|
477
631
|
slug: "indent-style-by-language",
|
|
478
632
|
layer: "personal",
|
|
479
|
-
|
|
633
|
+
relevance_scope: "broad",
|
|
480
634
|
relevance_paths: [],
|
|
481
635
|
proposed_reason: "explicit-user-mark",
|
|
482
636
|
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."
|