@fenglimg/fabric-cli 2.1.0-rc.2 → 2.2.0-rc.3
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-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
- package/dist/chunk-5LQIHYFC.js +64 -0
- package/dist/chunk-5ZUMLCD5.js +248 -0
- package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/{chunk-BATF4PEJ.js → chunk-F6ITRM7T.js} +4 -4
- package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
- package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
- package/dist/chunk-XCBVSGCS.js +25 -0
- package/dist/{chunk-F46ORPOA.js → chunk-XHHCRDIR.js} +149 -7
- package/dist/{config-XJIPZNUP.js → config-VJMXCLXW.js} +3 -3
- package/dist/{doctor-QVNPHLJK.js → doctor-J4O3X54I.js} +154 -30
- package/dist/index.js +57 -16
- package/dist/{install-2HDO5FTQ.js → install-BULNDUIM.js} +241 -108
- package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
- package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
- package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
- package/dist/{scope-explain-2F2R5URO.js → scope-explain-BWRWBCCP.js} +19 -5
- package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
- package/dist/store-66NK2FTQ.js +443 -0
- package/dist/sync-EA5HZMXM.js +395 -0
- package/dist/{uninstall-TAXSUSKH.js → uninstall-F75MPKQC.js} +61 -4
- package/dist/whoami-66YKY5DZ.js +47 -0
- package/package.json +3 -3
- package/templates/hooks/cite-policy-evict.cjs +412 -160
- package/templates/hooks/configs/claude-code.json +17 -2
- package/templates/hooks/configs/codex-hooks.json +14 -2
- package/templates/hooks/configs/cursor-hooks.json +14 -2
- package/templates/hooks/fabric-hint.cjs +247 -19
- package/templates/hooks/knowledge-hint-broad.cjs +176 -10
- package/templates/hooks/knowledge-hint-narrow.cjs +64 -5
- package/templates/hooks/lib/injection-log.cjs +91 -0
- package/templates/hooks/lib/state-store.cjs +30 -11
- package/templates/hooks/post-tooluse-mutation.cjs +285 -0
- package/templates/hooks/session-end-marker.cjs +140 -0
- package/templates/skills/fabric-archive/SKILL.md +7 -1
- package/templates/skills/fabric-audit/SKILL.md +53 -0
- package/templates/skills/fabric-connect/SKILL.md +48 -0
- package/templates/skills/fabric-review/SKILL.md +2 -0
- package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
- package/templates/skills/fabric-store/SKILL.md +44 -0
- package/dist/chunk-HFQVXY6P.js +0 -86
- package/dist/chunk-L4Q55UC4.js +0 -52
- package/dist/chunk-LFIKMVY7.js +0 -27
- package/dist/chunk-RYAFBNES.js +0 -33
- package/dist/chunk-T5RPGCCM.js +0 -40
- package/dist/store-XTSE5TY6.js +0 -105
- package/dist/sync-BJCWDPNC.js +0 -245
- package/dist/whoami-B6AEMSEV.js +0 -31
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lifecycle-refactor W2-T3 — PostToolUse mutation marker hook (previously
|
|
4
|
+
* dormant). Closes the mutation env opened by the PreToolUse narrow hint.
|
|
5
|
+
*
|
|
6
|
+
* PostToolUse fires AFTER an Edit/Write/MultiEdit tool call completes. This
|
|
7
|
+
* hook appends one `file_mutated` event per edited path to
|
|
8
|
+
* `.fabric/events.jsonl`, carrying the `tool_call_id` so doctor can pair the
|
|
9
|
+
* Pre (intent) and Post (mutation) halves of a single tool call — the per-call
|
|
10
|
+
* key also guards against parallel-fire races (two concurrent edits never
|
|
11
|
+
* collapse to one ledger key).
|
|
12
|
+
*
|
|
13
|
+
* Design (lifecycle-concept-final.md §1 FROZEN invariants + §5 row7):
|
|
14
|
+
* - LOW compute: extract paths + tool_call_id, append; the hook never reads
|
|
15
|
+
* or aggregates the ledger, never runs `git diff`. ALL mutation_pool /
|
|
16
|
+
* attribution work is doctor-side (offline, §5 row7).
|
|
17
|
+
* - hook = nudge/marker, never a gate (KT-DEC-0007): every error path ends
|
|
18
|
+
* in a silent exit 0; we never throw upward.
|
|
19
|
+
* - Front-stage O(1) per path: advisory-locked append, no traversal.
|
|
20
|
+
* - Per-event session_id: threaded from the REAL payload when present
|
|
21
|
+
* (omitted when the client omits it — the marker is still useful for the
|
|
22
|
+
* tool_call_id pairing even without a session).
|
|
23
|
+
* - Hooks never require() the server package — only co-located lib/*.cjs.
|
|
24
|
+
*
|
|
25
|
+
* Each emitted line matches `fileMutatedEventSchema`
|
|
26
|
+
* (packages/shared/src/schemas/event-ledger.ts):
|
|
27
|
+
* { kind:"fabric-event", id, ts, schema_version:1, session_id?,
|
|
28
|
+
* event_type:"file_mutated", path, tool_call_id, tool_name? }
|
|
29
|
+
*
|
|
30
|
+
* Stdout/stderr are intentionally empty — PostToolUse is observation-only and
|
|
31
|
+
* never blocks the host's tool pipeline.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const { randomUUID } = require("node:crypto");
|
|
35
|
+
const { existsSync } = require("node:fs");
|
|
36
|
+
const { isAbsolute, join, relative } = require("node:path");
|
|
37
|
+
|
|
38
|
+
// W1-01 (ISS-011) parity: route every shared-ledger append through the
|
|
39
|
+
// advisory-lock primitive so concurrent PostToolUse fires (multi-window /
|
|
40
|
+
// parallel edits) never interleave a partial line. Best-effort, drop-on-
|
|
41
|
+
// contention — same primitive the narrow/broad hooks use.
|
|
42
|
+
const { appendLockedLine } = require("./lib/injection-log.cjs");
|
|
43
|
+
|
|
44
|
+
const FABRIC_DIR_REL = ".fabric";
|
|
45
|
+
const EVENTS_LEDGER_FILE = "events.jsonl";
|
|
46
|
+
|
|
47
|
+
// Tool names that trigger the mutation marker. PostToolUse fires on many tool
|
|
48
|
+
// names across clients; we only react to the file-edit tools (matches the
|
|
49
|
+
// PreToolUse narrow hint's EDIT_TOOL_NAMES so Pre/Post pair on the same set).
|
|
50
|
+
const EDIT_TOOL_NAMES = new Set(["Edit", "Write", "MultiEdit"]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read stdin (or a test-supplied raw string) as JSON. Returns null on any
|
|
54
|
+
* parse failure — the hook stays silent rather than crashing the tool pipeline.
|
|
55
|
+
*/
|
|
56
|
+
function readPayload(rawStdin) {
|
|
57
|
+
if (typeof rawStdin !== "string" || rawStdin.length === 0) return null;
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(rawStdin);
|
|
60
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extract the tool name. Mirrors the narrow hint's probe:
|
|
71
|
+
* - Claude Code / Codex: { tool_name, ... }
|
|
72
|
+
* - Cursor (legacy): { tool, ... }
|
|
73
|
+
*/
|
|
74
|
+
function extractToolName(payload) {
|
|
75
|
+
if (!payload || typeof payload !== "object") return null;
|
|
76
|
+
if (typeof payload.tool_name === "string") return payload.tool_name;
|
|
77
|
+
if (typeof payload.tool === "string") return payload.tool;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract the tool_input object, accepting both the `tool_input`
|
|
83
|
+
* (Claude/Codex) and `input` (Cursor) conventions.
|
|
84
|
+
*/
|
|
85
|
+
function extractToolInput(payload) {
|
|
86
|
+
if (!payload || typeof payload !== "object") return null;
|
|
87
|
+
if (payload.tool_input && typeof payload.tool_input === "object") {
|
|
88
|
+
return payload.tool_input;
|
|
89
|
+
}
|
|
90
|
+
if (payload.input && typeof payload.input === "object") {
|
|
91
|
+
return payload.input;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Pull file paths out of a tool_input object. Same three shapes the narrow
|
|
98
|
+
* hint handles (single file_path / array file_paths / MultiEdit edits[]).
|
|
99
|
+
* Returns a deduped array of strings — empty when none recognizable.
|
|
100
|
+
*/
|
|
101
|
+
function extractPaths(toolInput) {
|
|
102
|
+
if (!toolInput || typeof toolInput !== "object") return [];
|
|
103
|
+
const collected = [];
|
|
104
|
+
|
|
105
|
+
if (typeof toolInput.file_path === "string" && toolInput.file_path.length > 0) {
|
|
106
|
+
collected.push(toolInput.file_path);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(toolInput.file_paths)) {
|
|
109
|
+
for (const p of toolInput.file_paths) {
|
|
110
|
+
if (typeof p === "string" && p.length > 0) collected.push(p);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(toolInput.edits)) {
|
|
114
|
+
for (const edit of toolInput.edits) {
|
|
115
|
+
if (
|
|
116
|
+
edit &&
|
|
117
|
+
typeof edit === "object" &&
|
|
118
|
+
typeof edit.file_path === "string" &&
|
|
119
|
+
edit.file_path.length > 0
|
|
120
|
+
) {
|
|
121
|
+
collected.push(edit.file_path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const p of collected) {
|
|
129
|
+
if (seen.has(p)) continue;
|
|
130
|
+
seen.add(p);
|
|
131
|
+
out.push(p);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract the per-call id. Claude Code's PostToolUse payload carries the id of
|
|
138
|
+
* the originating tool_use block as `tool_use_id`; older drafts / other clients
|
|
139
|
+
* variously used `tool_call_id` / `call_id` / `id`. We probe in that order.
|
|
140
|
+
*
|
|
141
|
+
* Returns null when none is present — the caller then synthesizes a best-effort
|
|
142
|
+
* fallback key so the marker still lands (per W2-T3: "缺失则用 best-effort
|
|
143
|
+
* fallback key 但仍 append"). A fallback key cannot pair with the Pre half but
|
|
144
|
+
* still records the mutation, which is strictly better than dropping it.
|
|
145
|
+
*/
|
|
146
|
+
function extractToolCallId(payload) {
|
|
147
|
+
if (!payload || typeof payload !== "object") return null;
|
|
148
|
+
const candidates = ["tool_use_id", "tool_call_id", "call_id", "id"];
|
|
149
|
+
for (const key of candidates) {
|
|
150
|
+
const v = payload[key];
|
|
151
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Normalize a path to a project-relative, forward-slash form. Drops paths that
|
|
158
|
+
* escape the project tree (mirrors appendEditIntentToLedger in the narrow
|
|
159
|
+
* hook). Returns null for out-of-tree / empty.
|
|
160
|
+
*/
|
|
161
|
+
function normalizePath(projectRoot, p) {
|
|
162
|
+
if (typeof p !== "string" || p.length === 0) return null;
|
|
163
|
+
let rel;
|
|
164
|
+
if (isAbsolute(p)) {
|
|
165
|
+
rel = relative(projectRoot, p);
|
|
166
|
+
if (rel.startsWith("..")) return null;
|
|
167
|
+
} else {
|
|
168
|
+
if (p.startsWith("..")) return null;
|
|
169
|
+
rel = p;
|
|
170
|
+
}
|
|
171
|
+
const slashed = rel.split(/[\\/]/).join("/");
|
|
172
|
+
return slashed.length > 0 ? slashed : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Append one `file_mutated` marker per edited path to `.fabric/events.jsonl`.
|
|
177
|
+
* Best-effort:
|
|
178
|
+
* - Skips silently when `.fabric/` does not exist (project not init'd).
|
|
179
|
+
* - Skips silently when there are no in-tree paths.
|
|
180
|
+
* - ANY error (append, JSON throw) is swallowed — never blocks the pipeline.
|
|
181
|
+
*
|
|
182
|
+
* One JSON line per path (PIPE_BUF-atomic). The per-path lines share a single
|
|
183
|
+
* tool_call_id so doctor can group them as one tool call.
|
|
184
|
+
*/
|
|
185
|
+
function appendFileMutated(projectRoot, now, paths, toolCallId, toolName, sessionId) {
|
|
186
|
+
try {
|
|
187
|
+
const fabricDir = join(projectRoot, FABRIC_DIR_REL);
|
|
188
|
+
if (!existsSync(fabricDir)) return;
|
|
189
|
+
const pathList = Array.isArray(paths)
|
|
190
|
+
? paths.map((p) => normalizePath(projectRoot, p)).filter((p) => p !== null)
|
|
191
|
+
: [];
|
|
192
|
+
if (pathList.length === 0) return;
|
|
193
|
+
const tsMs = now instanceof Date ? now.getTime() : Number(now);
|
|
194
|
+
// Best-effort fallback key when the client omits the tool-call id: the
|
|
195
|
+
// mutation is still recorded (can't pair with the Pre half, but the path
|
|
196
|
+
// signal is preserved). Per-fire UUID keeps parallel fires distinct.
|
|
197
|
+
const callId =
|
|
198
|
+
typeof toolCallId === "string" && toolCallId.length > 0
|
|
199
|
+
? toolCallId
|
|
200
|
+
: `fallback:${randomUUID()}`;
|
|
201
|
+
const validSessionId =
|
|
202
|
+
typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
|
|
203
|
+
const validToolName =
|
|
204
|
+
typeof toolName === "string" && toolName.length > 0 ? toolName : null;
|
|
205
|
+
const lines =
|
|
206
|
+
pathList
|
|
207
|
+
.map((p) =>
|
|
208
|
+
JSON.stringify({
|
|
209
|
+
kind: "fabric-event",
|
|
210
|
+
id: `event:${randomUUID()}`,
|
|
211
|
+
ts: tsMs,
|
|
212
|
+
schema_version: 1,
|
|
213
|
+
...(validSessionId ? { session_id: validSessionId } : {}),
|
|
214
|
+
event_type: "file_mutated",
|
|
215
|
+
path: p,
|
|
216
|
+
tool_call_id: callId,
|
|
217
|
+
...(validToolName ? { tool_name: validToolName } : {}),
|
|
218
|
+
}),
|
|
219
|
+
)
|
|
220
|
+
.join("\n") + "\n";
|
|
221
|
+
appendLockedLine(join(fabricDir, EVENTS_LEDGER_FILE), lines);
|
|
222
|
+
} catch {
|
|
223
|
+
// Silent — marker failure must never block the tool pipeline.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// -----------------------------------------------------------------------------
|
|
228
|
+
// Main — invoked as a CLI (require.main === module) and in-process by tests.
|
|
229
|
+
// Wraps the entire flow in try/catch: ANY error → silent exit 0.
|
|
230
|
+
// -----------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
function main(env) {
|
|
233
|
+
try {
|
|
234
|
+
const cwd = (env && env.cwd) || process.cwd();
|
|
235
|
+
const now = (env && env.now) || new Date();
|
|
236
|
+
const payload =
|
|
237
|
+
env && env.payload !== undefined ? env.payload : readPayload(env && env.stdin);
|
|
238
|
+
if (payload === null || payload === undefined) return;
|
|
239
|
+
|
|
240
|
+
const toolName = extractToolName(payload);
|
|
241
|
+
if (!toolName || !EDIT_TOOL_NAMES.has(toolName)) return;
|
|
242
|
+
|
|
243
|
+
const toolInput = extractToolInput(payload);
|
|
244
|
+
const paths = extractPaths(toolInput);
|
|
245
|
+
if (paths.length === 0) return;
|
|
246
|
+
|
|
247
|
+
const toolCallId = extractToolCallId(payload);
|
|
248
|
+
const sessionId =
|
|
249
|
+
payload && typeof payload === "object" && typeof payload.session_id === "string"
|
|
250
|
+
? payload.session_id
|
|
251
|
+
: null;
|
|
252
|
+
|
|
253
|
+
appendFileMutated(cwd, now, paths, toolCallId, toolName, sessionId);
|
|
254
|
+
} catch {
|
|
255
|
+
// Silent — never block the tool pipeline on hook failure.
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
main,
|
|
261
|
+
readPayload,
|
|
262
|
+
extractToolName,
|
|
263
|
+
extractToolInput,
|
|
264
|
+
extractPaths,
|
|
265
|
+
extractToolCallId,
|
|
266
|
+
normalizePath,
|
|
267
|
+
appendFileMutated,
|
|
268
|
+
CONSTANTS: {
|
|
269
|
+
FABRIC_DIR_REL,
|
|
270
|
+
EVENTS_LEDGER_FILE,
|
|
271
|
+
EDIT_TOOL_NAMES,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (require.main === module) {
|
|
276
|
+
// Read stdin synchronously (small hook payloads, no concurrency concerns).
|
|
277
|
+
let stdinRaw = "";
|
|
278
|
+
try {
|
|
279
|
+
stdinRaw = require("node:fs").readFileSync(0, "utf8");
|
|
280
|
+
} catch {
|
|
281
|
+
// No stdin — proceed with empty payload (E*: no append without paths).
|
|
282
|
+
}
|
|
283
|
+
main({ cwd: process.cwd(), now: new Date(), stdin: stdinRaw });
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lifecycle-refactor W2-T2 — SessionEnd marker hook (previously dormant).
|
|
4
|
+
*
|
|
5
|
+
* SessionEnd fires once when a client session boots down. This hook is a
|
|
6
|
+
* PURE MARKER: it appends a single `session_ended` event (session_id + ts,
|
|
7
|
+
* via the shared envelope) to `.fabric/events.jsonl` and does NOTHING else.
|
|
8
|
+
*
|
|
9
|
+
* Design (lifecycle-concept-final.md §1 FROZEN invariants + §5 row2):
|
|
10
|
+
* - ZERO compute: the hook never reads/aggregates the ledger. ALL
|
|
11
|
+
* surfaced→cited→edited funnel reconciliation is doctor-side (offline).
|
|
12
|
+
* - hook = nudge/marker, never a gate (KT-DEC-0007): every error path ends
|
|
13
|
+
* in a silent exit 0; we never throw upward.
|
|
14
|
+
* - Front-stage O(1): one advisory-locked append, no traversal.
|
|
15
|
+
* - Per-event session_id: when the client omits session_id we DEGRADE by
|
|
16
|
+
* skipping the append entirely (a marker with no session is useless for
|
|
17
|
+
* the per-session funnel doctor reconstructs).
|
|
18
|
+
* - Hooks never require() the server package — only the co-located lib/*.cjs.
|
|
19
|
+
*
|
|
20
|
+
* The emitted line matches `sessionEndedEventSchema`
|
|
21
|
+
* (packages/shared/src/schemas/event-ledger.ts):
|
|
22
|
+
* { kind:"fabric-event", id, ts, schema_version:1, session_id?, event_type:"session_ended" }
|
|
23
|
+
*
|
|
24
|
+
* Stdout/stderr are intentionally empty — SessionEnd is observation-only.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const { randomUUID } = require("node:crypto");
|
|
28
|
+
const { existsSync } = require("node:fs");
|
|
29
|
+
const { join } = require("node:path");
|
|
30
|
+
|
|
31
|
+
// W1-01 (ISS-011) parity: route the shared-ledger append through the
|
|
32
|
+
// advisory-lock primitive so concurrent SessionEnd fires (multi-window) never
|
|
33
|
+
// interleave a partial line. Drop-on-contention, best-effort — same primitive
|
|
34
|
+
// the narrow/broad hooks use.
|
|
35
|
+
const { appendLockedLine } = require("./lib/injection-log.cjs");
|
|
36
|
+
|
|
37
|
+
const FABRIC_DIR_REL = ".fabric";
|
|
38
|
+
const EVENTS_LEDGER_FILE = "events.jsonl";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read stdin (or a test-supplied raw string) as JSON. Returns null on any
|
|
42
|
+
* parse failure — the hook stays silent rather than crashing session teardown.
|
|
43
|
+
*/
|
|
44
|
+
function readPayload(rawStdin) {
|
|
45
|
+
if (typeof rawStdin !== "string" || rawStdin.length === 0) return null;
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(rawStdin);
|
|
48
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract the REAL payload session_id (never a synthetic fallback). The
|
|
59
|
+
* funnel reconciliation in doctor keys on the client's own session id, so a
|
|
60
|
+
* fabricated id would silently corrupt the per-session join. Returns null when
|
|
61
|
+
* absent — the caller then skips the append (degraded, per §1).
|
|
62
|
+
*/
|
|
63
|
+
function extractSessionId(payload) {
|
|
64
|
+
if (
|
|
65
|
+
payload &&
|
|
66
|
+
typeof payload === "object" &&
|
|
67
|
+
typeof payload.session_id === "string" &&
|
|
68
|
+
payload.session_id.length > 0
|
|
69
|
+
) {
|
|
70
|
+
return payload.session_id;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Append one `session_ended` marker to `.fabric/events.jsonl`. Best-effort:
|
|
77
|
+
* - Skips silently when `.fabric/` does not exist (project not init'd).
|
|
78
|
+
* - Skips silently when sessionId is null (degraded — no session to mark).
|
|
79
|
+
* - ANY error (append, JSON throw) is swallowed — never blocks teardown.
|
|
80
|
+
*/
|
|
81
|
+
function appendSessionEnded(projectRoot, now, sessionId) {
|
|
82
|
+
try {
|
|
83
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return;
|
|
84
|
+
const fabricDir = join(projectRoot, FABRIC_DIR_REL);
|
|
85
|
+
if (!existsSync(fabricDir)) return;
|
|
86
|
+
const tsMs = now instanceof Date ? now.getTime() : Number(now);
|
|
87
|
+
const event = {
|
|
88
|
+
kind: "fabric-event",
|
|
89
|
+
id: `event:${randomUUID()}`,
|
|
90
|
+
ts: tsMs,
|
|
91
|
+
schema_version: 1,
|
|
92
|
+
session_id: sessionId,
|
|
93
|
+
event_type: "session_ended",
|
|
94
|
+
};
|
|
95
|
+
appendLockedLine(join(fabricDir, EVENTS_LEDGER_FILE), JSON.stringify(event) + "\n");
|
|
96
|
+
} catch {
|
|
97
|
+
// Silent — marker failure must never block session teardown.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// -----------------------------------------------------------------------------
|
|
102
|
+
// Main — invoked as a CLI (require.main === module) and in-process by tests.
|
|
103
|
+
// Wraps the entire flow in try/catch: ANY error → silent exit 0.
|
|
104
|
+
// -----------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
function main(env) {
|
|
107
|
+
try {
|
|
108
|
+
const cwd = (env && env.cwd) || process.cwd();
|
|
109
|
+
const now = (env && env.now) || new Date();
|
|
110
|
+
const payload =
|
|
111
|
+
env && env.payload !== undefined ? env.payload : readPayload(env && env.stdin);
|
|
112
|
+
const sessionId = extractSessionId(payload);
|
|
113
|
+
appendSessionEnded(cwd, now, sessionId);
|
|
114
|
+
} catch {
|
|
115
|
+
// Silent — never block session teardown on hook failure.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
main,
|
|
121
|
+
readPayload,
|
|
122
|
+
extractSessionId,
|
|
123
|
+
appendSessionEnded,
|
|
124
|
+
CONSTANTS: {
|
|
125
|
+
FABRIC_DIR_REL,
|
|
126
|
+
EVENTS_LEDGER_FILE,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (require.main === module) {
|
|
131
|
+
// Read stdin synchronously (small hook payloads, no concurrency concerns).
|
|
132
|
+
let stdinRaw = "";
|
|
133
|
+
try {
|
|
134
|
+
stdinRaw = require("node:fs").readFileSync(0, "utf8");
|
|
135
|
+
} catch {
|
|
136
|
+
// No stdin — proceed with empty payload (degrades to a no-op append).
|
|
137
|
+
}
|
|
138
|
+
main({ cwd: process.cwd(), now: new Date(), stdin: stdinRaw });
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
@@ -113,6 +113,12 @@ Assign `relevance_scope` ∈ {narrow, broad} + derive `relevance_paths` BEFORE b
|
|
|
113
113
|
|
|
114
114
|
`Read ref/phase-3-5-scope.md` for the 6-step relevance_paths derivation pseudocode (COLLECT → DEDUPE → BLACKLIST → PUBLIC-PREFIX GENERALIZE → SCOPE GATE → ATTACH READ-ONLY EVIDENCE), worked example (5 sample paths → glob + literal output), and narrow↔broad inline-edit re-derivation rules.
|
|
115
115
|
|
|
116
|
+
### Phase 3.6 — Related-edge Extraction (§7 graph generation)
|
|
117
|
+
|
|
118
|
+
For each candidate, identify the **`related`** graph edges to other KB entries — the store-qualified `stable_id`s this entry semantically links to (the decision it supersedes, the pitfall it explains, the model it instantiates). You discovered these ids during the session via `fab_recall` / plan-context, so cite the ones you actually saw, NEVER invent stable_ids. Because `fab_extract_knowledge` has no dedicated `related` input, record the candidate edges as one line inside `session_context` (e.g. `related: team:KT-DEC-0007, team:KT-PIT-0011`) so they survive to approve-time frontmatter authoring (`fabric-review` writes the canonical `related: [...]` frontmatter).
|
|
119
|
+
|
|
120
|
+
**§4 privacy iron law — KT→KP is FORBIDDEN.** A **team** (`KT-*`) entry's `related` MUST NOT point at a **personal** (`KP-*`) id: that would write a personal-knowledge topology pointer into the project's shared physical ledger (`./.fabric/agents.meta.json`). Allowed: `KT→KT`, `KP→KP`, `KP→KT`. Forbidden: `KT→KP` only. When unsure whether a target is personal, OMIT the edge. (The meta-builder also strips any KT→KP edge that slips through, but do not rely on that — author clean edges.)
|
|
121
|
+
|
|
116
122
|
### Phase 4 — Persist via MCP
|
|
117
123
|
|
|
118
124
|
For each user-confirmed candidate, call `fab_extract_knowledge` ONCE (NEVER batch). Required: `source_sessions[]`, `recent_paths[]` (cap 20), `user_messages_summary`, `type` (plural form: decisions/pitfalls/guidelines/models/processes), `slug`, `layer`, `relevance_scope`, `relevance_paths[]`, `proposed_reason` (enum), `session_context` (3-5 line narrative). Four OPTIONAL rc.23 triage fields (`intent_clues`, `tech_stack`, `impact`, `must_read_if`) — populate when clean, **omit rather than guess**.
|
|
@@ -155,7 +161,7 @@ MANDATORY closing step on EVERY invocation (Phase 4 success path + every early-e
|
|
|
155
161
|
- v2.0.0-rc.37 NEW-7 widened Phase 3.5: `edit_paths` ∪ `user_mentioned_paths` drives `relevance_paths`; `read_paths` flows separately to `evidence_paths` (structured frontmatter, not body markdown). NEVER lift body regex / symbol extraction into `relevance_paths` — those remain reserved for v2.1+.
|
|
156
162
|
- NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
|
|
157
163
|
- NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
|
|
158
|
-
- 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`.
|
|
164
|
+
- 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`, `related`, `KT→KP`.
|
|
159
165
|
|
|
160
166
|
## Worked Examples / E5 Cron / Dry-run (ref-only)
|
|
161
167
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fabric-audit
|
|
3
|
+
description: 知识库语义淘汰门面 — 审计 KB 健康并以 deprecate-over-delete + rescue-before-delete 收口陈旧/孤儿/被取代条目。引擎是 `fabric doctor`;本 skill 按用户意图挑动作并守「不硬删、先抢救」红线。Triggers 审计知识库/清理陈旧知识/知识库体检/deprecate 条目/prune stale knowledge/知识库瘦身/淘汰旧决策.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fabric-audit — 知识库语义淘汰
|
|
7
|
+
|
|
8
|
+
知识库 *维护期* 的对话入口:体检 KB,把陈旧 / 孤儿 / 被取代的条目按 **语义淘汰** 收口 —— 而不是一删了之。CLI (`fabric doctor`) 是引擎(跑 lint、算 health、给 orphan/stale 信号);本 skill 按用户意图挑动作,并守两条红线:**deprecate-over-delete** 与 **rescue-before-delete**。
|
|
9
|
+
|
|
10
|
+
写新条目用 `fabric-archive`;批量审 pending 用 `fabric-review`;本 skill 专管 *已归档条目的退役*。
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- 「审计 / 体检知识库」「知识库健康度怎样?」
|
|
15
|
+
- 「清理陈旧知识」「这些旧决策还要吗?」「知识库瘦身」
|
|
16
|
+
- 发布 / 大重构前想把过时知识收口。
|
|
17
|
+
- doctor 报了 orphan / stale / 低 health,想逐条处置。
|
|
18
|
+
|
|
19
|
+
## When NOT to use
|
|
20
|
+
|
|
21
|
+
- 写 / 提议新知识条目 → `fabric-archive`。
|
|
22
|
+
- 批量审 `.fabric/knowledge/pending/` 的 draft → `fabric-review`。
|
|
23
|
+
- store 运维 / 同步 → `fabric-store` / `fabric-sync`。
|
|
24
|
+
|
|
25
|
+
## 两条红线
|
|
26
|
+
|
|
27
|
+
1. **deprecate-over-delete**:陈旧 ≠ 该删。一条「当时为什么这么决策」的 decision/pitfall 即使方案已换,其 **rationale 仍是知识**。退役 = 降 maturity / 标记 deprecated(保留正文 + 记录被什么取代),而非 `rm`。删除只用于「从未成立 / 纯噪声 / 重复」的条目。
|
|
28
|
+
2. **rescue-before-delete**:任何 *打算删* 的条目,删前必做抢救检查 —— 它是否携带别处没有的独特 rationale / 反例 / 边界?有则先 **merge 进取代它的条目**(或在新条目加 `related` 边指回),再删空壳。抢救检查没做过,不许删。
|
|
29
|
+
|
|
30
|
+
## 意图 → 动作映射
|
|
31
|
+
|
|
32
|
+
| 意图 | 动作 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| 体检 / 健康度 | `fabric doctor`(读 lint + health rollup);零阻断,只报告 |
|
|
35
|
+
| 找孤儿 / 陈旧条目 | `fabric doctor`(消费 orphan / stale / orphan-demote 信号) |
|
|
36
|
+
| 退役一条陈旧条目 | **不删** → 降 maturity(proven→verified→draft)或在 frontmatter 标 deprecated + 记 superseded-by;经 `fabric-review` 落盘 |
|
|
37
|
+
| 删一条「从未成立 / 重复」条目 | 先跑 rescue 检查(独特 rationale?有则 merge/加 related);确认空壳后才删 |
|
|
38
|
+
| 被取代但有价值 | rescue:把独特 rationale merge 进取代条目,新条目加 `related` 边指回,再退役旧条目 |
|
|
39
|
+
|
|
40
|
+
## 流程(逐条处置)
|
|
41
|
+
|
|
42
|
+
1. `fabric doctor` 取 KB health + orphan/stale 候选清单(引擎给信号,本 skill 不自算)。
|
|
43
|
+
2. 对每个候选判 **三态**:still-valid(留) / superseded(退役,走 deprecate) / never-valid(删,走 rescue 检查)。
|
|
44
|
+
3. superseded → deprecate:降 maturity 或标 deprecated + `superseded-by`,保留正文 rationale。
|
|
45
|
+
4. never-valid → rescue-before-delete:独特知识?有则 merge + `related`,无则删空壳。
|
|
46
|
+
5. 处置经 `fabric-review` skill 落盘(本 skill 给决策,review 做写入),保持单一写路径。
|
|
47
|
+
|
|
48
|
+
## Constraints
|
|
49
|
+
|
|
50
|
+
- 本 skill **只读 + 给处置建议**;实际写入(降级 / 标记 / 删)经 `fabric-review` 的写路径,不自行改 `.fabric/knowledge/`。
|
|
51
|
+
- NEVER 绕过 rescue 检查直接删;删前 MUST 先跑抢救检查。删是最后手段,默认是 deprecate。
|
|
52
|
+
- `agents.meta.json` 派生态严禁手改;退役动作改的是 `.fabric/knowledge/<type>/` 下 markdown 的 frontmatter(maturity / deprecated / superseded-by),再 `fabric doctor --fix` reconcile。
|
|
53
|
+
- health / orphan / stale 一律取自 `fabric doctor` 的 JSON 输出,不在 skill 内重算(单一真源)。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fabric-connect
|
|
3
|
+
description: 知识图谱关联门面 — 发现 KB 条目间隐藏关联并回写 H2 `related` 图边。读 fab_recall/fab_plan_context 看候选, 按语义/共路径/共引提议 related 边, 经 fabric-review 写路径落盘。Triggers 连接知识/找关联条目/建知识图谱/link related entries/补 related 边/知识库连通性.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fabric-connect — 知识图谱关联
|
|
7
|
+
|
|
8
|
+
把孤立的 KB 条目连成图:发现彼此**隐藏关联**(同一决策的正反面、pitfall↔规避它的 guideline、被取代↔取代),回写到 frontmatter 的 `related: [<stable_id>...]` 图边(v2.2 H2)。下游 `fab_recall include_related:true` 据此一跳拉回连通知识。
|
|
9
|
+
|
|
10
|
+
`related` 是**有向引用**(A.related=[B] 表示「读 A 时也该看 B」),按需补反向边(B.related=[A])。
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- 「把这些知识连起来」「找出相关条目」「补 related 边」
|
|
15
|
+
- 「知识库连通性怎样?」「有哪些孤岛条目?」
|
|
16
|
+
- 新增一批条目后想建立彼此引用。
|
|
17
|
+
|
|
18
|
+
## When NOT to use
|
|
19
|
+
|
|
20
|
+
- 写新条目 → `fabric-archive`。
|
|
21
|
+
- 审 pending / 退役陈旧 → `fabric-review` / `fabric-audit`。
|
|
22
|
+
- 检索时临时拉关联 → 直接 `fab_recall include_related:true`(无需建边)。
|
|
23
|
+
|
|
24
|
+
## 关联类型(提议 related 边的判据)
|
|
25
|
+
|
|
26
|
+
| 类型 | 例 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| 互补 | decision「用 JWT」 ↔ pitfall「JWT 过期未刷新踩坑」 |
|
|
29
|
+
| 规避 | pitfall「sprite 黑边」 ↔ guideline「premultiplyAlpha 正确设置」 |
|
|
30
|
+
| 取代 | 旧 decision ↔ 取代它的新 decision(配合 deprecated/superseded-by) |
|
|
31
|
+
| 同域 | 同一子系统 / 共 relevance_paths 的条目 |
|
|
32
|
+
| 引用链 | A 的 rationale 依赖 B 的结论 |
|
|
33
|
+
|
|
34
|
+
## 流程
|
|
35
|
+
|
|
36
|
+
1. `fab_recall(paths=[...])` 或 `fab_plan_context` 拿候选 + 现有 `related`(读 description.related 看已连状态)。
|
|
37
|
+
2. 对候选两两/成簇判隐藏关联(用上表判据);只提议**高置信**边,不为「话题相邻」乱连(噪声边稀释图价值)。
|
|
38
|
+
3. 每条提议 = `(源 id, 目标 id, 类型, 一句理由)`;按需提议反向边。
|
|
39
|
+
4. 落盘经 `fabric-review` 写路径:在源条目 frontmatter 的 `related` inline 数组追加目标 stable_id;`fabric doctor --fix` reconcile 进 agents.meta。
|
|
40
|
+
5. 回报新增/反向边数 + 连通性变化(孤岛减少)。
|
|
41
|
+
|
|
42
|
+
## Constraints
|
|
43
|
+
|
|
44
|
+
- 本 skill **只提议 + 经 review 写路径落盘**;不自行改 `.fabric/knowledge/`,不手改 `agents.meta.json`(派生态)。
|
|
45
|
+
- `related` MUST 只填**真实存在的 stable_id**(先 `fab_recall` 验证目标在库);NEVER 编造 / 指向 pending。
|
|
46
|
+
- **稀疏优于稠密**:宁缺毋滥。只连高置信关联;低置信「相邻」不连(图的信噪比比覆盖率重要)。
|
|
47
|
+
- 反向边按需补,不强制双向(有向语义:A 该带出 B ≠ B 该带出 A)。
|
|
48
|
+
- 写 `related` 复用 H2 字段(`fabric-review` 的 modify 路径);schema 已支持,无需迁移。
|
|
@@ -44,6 +44,8 @@ Missing or unreadable → defaults silently.
|
|
|
44
44
|
|
|
45
45
|
Review iterates **per-store** — the read-set may span multiple stores (`fabric scope-explain team` → resolved `readSet.stores`). Pending/backlog is reported per-store (NOT aggregated into one undifferentiated pile); each candidate's provenance store is surfaced in cites as `KB: <store-alias>:<id>`. Promotion (draft → verified/proven) is a normal edit + git commit **inside that store's own repo** — no cross-store move. A `dismissed`/`modify` that flips layer between team and personal still goes through `AskUserQuestion`. Never read `~/.fabric` store trees directly; go through the MCP recall path / `scope-explain`.
|
|
46
46
|
|
|
47
|
+
`Read ref/cite-contract.md` (v2.2 SK5) for the authoritative cite-contract reference — operator syntax, skip/dismissed reason dictionaries, type routing, audit semantics, backward-compat, and the adjudication ladder (AI-self → multi-LLM cold-eval → non-blocking queue) — sunk out of the bootstrap so cite/governance depth lives here, not in `.fabric/AGENTS.md`.
|
|
48
|
+
|
|
47
49
|
### UX i18n Policy
|
|
48
50
|
|
|
49
51
|
Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`); emit user-facing prose in resolved variant. Protected tokens (`fab_review`, `fab_extract_knowledge`, `relevance_scope`, layer/scope enums, `stable_id`, the verbatim `强 team` / `强 personal` / `默认 team` block) NEVER translated. `AskUserQuestion` policy: `header` + `question` translate; `options[]` stay English (routing keys).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Cite-contract + 裁决阶梯参考 (v2.2 SK5)
|
|
2
|
+
|
|
3
|
+
> 本文是 cite policy 与裁决(adjudication)的**权威详参**,从 bootstrap (`.fabric/AGENTS.md`) 下沉至此,避免 bootstrap 随治理细节膨胀。bootstrap 只留**可执行 core**(cite 行格式 + 验证义务 + operator 例),完整 enum 词典 / 类型路由 / 稽核 / backward-compat / 裁决阶梯看这里。`fabric doctor --cite-coverage` 的稽核口径以本文为准。
|
|
4
|
+
|
|
5
|
+
## 1. Cite 行格式 (回顾)
|
|
6
|
+
|
|
7
|
+
edit / decide / propose plan 之前,**回复首行**:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
KB: <id> (<≤8字 用法>) [applied|dismissed:<reason>]
|
|
11
|
+
KB: none [<reason>]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- `[applied]` 前必须先 `fab_recall`(或 `fab_plan_context` → `fab_get_knowledge_sections`)实际抓 KB body —— 防编造 id。验证不通过 = 不能 cite。
|
|
15
|
+
- **store 前缀** (多 store):read-set 含多 store 且同一 local id 跨 store shadow 时,cite 必须 store-qualified:`KB: <store-alias>:<id> ...`(如 `KB: team:KT-DEC-0001 (auth) [applied]`)。单 store / 无歧义时裸 `KB: <id>` 仍 valid。personal-only 条目 cite 进团队产物 = 强 warning(防泄漏 R5#3)。
|
|
16
|
+
|
|
17
|
+
## 2. Contract 语法 (decisions/pitfalls 类 `[applied]`)
|
|
18
|
+
|
|
19
|
+
cite 尾段加 contract:`→ <operator> [<operator> ...]`
|
|
20
|
+
|
|
21
|
+
| operator | 含义 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `edit:<glob>` | 本 cite 承诺会改的文件范围 |
|
|
24
|
+
| `!edit:<glob>` | 承诺**不**改的范围 |
|
|
25
|
+
| `require:<symbol>` | 实现必须含某 symbol |
|
|
26
|
+
| `forbid:<symbol>` | 实现必须不含某 symbol |
|
|
27
|
+
| `skip:<reason>` | 本条 applied 但某 operator 跳过,附理由 |
|
|
28
|
+
|
|
29
|
+
例:`KB: K-001 (auth) [applied] → edit:src/auth/**/*.ts !edit:src/legacy/**`
|
|
30
|
+
|
|
31
|
+
## 3. 枚举词典
|
|
32
|
+
|
|
33
|
+
- **skip reason**:`sequencing | conditional | semantic | aesthetic | architectural | other:<text>`
|
|
34
|
+
- **dismissed reason**:`scope-mismatch | outdated | not-applicable | other:<text>`
|
|
35
|
+
- **`KB: none` sentinel**:`[no-relevant]`(已调 recall/plan_context 但无可用条目) / `[not-applicable]`(当前动作不在 cite 范围:纯探索 / Bash 只读 / 用户问答)。裸 `KB: none` 仍 valid,归 `[unspecified]`(legacy 兼容)。
|
|
36
|
+
|
|
37
|
+
## 4. 类型路由
|
|
38
|
+
|
|
39
|
+
- `models` 类引用 = reference cite,**不需 contract**。
|
|
40
|
+
- `guidelines` / `processes` 类暂不强制 contract,推后 LLM-judge。
|
|
41
|
+
- `decisions` / `pitfalls` 类 `[applied]` **需** contract。
|
|
42
|
+
|
|
43
|
+
## 5. 稽核 + Backward compat
|
|
44
|
+
|
|
45
|
+
- 稽核:`fabric doctor --cite-coverage [--since=7d] [--client=cc|codex|all]` 输出覆盖率,含 `KB: none` sentinel 拆分。不阻断工作,只记录。
|
|
46
|
+
- Backward compat:解析器同时接受老 4-state tags(`planned` / `recalled` / `chained-from <id>`),都映射到 `[applied]` 语义;旧 session cite 仍计入 cite-coverage。
|
|
47
|
+
|
|
48
|
+
## 6. 裁决阶梯 (adjudication ladder)
|
|
49
|
+
|
|
50
|
+
执行中遇到**需要拍板**的分歧(plan 选型 / review 取舍 / cross-LLM 不一致),按三级阶梯收口,**只有真正属于 human 的决定才阻塞**:
|
|
51
|
+
|
|
52
|
+
1. **AI 自决** — 已有清晰证据 + 明确推荐时,直接执行,记 rationale。不为有默认值的选择拆 sub-question。
|
|
53
|
+
2. **多-LLM 评审(含 ≥1 零上下文冷评)** — 主观 / 高风险 / 跨 LLM 分歧时,maestro delegate ≥2 LLM(至少一个零上下文,防执行者自我合理化)。一致 PASS 自动闭;一致 BLOCK + fix verbatim 采纳重跑。
|
|
54
|
+
3. **非阻塞队列** — 仍分歧 / 主观 / 不可逆 / 属 frame 级(只有 human 能挑战 frame)→ 升 `needs_adjudication` 非阻塞队列,带两方理由 + AI 倾向裁决,继续推进不卡死。
|
|
55
|
+
|
|
56
|
+
**Anchor**:critic 只能 frame 内审计;多-LLM 收敛 ≠ 正确,frame 级判断留给 human。cross-LLM 给 suggested fix 时直接 verbatim 采纳(+ trade-off 注释)。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fabric-store
|
|
3
|
+
description: 知识 store 运维门面 — 创建 / 挂载 / 绑定 / 列出 / 切换写目标。CLI `fabric store …` 做事,本 skill 按用户意图选命令。NOT for git-managing 用户自己的产品仓。Triggers 创建 store/挂载 store/绑定知识库/store 列表/切换写库/set up knowledge store.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fabric-store — 知识 store 运维
|
|
7
|
+
|
|
8
|
+
每个「知识 store」操作的对话入口。CLI (`fabric store …`) 是引擎;本 skill 按用户意图挑命令。*store* 是 `~/.fabric/stores/<uuid>/` 下的平行 git 仓 —— 与用户的产品仓不同。同步 (pull+push) 见姊妹 skill `fabric-sync`。
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- 「创建团队 store」「建个人知识 store」
|
|
13
|
+
- 「挂载团队 store」「这个项目要绑团队 store」
|
|
14
|
+
- 「列出挂了哪些 store?」「我的共享决策写到哪个 store?」
|
|
15
|
+
|
|
16
|
+
## When NOT to use
|
|
17
|
+
|
|
18
|
+
- git 同步用户自己的产品仓 (那是普通 `git`)。
|
|
19
|
+
- 同步知识 store (pull/push 冲突解决) → 用 `fabric-sync` skill。
|
|
20
|
+
- 写知识条目 → 用 `fabric-archive` / `fabric-review`。
|
|
21
|
+
|
|
22
|
+
## 意图 → 命令映射
|
|
23
|
+
|
|
24
|
+
| 意图 | 命令 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| 创建全新本地 store | `fabric store create --alias <a> [--remote <url>]` |
|
|
27
|
+
| 挂载已存在的磁盘 store | `fabric store add --uuid <u> --alias <a> [--remote <url>]` |
|
|
28
|
+
| 本项目声明需要某 store | `fabric store bind <alias-or-uuid>` |
|
|
29
|
+
| 列出挂载的 store | `fabric store list` |
|
|
30
|
+
| 设置非 personal scope 的写目标 | `fabric store switch-write <alias>` |
|
|
31
|
+
| 解释某 alias 如何解析 | `fabric store explain <alias>` |
|
|
32
|
+
| 同步 (pull+push) | 见 `fabric-sync` skill |
|
|
33
|
+
|
|
34
|
+
## Precondition
|
|
35
|
+
|
|
36
|
+
已 `fabric install --global` (存在 `~/.fabric` + 全局 store registry)。无全局配置 → 提示先 `fabric install --global`,停止。
|
|
37
|
+
|
|
38
|
+
## Constraints
|
|
39
|
+
|
|
40
|
+
- `store remove` 是 *detach ≠ delete*:从 registry 卸载但 MUST 保留磁盘 git 树。
|
|
41
|
+
- `store add` MUST 拒绝磁盘无 store 树的 uuid (无「幽灵挂载」) —— 先 clone (`fabric install --global --url <remote>`) 或 `store create`。
|
|
42
|
+
- 知识条目写在各 store 的 `.fabric/knowledge/` 下;本 skill 只管 store 生命周期,不写条目。
|
|
43
|
+
- Personal-scope 写永远落在隐式 personal store,与 active write store 无关。
|
|
44
|
+
- Hook/skill NEVER 直接解析 store 或执行 store 内文件 (S65 RCE 防线:store 是数据-only);所有 store 状态 MUST 经 CLI JSON 输出获取。
|