@fenglimg/fabric-cli 2.2.0-rc.1 → 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-5LQIHYFC.js +64 -0
- package/dist/chunk-5ZUMLCD5.js +248 -0
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/{chunk-AOE6AYI7.js → chunk-F6ITRM7T.js} +2 -2
- package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
- package/dist/chunk-XCBVSGCS.js +25 -0
- package/dist/{chunk-2R55HNVD.js → chunk-XHHCRDIR.js} +71 -6
- package/dist/{config-XYRBZJDU.js → config-VJMXCLXW.js} +1 -1
- package/dist/{doctor-YONYXDX6.js → doctor-J4O3X54I.js} +118 -7
- package/dist/index.js +13 -12
- package/dist/{install-74ANPCCP.js → install-BULNDUIM.js} +159 -80
- package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
- package/dist/{scope-explain-CDIZESP5.js → scope-explain-BWRWBCCP.js} +14 -4
- package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
- package/dist/store-66NK2FTQ.js +443 -0
- package/dist/{sync-UJ4BBCZJ.js → sync-EA5HZMXM.js} +165 -21
- package/dist/{uninstall-C3QXKOO6.js → uninstall-F75MPKQC.js} +27 -1
- package/dist/{whoami-2MLO4Y37.js → whoami-66YKY5DZ.js} +16 -5
- 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 +151 -15
- package/templates/hooks/knowledge-hint-broad.cjs +12 -1
- package/templates/hooks/knowledge-hint-narrow.cjs +54 -1
- 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/dist/chunk-4R2CYEA4.js +0 -116
- 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-XB3ADT65.js +0 -144
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
storeGitRemote
|
|
4
|
+
} from "./chunk-5ZUMLCD5.js";
|
|
5
|
+
import {
|
|
6
|
+
loadGlobalConfig,
|
|
7
|
+
loadProjectConfig,
|
|
8
|
+
resolveGlobalRoot
|
|
9
|
+
} from "./chunk-XCBVSGCS.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/unknown-flags.ts
|
|
12
|
+
function warnUnknownFlags(known) {
|
|
13
|
+
const knownSet = /* @__PURE__ */ new Set([...known, "help", "version"]);
|
|
14
|
+
const unknown = [];
|
|
15
|
+
for (const tok of process.argv.slice(2)) {
|
|
16
|
+
if (!tok.startsWith("--")) continue;
|
|
17
|
+
const name = tok.slice(2).split("=")[0].replace(/^no-/, "");
|
|
18
|
+
if (name.length === 0) continue;
|
|
19
|
+
if (!knownSet.has(name)) unknown.push(tok.split("=")[0]);
|
|
20
|
+
}
|
|
21
|
+
if (unknown.length > 0) {
|
|
22
|
+
process.stderr.write(`[fabric] ignored unknown flag(s): ${unknown.join(", ")}
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/store/info-ops.ts
|
|
28
|
+
function whoami(globalRoot = resolveGlobalRoot()) {
|
|
29
|
+
const config = loadGlobalConfig(globalRoot);
|
|
30
|
+
if (config === null) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
uid: config.uid,
|
|
35
|
+
stores: config.stores.map((s) => ({
|
|
36
|
+
alias: s.alias,
|
|
37
|
+
store_uuid: s.store_uuid,
|
|
38
|
+
// F4: parity with `fabric store list` — local-only reflects the store
|
|
39
|
+
// repo's TRUE git remote (what sync actually pushes to), not the registry
|
|
40
|
+
// metadata. A store with a physical `origin` but no registry `remote`
|
|
41
|
+
// (e.g. the personal store) was misreported as local-only by whoami while
|
|
42
|
+
// `store list` honestly showed its remote. Both now read the same source.
|
|
43
|
+
local_only: storeGitRemote(s.store_uuid, globalRoot) === void 0
|
|
44
|
+
}))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function projectStatus(projectRoot, globalRoot = resolveGlobalRoot()) {
|
|
48
|
+
const global = loadGlobalConfig(globalRoot);
|
|
49
|
+
const project = loadProjectConfig(projectRoot);
|
|
50
|
+
return {
|
|
51
|
+
uid: global?.uid ?? null,
|
|
52
|
+
mounted: (global?.stores ?? []).map((s) => s.alias),
|
|
53
|
+
project_id: project?.project_id ?? null,
|
|
54
|
+
is_fabric_project: project !== null,
|
|
55
|
+
required: (project?.required_stores ?? []).map((r) => r.id),
|
|
56
|
+
active_write_store: project?.active_write_store ?? null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
warnUnknownFlags,
|
|
62
|
+
whoami,
|
|
63
|
+
projectStatus
|
|
64
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadGlobalConfig,
|
|
4
|
+
loadProjectConfig,
|
|
5
|
+
resolveGlobalRoot,
|
|
6
|
+
saveGlobalConfig,
|
|
7
|
+
saveProjectConfig
|
|
8
|
+
} from "./chunk-XCBVSGCS.js";
|
|
9
|
+
|
|
10
|
+
// src/store/store-ops.ts
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import {
|
|
16
|
+
addMountedStore,
|
|
17
|
+
bindRequiredStore,
|
|
18
|
+
detachMountedStore,
|
|
19
|
+
explainStore,
|
|
20
|
+
initStore,
|
|
21
|
+
STORES_ROOT_DIR,
|
|
22
|
+
storeRelativePath
|
|
23
|
+
} from "@fenglimg/fabric-shared";
|
|
24
|
+
var NO_GLOBAL_CONFIG = "no global Fabric config found \u2014 run `fabric install --global <url>` first";
|
|
25
|
+
function requireConfig(globalRoot) {
|
|
26
|
+
const config = loadGlobalConfig(globalRoot);
|
|
27
|
+
if (config === null) {
|
|
28
|
+
throw new Error(NO_GLOBAL_CONFIG);
|
|
29
|
+
}
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
function storeList(globalRoot = resolveGlobalRoot()) {
|
|
33
|
+
return requireConfig(globalRoot).stores;
|
|
34
|
+
}
|
|
35
|
+
var STORE_BY_ALIAS_DIR = "by-alias";
|
|
36
|
+
function syncStoreAliasLinks(globalRoot = resolveGlobalRoot()) {
|
|
37
|
+
const result = { created: [], removed: [], errors: [] };
|
|
38
|
+
const config = loadGlobalConfig(globalRoot);
|
|
39
|
+
if (config === null) {
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
const byAliasDir = join(globalRoot, STORES_ROOT_DIR, STORE_BY_ALIAS_DIR);
|
|
43
|
+
const desired = new Map(config.stores.map((s) => [s.alias, s.store_uuid]));
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(byAliasDir, { recursive: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
let existing = [];
|
|
50
|
+
try {
|
|
51
|
+
existing = readdirSync(byAliasDir);
|
|
52
|
+
} catch {
|
|
53
|
+
existing = [];
|
|
54
|
+
}
|
|
55
|
+
for (const name of existing) {
|
|
56
|
+
if (desired.has(name)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
rmSync(join(byAliasDir, name), { force: true, recursive: false });
|
|
61
|
+
result.removed.push(name);
|
|
62
|
+
} catch {
|
|
63
|
+
result.errors.push(name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const [alias, uuid] of desired) {
|
|
67
|
+
const link = join(byAliasDir, alias);
|
|
68
|
+
const target = join("..", uuid);
|
|
69
|
+
try {
|
|
70
|
+
let current = null;
|
|
71
|
+
try {
|
|
72
|
+
if (lstatSync(link).isSymbolicLink()) {
|
|
73
|
+
current = readlinkSync(link);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
current = null;
|
|
77
|
+
}
|
|
78
|
+
if (current === target) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
rmSync(link, { force: true });
|
|
82
|
+
symlinkSync(target, link);
|
|
83
|
+
result.created.push(alias);
|
|
84
|
+
} catch {
|
|
85
|
+
result.errors.push(alias);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function detectAliasLinkDrift(globalRoot = resolveGlobalRoot()) {
|
|
91
|
+
const config = loadGlobalConfig(globalRoot);
|
|
92
|
+
if (config === null) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const byAliasDir = join(globalRoot, STORES_ROOT_DIR, STORE_BY_ALIAS_DIR);
|
|
96
|
+
if (!existsSync(byAliasDir)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const drifted = [];
|
|
100
|
+
for (const store of config.stores) {
|
|
101
|
+
const link = join(byAliasDir, store.alias);
|
|
102
|
+
const target = join("..", store.store_uuid);
|
|
103
|
+
try {
|
|
104
|
+
if (!lstatSync(link).isSymbolicLink() || readlinkSync(link) !== target) {
|
|
105
|
+
drifted.push(store.alias);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
drifted.push(store.alias);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return drifted;
|
|
112
|
+
}
|
|
113
|
+
function storeAdd(store, globalRoot = resolveGlobalRoot()) {
|
|
114
|
+
const next = addMountedStore(requireConfig(globalRoot), store);
|
|
115
|
+
saveGlobalConfig(next, globalRoot);
|
|
116
|
+
syncStoreAliasLinks(globalRoot);
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
function storeCreate(alias, now, options = {}) {
|
|
120
|
+
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
121
|
+
const config = requireConfig(globalRoot);
|
|
122
|
+
const uuid = options.uuid ?? randomUUID();
|
|
123
|
+
const storeDir = join(globalRoot, storeRelativePath(uuid));
|
|
124
|
+
initStore(
|
|
125
|
+
storeDir,
|
|
126
|
+
{ store_uuid: uuid, created_at: now, canonical_alias: alias },
|
|
127
|
+
{ git: options.git }
|
|
128
|
+
);
|
|
129
|
+
if (options.remote !== void 0 && options.git !== false) {
|
|
130
|
+
gitRemoteAdd(storeDir, options.remote);
|
|
131
|
+
}
|
|
132
|
+
const mounted = options.remote === void 0 ? { store_uuid: uuid, alias } : { store_uuid: uuid, alias, remote: options.remote };
|
|
133
|
+
const next = addMountedStore(config, mounted);
|
|
134
|
+
saveGlobalConfig(next, globalRoot);
|
|
135
|
+
syncStoreAliasLinks(globalRoot);
|
|
136
|
+
return { config: next, store_uuid: uuid, storeDir };
|
|
137
|
+
}
|
|
138
|
+
function gitRemoteAdd(storeDir, remote) {
|
|
139
|
+
try {
|
|
140
|
+
execFileSync("git", ["remote", "add", "origin", remote], {
|
|
141
|
+
cwd: storeDir,
|
|
142
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
143
|
+
});
|
|
144
|
+
} catch {
|
|
145
|
+
try {
|
|
146
|
+
execFileSync("git", ["remote", "set-url", "origin", remote], {
|
|
147
|
+
cwd: storeDir,
|
|
148
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function storeGitRemote(uuid, globalRoot = resolveGlobalRoot()) {
|
|
155
|
+
const storeDir = join(globalRoot, storeRelativePath(uuid));
|
|
156
|
+
if (!existsSync(storeDir)) {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const out = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
161
|
+
cwd: storeDir,
|
|
162
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
163
|
+
encoding: "utf8"
|
|
164
|
+
});
|
|
165
|
+
const trimmed = out.trim();
|
|
166
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
167
|
+
} catch {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function assertStoreMountable(uuid, globalRoot = resolveGlobalRoot()) {
|
|
172
|
+
const storeDir = join(globalRoot, storeRelativePath(uuid));
|
|
173
|
+
if (!existsSync(join(storeDir, "store.json"))) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`cannot mount store ${uuid}: no store tree at ${storeDir} \u2014 clone it first (\`fabric install --global --url <remote>\`) or create it locally, then re-run \`fabric store add\`. Refusing to register a phantom store.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function storeRemove(alias, globalRoot = resolveGlobalRoot()) {
|
|
180
|
+
const result = detachMountedStore(requireConfig(globalRoot), alias);
|
|
181
|
+
saveGlobalConfig(result.config, globalRoot);
|
|
182
|
+
syncStoreAliasLinks(globalRoot);
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function storeExplain(alias, globalRoot = resolveGlobalRoot()) {
|
|
186
|
+
return explainStore(requireConfig(globalRoot), alias);
|
|
187
|
+
}
|
|
188
|
+
var NO_PROJECT_CONFIG = "no project Fabric config \u2014 run `fabric install` in this repo first";
|
|
189
|
+
function requireProjectConfig(projectRoot) {
|
|
190
|
+
const config = loadProjectConfig(projectRoot);
|
|
191
|
+
if (config === null) {
|
|
192
|
+
throw new Error(NO_PROJECT_CONFIG);
|
|
193
|
+
}
|
|
194
|
+
return config;
|
|
195
|
+
}
|
|
196
|
+
function storeBind(projectRoot, entry) {
|
|
197
|
+
const config = requireProjectConfig(projectRoot);
|
|
198
|
+
const next = {
|
|
199
|
+
...config,
|
|
200
|
+
required_stores: bindRequiredStore(config.required_stores ?? [], entry)
|
|
201
|
+
};
|
|
202
|
+
saveProjectConfig(next, projectRoot);
|
|
203
|
+
return next;
|
|
204
|
+
}
|
|
205
|
+
function storeSwitchWrite(projectRoot, alias) {
|
|
206
|
+
const config = requireProjectConfig(projectRoot);
|
|
207
|
+
const next = { ...config, active_write_store: alias };
|
|
208
|
+
saveProjectConfig(next, projectRoot);
|
|
209
|
+
return next;
|
|
210
|
+
}
|
|
211
|
+
function missingRequiredStores(projectRoot, globalRoot = resolveGlobalRoot()) {
|
|
212
|
+
const project = loadProjectConfig(projectRoot);
|
|
213
|
+
if (project === null || project.required_stores === void 0) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const global = loadGlobalConfig(globalRoot);
|
|
217
|
+
const mounted = new Set(
|
|
218
|
+
(global?.stores ?? []).flatMap((s) => [s.alias, s.store_uuid])
|
|
219
|
+
);
|
|
220
|
+
return project.required_stores.filter((r) => !mounted.has(r.id));
|
|
221
|
+
}
|
|
222
|
+
function unboundAvailableStores(projectRoot, globalRoot = resolveGlobalRoot()) {
|
|
223
|
+
const global = loadGlobalConfig(globalRoot);
|
|
224
|
+
if (global === null) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
const project = loadProjectConfig(projectRoot);
|
|
228
|
+
const declared = new Set((project?.required_stores ?? []).map((r) => r.id));
|
|
229
|
+
return global.stores.filter(
|
|
230
|
+
(s) => s.personal !== true && !declared.has(s.alias) && !declared.has(s.store_uuid)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export {
|
|
235
|
+
storeList,
|
|
236
|
+
syncStoreAliasLinks,
|
|
237
|
+
detectAliasLinkDrift,
|
|
238
|
+
storeAdd,
|
|
239
|
+
storeCreate,
|
|
240
|
+
storeGitRemote,
|
|
241
|
+
assertStoreMountable,
|
|
242
|
+
storeRemove,
|
|
243
|
+
storeExplain,
|
|
244
|
+
storeBind,
|
|
245
|
+
storeSwitchWrite,
|
|
246
|
+
missingRequiredStores,
|
|
247
|
+
unboundAvailableStores
|
|
248
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/store/scope-explain.ts
|
|
4
|
+
import {
|
|
5
|
+
SCOPE_COORDINATE_PATTERN,
|
|
6
|
+
buildStoreResolveInput,
|
|
7
|
+
createStoreResolver,
|
|
8
|
+
resolveGlobalRoot
|
|
9
|
+
} from "@fenglimg/fabric-shared";
|
|
10
|
+
import { GenericConfigError } from "@fenglimg/fabric-shared/errors";
|
|
11
|
+
function buildResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
|
|
12
|
+
return buildStoreResolveInput(projectRoot, globalRoot);
|
|
13
|
+
}
|
|
14
|
+
function scopeExplain(projectRoot, scope, globalRoot = resolveGlobalRoot()) {
|
|
15
|
+
if (!SCOPE_COORDINATE_PATTERN.test(scope)) {
|
|
16
|
+
throw new GenericConfigError(`invalid scope coordinate '${scope}'`, {
|
|
17
|
+
actionHint: "use ':'-joined lowercase [a-z0-9_-] segments, e.g. `team`, `personal`, `project:fabric-v2`, `org:acme:team:platform`",
|
|
18
|
+
details: { scope }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const input = buildResolveInput(projectRoot, globalRoot);
|
|
22
|
+
if (input === null) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const resolver = createStoreResolver();
|
|
26
|
+
return {
|
|
27
|
+
scope,
|
|
28
|
+
readSet: resolver.resolveReadSet(input),
|
|
29
|
+
writeTarget: resolver.resolveWriteTarget(input, scope).target
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
buildResolveInput,
|
|
35
|
+
scopeExplain
|
|
36
|
+
};
|
|
@@ -178,8 +178,8 @@ var configCmd = defineCommand({
|
|
|
178
178
|
"onboard-reset": onboardResetCmd
|
|
179
179
|
},
|
|
180
180
|
async run({ args }) {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
181
|
+
const argvAfterConfig = process.argv.slice(3);
|
|
182
|
+
if (argvAfterConfig.includes("dismiss-slot") || argvAfterConfig.includes("onboard-reset")) {
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
185
|
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildResolveInput
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
loadProjectConfig
|
|
7
|
-
} from "./chunk-LFIKMVY7.js";
|
|
4
|
+
} from "./chunk-EOT63RDH.js";
|
|
8
5
|
import {
|
|
6
|
+
loadProjectConfig,
|
|
9
7
|
resolveGlobalRoot
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XCBVSGCS.js";
|
|
11
9
|
|
|
12
10
|
// src/store/bindings-io.ts
|
|
13
11
|
import { writeBindingsSnapshot } from "@fenglimg/fabric-shared";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/store/global-config-io.ts
|
|
4
|
+
import {
|
|
5
|
+
resolveGlobalRoot,
|
|
6
|
+
globalConfigPath,
|
|
7
|
+
loadGlobalConfig,
|
|
8
|
+
saveGlobalConfig
|
|
9
|
+
} from "@fenglimg/fabric-shared";
|
|
10
|
+
|
|
11
|
+
// src/store/project-config-io.ts
|
|
12
|
+
import {
|
|
13
|
+
projectConfigPath,
|
|
14
|
+
loadProjectConfig,
|
|
15
|
+
saveProjectConfig
|
|
16
|
+
} from "@fenglimg/fabric-shared";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
resolveGlobalRoot,
|
|
20
|
+
globalConfigPath,
|
|
21
|
+
loadGlobalConfig,
|
|
22
|
+
saveGlobalConfig,
|
|
23
|
+
loadProjectConfig,
|
|
24
|
+
saveProjectConfig
|
|
25
|
+
};
|
|
@@ -62,6 +62,8 @@ var HOOK_SCRIPT_TEMPLATE_REL = "hooks/fabric-hint.cjs";
|
|
|
62
62
|
var HOOK_BROAD_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-broad.cjs";
|
|
63
63
|
var HOOK_NARROW_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-narrow.cjs";
|
|
64
64
|
var HOOK_CITE_EVICT_SCRIPT_TEMPLATE_REL = "hooks/cite-policy-evict.cjs";
|
|
65
|
+
var HOOK_SESSION_END_SCRIPT_TEMPLATE_REL = "hooks/session-end-marker.cjs";
|
|
66
|
+
var HOOK_POST_TOOLUSE_SCRIPT_TEMPLATE_REL = "hooks/post-tooluse-mutation.cjs";
|
|
65
67
|
var HOOK_LIB_TEMPLATE_DIR_REL = "hooks/lib";
|
|
66
68
|
var CLAUDE_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/claude-code.json";
|
|
67
69
|
var CODEX_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/codex-hooks.json";
|
|
@@ -135,6 +137,18 @@ var HOOK_SCRIPT_DESTINATIONS = {
|
|
|
135
137
|
".claude/hooks/cite-policy-evict.cjs",
|
|
136
138
|
".codex/hooks/cite-policy-evict.cjs",
|
|
137
139
|
".cursor/hooks/cite-policy-evict.cjs"
|
|
140
|
+
],
|
|
141
|
+
// lifecycle-refactor W2-T2: SessionEnd marker hook — all three clients.
|
|
142
|
+
sessionEndMarker: [
|
|
143
|
+
".claude/hooks/session-end-marker.cjs",
|
|
144
|
+
".codex/hooks/session-end-marker.cjs",
|
|
145
|
+
".cursor/hooks/session-end-marker.cjs"
|
|
146
|
+
],
|
|
147
|
+
// lifecycle-refactor W2-T3: PostToolUse mutation marker hook — all three.
|
|
148
|
+
postTooluseMutation: [
|
|
149
|
+
".claude/hooks/post-tooluse-mutation.cjs",
|
|
150
|
+
".codex/hooks/post-tooluse-mutation.cjs",
|
|
151
|
+
".cursor/hooks/post-tooluse-mutation.cjs"
|
|
138
152
|
]
|
|
139
153
|
};
|
|
140
154
|
var HOOK_LIB_DESTINATIONS = [
|
|
@@ -152,9 +166,19 @@ var HOOK_CONFIG_ARRAY_PATHS = {
|
|
|
152
166
|
// ships a UserPromptSubmit cite-policy hook, so without this path deepMerge
|
|
153
167
|
// array-REPLACEs (instead of append-with-dedupe) on re-install, silently
|
|
154
168
|
// clobbering any user-defined UserPromptSubmit hook.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
// lifecycle-refactor W2-T2/T3: PostToolUse + SessionEnd arrays added so
|
|
170
|
+
// deepMerge append-with-dedupes them on re-install (omitting them would
|
|
171
|
+
// array-REPLACE, clobbering any user-defined entries in those slots).
|
|
172
|
+
claudeCode: [
|
|
173
|
+
"hooks.Stop",
|
|
174
|
+
"hooks.SessionStart",
|
|
175
|
+
"hooks.PreToolUse",
|
|
176
|
+
"hooks.UserPromptSubmit",
|
|
177
|
+
"hooks.PostToolUse",
|
|
178
|
+
"hooks.SessionEnd"
|
|
179
|
+
],
|
|
180
|
+
codex: ["events.Stop", "events.SessionStart", "events.PreToolUse", "events.PostToolUse", "events.SessionEnd"],
|
|
181
|
+
cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse", "hooks.postToolUse", "hooks.sessionEnd"]
|
|
158
182
|
};
|
|
159
183
|
var FABRIC_HOOK_COMMAND_PATHS = {
|
|
160
184
|
claudeCode: {
|
|
@@ -163,19 +187,26 @@ var FABRIC_HOOK_COMMAND_PATHS = {
|
|
|
163
187
|
knowledgeHintNarrow: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs",
|
|
164
188
|
// F3: the UserPromptSubmit cite-policy-evict hook must be a known fabric
|
|
165
189
|
// command so uninstall prunes it (matches the literal in claude-code.json).
|
|
166
|
-
citePolicyEvict: "${CLAUDE_PROJECT_DIR}/.claude/hooks/cite-policy-evict.cjs"
|
|
190
|
+
citePolicyEvict: "${CLAUDE_PROJECT_DIR}/.claude/hooks/cite-policy-evict.cjs",
|
|
191
|
+
// lifecycle-refactor W2-T2/T3: SessionEnd + PostToolUse marker hooks.
|
|
192
|
+
sessionEndMarker: "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-end-marker.cjs",
|
|
193
|
+
postTooluseMutation: "${CLAUDE_PROJECT_DIR}/.claude/hooks/post-tooluse-mutation.cjs"
|
|
167
194
|
},
|
|
168
195
|
codex: {
|
|
169
196
|
fabricHint: '"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs"',
|
|
170
197
|
knowledgeHintBroad: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs"',
|
|
171
198
|
knowledgeHintNarrow: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs"',
|
|
172
|
-
citePolicyEvict: '"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs"'
|
|
199
|
+
citePolicyEvict: '"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs"',
|
|
200
|
+
sessionEndMarker: '"$(git rev-parse --show-toplevel)/.codex/hooks/session-end-marker.cjs"',
|
|
201
|
+
postTooluseMutation: '"$(git rev-parse --show-toplevel)/.codex/hooks/post-tooluse-mutation.cjs"'
|
|
173
202
|
},
|
|
174
203
|
cursor: {
|
|
175
204
|
fabricHint: ".cursor/hooks/fabric-hint.cjs",
|
|
176
205
|
knowledgeHintBroad: ".cursor/hooks/knowledge-hint-broad.cjs",
|
|
177
206
|
knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs",
|
|
178
|
-
citePolicyEvict: ".cursor/hooks/cite-policy-evict.cjs"
|
|
207
|
+
citePolicyEvict: ".cursor/hooks/cite-policy-evict.cjs",
|
|
208
|
+
sessionEndMarker: ".cursor/hooks/session-end-marker.cjs",
|
|
209
|
+
postTooluseMutation: ".cursor/hooks/post-tooluse-mutation.cjs"
|
|
179
210
|
}
|
|
180
211
|
};
|
|
181
212
|
function readFabricLanguagePreference(projectRoot) {
|
|
@@ -517,6 +548,38 @@ async function installCitePolicyEvictHook(projectRoot, _options = {}) {
|
|
|
517
548
|
}
|
|
518
549
|
return results;
|
|
519
550
|
}
|
|
551
|
+
async function installSessionEndMarkerHook(projectRoot, _options = {}) {
|
|
552
|
+
const source = await readTemplate(HOOK_SESSION_END_SCRIPT_TEMPLATE_REL);
|
|
553
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.sessionEndMarker.map((rel) => join2(projectRoot, rel));
|
|
554
|
+
const results = [];
|
|
555
|
+
for (const target of targets) {
|
|
556
|
+
const result = await copyTextIdempotent("hook-session-end-script", source, target);
|
|
557
|
+
if (result.status === "written" && process.platform !== "win32") {
|
|
558
|
+
try {
|
|
559
|
+
chmodSync(target, 493);
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
results.push(result);
|
|
564
|
+
}
|
|
565
|
+
return results;
|
|
566
|
+
}
|
|
567
|
+
async function installPostTooluseMutationHook(projectRoot, _options = {}) {
|
|
568
|
+
const source = await readTemplate(HOOK_POST_TOOLUSE_SCRIPT_TEMPLATE_REL);
|
|
569
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.postTooluseMutation.map((rel) => join2(projectRoot, rel));
|
|
570
|
+
const results = [];
|
|
571
|
+
for (const target of targets) {
|
|
572
|
+
const result = await copyTextIdempotent("hook-post-tooluse-script", source, target);
|
|
573
|
+
if (result.status === "written" && process.platform !== "win32") {
|
|
574
|
+
try {
|
|
575
|
+
chmodSync(target, 493);
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
results.push(result);
|
|
580
|
+
}
|
|
581
|
+
return results;
|
|
582
|
+
}
|
|
520
583
|
async function installHookLibs(projectRoot, _options = {}) {
|
|
521
584
|
const libTemplateDir = findTemplatePath(HOOK_LIB_TEMPLATE_DIR_REL);
|
|
522
585
|
let libFiles;
|
|
@@ -970,6 +1033,8 @@ export {
|
|
|
970
1033
|
installKnowledgeHintBroadHook,
|
|
971
1034
|
installKnowledgeHintNarrowHook,
|
|
972
1035
|
installCitePolicyEvictHook,
|
|
1036
|
+
installSessionEndMarkerHook,
|
|
1037
|
+
installPostTooluseMutationHook,
|
|
973
1038
|
installHookLibs,
|
|
974
1039
|
mergeClaudeCodeHookConfig,
|
|
975
1040
|
mergeCodexHookConfig,
|