@fenglimg/fabric-cli 2.0.0 → 2.1.0-rc.2

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -5
  3. package/dist/chunk-BATF4PEJ.js +361 -0
  4. package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
  5. package/dist/chunk-F46ORPOA.js +903 -0
  6. package/dist/chunk-HFQVXY6P.js +86 -0
  7. package/dist/chunk-L4Q55UC4.js +52 -0
  8. package/dist/chunk-LFIKMVY7.js +27 -0
  9. package/dist/chunk-MF3OTILQ.js +544 -0
  10. package/dist/chunk-PWLW3B57.js +18 -0
  11. package/dist/chunk-RYAFBNES.js +33 -0
  12. package/dist/chunk-T5RPGCCM.js +40 -0
  13. package/dist/chunk-WU6GAPKH.js +36 -0
  14. package/dist/config-XJIPZNUP.js +13 -0
  15. package/dist/doctor-QVNPHLJK.js +920 -0
  16. package/dist/index.js +23 -8
  17. package/dist/{init-BIRSIOXO.js → install-2HDO5FTQ.js} +807 -705
  18. package/dist/metrics-ACEQFPDU.js +122 -0
  19. package/dist/onboard-coverage-MFCAEBDO.js +220 -0
  20. package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
  21. package/dist/scope-explain-2F2R5URO.js +33 -0
  22. package/dist/status-GLQWLWH6.js +23 -0
  23. package/dist/store-XTSE5TY6.js +105 -0
  24. package/dist/sync-BJCWDPNC.js +245 -0
  25. package/dist/uninstall-TAXSUSKH.js +1073 -0
  26. package/dist/whoami-B6AEMSEV.js +31 -0
  27. package/package.json +30 -5
  28. package/templates/hooks/cite-policy-evict.cjs +231 -0
  29. package/templates/hooks/configs/README.md +29 -6
  30. package/templates/hooks/configs/claude-code.json +14 -3
  31. package/templates/hooks/configs/codex-hooks.json +6 -3
  32. package/templates/hooks/configs/cursor-hooks.json +8 -10
  33. package/templates/hooks/fabric-hint.cjs +873 -105
  34. package/templates/hooks/knowledge-hint-broad.cjs +549 -135
  35. package/templates/hooks/knowledge-hint-narrow.cjs +830 -26
  36. package/templates/hooks/lib/banner-i18n.cjs +309 -0
  37. package/templates/hooks/lib/bindings-snapshot-reader.cjs +81 -0
  38. package/templates/hooks/lib/cite-contract-reminder.cjs +179 -0
  39. package/templates/hooks/lib/cite-line-parser.cjs +180 -0
  40. package/templates/hooks/lib/client-adapter.cjs +106 -0
  41. package/templates/hooks/lib/config-cache.cjs +107 -0
  42. package/templates/hooks/lib/state-store.cjs +84 -0
  43. package/templates/hooks/lib/summary-fallback.cjs +210 -0
  44. package/templates/skills/fabric-archive/SKILL.md +97 -419
  45. package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
  46. package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
  47. package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
  48. package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
  49. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
  50. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
  51. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
  52. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
  53. package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
  54. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
  55. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
  56. package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
  57. package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
  58. package/templates/skills/fabric-import/SKILL.md +77 -514
  59. package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
  60. package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
  61. package/templates/skills/fabric-import/ref/output-contract.md +61 -0
  62. package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
  63. package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
  64. package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
  65. package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
  66. package/templates/skills/fabric-review/SKILL.md +90 -284
  67. package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
  68. package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
  69. package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
  70. package/templates/skills/fabric-review/ref/output-contract.md +58 -0
  71. package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
  72. package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
  73. package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
  74. package/templates/skills/fabric-sync/SKILL.md +46 -0
  75. package/templates/skills/lib/shared-policy.md +69 -0
  76. package/dist/chunk-6ICJICVU.js +0 -10
  77. package/dist/chunk-74SZWYPH.js +0 -658
  78. package/dist/chunk-EYIDD2YS.js +0 -1000
  79. package/dist/doctor-T7JWODKG.js +0 -282
  80. package/dist/hooks-Y74Y5LQS.js +0 -12
  81. package/dist/scan-LMK3UCWL.js +0 -22
  82. package/dist/serve-H554BHLG.js +0 -124
  83. package/templates/agents-md/AGENTS.md.template +0 -59
  84. package/templates/bootstrap/CLAUDE.md +0 -8
  85. package/templates/bootstrap/codex-AGENTS-header.md +0 -6
  86. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ whoami
