@fenglimg/fabric-cli 2.2.0-rc.3 → 2.2.0-rc.8

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 (75) hide show
  1. package/README.md +8 -5
  2. package/dist/{chunk-5LQIHYFC.js → chunk-27HK6H5Y.js} +10 -5
  3. package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
  4. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  5. package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
  6. package/dist/chunk-FEOPLBGA.js +150 -0
  7. package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
  8. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  9. package/dist/{doctor-J4O3X54I.js → chunk-JTHWLUD3.js} +103 -51
  10. package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
  11. package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
  12. package/dist/chunk-QFIVFZRH.js +13 -0
  13. package/dist/chunk-QPAW6IYT.js +387 -0
  14. package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
  15. package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
  16. package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
  17. package/dist/context-7NUKXDB6.js +117 -0
  18. package/dist/doctor-REZDNH4A.js +24 -0
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +131 -21
  21. package/dist/info-7FKBTMVO.js +139 -0
  22. package/dist/install-v2-2COC3DO3.js +3277 -0
  23. package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
  24. package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
  25. package/dist/plan-context-hint-G75R4P4J.js +12 -0
  26. package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
  27. package/dist/{status-PANEGKU2.js → status-4R3TM4FJ.js} +8 -5
  28. package/dist/store-HOCORVL3.js +563 -0
  29. package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
  30. package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
  31. package/dist/{whoami-66YKY5DZ.js → whoami-ITGEFWH4.js} +9 -7
  32. package/package.json +7 -5
  33. package/templates/hooks/cite-policy-evict.cjs +5 -5
  34. package/templates/hooks/configs/README.md +14 -27
  35. package/templates/hooks/configs/claude-code.json +1 -1
  36. package/templates/hooks/configs/codex-hooks.json +3 -3
  37. package/templates/hooks/fabric-hint.cjs +301 -161
  38. package/templates/hooks/knowledge-hint-broad.cjs +426 -207
  39. package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
  40. package/templates/hooks/lib/banner-i18n.cjs +31 -0
  41. package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -7
  42. package/templates/hooks/lib/cite-line-parser.cjs +12 -20
  43. package/templates/hooks/lib/client-adapter.cjs +66 -7
  44. package/templates/hooks/lib/nudge-policy.cjs +117 -0
  45. package/templates/hooks/lib/state-store.cjs +60 -0
  46. package/templates/hooks/lib/summary-fallback.cjs +82 -19
  47. package/templates/hooks/post-tooluse-mutation.cjs +112 -11
  48. package/templates/skills/fabric/SKILL.md +94 -0
  49. package/templates/skills/fabric-archive/SKILL.md +29 -26
  50. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  51. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  52. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  53. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  54. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  55. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  56. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  57. package/templates/skills/fabric-audit/SKILL.md +13 -3
  58. package/templates/skills/fabric-connect/SKILL.md +3 -3
  59. package/templates/skills/fabric-import/SKILL.md +7 -7
  60. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  61. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  62. package/templates/skills/fabric-review/SKILL.md +5 -5
  63. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  64. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  65. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  66. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  67. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  68. package/templates/skills/fabric-store/SKILL.md +1 -1
  69. package/templates/skills/fabric-sync/SKILL.md +1 -1
  70. package/templates/skills/lib/shared-policy.md +2 -2
  71. package/dist/chunk-5ZUMLCD5.js +0 -248
  72. package/dist/install-BULNDUIM.js +0 -2816
  73. package/dist/store-66NK2FTQ.js +0 -443
  74. package/templates/hooks/configs/cursor-hooks.json +0 -30
  75. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
