@fenglimg/fabric-cli 2.0.0 → 2.0.1
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/LICENSE +21 -0
- package/README.md +6 -5
- package/dist/chunk-BATF4PEJ.js +361 -0
- package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
- package/dist/chunk-D25XJ4BC.js +880 -0
- package/dist/chunk-MF3OTILQ.js +544 -0
- package/dist/chunk-PWLW3B57.js +18 -0
- package/dist/config-XJIPZNUP.js +13 -0
- package/dist/doctor-EJDSEJSS.js +810 -0
- package/dist/index.js +15 -8
- package/dist/{init-BIRSIOXO.js → install-EKWMFLUU.js} +622 -711
- package/dist/metrics-ACEQFPDU.js +122 -0
- package/dist/onboard-coverage-MFCAEBDO.js +220 -0
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
- package/dist/uninstall-MH7ZIB6M.js +1064 -0
- package/package.json +30 -5
- package/templates/hooks/cite-policy-evict.cjs +231 -0
- package/templates/hooks/configs/README.md +29 -6
- package/templates/hooks/configs/claude-code.json +14 -3
- package/templates/hooks/configs/codex-hooks.json +6 -3
- package/templates/hooks/configs/cursor-hooks.json +8 -10
- package/templates/hooks/fabric-hint.cjs +833 -105
- package/templates/hooks/knowledge-hint-broad.cjs +509 -135
- package/templates/hooks/knowledge-hint-narrow.cjs +791 -26
- package/templates/hooks/lib/banner-i18n.cjs +309 -0
- package/templates/hooks/lib/cite-contract-reminder.cjs +173 -0
- package/templates/hooks/lib/cite-line-parser.cjs +158 -0
- package/templates/hooks/lib/client-adapter.cjs +106 -0
- package/templates/hooks/lib/config-cache.cjs +107 -0
- package/templates/hooks/lib/state-store.cjs +84 -0
- package/templates/hooks/lib/summary-fallback.cjs +210 -0
- package/templates/skills/fabric-archive/SKILL.md +93 -419
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
- package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
- package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
- package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
- package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
- package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
- package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
- package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -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 +75 -516
- package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
- package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
- package/templates/skills/fabric-import/ref/output-contract.md +61 -0
- package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
- package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
- package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
- package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
- package/templates/skills/fabric-review/SKILL.md +86 -284
- package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
- package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
- package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
- package/templates/skills/fabric-review/ref/output-contract.md +58 -0
- package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
- package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
- package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
- package/templates/skills/lib/shared-policy.md +69 -0
- package/dist/chunk-6ICJICVU.js +0 -10
- package/dist/chunk-74SZWYPH.js +0 -658
- package/dist/chunk-EYIDD2YS.js +0 -1000
- package/dist/doctor-T7JWODKG.js +0 -282
- package/dist/hooks-Y74Y5LQS.js +0 -12
- package/dist/scan-LMK3UCWL.js +0 -22
- package/dist/serve-H554BHLG.js +0 -124
- package/templates/agents-md/AGENTS.md.template +0 -59
- package/templates/bootstrap/CLAUDE.md +0 -8
- package/templates/bootstrap/codex-AGENTS-header.md +0 -6
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
package/package.json
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code, Cursor, and Codex CLI; runs doctor / knowledge maintenance.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "wangzhichao <fenglimg90@gmail.com>",
|
|
7
|
+
"homepage": "https://github.com/fenglimg/fabric-v2#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/fenglimg/fabric-v2.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/fenglimg/fabric-v2/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"fabric",
|
|
18
|
+
"mcp",
|
|
19
|
+
"cli",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"cursor",
|
|
22
|
+
"codex-cli",
|
|
23
|
+
"ai-knowledge-management"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
4
28
|
"type": "module",
|
|
5
29
|
"bin": {
|
|
6
|
-
"fab": "dist/index.js",
|
|
7
30
|
"fabric": "dist/index.js"
|
|
8
31
|
},
|
|
9
32
|
"main": "./dist/index.js",
|
|
10
33
|
"types": "./dist/index.d.ts",
|
|
11
34
|
"files": [
|
|
12
35
|
"dist",
|
|
13
|
-
"templates"
|
|
36
|
+
"templates",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md"
|
|
14
39
|
],
|
|
15
40
|
"dependencies": {
|
|
16
41
|
"@clack/prompts": "^1.2.0",
|
|
@@ -20,8 +45,8 @@
|
|
|
20
45
|
"tree-sitter-javascript": "^0.25.0",
|
|
21
46
|
"tree-sitter-typescript": "^0.23.2",
|
|
22
47
|
"web-tree-sitter": "^0.26.8",
|
|
23
|
-
"@fenglimg/fabric-server": "2.0.
|
|
24
|
-
"@fenglimg/fabric-shared": "2.0.
|
|
48
|
+
"@fenglimg/fabric-server": "2.0.1",
|
|
49
|
+
"@fenglimg/fabric-shared": "2.0.1"
|
|
25
50
|
},
|
|
26
51
|
"devDependencies": {
|
|
27
52
|
"@types/node": "^22.15.0",
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* v2.0.0-rc.34 TASK-06 — cite-policy long-session evict sidecar.
|
|
4
|
+
*
|
|
5
|
+
* UserPromptSubmit hook (Claude Code only). Drives periodic cite-policy
|
|
6
|
+
* reminder injection in long sessions where attention decay erodes contract
|
|
7
|
+
* adherence (rc.32 Batch 1: 3.1% cite coverage baseline).
|
|
8
|
+
*
|
|
9
|
+
* Strategy: **turn-count window** (locked decision per rc.34 plan 2026-05-26;
|
|
10
|
+
* time-based and token-budget strategies pushed to rc.35). The hook maintains
|
|
11
|
+
* a per-session counter in `.fabric/.cache/cite-evict-state.json`; on each
|
|
12
|
+
* UserPromptSubmit, increment the counter and — when
|
|
13
|
+
* `turn_count % cite_evict_interval == 0` AND `cite_evict_interval > 0` —
|
|
14
|
+
* emit a compact cite-contract reminder via Claude Code's stdout JSON
|
|
15
|
+
* envelope (hookSpecificOutput.additionalContext, same channel as rc.33 W2
|
|
16
|
+
* knowledge-hint-broad reminder-to-context).
|
|
17
|
+
*
|
|
18
|
+
* Config: `cite_evict_interval` (number, default 0 = OFF, opt-in). Recommend
|
|
19
|
+
* 10-20 for active sessions; 5 for high-contract-criticality projects.
|
|
20
|
+
*
|
|
21
|
+
* State sidecar shape:
|
|
22
|
+
* { session_id: string, turn_count: number }
|
|
23
|
+
*
|
|
24
|
+
* Session-boundary semantics: when incoming `session_id` (read from stdin
|
|
25
|
+
* payload) differs from sidecar's `session_id`, the counter resets to 1 (new
|
|
26
|
+
* session always starts at 1, never 0 — first turn is "turn 1" not "turn 0").
|
|
27
|
+
*
|
|
28
|
+
* Failure invariant: any error path (sidecar I/O failure, stdin parse error,
|
|
29
|
+
* config read failure) MUST end in silent exit 0. The hook never blocks user
|
|
30
|
+
* prompt submission on its own malfunction.
|
|
31
|
+
*
|
|
32
|
+
* Cross-client scope: Claude Code only (relies on hookSpecificOutput contract
|
|
33
|
+
* + UserPromptSubmit event registration). Codex CLI and Cursor don't have an
|
|
34
|
+
* equivalent event hook; cite-coverage telemetry there relies on Stop-hook
|
|
35
|
+
* fabric-hint and SessionStart knowledge-hint-broad (rc.33 W2 channel).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// v2.0.0-rc.37 NEW-19: config + sidecar I/O now flow through shared libs so
|
|
39
|
+
// the read-config-or-default and read/write-sidecar boilerplate lives in one
|
|
40
|
+
// canonical place. Unguarded require mirrors knowledge-hint-broad's
|
|
41
|
+
// banner-i18n import — the installer copies every lib/*.cjs alongside the hook.
|
|
42
|
+
const { readConfigNumber } = require("./lib/config-cache.cjs");
|
|
43
|
+
const { readJsonState, writeJsonState } = require("./lib/state-store.cjs");
|
|
44
|
+
// v2.0.0-rc.37 NEW-30: client detect + stdin + channel-aware emit now flow
|
|
45
|
+
// through the shared adapter (Claude Code stdout envelope vs Codex/Cursor
|
|
46
|
+
// stderr). Replaces the local isClaudeCode + readStdinJson + inline emits.
|
|
47
|
+
const { isClaudeCode, readStdinJson, emitContext } = require("./lib/client-adapter.cjs");
|
|
48
|
+
|
|
49
|
+
// Sidecar basename resolved under .fabric/.cache/ by state-store.
|
|
50
|
+
const EVICT_STATE_FILE_NAME = "cite-evict-state.json";
|
|
51
|
+
|
|
52
|
+
// Default OFF (opt-in). Mirrors hint_broad_cooldown_hours and
|
|
53
|
+
// archive_hint_cooldown_hours convention of "feature exists but inert until
|
|
54
|
+
// user enables it." Schema in packages/shared/src/schemas/fabric-config.ts
|
|
55
|
+
// caps at sensible bounds (positive int).
|
|
56
|
+
// v2.0.0-rc.37 NEW-18: default flipped 0 (opt-in OFF) → 10 (default ON every
|
|
57
|
+
// 10 turns) so users get cite-policy nudges out-of-the-box. Operators on
|
|
58
|
+
// short / scripted sessions can still set `cite_evict_interval: 0` in
|
|
59
|
+
// .fabric/fabric-config.json to opt back out. Per-NEW-1 reminder body now
|
|
60
|
+
// uses the simplified 2-state vocabulary ([applied] / [dismissed:<reason>]).
|
|
61
|
+
const DEFAULT_CITE_EVICT_INTERVAL = 10;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Read .fabric/fabric-config.json#cite_evict_interval. Returns the parsed
|
|
65
|
+
* positive integer OR DEFAULT_CITE_EVICT_INTERVAL on any failure path
|
|
66
|
+
* (missing file, parse error, non-numeric value, negative). Mirrors the
|
|
67
|
+
* defensive config-read pattern in knowledge-hint-broad.cjs readBroadCooldownHours.
|
|
68
|
+
*/
|
|
69
|
+
function readEvictInterval(cwd) {
|
|
70
|
+
return readConfigNumber(cwd, "cite_evict_interval", DEFAULT_CITE_EVICT_INTERVAL, {
|
|
71
|
+
min: 0,
|
|
72
|
+
integer: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read prior state sidecar. Returns `null` on first-run or any failure;
|
|
78
|
+
* callers treat null as "no prior state" (caller will write fresh state
|
|
79
|
+
* with turn_count=1).
|
|
80
|
+
*/
|
|
81
|
+
function readEvictState(cwd) {
|
|
82
|
+
return readJsonState(
|
|
83
|
+
cwd,
|
|
84
|
+
EVICT_STATE_FILE_NAME,
|
|
85
|
+
(parsed) =>
|
|
86
|
+
parsed &&
|
|
87
|
+
typeof parsed.session_id === "string" &&
|
|
88
|
+
typeof parsed.turn_count === "number" &&
|
|
89
|
+
Number.isInteger(parsed.turn_count) &&
|
|
90
|
+
parsed.turn_count >= 0,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function writeEvictState(cwd, sessionId, turnCount) {
|
|
95
|
+
// best-effort — counter loss is acceptable, hook never blocks
|
|
96
|
+
writeJsonState(cwd, EVICT_STATE_FILE_NAME, { session_id: sessionId, turn_count: turnCount });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pure helper for unit-testing. Given current `turnCount` (post-increment)
|
|
101
|
+
* and `interval`, decide whether to emit the reminder.
|
|
102
|
+
*
|
|
103
|
+
* Contract:
|
|
104
|
+
* - interval <= 0 → never emit (feature off)
|
|
105
|
+
* - turnCount <= 0 → never emit (guard against bogus state)
|
|
106
|
+
* - emit iff turnCount % interval === 0
|
|
107
|
+
*
|
|
108
|
+
* Examples:
|
|
109
|
+
* evaluateCiteEvict(10, 10) → true (10 % 10 === 0)
|
|
110
|
+
* evaluateCiteEvict(20, 10) → true
|
|
111
|
+
* evaluateCiteEvict(15, 10) → false
|
|
112
|
+
* evaluateCiteEvict(5, 0) → false (off)
|
|
113
|
+
* evaluateCiteEvict(0, 10) → false (no turns yet)
|
|
114
|
+
*/
|
|
115
|
+
function evaluateCiteEvict(turnCount, interval) {
|
|
116
|
+
if (typeof interval !== "number" || interval <= 0) return false;
|
|
117
|
+
if (typeof turnCount !== "number" || turnCount <= 0) return false;
|
|
118
|
+
return turnCount % interval === 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build the cite-contract reminder body. Compact — under 10 lines. The
|
|
123
|
+
* fully-specified contract lives in `.fabric/AGENTS.md` Cite policy section;
|
|
124
|
+
* the reminder is a tactical re-anchor, not the canonical reference.
|
|
125
|
+
*
|
|
126
|
+
* Returns a multi-line string ready for hookSpecificOutput.additionalContext.
|
|
127
|
+
*/
|
|
128
|
+
function renderReminder(turnCount, interval) {
|
|
129
|
+
// v2.0.0-rc.37 NEW-1: cite policy simplified 4-state → 2-state.
|
|
130
|
+
// [applied] consolidates planned/recalled/chained-from; dismissed:<reason>
|
|
131
|
+
// unchanged. Old tags still parse for back-compat.
|
|
132
|
+
return [
|
|
133
|
+
`[fabric cite-evict] long-session reminder (turn ${turnCount}, interval ${interval}):`,
|
|
134
|
+
"Before edit / decide / propose plan, write KB: <id> (<≤8字 用法>) [applied|dismissed:<reason>] OR KB: none [<reason>].",
|
|
135
|
+
"Verify [applied] by actually fetching KB body via fab_recall(paths) or fab_plan_context → fab_get_knowledge_sections (no fabricated ids).",
|
|
136
|
+
"decisions/pitfalls [applied] cite MUST end with contract: → <operator> [<operator>...] where operator ∈ {edit:<glob> !edit:<glob> require:<symbol> forbid:<symbol> skip:<reason>}.",
|
|
137
|
+
"skip reasons: sequencing | conditional | semantic | aesthetic | architectural | other:<text>.",
|
|
138
|
+
"KB: none sentinels: [no-relevant] (queried but nothing matched) | [not-applicable] (pure exploration / read-only / user Q&A).",
|
|
139
|
+
"Audit: fabric doctor --cite-coverage — this rule does not block work, only records.",
|
|
140
|
+
].join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function main(env) {
|
|
144
|
+
try {
|
|
145
|
+
const cwd =
|
|
146
|
+
(env && typeof env.cwd === "string" && env.cwd) ||
|
|
147
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
148
|
+
process.cwd();
|
|
149
|
+
|
|
150
|
+
const interval = readEvictInterval(cwd);
|
|
151
|
+
if (interval <= 0) {
|
|
152
|
+
return; // feature off — silent exit
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Read stdin payload (Claude Code passes hook_event_name; Codex/Cursor
|
|
156
|
+
// SessionStart payloads are smaller but still JSON). Tests inject
|
|
157
|
+
// env.payload to bypass the stdin read.
|
|
158
|
+
const payload = env && env.payload !== undefined ? env.payload : await readStdinJson();
|
|
159
|
+
|
|
160
|
+
// v2.0.0-rc.37 NEW-21: SessionStart-mode parity for Codex/Cursor.
|
|
161
|
+
// When the hook fires on SessionStart (instead of UserPromptSubmit),
|
|
162
|
+
// emit ONE unconditional cite-policy reminder to stderr. This gives
|
|
163
|
+
// Codex/Cursor users the cite-contract nudge at session boot — lower
|
|
164
|
+
// cadence than Claude Code's per-prompt UserPromptSubmit window, but
|
|
165
|
+
// strictly better than 0 (rc.32 cite-coverage baseline 3.1% measured
|
|
166
|
+
// when Codex/Cursor had no cite-reminder surface at all).
|
|
167
|
+
const eventName =
|
|
168
|
+
payload && typeof payload.hook_event_name === "string"
|
|
169
|
+
? payload.hook_event_name
|
|
170
|
+
: null;
|
|
171
|
+
const sessionStartMode =
|
|
172
|
+
(env && env.forceSessionStart === true) || eventName === "SessionStart";
|
|
173
|
+
|
|
174
|
+
const streams = (env && env.stdio) || {};
|
|
175
|
+
|
|
176
|
+
if (sessionStartMode) {
|
|
177
|
+
// One-shot stderr emit (knowledge-hint-broad convention). forceStderr
|
|
178
|
+
// pins stderr even on Claude Code — Codex/Cursor parse stderr; CC
|
|
179
|
+
// SessionStart also surfaces stderr to the user.
|
|
180
|
+
emitContext(renderReminder(/* turnCount = */ 0, interval), {
|
|
181
|
+
forceStderr: true,
|
|
182
|
+
streams,
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Claude Code UserPromptSubmit path (unchanged from rc.34 TASK-06).
|
|
188
|
+
// Skip Claude Code-specific stdout envelope on Codex/Cursor when not
|
|
189
|
+
// in SessionStart mode (no UserPromptSubmit event registration there).
|
|
190
|
+
if (!isClaudeCode() && !(env && env.forceClaudeCode === true)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const sessionId =
|
|
195
|
+
payload && typeof payload.session_id === "string" && payload.session_id.length > 0
|
|
196
|
+
? payload.session_id
|
|
197
|
+
: "anonymous";
|
|
198
|
+
|
|
199
|
+
const prior = readEvictState(cwd);
|
|
200
|
+
const turnCount = prior && prior.session_id === sessionId ? prior.turn_count + 1 : 1;
|
|
201
|
+
writeEvictState(cwd, sessionId, turnCount);
|
|
202
|
+
|
|
203
|
+
if (!evaluateCiteEvict(turnCount, interval)) {
|
|
204
|
+
return; // not on a window boundary — silent
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Claude Code UserPromptSubmit: stdout JSON envelope. client:'cc' forces
|
|
208
|
+
// the envelope since the isClaudeCode/forceClaudeCode gate above already
|
|
209
|
+
// confirmed this is the Claude Code path.
|
|
210
|
+
emitContext(renderReminder(turnCount, interval), {
|
|
211
|
+
client: "cc",
|
|
212
|
+
eventName: "UserPromptSubmit",
|
|
213
|
+
streams,
|
|
214
|
+
});
|
|
215
|
+
} catch {
|
|
216
|
+
// Silent — never block user prompt on hook failure.
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
main,
|
|
222
|
+
evaluateCiteEvict,
|
|
223
|
+
renderReminder,
|
|
224
|
+
readEvictInterval,
|
|
225
|
+
readEvictState,
|
|
226
|
+
writeEvictState,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (require.main === module) {
|
|
230
|
+
main();
|
|
231
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Client hook config templates
|
|
2
2
|
|
|
3
|
-
These JSON files are **fragment templates** consumed by `fabric
|
|
3
|
+
These JSON files are **fragment templates** consumed by `fabric install` and
|
|
4
4
|
`fabric hooks install`. They are not standalone client config files.
|
|
5
5
|
|
|
6
6
|
The supported clients are pinned by `packages/shared/src/schemas/fabric-config.ts`
|
|
@@ -27,11 +27,34 @@ config (`~/.codex/config.toml`) is TOML.
|
|
|
27
27
|
|
|
28
28
|
## cursor-hooks.json
|
|
29
29
|
|
|
30
|
-
Written to (or merged into) the user repo's `.cursor/hooks.json`.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
Written to (or merged into) the user repo's `.cursor/hooks.json`. Schema
|
|
31
|
+
authoritative source: https://cursor.com/cn/docs/hooks. Top-level requires
|
|
32
|
+
`version: 1` (number literal, NOT string) and a `hooks` object (NOT `events`)
|
|
33
|
+
keyed by camelCase event names: `stop`, `sessionStart`, `preToolUse`. Per-entry
|
|
34
|
+
shape stays flat (Codex-style): `{command, matcher?, type?, timeout?,
|
|
35
|
+
loop_limit?, failClosed?}`. rc.14 TASK-001 corrected rc.13's wrong top-level
|
|
36
|
+
envelope (was `{events: {Stop, SessionStart, PreToolUse}}` PascalCase, which
|
|
37
|
+
Cursor rejects with "Config version must be a number; Config hooks must be an
|
|
38
|
+
object").
|
|
39
|
+
|
|
40
|
+
## Per-client schema comparison (v2.0.0-rc.37 NEW-29)
|
|
41
|
+
|
|
42
|
+
Each host program enforces its own wire format — `fabric install` cannot
|
|
43
|
+
serialize one shared shape across all three. Differences are pinned here
|
|
44
|
+
side-by-side so anyone editing one config knows what the others require.
|
|
45
|
+
|
|
46
|
+
| Axis | Claude Code | Codex CLI | Cursor |
|
|
47
|
+
| -------------------- | ---------------------------------------- | -------------------------------------------------- | ----------------------------------------------- |
|
|
48
|
+
| Settings file | `.claude/settings.json` | `.codex/hooks.json` | `.cursor/hooks.json` |
|
|
49
|
+
| Top-level envelope | `hooks: { ... }` (no version) | `events: { ... }` (no version) | `{ version: 1, hooks: { ... } }` (number, not string) |
|
|
50
|
+
| Event-name case | PascalCase: `Stop`, `SessionStart`, `PreToolUse`, `UserPromptSubmit` | PascalCase: `Stop`, `SessionStart`, `PreToolUse` | camelCase: `stop`, `sessionStart`, `preToolUse` |
|
|
51
|
+
| Per-entry shape | Nested matcher: `[{matcher, hooks:[{type:"command", command}]}]` | Flat: `[{command, matcher?}]` | Flat: `[{command, matcher?, type?, timeout?, loop_limit?, failClosed?}]` |
|
|
52
|
+
| Path interpolation | `${CLAUDE_PROJECT_DIR}` (env var) | `"$(git rev-parse --show-toplevel)"` (shell expansion) | project-relative (resolved by Cursor) |
|
|
53
|
+
| Cite-policy event | `UserPromptSubmit` (per-prompt) | `SessionStart` 2nd entry (rc.37 NEW-21 parity) | `sessionStart` 2nd entry (rc.37 NEW-21 parity) |
|
|
54
|
+
|
|
55
|
+
Whenever a hook is added to one config, walk this table and add the equivalent
|
|
56
|
+
entry to the other two — `fabric install` merges each into its respective
|
|
57
|
+
target verbatim, so missing entries silently degrade the cross-client surface.
|
|
35
58
|
|
|
36
59
|
## fabric-hint.cjs script paths
|
|
37
60
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "
|
|
9
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/fabric-hint.cjs"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"hooks": [
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
|
-
"command": "
|
|
20
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-broad.cjs"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
|
@@ -28,7 +28,18 @@
|
|
|
28
28
|
"hooks": [
|
|
29
29
|
{
|
|
30
30
|
"type": "command",
|
|
31
|
-
"command": "
|
|
31
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"UserPromptSubmit": [
|
|
37
|
+
{
|
|
38
|
+
"matcher": "*",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/cite-policy-evict.cjs"
|
|
32
43
|
}
|
|
33
44
|
]
|
|
34
45
|
}
|
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
"events": {
|
|
3
3
|
"Stop": [
|
|
4
4
|
{
|
|
5
|
-
"command": "
|
|
5
|
+
"command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs\""
|
|
6
6
|
}
|
|
7
7
|
],
|
|
8
8
|
"SessionStart": [
|
|
9
9
|
{
|
|
10
|
-
"command": "
|
|
10
|
+
"command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs\""
|
|
11
14
|
}
|
|
12
15
|
],
|
|
13
16
|
"PreToolUse": [
|
|
14
17
|
{
|
|
15
18
|
"matcher": "Edit|Write|MultiEdit",
|
|
16
|
-
"command": "
|
|
19
|
+
"command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs\""
|
|
17
20
|
}
|
|
18
21
|
]
|
|
19
22
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hooks": {
|
|
4
|
+
"stop": [
|
|
5
|
+
{ "command": ".cursor/hooks/fabric-hint.cjs" }
|
|
7
6
|
],
|
|
8
|
-
"
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
}
|
|
7
|
+
"sessionStart": [
|
|
8
|
+
{ "command": ".cursor/hooks/knowledge-hint-broad.cjs" },
|
|
9
|
+
{ "command": ".cursor/hooks/cite-policy-evict.cjs" }
|
|
12
10
|
],
|
|
13
|
-
"
|
|
11
|
+
"preToolUse": [
|
|
14
12
|
{
|
|
15
13
|
"matcher": "Edit|Write|MultiEdit",
|
|
16
14
|
"command": ".cursor/hooks/knowledge-hint-narrow.cjs"
|