@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
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
regenerateBindingsSnapshot
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-H3FE6VIK.js";
|
|
5
|
+
import "./chunk-EOT63RDH.js";
|
|
6
6
|
import {
|
|
7
7
|
getProjectTranslator
|
|
8
8
|
} from "./chunk-2CY4BMTH.js";
|
|
9
|
-
import "./chunk-LFIKMVY7.js";
|
|
10
9
|
import {
|
|
11
10
|
loadGlobalConfig,
|
|
12
11
|
resolveGlobalRoot
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XCBVSGCS.js";
|
|
14
13
|
|
|
15
14
|
// src/commands/sync.ts
|
|
16
15
|
import { defineCommand } from "citty";
|
|
17
16
|
|
|
18
17
|
// src/sync/run-sync.ts
|
|
19
18
|
import { execFileSync } from "child_process";
|
|
20
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
21
20
|
import { join } from "path";
|
|
22
21
|
import { GLOBAL_STATE_DIR, storeRelativePath } from "@fenglimg/fabric-shared";
|
|
23
22
|
import { GenericIOError } from "@fenglimg/fabric-shared/errors";
|
|
@@ -33,6 +32,7 @@ function syncTransition(state, event) {
|
|
|
33
32
|
case "conflict":
|
|
34
33
|
if (event === "user_continue") return "synced";
|
|
35
34
|
if (event === "user_abort") return "aborted";
|
|
35
|
+
if (event === "network_unavailable") return "offline";
|
|
36
36
|
break;
|
|
37
37
|
case "offline":
|
|
38
38
|
if (event === "retry" || event === "rebase_clean") return "synced";
|
|
@@ -88,13 +88,31 @@ function loadSession(globalRoot) {
|
|
|
88
88
|
if (!existsSync(path)) {
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
const raw = readFileSync(path, "utf8");
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(raw);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const corruptedPath = `${path}.corrupted.${Date.now()}`;
|
|
96
|
+
try {
|
|
97
|
+
writeFileSync(corruptedPath, raw, "utf8");
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
throw new GenericIOError(
|
|
101
|
+
`sync-session.json is corrupt (forensic copy: ${corruptedPath}). Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
102
|
+
{
|
|
103
|
+
actionHint: `Delete ${path} to start a fresh sync (any in-progress rebase must be resolved manually with git first).`,
|
|
104
|
+
details: { path, corruptedPath }
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
92
108
|
}
|
|
93
109
|
function saveSession(globalRoot, session) {
|
|
94
110
|
const path = syncSessionPath(globalRoot);
|
|
95
111
|
mkdirSync(join(path, ".."), { recursive: true });
|
|
96
|
-
|
|
112
|
+
const tmpPath = `${path}.${process.pid}.tmp`;
|
|
113
|
+
writeFileSync(tmpPath, `${JSON.stringify(session, null, 2)}
|
|
97
114
|
`, "utf8");
|
|
115
|
+
renameSync(tmpPath, path);
|
|
98
116
|
}
|
|
99
117
|
function clearSession(globalRoot) {
|
|
100
118
|
rmSync(syncSessionPath(globalRoot), { force: true });
|
|
@@ -127,28 +145,105 @@ function gitErrText(error, key) {
|
|
|
127
145
|
const value = error[key];
|
|
128
146
|
return typeof value === "string" || Buffer.isBuffer(value) ? String(value) : "";
|
|
129
147
|
}
|
|
148
|
+
function defaultPush(storeDir) {
|
|
149
|
+
try {
|
|
150
|
+
execFileSync("git", ["push"], {
|
|
151
|
+
cwd: storeDir,
|
|
152
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
153
|
+
});
|
|
154
|
+
return "clean";
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`;
|
|
157
|
+
if (/could not resolve host|could not read from remote|unable to access|connection|network is unreachable|timed out/i.test(
|
|
158
|
+
detail
|
|
159
|
+
)) {
|
|
160
|
+
return "offline";
|
|
161
|
+
}
|
|
162
|
+
const gitMessage = detail.trim().length > 0 ? detail.trim() : "unknown git error";
|
|
163
|
+
throw new GenericIOError(`git push failed in ${storeDir}: ${gitMessage}`, {
|
|
164
|
+
actionHint: "resolve the git issue above (e.g. authentication, no upstream branch, or a rejected non-fast-forward push), then re-run `fabric sync`",
|
|
165
|
+
details: error
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function defaultCommitDirty(storeDir) {
|
|
170
|
+
try {
|
|
171
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
172
|
+
cwd: storeDir,
|
|
173
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
|
|
180
|
+
try {
|
|
181
|
+
execFileSync("git", ["diff", "--cached", "--quiet"], {
|
|
182
|
+
cwd: storeDir,
|
|
183
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
} catch {
|
|
187
|
+
execFileSync("git", ["commit", "-m", "fabric: sync local knowledge changes"], {
|
|
188
|
+
cwd: storeDir,
|
|
189
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function runRebaseStep(storeDir, step) {
|
|
196
|
+
try {
|
|
197
|
+
execFileSync("git", ["rebase", `--${step}`], {
|
|
198
|
+
cwd: storeDir,
|
|
199
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`.trim();
|
|
203
|
+
const gitMessage = detail.length > 0 ? detail : "unknown git error";
|
|
204
|
+
throw new GenericIOError(`git rebase --${step} failed in ${storeDir}: ${gitMessage}`, {
|
|
205
|
+
actionHint: step === "continue" ? "resolve the remaining conflicts (git status) and stage them, then re-run `fabric sync --continue`; or run `fabric sync --abort` to discard the rebase" : "inspect the store with `git status`; if no rebase is in progress the session may already be resolved \u2014 delete sync-session.json to reset",
|
|
206
|
+
details: error
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
130
210
|
function defaultRebaseContinue(storeDir) {
|
|
131
|
-
|
|
211
|
+
runRebaseStep(storeDir, "continue");
|
|
132
212
|
}
|
|
133
213
|
function defaultRebaseAbort(storeDir) {
|
|
134
|
-
|
|
214
|
+
runRebaseStep(storeDir, "abort");
|
|
135
215
|
}
|
|
136
216
|
var OUTCOME_EVENT = {
|
|
137
217
|
clean: "rebase_clean",
|
|
138
218
|
conflict: "rebase_conflict",
|
|
139
219
|
offline: "network_unavailable"
|
|
140
220
|
};
|
|
141
|
-
function walkPending(session, storeDirOf, pull) {
|
|
221
|
+
function walkPending(session, storeDirOf, pull, push, commit, pushableAliases) {
|
|
142
222
|
let next = session;
|
|
143
223
|
for (const store of session.stores) {
|
|
144
224
|
if (store.state !== "pending") {
|
|
145
225
|
continue;
|
|
146
226
|
}
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
227
|
+
const dir = storeDirOf(store);
|
|
228
|
+
commit(dir);
|
|
229
|
+
const pullOutcome = pull(dir);
|
|
230
|
+
if (pullOutcome !== "clean") {
|
|
231
|
+
next = applySyncEvent(next, store.alias, OUTCOME_EVENT[pullOutcome]);
|
|
232
|
+
if (pullOutcome === "conflict") {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
151
236
|
}
|
|
237
|
+
if (!pushableAliases.has(store.alias)) {
|
|
238
|
+
next = applySyncEvent(next, store.alias, "rebase_clean");
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const pushOutcome = push(dir);
|
|
242
|
+
next = applySyncEvent(
|
|
243
|
+
next,
|
|
244
|
+
store.alias,
|
|
245
|
+
pushOutcome === "clean" ? "rebase_clean" : "network_unavailable"
|
|
246
|
+
);
|
|
152
247
|
}
|
|
153
248
|
return next;
|
|
154
249
|
}
|
|
@@ -179,37 +274,86 @@ function runStartSync(options) {
|
|
|
179
274
|
syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
|
|
180
275
|
);
|
|
181
276
|
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
182
|
-
const
|
|
277
|
+
const pushableAliases = pushableAliasesOf(config);
|
|
278
|
+
const walked = walkPending(
|
|
279
|
+
session,
|
|
280
|
+
storeDirOf,
|
|
281
|
+
options.pull ?? defaultPull,
|
|
282
|
+
options.push ?? defaultPush,
|
|
283
|
+
options.commit ?? defaultCommitDirty,
|
|
284
|
+
pushableAliases
|
|
285
|
+
);
|
|
183
286
|
return finalize(walked, options, globalRoot);
|
|
184
287
|
}
|
|
288
|
+
function pushableAliasesOf(config) {
|
|
289
|
+
return new Set(
|
|
290
|
+
config.stores.filter((store) => store.remote !== void 0 && (store.writable ?? true)).map((store) => store.alias)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
185
293
|
function runContinueSync(options) {
|
|
186
294
|
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
187
295
|
const session = loadSession(globalRoot);
|
|
188
296
|
if (session === null) {
|
|
189
|
-
throw new
|
|
297
|
+
throw new GenericIOError(NO_SESSION, {
|
|
298
|
+
actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
|
|
299
|
+
});
|
|
190
300
|
}
|
|
191
301
|
const conflicted = session.stores.find((store) => store.state === "conflict");
|
|
192
302
|
if (conflicted === void 0) {
|
|
193
|
-
throw new
|
|
303
|
+
throw new GenericIOError(NO_CONFLICT, {
|
|
304
|
+
actionHint: "The sync is not paused on a conflict; there is nothing to resume."
|
|
305
|
+
});
|
|
194
306
|
}
|
|
195
307
|
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
196
308
|
(options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
|
|
197
|
-
const
|
|
309
|
+
const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
|
|
310
|
+
const push = options.push ?? defaultPush;
|
|
311
|
+
let advanced;
|
|
312
|
+
if (pushableAliases.has(conflicted.alias)) {
|
|
313
|
+
const pushOutcome = push(storeDirOf(conflicted));
|
|
314
|
+
advanced = applySyncEvent(
|
|
315
|
+
session,
|
|
316
|
+
conflicted.alias,
|
|
317
|
+
pushOutcome === "clean" ? "user_continue" : "network_unavailable"
|
|
318
|
+
);
|
|
319
|
+
} else {
|
|
320
|
+
advanced = continueSync(session);
|
|
321
|
+
}
|
|
322
|
+
const resumed = walkPending(
|
|
323
|
+
advanced,
|
|
324
|
+
storeDirOf,
|
|
325
|
+
options.pull ?? defaultPull,
|
|
326
|
+
push,
|
|
327
|
+
options.commit ?? defaultCommitDirty,
|
|
328
|
+
pushableAliases
|
|
329
|
+
);
|
|
198
330
|
return finalize(resumed, options, globalRoot);
|
|
199
331
|
}
|
|
200
332
|
function runAbortSync(options) {
|
|
201
333
|
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
202
334
|
const session = loadSession(globalRoot);
|
|
203
335
|
if (session === null) {
|
|
204
|
-
throw new
|
|
336
|
+
throw new GenericIOError(NO_SESSION, {
|
|
337
|
+
actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
|
|
338
|
+
});
|
|
205
339
|
}
|
|
206
340
|
const conflicted = session.stores.find((store) => store.state === "conflict");
|
|
207
341
|
if (conflicted === void 0) {
|
|
208
|
-
throw new
|
|
342
|
+
throw new GenericIOError(NO_CONFLICT, {
|
|
343
|
+
actionHint: "The sync is not paused on a conflict; there is nothing to resume."
|
|
344
|
+
});
|
|
209
345
|
}
|
|
210
346
|
const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
|
|
211
347
|
(options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
|
|
212
|
-
const
|
|
348
|
+
const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
|
|
349
|
+
const resumed = walkPending(
|
|
350
|
+
abortSync(session),
|
|
351
|
+
storeDirOf,
|
|
352
|
+
options.pull ?? defaultPull,
|
|
353
|
+
options.push ?? defaultPush,
|
|
354
|
+
options.commit ?? defaultCommitDirty,
|
|
355
|
+
pushableAliases
|
|
356
|
+
);
|
|
213
357
|
return finalize(resumed, options, globalRoot);
|
|
214
358
|
}
|
|
215
359
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
HOOK_SCRIPT_DESTINATIONS,
|
|
8
8
|
SKILL_DESTINATIONS,
|
|
9
9
|
fabricAgentsSnapshotPath
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XHHCRDIR.js";
|
|
11
11
|
import {
|
|
12
12
|
paint
|
|
13
13
|
} from "./chunk-BO4XIZWZ.js";
|
|
@@ -88,6 +88,20 @@ async function removeCitePolicyEvictHook(projectRoot) {
|
|
|
88
88
|
projectRoot
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
|
+
async function removeSessionEndMarkerHook(projectRoot) {
|
|
92
|
+
return removeHookScripts(
|
|
93
|
+
"hook-session-end-script",
|
|
94
|
+
HOOK_SCRIPT_DESTINATIONS.sessionEndMarker,
|
|
95
|
+
projectRoot
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
async function removePostTooluseMutationHook(projectRoot) {
|
|
99
|
+
return removeHookScripts(
|
|
100
|
+
"hook-post-tooluse-script",
|
|
101
|
+
HOOK_SCRIPT_DESTINATIONS.postTooluseMutation,
|
|
102
|
+
projectRoot
|
|
103
|
+
);
|
|
104
|
+
}
|
|
91
105
|
async function removeHookScripts(step, rels, projectRoot) {
|
|
92
106
|
const results = [];
|
|
93
107
|
for (const rel of rels) {
|
|
@@ -323,6 +337,18 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
|
|
|
323
337
|
projectRoot,
|
|
324
338
|
() => removeCitePolicyEvictHook(projectRoot)
|
|
325
339
|
);
|
|
340
|
+
await runAndCollect(
|
|
341
|
+
results,
|
|
342
|
+
"hook-session-end-script",
|
|
343
|
+
projectRoot,
|
|
344
|
+
() => removeSessionEndMarkerHook(projectRoot)
|
|
345
|
+
);
|
|
346
|
+
await runAndCollect(
|
|
347
|
+
results,
|
|
348
|
+
"hook-post-tooluse-script",
|
|
349
|
+
projectRoot,
|
|
350
|
+
() => removePostTooluseMutationHook(projectRoot)
|
|
351
|
+
);
|
|
326
352
|
await runAndCollect(
|
|
327
353
|
results,
|
|
328
354
|
"skill-connect",
|
|
@@ -3,18 +3,29 @@ import {
|
|
|
3
3
|
getProjectTranslator
|
|
4
4
|
} from "./chunk-2CY4BMTH.js";
|
|
5
5
|
import {
|
|
6
|
+
warnUnknownFlags,
|
|
6
7
|
whoami
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-5LQIHYFC.js";
|
|
9
|
+
import "./chunk-5ZUMLCD5.js";
|
|
10
|
+
import "./chunk-XCBVSGCS.js";
|
|
10
11
|
|
|
11
12
|
// src/commands/whoami.ts
|
|
12
13
|
import { defineCommand } from "citty";
|
|
13
14
|
var whoami_default = defineCommand({
|
|
14
15
|
meta: { name: "whoami", description: "Show this machine's Fabric uid and mounted stores" },
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
args: {
|
|
17
|
+
// F27: `--json` machine-readable output (was silently ignored — the command
|
|
18
|
+
// declared no args, so citty swallowed the flag and still printed text).
|
|
19
|
+
json: { type: "boolean", description: "Emit machine-readable JSON instead of text" }
|
|
20
|
+
},
|
|
21
|
+
run({ args }) {
|
|
22
|
+
warnUnknownFlags(["json"]);
|
|
17
23
|
const info = whoami();
|
|
24
|
+
if (args.json === true) {
|
|
25
|
+
console.log(JSON.stringify(info, null, 2));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const t = getProjectTranslator();
|
|
18
29
|
if (info === null) {
|
|
19
30
|
console.log(t("cli.cmd.no-global-config"));
|
|
20
31
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-cli",
|
|
3
|
-
"version": "2.2.0-rc.
|
|
3
|
+
"version": "2.2.0-rc.3",
|
|
4
4
|
"description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code, Cursor, and Codex CLI; runs doctor / knowledge maintenance.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wangzhichao <fenglimg90@gmail.com>",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"tree-sitter-javascript": "^0.25.0",
|
|
46
46
|
"tree-sitter-typescript": "^0.23.2",
|
|
47
47
|
"web-tree-sitter": "^0.26.8",
|
|
48
|
-
"@fenglimg/fabric-server": "2.2.0-rc.
|
|
49
|
-
"@fenglimg/fabric-shared": "2.2.0-rc.
|
|
48
|
+
"@fenglimg/fabric-server": "2.2.0-rc.3",
|
|
49
|
+
"@fenglimg/fabric-shared": "2.2.0-rc.3"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^22.15.0",
|