@@ -0,0 +1,563 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ regenerateBindingsSnapshot
4
+ } from "./chunk-PTGQAZEW.js";
5
+ import "./chunk-EOT63RDH.js";
6
+ import {
7
+ assertStoreMountable,
8
+ resolveStoreDir,
9
+ storeAdd,
10
+ storeBind,
11
+ storeCreate,
12
+ storeExplain,
13
+ storeGitRemote,
14
+ storeList,
15
+ storeProjectCreate,
16
+ storeProjectList,
17
+ storeRemove,
18
+ storeSetWriteRoute,
19
+ storeSwitchWrite
20
+ } from "./chunk-QPAW6IYT.js";
21
+ import {
22
+ loadProjectConfig
23
+ } from "./chunk-QFIVFZRH.js";
24
+ import "./chunk-FNHDQTPC.js";
25
+ import {
26
+ getProjectTranslator
27
+ } from "./chunk-HORSMSZL.js";
28
+
29
+ // src/commands/store.ts
30
+ import { defineCommand } from "citty";
31
+ import { join as join3 } from "path";
32
+
33
+ // src/store/scope-backfill.ts
34
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs";
35
+ import { join } from "path";
36
+ import {
37
+ STORE_KNOWLEDGE_TYPE_DIRS,
38
+ isPersonalScope,
39
+ parseKnowledgeId
40
+ } from "@fenglimg/fabric-shared";
41
+ var FRONTMATTER_RE = /^(?:)?---\r?\n([\s\S]*?)\r?\n---/u;
42
+ function readKey(block, key) {
43
+ const m = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "mu").exec(block);
44
+ return m?.[1];
45
+ }
46
+ function setKey(block, key, value, anchorKey) {
47
+ const lines = block.split(/\r?\n/u);
48
+ const idx = lines.findIndex((l) => new RegExp(`^${key}:`).test(l));
49
+ if (idx !== -1) {
50
+ lines[idx] = `${key}: ${value}`;
51
+ return lines.join("\n");
52
+ }
53
+ const anchorIdx = lines.findIndex((l) => new RegExp(`^${anchorKey}:`).test(l));
54
+ const at = anchorIdx === -1 ? lines.length - 1 : anchorIdx + 1;
55
+ lines.splice(at, 0, `${key}: ${value}`);
56
+ return lines.join("\n");
57
+ }
58
+ function backfillEntryContent(content, visibilityStore) {
59
+ const match = FRONTMATTER_RE.exec(content);
60
+ if (match === null) {
61
+ return null;
62
+ }
63
+ const block = match[1] ?? "";
64
+ const id = readKey(block, "id") ?? null;
65
+ const parsed = id === null ? null : parseKnowledgeId(id);
66
+ const declaredLayer = readKey(block, "layer");
67
+ const layer = parsed?.layer ?? (declaredLayer === "personal" ? "personal" : "team");
68
+ const semanticScope = layer === "personal" ? "personal" : "team";
69
+ const visibility = isPersonalScope(semanticScope) ? "personal" : visibilityStore;
70
+ const changed = [];
71
+ let newBlock = block;
72
+ if (declaredLayer !== layer) {
73
+ newBlock = setKey(newBlock, "layer", layer, "maturity");
74
+ changed.push("layer");
75
+ }
76
+ if (readKey(block, "semantic_scope") !== semanticScope) {
77
+ newBlock = setKey(newBlock, "semantic_scope", semanticScope, "layer");
78
+ changed.push("semantic_scope");
79
+ }
80
+ if (readKey(block, "visibility_store") !== visibility) {
81
+ newBlock = setKey(newBlock, "visibility_store", `"${visibility}"`, "semantic_scope");
82
+ changed.push("visibility_store");
83
+ }
84
+ if (changed.length === 0) {
85
+ return { content, change: { file: "", id, changed } };
86
+ }
87
+ const before = content.slice(0, match.index);
88
+ const after = content.slice(match.index + match[0].length);
89
+ return {
90
+ content: `${before}---
91
+ ${newBlock}
92
+ ---${after}`,
93
+ change: { file: "", id, changed }
94
+ };
95
+ }
96
+ function backfillKnowledgeDir(knowledgeDir, options) {
97
+ const report = { dryRun: options.dryRun === true, unchanged: 0, changes: [], skipped: [] };
98
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
99
+ const dir = join(knowledgeDir, type);
100
+ if (!existsSync(dir)) {
101
+ continue;
102
+ }
103
+ for (const name of readdirSync(dir).filter((n) => n.endsWith(".md")).sort()) {
104
+ const file = join(dir, name);
105
+ const result = backfillEntryContent(readFileSync(file, "utf8"), options.visibilityStore);
106
+ if (result === null) {
107
+ report.skipped.push(file);
108
+ continue;
109
+ }
110
+ if (result.change.changed.length === 0) {
111
+ report.unchanged += 1;
112
+ continue;
113
+ }
114
+ result.change.file = file;
115
+ report.changes.push(result.change);
116
+ if (options.dryRun !== true) {
117
+ writeFileSync(file, result.content, "utf8");
118
+ }
119
+ }
120
+ }
121
+ return report;
122
+ }
123
+
124
+ // src/store/store-rescope.ts
125
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
126
+ import { join as join2 } from "path";
127
+ import {
128
+ STORE_KNOWLEDGE_TYPE_DIRS as STORE_KNOWLEDGE_TYPE_DIRS2,
129
+ STORE_LAYOUT,
130
+ isPersonalScope as isPersonalScope2,
131
+ readStoreProjects,
132
+ scopeCoordinateSchema,
133
+ scopeRoot
134
+ } from "@fenglimg/fabric-shared";
135
+ async function validateToScope(toScope, storeDir, storeVisibility) {
136
+ if (!scopeCoordinateSchema.safeParse(toScope).success) {
137
+ return `invalid scope coordinate '${toScope}'`;
138
+ }
139
+ if (isPersonalScope2(toScope) && storeVisibility === "shared") {
140
+ return "refusing personal scope in a shared store (R5#3 privacy boundary)";
141
+ }
142
+ if (scopeRoot(toScope) === "project") {
143
+ const projectId = toScope.split(":")[1] ?? "";
144
+ if (projectId.length === 0 || !(await readStoreProjects(storeDir)).some((p) => p.id === projectId)) {
145
+ return `project '${projectId}' is not registered in this store (run \`fabric store project add ${projectId}\` first)`;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ function matchesSelection(options, id, currentScope) {
151
+ if (options.id !== void 0 && id !== options.id) {
152
+ return false;
153
+ }
154
+ if (options.fromScope !== void 0 && currentScope !== options.fromScope) {
155
+ return false;
156
+ }
157
+ if (options.fromScopeRoot !== void 0 && (currentScope === void 0 || scopeRoot(currentScope) !== options.fromScopeRoot)) {
158
+ return false;
159
+ }
160
+ return true;
161
+ }
162
+ async function rescopeStore(storeDir, toScope, options) {
163
+ const report = {
164
+ dryRun: options.dryRun === true,
165
+ toScope,
166
+ changes: [],
167
+ refusals: [],
168
+ unchanged: 0,
169
+ skipped: []
170
+ };
171
+ const toScopeError = await validateToScope(toScope, storeDir, options.storeVisibility);
172
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS2) {
173
+ const dir = join2(storeDir, STORE_LAYOUT.knowledgeDir, type);
174
+ if (!existsSync2(dir)) {
175
+ continue;
176
+ }
177
+ for (const name of readdirSync2(dir).filter((n) => n.endsWith(".md")).sort()) {
178
+ const file = join2(dir, name);
179
+ const content = readFileSync2(file, "utf8");
180
+ const match = FRONTMATTER_RE.exec(content);
181
+ if (match === null) {
182
+ report.skipped.push(file);
183
+ continue;
184
+ }
185
+ const block = match[1] ?? "";
186
+ const id = readKey(block, "id") ?? null;
187
+ const currentScope = readKey(block, "semantic_scope");
188
+ if (!matchesSelection(options, id, currentScope)) {
189
+ continue;
190
+ }
191
+ if (currentScope === toScope) {
192
+ report.unchanged += 1;
193
+ continue;
194
+ }
195
+ if (toScopeError !== null) {
196
+ report.refusals.push({ file, id, reason: toScopeError });
197
+ continue;
198
+ }
199
+ const newBlock = setKey(block, "semantic_scope", toScope, "layer");
200
+ const before = content.slice(0, match.index);
201
+ const after = content.slice(match.index + match[0].length);
202
+ report.changes.push({ file, id, fromScope: currentScope, toScope });
203
+ if (options.dryRun !== true) {
204
+ writeFileSync2(file, `${before}---
205
+ ${newBlock}
206
+ ---${after}`, "utf8");
207
+ }
208
+ }
209
+ }
210
+ return report;
211
+ }
212
+ async function promoteProjectToTeam(storeDir, options) {
213
+ const selection = options.projectId !== void 0 ? { fromScope: `project:${options.projectId}`, storeVisibility: options.storeVisibility, dryRun: options.dryRun } : { fromScopeRoot: "project", storeVisibility: options.storeVisibility, dryRun: options.dryRun };
214
+ return rescopeStore(storeDir, "team", selection);
215
+ }
216
+
217
+ // src/commands/store.ts
218
+ import {
219
+ STORE_LAYOUT as STORE_LAYOUT2,
220
+ loadGlobalConfig,
221
+ resolveGlobalRoot
222
+ } from "@fenglimg/fabric-shared";
223
+ var listCommand = defineCommand({
224
+ meta: { name: "list", description: "List mounted knowledge stores" },
225
+ run() {
226
+ const t = getProjectTranslator();
227
+ const stores = storeList();
228
+ if (stores.length === 0) {
229
+ console.log(t("cli.store.none-mounted"));
230
+ return;
231
+ }
232
+ const localOnly = t("cli.shared.local-only");
233
+ for (const store of stores) {
234
+ const realRemote = storeGitRemote(store.alias);
235
+ console.log(
236
+ `${store.alias} ${store.mount_name ?? store.store_uuid} ${store.store_uuid} ${realRemote ?? localOnly}`
237
+ );
238
+ }
239
+ }
240
+ });
241
+ var addCommand = defineCommand({
242
+ meta: { name: "add", description: "Mount a knowledge store into the global registry" },
243
+ args: {
244
+ uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
245
+ alias: { type: "string", required: true, description: "Local alias for this store" },
246
+ "mount-name": { type: "string", description: "Stable local directory under ~/.fabric/stores/" },
247
+ remote: { type: "string", description: "Git remote locator (omit for local-only)" }
248
+ },
249
+ async run({ args }) {
250
+ assertStoreMountable(args.uuid, void 0, args["mount-name"]);
251
+ const store = args.remote === void 0 ? {
252
+ store_uuid: args.uuid,
253
+ alias: args.alias,
254
+ ...args["mount-name"] === void 0 ? {} : { mount_name: args["mount-name"] }
255
+ } : {
256
+ store_uuid: args.uuid,
257
+ alias: args.alias,
258
+ ...args["mount-name"] === void 0 ? {} : { mount_name: args["mount-name"] },
259
+ remote: args.remote
260
+ };
261
+ const next = storeAdd(store);
262
+ const t = getProjectTranslator();
263
+ console.log(
264
+ t("cli.store.mounted", {
265
+ alias: args.alias,
266
+ count: String(next.stores.length)
267
+ })
268
+ );
269
+ }
270
+ });
271
+ var createCommand = defineCommand({
272
+ meta: { name: "create", description: "Create a brand-new local knowledge store and mount it" },
273
+ args: {
274
+ alias: { type: "string", required: true, description: "Local alias for the new store" },
275
+ "mount-name": { type: "string", description: "Stable local directory under ~/.fabric/stores/" },
276
+ remote: { type: "string", description: "Git remote to associate (push target; optional)" }
277
+ },
278
+ async run({ args }) {
279
+ const result = await storeCreate(args.alias, (/* @__PURE__ */ new Date()).toISOString(), {
280
+ ...args["mount-name"] === void 0 ? {} : { mountName: args["mount-name"] },
281
+ ...args.remote === void 0 ? {} : { remote: args.remote }
282
+ });
283
+ const t = getProjectTranslator();
284
+ console.log(
285
+ t("cli.store.created", { alias: args.alias, uuid: result.store_uuid, dir: result.storeDir }) + (args.remote === void 0 ? `
286
+ ${t("cli.store.created-local-hint")}` : "")
287
+ );
288
+ }
289
+ });
290
+ var removeCommand = defineCommand({
291
+ meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
292
+ args: {
293
+ alias: { type: "positional", required: true, description: "Alias to detach" }
294
+ },
295
+ async run({ args }) {
296
+ const { detached } = storeRemove(args.alias);
297
+ const t = getProjectTranslator();
298
+ if (detached === null) {
299
+ process.exitCode = 1;
300
+ }
301
+ console.log(
302
+ detached === null ? t("cli.store.no-alias", { alias: args.alias }) : t("cli.store.detached", { alias: args.alias })
303
+ );
304
+ }
305
+ });
306
+ var explainCommand = defineCommand({
307
+ meta: { name: "explain", description: "Explain how a store alias resolves" },
308
+ args: {
309
+ alias: { type: "positional", required: true, description: "Alias to explain" }
310
+ },
311
+ run({ args }) {
312
+ const explanation = storeExplain(args.alias);
313
+ if (explanation === null) {
314
+ process.exitCode = 1;
315
+ }
316
+ console.log(
317
+ explanation === null ? getProjectTranslator()("cli.store.no-alias", { alias: args.alias }) : JSON.stringify(explanation, null, 2)
318
+ );
319
+ }
320
+ });
321
+ var bindCommand = defineCommand({
322
+ meta: { name: "bind", description: "Declare a required store on this project's config" },
323
+ args: {
324
+ id: { type: "positional", required: true, description: "Store alias/UUID to require" },
325
+ remote: { type: "string", description: "Suggested remote for clone onboarding" },
326
+ project: {
327
+ type: "string",
328
+ description: "Bind this repo to a project:<id> in the store (must already exist)"
329
+ }
330
+ },
331
+ async run({ args }) {
332
+ const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
333
+ const projectRoot = process.cwd();
334
+ const next = await storeBind(
335
+ projectRoot,
336
+ entry,
337
+ args.project === void 0 ? {} : { project: args.project }
338
+ );
339
+ console.log(
340
+ getProjectTranslator(projectRoot)("cli.store.bound", {
341
+ id: args.id,
342
+ count: String(next.required_stores?.length ?? 0)
343
+ })
344
+ );
345
+ regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
346
+ }
347
+ });
348
+ var switchWriteCommand = defineCommand({
349
+ meta: { name: "switch-write", description: "Set the default write store for non-personal scopes" },
350
+ args: {
351
+ alias: { type: "positional", required: true, description: "Alias of the store to write to" }
352
+ },
353
+ async run({ args }) {
354
+ const projectRoot = process.cwd();
355
+ storeSwitchWrite(projectRoot, args.alias);
356
+ regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
357
+ console.log(getProjectTranslator(projectRoot)("cli.store.switch-write", { alias: args.alias }));
358
+ }
359
+ });
360
+ var routeWriteCommand = defineCommand({
361
+ meta: { name: "route-write", description: "Route a semantic scope to a writable shared store" },
362
+ args: {
363
+ scope: { type: "positional", required: true, description: "Semantic scope, e.g. team or project:fabric-v2" },
364
+ alias: { type: "positional", required: true, description: "Alias of the shared store to write to" }
365
+ },
366
+ run({ args }) {
367
+ const projectRoot = process.cwd();
368
+ storeSetWriteRoute(projectRoot, args.scope, args.alias);
369
+ console.log(`write route: ${args.scope} -> ${args.alias}`);
370
+ }
371
+ });
372
+ var projectListCommand = defineCommand({
373
+ meta: { name: "list", description: "List projects registered in a store" },
374
+ args: {
375
+ store: { type: "positional", required: true, description: "Store alias/UUID" }
376
+ },
377
+ async run({ args }) {
378
+ const projects = await storeProjectList(args.store);
379
+ if (projects.length === 0) {
380
+ console.log(`store '${args.store}' has no registered projects.`);
381
+ return;
382
+ }
383
+ for (const p of projects) {
384
+ console.log(`${p.id}${p.name === void 0 ? "" : ` ${p.name}`}`);
385
+ }
386
+ }
387
+ });
388
+ var projectCreateCommand = defineCommand({
389
+ meta: { name: "create", description: "Register a new project in a store" },
390
+ args: {
391
+ store: { type: "positional", required: true, description: "Store alias/UUID" },
392
+ id: { type: "positional", required: true, description: "Project id (single [a-z0-9_-] segment)" },
393
+ name: { type: "string", description: "Optional human-facing label" }
394
+ },
395
+ async run({ args }) {
396
+ const project = await storeProjectCreate(args.store, args.id, (/* @__PURE__ */ new Date()).toISOString(), {
397
+ ...args.name === void 0 ? {} : { name: args.name }
398
+ });
399
+ console.log(`registered project '${project.id}' in store '${args.store}'.`);
400
+ }
401
+ });
402
+ var projectCommand = defineCommand({
403
+ meta: { name: "project", description: "Manage the projects a store serves" },
404
+ subCommands: {
405
+ list: projectListCommand,
406
+ create: projectCreateCommand
407
+ }
408
+ });
409
+ var backfillScopeCommand = defineCommand({
410
+ meta: {
411
+ name: "backfill-scope",
412
+ description: "Backfill semantic_scope + visibility_store on existing knowledge (repairs dirty layer)"
413
+ },
414
+ args: {
415
+ store: { type: "string", description: "Backfill a mounted store's knowledge" },
416
+ "dry-run": { type: "boolean", description: "Preview changes without writing" }
417
+ },
418
+ run({ args }) {
419
+ const dryRun = args["dry-run"] === true;
420
+ let knowledgeDir;
421
+ let visibilityStore;
422
+ const selectedStore = typeof args.store === "string" && args.store.length > 0 ? args.store : loadProjectConfig(process.cwd())?.active_write_store;
423
+ if (typeof selectedStore !== "string" || selectedStore.length === 0) {
424
+ console.error(
425
+ "no store selected for scope backfill; pass --store <alias> or run `fabric store switch-write <alias>`"
426
+ );
427
+ process.exitCode = 1;
428
+ return;
429
+ }
430
+ {
431
+ const storeDir = resolveStoreDir(selectedStore);
432
+ if (storeDir === null) {
433
+ console.error(`no mounted store '${selectedStore}'`);
434
+ process.exitCode = 1;
435
+ return;
436
+ }
437
+ knowledgeDir = join3(storeDir, STORE_LAYOUT2.knowledgeDir);
438
+ visibilityStore = selectedStore;
439
+ }
440
+ const report = backfillKnowledgeDir(knowledgeDir, { visibilityStore, dryRun });
441
+ if (report.changes.length === 0) {
442
+ console.log(`scope backfill: nothing to do (${report.unchanged} already consistent).`);
443
+ return;
444
+ }
445
+ console.log(
446
+ `${dryRun ? "[dry-run] " : ""}scope backfill: ${report.changes.length} entr${report.changes.length === 1 ? "y" : "ies"} updated, ${report.unchanged} unchanged.`
447
+ );
448
+ for (const c of report.changes) {
449
+ console.log(` ${c.id ?? "(no id)"} [${c.changed.join(", ")}]`);
450
+ }
451
+ const scopeAssigned = report.changes.filter((c) => c.changed.includes("semantic_scope")).length;
452
+ if (scopeAssigned > 0) {
453
+ console.error(
454
+ `${dryRun ? "[dry-run] " : ""}note: ${scopeAssigned} entr${scopeAssigned === 1 ? "y" : "ies"} defaulted to semantic_scope: team. Demote project-specific ones with \`fabric store re-scope <store> --to project:<id> --id <id>\`.`
455
+ );
456
+ }
457
+ }
458
+ });
459
+ function resolveStoreDirAndVisibility(aliasOrUuid) {
460
+ const dir = resolveStoreDir(aliasOrUuid);
461
+ if (dir === null) {
462
+ return null;
463
+ }
464
+ const store = loadGlobalConfig(resolveGlobalRoot())?.stores.find(
465
+ (s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid
466
+ );
467
+ return { dir, visibility: store?.personal === true ? "personal" : "shared" };
468
+ }
469
+ function printRescopeReport(report) {
470
+ const prefix = report.dryRun ? "[dry-run] " : "";
471
+ if (report.changes.length === 0 && report.refusals.length === 0) {
472
+ console.log(`re-scope: nothing to do (${report.unchanged} already at '${report.toScope}').`);
473
+ } else if (report.changes.length > 0) {
474
+ console.log(
475
+ `${prefix}re-scope \u2192 ${report.toScope}: ${report.changes.length} entr${report.changes.length === 1 ? "y" : "ies"} updated, ${report.unchanged} unchanged.`
476
+ );
477
+ for (const c of report.changes) {
478
+ console.log(` ${c.id ?? "(no id)"} ${c.fromScope ?? "(none)"} \u2192 ${c.toScope}`);
479
+ }
480
+ }
481
+ if (report.refusals.length > 0) {
482
+ console.error(`${report.refusals.length} entr${report.refusals.length === 1 ? "y" : "ies"} refused:`);
483
+ for (const r of report.refusals) {
484
+ console.error(` ${r.id ?? "(no id)"}: ${r.reason}`);
485
+ }
486
+ process.exitCode = 1;
487
+ }
488
+ }
489
+ var rescopeCommand = defineCommand({
490
+ meta: {
491
+ name: "re-scope",
492
+ description: "Rewrite knowledge entries' semantic_scope coordinate in a store"
493
+ },
494
+ args: {
495
+ store: { type: "positional", required: true, description: "Target store alias or uuid" },
496
+ to: { type: "string", required: true, description: "New semantic_scope (e.g. team, project:alpha)" },
497
+ id: { type: "string", description: "Only the entry with this stable_id" },
498
+ from: { type: "string", description: "Only entries currently at this semantic_scope" },
499
+ "dry-run": { type: "boolean", description: "Preview changes without writing" }
500
+ },
501
+ async run({ args }) {
502
+ const resolved = resolveStoreDirAndVisibility(args.store);
503
+ if (resolved === null) {
504
+ console.error(`no mounted store '${args.store}'`);
505
+ process.exitCode = 1;
506
+ return;
507
+ }
508
+ printRescopeReport(
509
+ await rescopeStore(resolved.dir, args.to, {
510
+ id: args.id,
511
+ fromScope: args.from,
512
+ storeVisibility: resolved.visibility,
513
+ dryRun: args["dry-run"] === true
514
+ })
515
+ );
516
+ }
517
+ });
518
+ var promoteCommand = defineCommand({
519
+ meta: {
520
+ name: "promote",
521
+ description: "Promote project-scoped entries to team scope (project absorption)"
522
+ },
523
+ args: {
524
+ store: { type: "positional", required: true, description: "Target store alias or uuid" },
525
+ project: { type: "string", description: "Only this project's entries (default: all project:*)" },
526
+ "dry-run": { type: "boolean", description: "Preview changes without writing" }
527
+ },
528
+ async run({ args }) {
529
+ const resolved = resolveStoreDirAndVisibility(args.store);
530
+ if (resolved === null) {
531
+ console.error(`no mounted store '${args.store}'`);
532
+ process.exitCode = 1;
533
+ return;
534
+ }
535
+ printRescopeReport(
536
+ await promoteProjectToTeam(resolved.dir, {
537
+ projectId: args.project,
538
+ storeVisibility: resolved.visibility,
539
+ dryRun: args["dry-run"] === true
540
+ })
541
+ );
542
+ }
543
+ });
544
+ var store_default = defineCommand({
545
+ meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
546
+ subCommands: {
547
+ list: listCommand,
548
+ create: createCommand,
549
+ add: addCommand,
550
+ remove: removeCommand,
551
+ explain: explainCommand,
552
+ bind: bindCommand,
553
+ "switch-write": switchWriteCommand,
554
+ "route-write": routeWriteCommand,
555
+ "backfill-scope": backfillScopeCommand,
556
+ "re-scope": rescopeCommand,
557
+ promote: promoteCommand,
558
+ project: projectCommand
559
+ }
560
+ });
561
+ export {
562
+ store_default as default
563
+ };
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ paint
4
+ } from "./chunk-NLNH64A3.js";
2
5
  import {
3
6
  regenerateBindingsSnapshot
4
- } from "./chunk-H3FE6VIK.js";
7
+ } from "./chunk-PTGQAZEW.js";
5
8
  import "./chunk-EOT63RDH.js";
6
- import {
7
- getProjectTranslator
8
- } from "./chunk-2CY4BMTH.js";
9
+ import "./chunk-QFIVFZRH.js";
9
10
  import {
10
11
  loadGlobalConfig,
11
12
  resolveGlobalRoot
12
- } from "./chunk-XCBVSGCS.js";
13
+ } from "./chunk-FNHDQTPC.js";
14
+ import {
15
+ getProjectTranslator
16
+ } from "./chunk-HORSMSZL.js";
13
17
 
