@fenglimg/fabric-cli 2.2.0-rc.1 → 2.2.0-rc.11

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 (83) hide show
  1. package/README.md +8 -5
  2. package/dist/chunk-27HK6H5Y.js +69 -0
  3. package/dist/{chunk-AOE6AYI7.js → chunk-2KBCTMID.js} +31 -8
  4. package/dist/chunk-3D7B2UAZ.js +149 -0
  5. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  6. package/dist/{plan-context-hint-FC6P3WFE.js → chunk-722JU5BP.js} +52 -12
  7. package/dist/{chunk-2R55HNVD.js → chunk-7ZDXBOOU.js} +234 -206
  8. package/dist/{doctor-YONYXDX6.js → chunk-E7HJUU34.js} +215 -52
  9. package/dist/chunk-EOT63RDH.js +36 -0
  10. package/dist/chunk-FNHDQTPC.js +16 -0
  11. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  12. package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
  13. package/dist/{chunk-WU6GAPKH.js → chunk-PTGQAZEW.js} +12 -4
  14. package/dist/chunk-QFIVFZRH.js +13 -0
  15. package/dist/chunk-QPAW6IYT.js +387 -0
  16. package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
  17. package/dist/{config-XYRBZJDU.js → config-A3LTECAY.js} +4 -3
  18. package/dist/context-UJCGYOT6.js +117 -0
  19. package/dist/doctor-MDTZWKBK.js +24 -0
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +133 -22
  22. package/dist/info-7FKBTMVO.js +139 -0
  23. package/dist/install-v2-WLEJ5XHT.js +3279 -0
  24. package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
  25. package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
  26. package/dist/plan-context-hint-5TNGH3R4.js +12 -0
  27. package/dist/{scope-explain-CDIZESP5.js → scope-explain-HLJZ2M33.js} +17 -6
  28. package/dist/status-4R3TM4FJ.js +37 -0
  29. package/dist/store-HOCORVL3.js +563 -0
  30. package/dist/{sync-UJ4BBCZJ.js → sync-DT5UJMMR.js} +197 -30
  31. package/dist/{uninstall-C3QXKOO6.js → uninstall-IFN2KYBK.js} +97 -140
  32. package/dist/whoami-ITGEFWH4.js +49 -0
  33. package/package.json +7 -5
  34. package/templates/hooks/cite-policy-evict.cjs +412 -160
  35. package/templates/hooks/configs/README.md +14 -27
  36. package/templates/hooks/configs/claude-code.json +17 -2
  37. package/templates/hooks/configs/codex-hooks.json +15 -3
  38. package/templates/hooks/fabric-hint.cjs +742 -259
  39. package/templates/hooks/knowledge-hint-broad.cjs +577 -274
  40. package/templates/hooks/knowledge-hint-narrow.cjs +113 -73
  41. package/templates/hooks/lib/banner-i18n.cjs +50 -1
  42. package/templates/hooks/lib/bindings-snapshot-reader.cjs +118 -7
  43. package/templates/hooks/lib/cite-line-parser.cjs +12 -20
  44. package/templates/hooks/lib/client-adapter.cjs +66 -7
  45. package/templates/hooks/lib/nudge-policy.cjs +117 -0
  46. package/templates/hooks/lib/state-store.cjs +60 -0
  47. package/templates/hooks/post-tooluse-mutation.cjs +386 -0
  48. package/templates/hooks/session-end-marker.cjs +140 -0
  49. package/templates/skills/fabric/SKILL.md +100 -0
  50. package/templates/skills/fabric-archive/SKILL.md +47 -24
  51. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  52. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  53. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  54. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  55. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  56. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  57. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  58. package/templates/skills/fabric-audit/SKILL.md +13 -3
  59. package/templates/skills/fabric-connect/SKILL.md +3 -3
  60. package/templates/skills/fabric-import/SKILL.md +7 -7
  61. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  62. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  63. package/templates/skills/fabric-review/SKILL.md +14 -5
  64. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  65. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  66. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  67. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  68. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  69. package/templates/skills/fabric-store/SKILL.md +1 -1
  70. package/templates/skills/fabric-sync/SKILL.md +1 -1
  71. package/templates/skills/lib/shared-policy.md +2 -2
  72. package/dist/chunk-4R2CYEA4.js +0 -116
  73. package/dist/chunk-L4Q55UC4.js +0 -52
  74. package/dist/chunk-LFIKMVY7.js +0 -27
  75. package/dist/chunk-RYAFBNES.js +0 -33
  76. package/dist/chunk-T5RPGCCM.js +0 -40
  77. package/dist/install-74ANPCCP.js +0 -2737
  78. package/dist/status-GLQWLWH6.js +0 -23
  79. package/dist/store-XB3ADT65.js +0 -144
  80. package/dist/whoami-2MLO4Y37.js +0 -36
  81. package/templates/hooks/configs/cursor-hooks.json +0 -18
  82. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
  83. package/templates/hooks/lib/summary-fallback.cjs +0 -210