4
+ } from "./chunk-T5RPGCCM.js";
5
+ import "./chunk-LFIKMVY7.js";
6
+ import "./chunk-RYAFBNES.js";
7
+
8
+ // src/commands/whoami.ts
9
+ import { defineCommand } from "citty";
10
+ var whoami_default = defineCommand({
11
+ meta: { name: "whoami", description: "Show this machine's Fabric uid and mounted stores" },
12
+ run() {
13
+ const info = whoami();
14
+ if (info === null) {
15
+ console.log("no global Fabric config \u2014 run `fabric install --global <url>` first");
16
+ return;
17
+ }
18
+ console.log(`uid: ${info.uid}`);
19
+ if (info.stores.length === 0) {
20
+ console.log("stores: (none mounted)");
21
+ return;
22
+ }
23
+ console.log("stores:");
24
+ for (const store of info.stores) {
25
+ console.log(` ${store.alias} ${store.store_uuid}${store.local_only ? " (local-only)" : ""}`);
26
+ }
27
+ }
28
+ });
29
+ export {
30
+ whoami_default as default
31
+ };
package/package.json CHANGED
@@ -1,16 +1,41 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0-rc.2",
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.0",
24
- "@fenglimg/fabric-shared": "2.0.0"
48
+ "@fenglimg/fabric-server": "2.1.0-rc.2",
49
+ "@fenglimg/fabric-shared": "2.1.0-rc.2"
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 init` and
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`. Mirrors the
31
- Codex `events.Stop[]` envelope shape — Cursor's hook event vocabulary is
32
- not stable across releases, so the canonical Stop-on-tool-finish lifecycle hook
33
- is the only entry we register today. SessionStart / PreToolUse slots are left
34
- unfilled for rc.6 to add when their semantics stabilise.
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": ".claude/hooks/fabric-hint.cjs"
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": ".claude/hooks/knowledge-hint-broad.cjs"
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": ".claude/hooks/knowledge-hint-narrow.cjs"
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": ".codex/hooks/fabric-hint.cjs"
5
+ "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs\""
6
6
  }
7
7
  ],
8
8
  "SessionStart": [
9
9
  {
10
- "command": ".codex/hooks/knowledge-hint-broad.cjs"
10
+ "command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs\""
11
+ },
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": ".codex/hooks/knowledge-hint-narrow.cjs"
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
- "events": {
3
- "Stop": [
4
- {
5
- "command": ".cursor/hooks/fabric-hint.cjs"
6
- }
2
+ "version": 1,
3
+ "hooks": {
4
+ "stop": [
5
+ { "command": ".cursor/hooks/fabric-hint.cjs" }
7
6
  ],
8
- "SessionStart": [
9
- {
10
- "command": ".cursor/hooks/knowledge-hint-broad.cjs"
11
- }
7
+ "sessionStart": [
8
+ { "command": ".cursor/hooks/knowledge-hint-broad.cjs" },
9
+ { "command": ".cursor/hooks/cite-policy-evict.cjs" }
12
10
  ],
13
- "PreToolUse": [
11
+ "preToolUse": [
14
12
  {
15
13
  "matcher": "Edit|Write|MultiEdit",
16
14
  "command": ".cursor/hooks/knowledge-hint-narrow.cjs"