14
18
  // src/commands/sync.ts
15
19
  import { defineCommand } from "citty";
@@ -18,7 +22,7 @@ import { defineCommand } from "citty";
18
22
  import { execFileSync } from "child_process";
19
23
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
20
24
  import { join } from "path";
21
- import { GLOBAL_STATE_DIR, storeRelativePath } from "@fenglimg/fabric-shared";
25
+ import { GLOBAL_STATE_DIR, storeRelativePathForMount } from "@fenglimg/fabric-shared";
22
26
  import { GenericIOError } from "@fenglimg/fabric-shared/errors";
23
27
 
24
28
  // src/sync/state-machine.ts
@@ -273,7 +277,7 @@ function runStartSync(options) {
273
277
  const session = planSync(
274
278
  syncable.map((store) => ({ alias: store.alias, store_uuid: store.store_uuid }))
275
279
  );
276
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
280
+ const storeDirOf = makeStoreDirResolver(globalRoot, config.stores);
277
281
  const pushableAliases = pushableAliasesOf(config);
278
282
  const walked = walkPending(
279
283
  session,
@@ -290,6 +294,16 @@ function pushableAliasesOf(config) {
290
294
  config.stores.filter((store) => store.remote !== void 0 && (store.writable ?? true)).map((store) => store.alias)
291
295
  );
292
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
+ }
293
307
  function runContinueSync(options) {
294
308
  const globalRoot = options.globalRoot ?? resolveGlobalRoot();
295
309
  const session = loadSession(globalRoot);
@@ -304,9 +318,10 @@ function runContinueSync(options) {
304
318
  actionHint: "The sync is not paused on a conflict; there is nothing to resume."
305
319
  });
306
320
  }