@@ -1,25 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- regenerateBindingsSnapshot
4
- } from "./chunk-WU6GAPKH.js";
5
- import "./chunk-L4Q55UC4.js";
3
+ paint
4
+ } from "./chunk-NLNH64A3.js";
6
5
  import {
7
- getProjectTranslator
8
- } from "./chunk-2CY4BMTH.js";
9
- import "./chunk-LFIKMVY7.js";
6
+ regenerateBindingsSnapshot
7
+ } from "./chunk-PTGQAZEW.js";
8
+ import "./chunk-EOT63RDH.js";
9
+ import "./chunk-QFIVFZRH.js";
10
10
  import {
11
11
  loadGlobalConfig,
12
12
  resolveGlobalRoot
13
- } from "./chunk-RYAFBNES.js";
13
+ } from "./chunk-FNHDQTPC.js";
14
+ import {
15
+ getProjectTranslator
16
+ } from "./chunk-HORSMSZL.js";
14
17
 
15
18
  // src/commands/sync.ts
16
19
  import { defineCommand } from "citty";
17
20
 
18
21
  // src/sync/run-sync.ts
19
22
  import { execFileSync } from "child_process";
20
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
23
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
21
24
  import { join } from "path";
22
- import { GLOBAL_STATE_DIR, storeRelativePath } from "@fenglimg/fabric-shared";
25
+ import { GLOBAL_STATE_DIR, storeRelativePathForMount } from "@fenglimg/fabric-shared";
23
26
  import { GenericIOError } from "@fenglimg/fabric-shared/errors";
24
27
 
25
28
  // src/sync/state-machine.ts
@@ -33,6 +36,7 @@ function syncTransition(state, event) {
33
36
  case "conflict":
34
37
  if (event === "user_continue") return "synced";
35
38
  if (event === "user_abort") return "aborted";
39
+ if (event === "network_unavailable") return "offline";
36
40
  break;
37
41
  case "offline":
38
42
  if (event === "retry" || event === "rebase_clean") return "synced";
@@ -88,13 +92,31 @@ function loadSession(globalRoot) {
88
92
  if (!existsSync(path)) {
89
93
  return null;
90
94
  }
91
- return JSON.parse(readFileSync(path, "utf8"));
95
+ const raw = readFileSync(path, "utf8");
96
+ try {
97
+ return JSON.parse(raw);
98
+ } catch (error) {
99
+ const corruptedPath = `${path}.corrupted.${Date.now()}`;
100
+ try {
101
+ writeFileSync(corruptedPath, raw, "utf8");
102
+ } catch {
103
+ }
104
+ throw new GenericIOError(
105
+ `sync-session.json is corrupt (forensic copy: ${corruptedPath}). Parse error: ${error instanceof Error ? error.message : String(error)}`,
106
+ {
107
+ actionHint: `Delete ${path} to start a fresh sync (any in-progress rebase must be resolved manually with git first).`,
108
+ details: { path, corruptedPath }
109
+ }
110
+ );
111
+ }
92
112
  }
93
113
  function saveSession(globalRoot, session) {
94
114
  const path = syncSessionPath(globalRoot);
95
115
  mkdirSync(join(path, ".."), { recursive: true });
96
- writeFileSync(path, `${JSON.stringify(session, null, 2)}
116
+ const tmpPath = `${path}.${process.pid}.tmp`;
117
+ writeFileSync(tmpPath, `${JSON.stringify(session, null, 2)}
97
118
  `, "utf8");
119
+ renameSync(tmpPath, path);
98
120
  }
99
121
  function clearSession(globalRoot) {
100
122
  rmSync(syncSessionPath(globalRoot), { force: true });
@@ -127,28 +149,105 @@ function gitErrText(error, key) {
127
149
  const value = error[key];
128
150
  return typeof value === "string" || Buffer.isBuffer(value) ? String(value) : "";
129
151
  }
152
+ function defaultPush(storeDir) {
153
+ try {
154
+ execFileSync("git", ["push"], {
155
+ cwd: storeDir,
156
+ stdio: ["ignore", "pipe", "pipe"]
157
+ });
158
+ return "clean";
159
+ } catch (error) {
160
+ const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`;
161
+ if (/could not resolve host|could not read from remote|unable to access|connection|network is unreachable|timed out/i.test(
162
+ detail
163
+ )) {
164
+ return "offline";
165
+ }
166
+ const gitMessage = detail.trim().length > 0 ? detail.trim() : "unknown git error";
167
+ throw new GenericIOError(`git push failed in ${storeDir}: ${gitMessage}`, {
168
+ actionHint: "resolve the git issue above (e.g. authentication, no upstream branch, or a rejected non-fast-forward push), then re-run `fabric sync`",
169
+ details: error
170
+ });
171
+ }
172
+ }
173
+ function defaultCommitDirty(storeDir) {
174
+ try {
175
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
176
+ cwd: storeDir,
177
+ stdio: ["ignore", "ignore", "ignore"]
178
+ });
179
+ } catch {
180
+ return;
181
+ }
182
+ try {
183
+ execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
184
+ try {
185
+ execFileSync("git", ["diff", "--cached", "--quiet"], {
186
+ cwd: storeDir,
187
+ stdio: ["ignore", "ignore", "ignore"]
188
+ });
189
+ return;
190
+ } catch {
191
+ execFileSync("git", ["commit", "-m", "fabric: sync local knowledge changes"], {
192
+ cwd: storeDir,
193
+ stdio: ["ignore", "ignore", "pipe"]
194
+ });
195
+ }
196
+ } catch {
197
+ }
198
+ }
199
+ function runRebaseStep(storeDir, step) {
200
+ try {
201
+ execFileSync("git", ["rebase", `--${step}`], {
202
+ cwd: storeDir,
203
+ stdio: ["ignore", "pipe", "pipe"]
204
+ });
205
+ } catch (error) {
206
+ const detail = `${gitErrText(error, "stdout")}${gitErrText(error, "stderr")}`.trim();
207
+ const gitMessage = detail.length > 0 ? detail : "unknown git error";
208
+ throw new GenericIOError(`git rebase --${step} failed in ${storeDir}: ${gitMessage}`, {
209
+ 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",
210
+ details: error
211
+ });
212
+ }
213
+ }
130
214
  function defaultRebaseContinue(storeDir) {
131
- execFileSync("git", ["rebase", "--continue"], { cwd: storeDir, stdio: "ignore" });
215
+ runRebaseStep(storeDir, "continue");
132
216
  }