307
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
321
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
322
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
308
323
  (options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
309
- const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
324
+ const pushableAliases = pushableAliasesOf(resumeConfig);
310
325
  const push = options.push ?? defaultPush;
311
326
  let advanced;
312
327
  if (pushableAliases.has(conflicted.alias)) {
@@ -343,9 +358,10 @@ function runAbortSync(options) {
343
358
  actionHint: "The sync is not paused on a conflict; there is nothing to resume."
344
359
  });
345
360
  }
346
- const storeDirOf = (status) => join(globalRoot, storeRelativePath(status.store_uuid));
361
+ const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
362
+ const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
347
363
  (options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
348
- const pushableAliases = pushableAliasesOf(loadGlobalConfig(globalRoot) ?? { stores: [] });
364
+ const pushableAliases = pushableAliasesOf(resumeConfig);
349
365
  const resumed = walkPending(
350
366
  abortSync(session),
351
367
  storeDirOf,
@@ -370,7 +386,7 @@ function report(result, projectRoot) {
370
386
  console.log(t("cli.sync.paused"));
371
387
  }
372
388
  }
373
- var sync_default = defineCommand({
389
+ var syncCommand = defineCommand({
374
390
  meta: { name: "sync", description: "Pull --rebase + push every mounted store; resume conflicts" },
375
391
  args: {
376
392
  continue: { type: "boolean", description: "Resume after resolving a rebase conflict" },
@@ -378,6 +394,11 @@ var sync_default = defineCommand({
378
394
  },
379
395
  run({ args }) {
380
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
+ }
381
402
  const options = { projectRoot, now: (/* @__PURE__ */ new Date()).toISOString() };
382
403
  if (args.continue === true) {
383
404
  report(runContinueSync(options), projectRoot);
@@ -390,6 +411,8 @@ var sync_default = defineCommand({
390
411
  report(runStartSync(options), projectRoot);
391
412
  }
392
413
  });
414
+ var sync_default = syncCommand;
393
415
  export {
394
- sync_default as default
416
+ sync_default as default,
417
+ syncCommand
395
418
  };