133
217
  function defaultRebaseAbort(storeDir) {
134
- execFileSync("git", ["rebase", "--abort"], { cwd: storeDir, stdio: "ignore" });
218
+ runRebaseStep(storeDir, "abort");
135
219
  }
136
220
  var OUTCOME_EVENT = {
137
221
  clean: "rebase_clean",
138
222
  conflict: "rebase_conflict",
139
223
  offline: "network_unavailable"
140
224
  };
141
- function walkPending(session, storeDirOf, pull) {
225
+ function walkPending(session, storeDirOf, pull, push, commit, pushableAliases) {
142
226
  let next = session;
143
227
  for (const store of session.stores) {
144
228
  if (store.state !== "pending") {
145
229
  continue;
146
230
  }
147
- const outcome = pull(storeDirOf(store));
148
- next = applySyncEvent(next, store.alias, OUTCOME_EVENT[outcome]);
149
- if (outcome === "conflict") {
150
- break;
231
+ const dir = storeDirOf(store);
232
+ commit(dir);
233
+ const pullOutcome = pull(dir);
234
+ if (pullOutcome !== "clean") {
235
+ next = applySyncEvent(next, store.alias, OUTCOME_EVENT[pullOutcome]);
236
+ if (pullOutcome === "conflict") {
237
+ break;
238
+ }
239
+ continue;
240
+ }
241
+ if (!pushableAliases.has(store.alias)) {
242
+ next = applySyncEvent(next, store.alias, "rebase_clean");
243
+ continue;
151
244
  }
245
+ const pushOutcome = push(dir);
246
+ next = applySyncEvent(
247
+ next,
248
+ store.alias,
249
+ pushOutcome === "clean" ? "rebase_clean" : "network_unavailable"
250
+ );
152
251
  }
153
252
  return next;
154
253
  }
@@ -178,38 +277,99 @@ function runStartSync(options) {
178
277
  const session = planSync(
179
278
  syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
180
279
  );
181
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
182
- const walked = walkPending(session, storeDirOf, options.pull ?? defaultPull);
280
+ const storeDirOf = makeStoreDirResolver(globalRoot, config.stores);
281
+ const pushableAliases = pushableAliasesOf(config);
282
+ const walked = walkPending(
283
+ session,
284
+ storeDirOf,
285
+ options.pull ?? defaultPull,
286
+ options.push ?? defaultPush,
287
+ options.commit ?? defaultCommitDirty,
288
+ pushableAliases
289
+ );
183
290
  return finalize(walked, options, globalRoot);
184
291
  }
292
+ function pushableAliasesOf(config) {
293
+ return new Set(
294
+ config.stores.filter((store) => store.remote !== void 0 && (store.writable ?? true)).map((store) => store.alias)
295
+ );
296
+ }
297
+ function makeStoreDirResolver(globalRoot, stores) {
298
+ return (status) => join(
299
+ globalRoot,
300
+ storeRelativePathForMount(
301
+ stores.find((store) => store.store_uuid === status.store_uuid) ?? {
302
+ store_uuid: status.store_uuid
303
+ }
304
+ )
305
+ );
306
+ }
185
307
  function runContinueSync(options) {
186
308
  const globalRoot = options.globalRoot ?? resolveGlobalRoot();
187
309
  const session = loadSession(globalRoot);
188
310
  if (session === null) {
189
- throw new Error(NO_SESSION);
311
+ throw new GenericIOError(NO_SESSION, {
312
+ actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
313
+ });
190
314
  }
191
315
  const conflicted = session.stores.find((store) => store.state === "conflict");
192
316
  if (conflicted === void 0) {
193
- throw new Error(NO_CONFLICT);
317
+ throw new GenericIOError(NO_CONFLICT, {
318
+ actionHint: "The sync is not paused on a conflict; there is nothing to resume."
319
+ });
194
320
  }
195
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
321
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
322
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
196
323
  (options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
197
- const resumed = walkPending(continueSync(session), storeDirOf, options.pull ?? defaultPull);
324
+ const pushableAliases = pushableAliasesOf(resumeConfig);
325
+ const push = options.push ?? defaultPush;
326
+ let advanced;
327
+ if (pushableAliases.has(conflicted.alias)) {
328
+ const pushOutcome = push(storeDirOf(conflicted));
329
+ advanced = applySyncEvent(
330
+ session,
331
+ conflicted.alias,
332
+ pushOutcome === "clean" ? "user_continue" : "network_unavailable"
333
+ );
334
+ } else {
335
+ advanced = continueSync(session);
336
+ }
337
+ const resumed = walkPending(
338
+ advanced,
339
+ storeDirOf,
340
+ options.pull ?? defaultPull,
341
+ push,
342
+ options.commit ?? defaultCommitDirty,
343
+ pushableAliases
344
+ );
198
345
  return finalize(resumed, options, globalRoot);
199
346
  }
200
347
  function runAbortSync(options) {
201
348
  const globalRoot = options.globalRoot ?? resolveGlobalRoot();
202
349
  const session = loadSession(globalRoot);
203
350
  if (session === null) {
204
- throw new Error(NO_SESSION);
351
+ throw new GenericIOError(NO_SESSION, {
352
+ actionHint: "Run `fabric sync` to start a sync before `--continue`/`--abort`."
353
+ });
205
354
  }
206
355
  const conflicted = session.stores.find((store) => store.state === "conflict");
207
356
  if (conflicted === void 0) {
208
- throw new Error(NO_CONFLICT);
357
+ throw new GenericIOError(NO_CONFLICT, {
358
+ actionHint: "The sync is not paused on a conflict; there is nothing to resume."
359
+ });
209
360
  }
210
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
361
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
362
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
211
363
  (options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
212
- const resumed = walkPending(abortSync(session), storeDirOf, options.pull ?? defaultPull);
364
+ const pushableAliases = pushableAliasesOf(resumeConfig);
365
+ const resumed = walkPending(
366
+ abortSync(session),
367
+ storeDirOf,
368
+ options.pull ?? defaultPull,
369
+ options.push ?? defaultPush,
370
+ options.commit ?? defaultCommitDirty,
371
+ pushableAliases
372
+ );
213
373
  return finalize(resumed, options, globalRoot);
214
374
  }
215
375
 
@@ -226,7 +386,7 @@ function report(result, projectRoot) {
226
386
  console.log(t("cli.sync.paused"));
227
387
  }
228
388
  }
229
- var sync_default = defineCommand({
389
+ var syncCommand = defineCommand({
230
390
  meta: { name: "sync", description: "Pull --rebase + push every mounted store; resume conflicts" },
231
391
  args: {
232
392
  continue: { type: "boolean", description: "Resume after resolving a rebase conflict" },
@@ -234,6 +394,11 @@ var sync_default = defineCommand({
234
394
  },
235
395
  run({ args }) {
236
396
  const projectRoot = process.cwd();
397
+ if (args.continue === true && args.abort === true) {
398
+ console.error(paint.error("fabric sync: --continue and --abort cannot be used together"));
399
+ process.exitCode = 1;
400
+ return;
401
+ }
237
402
  const options = { projectRoot, now: (/* @__PURE__ */ new Date()).toISOString() };
238
403
  if (args.continue === true) {
239
404
  report(runContinueSync(options), projectRoot);
@@ -246,6 +411,8 @@ var sync_default = defineCommand({
246
411
  report(runStartSync(options), projectRoot);
247
412
  }
248
413
  });
414
+ var sync_default = syncCommand;
249
415
  export {
250
- sync_default as default
416
+ sync_default as default,
417
+ syncCommand
251
418
  };