@fenglimg/fabric-cli 2.1.0-rc.2 → 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.
Files changed (49) hide show
  1. package/dist/{chunk-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
  2. package/dist/chunk-5LQIHYFC.js +64 -0
  3. package/dist/chunk-5ZUMLCD5.js +248 -0
  4. package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
  5. package/dist/chunk-EOT63RDH.js +36 -0
  6. package/dist/{chunk-BATF4PEJ.js → chunk-F6ITRM7T.js} +4 -4
  7. package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
  8. package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
  9. package/dist/chunk-XCBVSGCS.js +25 -0
  10. package/dist/{chunk-F46ORPOA.js → chunk-XHHCRDIR.js} +149 -7
  11. package/dist/{config-XJIPZNUP.js → config-VJMXCLXW.js} +3 -3
  12. package/dist/{doctor-QVNPHLJK.js → doctor-J4O3X54I.js} +154 -30
  13. package/dist/index.js +57 -16
  14. package/dist/{install-2HDO5FTQ.js → install-BULNDUIM.js} +241 -108
  15. package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
  16. package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
  17. package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
  18. package/dist/{scope-explain-2F2R5URO.js → scope-explain-BWRWBCCP.js} +19 -5
  19. package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
  20. package/dist/store-66NK2FTQ.js +443 -0
  21. package/dist/sync-EA5HZMXM.js +395 -0
  22. package/dist/{uninstall-TAXSUSKH.js → uninstall-F75MPKQC.js} +61 -4
  23. package/dist/whoami-66YKY5DZ.js +47 -0
  24. package/package.json +3 -3
  25. package/templates/hooks/cite-policy-evict.cjs +412 -160
  26. package/templates/hooks/configs/claude-code.json +17 -2
  27. package/templates/hooks/configs/codex-hooks.json +14 -2
  28. package/templates/hooks/configs/cursor-hooks.json +14 -2
  29. package/templates/hooks/fabric-hint.cjs +247 -19
  30. package/templates/hooks/knowledge-hint-broad.cjs +176 -10
  31. package/templates/hooks/knowledge-hint-narrow.cjs +64 -5
  32. package/templates/hooks/lib/injection-log.cjs +91 -0
  33. package/templates/hooks/lib/state-store.cjs +30 -11
  34. package/templates/hooks/post-tooluse-mutation.cjs +285 -0
  35. package/templates/hooks/session-end-marker.cjs +140 -0
  36. package/templates/skills/fabric-archive/SKILL.md +7 -1
  37. package/templates/skills/fabric-audit/SKILL.md +53 -0
  38. package/templates/skills/fabric-connect/SKILL.md +48 -0
  39. package/templates/skills/fabric-review/SKILL.md +2 -0
  40. package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
  41. package/templates/skills/fabric-store/SKILL.md +44 -0
  42. package/dist/chunk-HFQVXY6P.js +0 -86
  43. package/dist/chunk-L4Q55UC4.js +0 -52
  44. package/dist/chunk-LFIKMVY7.js +0 -27
  45. package/dist/chunk-RYAFBNES.js +0 -33
  46. package/dist/chunk-T5RPGCCM.js +0 -40
  47. package/dist/store-XTSE5TY6.js +0 -105
  48. package/dist/sync-BJCWDPNC.js +0 -245
  49. package/dist/whoami-B6AEMSEV.js +0 -31
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getProjectTranslator
4
+ } from "./chunk-2CY4BMTH.js";
2
5
 
3
6
  // src/commands/metrics.ts
4
7
  import { resolve } from "path";
5
8
  import { defineCommand } from "citty";
6
9
  import { readMetrics } from "@fenglimg/fabric-server";
7
- function parseSinceArg(raw) {
10
+ function parseSinceArg(raw, t) {
8
11
  if (raw === void 0 || raw.length === 0) return 0;
9
12
  const match = /^(\d+)([smhd]?)$/u.exec(raw);
10
13
  if (match === null) {
11
- throw new Error(`--since: invalid duration "${raw}" (expected e.g. 24h, 7d, 30m)`);
14
+ throw new Error(t("cli.metrics.invalid-since", { raw }));
12
15
  }
13
16
  const n = Number.parseInt(match[1], 10);
14
17
  const unit = match[2] ?? "s";
@@ -36,6 +39,8 @@ function aggregate(rows, sinceMs, now) {
36
39
  }
37
40
  }
38
41
  return {
42
+ // Stable token (NOT localized) so the --json contract is locale-independent;
43
+ // renderText localizes "all-time" at presentation time only.
39
44
  windowDescription: sinceMs > 0 ? formatDuration(sinceMs) : "all-time",
40
45
  rowCount: filtered.length,
41
46
  totals,
@@ -50,17 +55,24 @@ function formatDuration(ms) {
50
55
  if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
51
56
  return `${Math.round(ms / 1e3)}s`;
52
57
  }
53
- function renderText(agg) {
58
+ function renderText(agg, t) {
54
59
  const lines = [];
55
- lines.push(`Fabric metrics \u2014 window: ${agg.windowDescription}`);
60
+ const windowDisplay = agg.windowDescription === "all-time" ? t("cli.metrics.window-all-time") : agg.windowDescription;
61
+ lines.push(t("cli.metrics.window", { window: windowDisplay }));
56
62
  if (agg.rangeStart && agg.rangeEnd) {
57
- lines.push(` rows: ${agg.rowCount} (${agg.rangeStart} \u2192 ${agg.rangeEnd})`);
63
+ lines.push(
64
+ t("cli.metrics.rows-range", {
65
+ count: String(agg.rowCount),
66
+ start: agg.rangeStart,
67
+ end: agg.rangeEnd
68
+ })
69
+ );
58
70
  } else {
59
- lines.push(` rows: ${agg.rowCount}`);
71
+ lines.push(t("cli.metrics.rows", { count: String(agg.rowCount) }));
60
72
  }
61
73
  lines.push("");
62
74
  if (Object.keys(agg.totals).length === 0) {
63
- lines.push(" (no counter activity in window \u2014 server may be idle or just started)");
75
+ lines.push(t("cli.metrics.no-activity"));
64
76
  return lines.join("\n");
65
77
  }
66
78
  lines.push(" counter total");
@@ -103,14 +115,15 @@ var metricsCommand = defineCommand({
103
115
  },
104
116
  async run({ args }) {
105
117
  const projectRoot = resolve(args.target ?? process.cwd());
106
- const sinceMs = parseSinceArg(args.since);
118
+ const t = getProjectTranslator(projectRoot);
119
+ const sinceMs = parseSinceArg(args.since, t);
107
120
  const rows = await readMetrics(projectRoot);
108
121
  const aggregated = aggregate(rows, sinceMs, /* @__PURE__ */ new Date());
109
122
  if (args.json === true) {
110
123
  process.stdout.write(`${JSON.stringify(aggregated)}
111
124
  `);
112
125
  } else {
113
- process.stdout.write(`${renderText(aggregated)}
126
+ process.stdout.write(`${renderText(aggregated, t)}
114
127
  `);
115
128
  }
116
129
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-PWLW3B57.js";
4
+ } from "./chunk-2CY4BMTH.js";
5
5
 
6
6
  // src/commands/onboard-coverage.ts
7
7
  import { existsSync, readdirSync, readFileSync } from "fs";
@@ -56,16 +56,29 @@ async function runPlanContextHint(opts) {
56
56
  const targetPaths = all ? [ALL_PATHS_SENTINEL] : explicitPaths.length > 0 ? explicitPaths : [ALL_PATHS_SENTINEL];
57
57
  const resolution = resolveDevMode(opts.target, process.cwd());
58
58
  const result = await planContext(resolution.target, {
59
- paths: targetPaths
59
+ paths: targetPaths,
60
+ // lifecycle-refactor W3-T2 (§7 图谱消费 / §5): default-enable graph二阶召回 for
61
+ // the hint path. planContext appends the one-hop `related` neighbours that
62
+ // ranked outside top_k of the surfaced set and reports them in
63
+ // `related_appended` (appended id → source id). Honest no-op when the
64
+ // surfaced set declares no in-corpus related edge.
65
+ include_related: true
60
66
  });
61
67
  const candidates = result.candidates;
62
- const entries = candidates.map((item) => ({
63
- id: item.stable_id,
64
- type: item.description.knowledge_type ?? "",
65
- maturity: item.description.maturity ?? "",
66
- summary: item.description.summary,
67
- relevance_scope: item.description.relevance_scope ?? "broad"
68
- }));
68
+ const relatedAppended = result.related_appended ?? {};
69
+ const entries = candidates.map((item) => {
70
+ const relatedTo = relatedAppended[item.stable_id];
71
+ return {
72
+ id: item.stable_id,
73
+ type: item.description.knowledge_type ?? "",
74
+ maturity: item.description.maturity ?? "",
75
+ summary: item.description.summary,
76
+ relevance_scope: item.description.relevance_scope ?? "broad",
77
+ // Only set when this entry was pulled in via a graph edge — its presence
78
+ // is the honest signal, never synthesized for ordinarily-ranked entries.
79
+ ...typeof relatedTo === "string" ? { related_to: relatedTo } : {}
80
+ };
81
+ });
69
82
  let narrow_count = 0;
70
83
  let broad_only_count = 0;
71
84
  for (const e of entries) {
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scopeExplain
4
- } from "./chunk-L4Q55UC4.js";
5
- import "./chunk-LFIKMVY7.js";
6
- import "./chunk-RYAFBNES.js";
4
+ } from "./chunk-EOT63RDH.js";
5
+ import {
6
+ getProjectTranslator
7
+ } from "./chunk-2CY4BMTH.js";
7
8
 
8
9
  // src/commands/scope-explain.ts
9
10
  import { defineCommand } from "citty";
11
+ import { FabricError } from "@fenglimg/fabric-shared/errors";
10
12
  var scope_explain_default = defineCommand({
11
13
  meta: {
12
14
  name: "scope-explain",
@@ -20,9 +22,21 @@ var scope_explain_default = defineCommand({
20
22
  }
21
23
  },
22
24
  run({ args }) {
23
- const result = scopeExplain(process.cwd(), args.scope);
25
+ const projectRoot = process.cwd();
26
+ let result;
27
+ try {
28
+ result = scopeExplain(projectRoot, args.scope);
29
+ } catch (error) {
30
+ if (error instanceof FabricError) {
31
+ console.error(`${error.message}
32
+ \u2192 ${error.actionHint}`);
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ throw error;
37
+ }
24
38
  if (result === null) {
25
- console.log("no global Fabric config \u2014 run `fabric install --global <url>` first");
39
+ console.log(getProjectTranslator(projectRoot)("cli.cmd.no-global-config"));
26
40
  return;
27
41
  }
28
42
  console.log(JSON.stringify(result, null, 2));
@@ -1,18 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- projectStatus
4
- } from "./chunk-T5RPGCCM.js";
5
- import "./chunk-LFIKMVY7.js";
6
- import "./chunk-RYAFBNES.js";
3
+ projectStatus,
4
+ warnUnknownFlags
5
+ } from "./chunk-5LQIHYFC.js";
6
+ import "./chunk-5ZUMLCD5.js";
7
+ import "./chunk-XCBVSGCS.js";
7
8
 
8
9
  // src/commands/status.ts
9
10
  import { defineCommand } from "citty";
10
11
  var status_default = defineCommand({
11
12
  meta: { name: "status", description: "Show this project's Fabric store status" },
12
- run() {
13
+ args: {
14
+ // F27: `--json` machine-readable output (was silently ignored pre-F27).
15
+ json: { type: "boolean", description: "Emit machine-readable JSON instead of text" }
16
+ },
17
+ run({ args }) {
18
+ warnUnknownFlags(["json"]);
13
19
  const status = projectStatus(process.cwd());
20
+ if (args.json === true) {
21
+ console.log(JSON.stringify(status, null, 2));
22
+ return;
23
+ }
14
24
  console.log(`uid: ${status.uid ?? "(no global config)"}`);
15
- console.log(`project_id: ${status.project_id ?? "(not a Fabric project)"}`);
25
+ const projectIdLabel = status.project_id ?? (status.is_fabric_project ? "(unset)" : "(not a Fabric project)");
26
+ console.log(`project_id: ${projectIdLabel}`);
16
27
  console.log(`mounted stores: ${status.mounted.length > 0 ? status.mounted.join(", ") : "(none)"}`);
17
28
  console.log(`required: ${status.required.length > 0 ? status.required.join(", ") : "(none)"}`);
18
29
  console.log(`active write: ${status.active_write_store ?? "(none \u2014 personal scope only)"}`);
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ regenerateBindingsSnapshot
4
+ } from "./chunk-H3FE6VIK.js";
5
+ import "./chunk-EOT63RDH.js";
6
+ import {
7
+ getProjectTranslator
8
+ } from "./chunk-2CY4BMTH.js";
9
+ import {
10
+ assertStoreMountable,
11
+ storeAdd,
12
+ storeBind,
13
+ storeCreate,
14
+ storeExplain,
15
+ storeGitRemote,
16
+ storeList,
17
+ storeRemove,
18
+ storeSwitchWrite
19
+ } from "./chunk-5ZUMLCD5.js";
20
+ import {
21
+ loadGlobalConfig
22
+ } from "./chunk-XCBVSGCS.js";
23
+
24
+ // src/commands/store.ts
25
+ import { defineCommand } from "citty";
26
+
27
+ // src/store/store-migrate.ts
28
+ import { execFileSync } from "child_process";
29
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
30
+ import { basename, join } from "path";
31
+ import {
32
+ STORE_KNOWLEDGE_TYPE_DIRS,
33
+ STORE_LAYOUT,
34
+ STORE_PENDING_DIR,
35
+ buildStoreResolveInput,
36
+ createStoreResolver,
37
+ formatKnowledgeId,
38
+ parseKnowledgeId,
39
+ resolveGlobalRoot,
40
+ storeRelativePath
41
+ } from "@fenglimg/fabric-shared";
42
+ function resolveTargetStore(layer, projectRoot, globalRoot) {
43
+ const input = buildStoreResolveInput(projectRoot, globalRoot);
44
+ if (input === null) {
45
+ return null;
46
+ }
47
+ const scope = layer === "personal" ? "personal" : "team";
48
+ const { target } = createStoreResolver().resolveWriteTarget(input, scope);
49
+ if (target === null) {
50
+ return null;
51
+ }
52
+ const alias = loadGlobalConfig(globalRoot)?.stores.find((s) => s.store_uuid === target.store_uuid)?.alias ?? target.store_uuid;
53
+ return {
54
+ uuid: target.store_uuid,
55
+ alias,
56
+ dir: join(globalRoot, storeRelativePath(target.store_uuid))
57
+ };
58
+ }
59
+ function listMd(dir) {
60
+ if (!existsSync(dir)) {
61
+ return [];
62
+ }
63
+ return readdirSync(dir).filter((name) => name.endsWith(".md")).sort();
64
+ }
65
+ function readId(content) {
66
+ const match = content.match(/^id:\s*(\S+)\s*$/mu);
67
+ return match ? match[1] : null;
68
+ }
69
+ function slugSuffix(fileName, oldId) {
70
+ const stem = fileName.replace(/\.md$/u, "");
71
+ if (oldId !== null && stem.startsWith(`${oldId}--`)) {
72
+ return stem.slice(oldId.length);
73
+ }
74
+ return "";
75
+ }
76
+ function buildStoreIdIndex(storeDir) {
77
+ const existing = /* @__PURE__ */ new Set();
78
+ const maxCounter = /* @__PURE__ */ new Map();
79
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
80
+ const dir = join(storeDir, STORE_LAYOUT.knowledgeDir, type);
81
+ for (const file of listMd(dir)) {
82
+ const content = readFileSync(join(dir, file), "utf8");
83
+ const id = readId(content) ?? file.replace(/\.md$/u, "").split("--")[0];
84
+ const parsed = parseKnowledgeId(id);
85
+ if (parsed === null) {
86
+ continue;
87
+ }
88
+ existing.add(id);
89
+ const key = id.slice(0, id.lastIndexOf("-"));
90
+ maxCounter.set(key, Math.max(maxCounter.get(key) ?? 0, parsed.counter));
91
+ }
92
+ }
93
+ return { existing, maxCounter };
94
+ }
95
+ function nextId(index, layer, type) {
96
+ const probe = formatKnowledgeId(layer, type, 1);
97
+ const key = probe.slice(0, probe.lastIndexOf("-"));
98
+ let counter = (index.maxCounter.get(key) ?? 0) + 1;
99
+ let id = formatKnowledgeId(layer, type, counter);
100
+ while (index.existing.has(id)) {
101
+ counter += 1;
102
+ id = formatKnowledgeId(layer, type, counter);
103
+ }
104
+ index.existing.add(id);
105
+ index.maxCounter.set(key, counter);
106
+ return id;
107
+ }
108
+ function typeDirToKnowledgeType(typeDir) {
109
+ return STORE_KNOWLEDGE_TYPE_DIRS.includes(typeDir) ? typeDir : null;
110
+ }
111
+ function migrateProjectKnowledge(projectRoot, options = {}) {
112
+ const dryRun = options.dryRun ?? false;
113
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
114
+ const runGit = options.git ?? true;
115
+ const items = [];
116
+ const skips = [];
117
+ const remap = {};
118
+ const targets = {};
119
+ const sourceRoots = {
120
+ team: join(projectRoot, ".fabric", "knowledge"),
121
+ personal: join(globalRoot, "knowledge")
122
+ };
123
+ const layerState = {};
124
+ for (const layer of ["team", "personal"]) {
125
+ if (!existsSync(sourceRoots[layer])) {
126
+ continue;
127
+ }
128
+ const target = resolveTargetStore(layer, projectRoot, globalRoot);
129
+ if (target === null) {
130
+ continue;
131
+ }
132
+ targets[layer] = { uuid: target.uuid, dir: target.dir };
133
+ layerState[layer] = { target, index: buildStoreIdIndex(target.dir) };
134
+ }
135
+ for (const layer of ["team", "personal"]) {
136
+ const root = sourceRoots[layer];
137
+ if (!existsSync(root)) {
138
+ continue;
139
+ }
140
+ const state = layerState[layer];
141
+ for (const typeDir of STORE_KNOWLEDGE_TYPE_DIRS) {
142
+ const dir = join(root, typeDir);
143
+ for (const file of listMd(dir)) {
144
+ const source = join(dir, file);
145
+ if (state === void 0) {
146
+ skips.push({
147
+ source,
148
+ reason: `no ${layer} write-target store \u2014 run \`fabric install --global\` then \`fabric store bind <alias>\`${layer === "team" ? " + `fabric store switch-write <alias>`" : ""}`
149
+ });
150
+ continue;
151
+ }
152
+ const content = readFileSync(source, "utf8");
153
+ const oldId = readId(content);
154
+ const knowledgeType = typeDirToKnowledgeType(typeDir);
155
+ let newId = null;
156
+ if (oldId !== null && state.index.existing.has(oldId) && knowledgeType !== null) {
157
+ const parsed = parseKnowledgeId(oldId);
158
+ const idLayer = parsed?.layer ?? layer;
159
+ newId = nextId(state.index, idLayer, knowledgeType);
160
+ remap[oldId] = newId;
161
+ } else if (oldId !== null) {
162
+ state.index.existing.add(oldId);
163
+ const parsed = parseKnowledgeId(oldId);
164
+ if (parsed !== null) {
165
+ const key = oldId.slice(0, oldId.lastIndexOf("-"));
166
+ state.index.maxCounter.set(
167
+ key,
168
+ Math.max(state.index.maxCounter.get(key) ?? 0, parsed.counter)
169
+ );
170
+ }
171
+ }
172
+ const effectiveId = newId ?? oldId;
173
+ const targetName = newId !== null && effectiveId !== null ? `${effectiveId}${slugSuffix(file, oldId)}.md` : file;
174
+ const targetFile = join(state.target.dir, STORE_LAYOUT.knowledgeDir, typeDir, targetName);
175
+ items.push({
176
+ source,
177
+ layer,
178
+ type: typeDir,
179
+ oldId,
180
+ newId,
181
+ target: targetFile,
182
+ storeUuid: state.target.uuid,
183
+ alias: state.target.alias
184
+ });
185
+ }
186
+ }
187
+ const pendingRoot = join(root, STORE_PENDING_DIR);
188
+ for (const sub of [".", "decisions", "guidelines", "pitfalls", "models", "processes"]) {
189
+ const dir = sub === "." ? pendingRoot : join(pendingRoot, sub);
190
+ for (const file of listMd(dir)) {
191
+ const source = join(dir, file);
192
+ if (state === void 0) {
193
+ skips.push({ source, reason: `no ${layer} write-target store` });
194
+ continue;
195
+ }
196
+ const rel = sub === "." ? file : join(sub, file);
197
+ const targetFile = join(
198
+ state.target.dir,
199
+ STORE_LAYOUT.knowledgeDir,
200
+ STORE_PENDING_DIR,
201
+ rel
202
+ );
203
+ if (existsSync(targetFile)) {
204
+ skips.push({ source, reason: `pending already present in store: ${basename(targetFile)}` });
205
+ continue;
206
+ }
207
+ items.push({
208
+ source,
209
+ layer,
210
+ type: STORE_PENDING_DIR,
211
+ oldId: null,
212
+ newId: null,
213
+ target: targetFile,
214
+ storeUuid: state.target.uuid,
215
+ alias: state.target.alias
216
+ });
217
+ }
218
+ }
219
+ }
220
+ if (dryRun || items.length === 0) {
221
+ return { dryRun, committed: false, items, skips, remap, targets };
222
+ }
223
+ for (const item of items) {
224
+ let content = readFileSync(item.source, "utf8");
225
+ if (item.newId !== null && item.oldId !== null) {
226
+ content = content.replace(/^id:\s*\S+\s*$/mu, `id: ${item.newId}`);
227
+ }
228
+ content = rewriteRelated(content, remap);
229
+ mkdirSync(join(item.target, ".."), { recursive: true });
230
+ writeFileSync(item.target, content, "utf8");
231
+ }
232
+ for (const item of items) {
233
+ rmSync(item.source, { force: true });
234
+ }
235
+ let committed = false;
236
+ if (runGit) {
237
+ for (const [layer, info] of Object.entries(targets)) {
238
+ const moved = items.filter((i) => i.layer === layer).length;
239
+ if (moved === 0) {
240
+ continue;
241
+ }
242
+ committed = gitCommitStore(info.dir, moved) || committed;
243
+ }
244
+ }
245
+ return { dryRun, committed, items, skips, remap, targets };
246
+ }
247
+ function rewriteRelated(content, remap) {
248
+ if (Object.keys(remap).length === 0) {
249
+ return content;
250
+ }
251
+ return content.replace(/^related:\s*\[(.*)\]\s*$/mu, (line, inner) => {
252
+ const rewritten = inner.split(",").map((token) => {
253
+ const trimmed = token.trim();
254
+ return remap[trimmed] ?? trimmed;
255
+ }).join(", ");
256
+ return `related: [${rewritten}]`;
257
+ });
258
+ }
259
+ function gitCommitStore(storeDir, count) {
260
+ if (!existsSync(join(storeDir, ".git"))) {
261
+ return false;
262
+ }
263
+ try {
264
+ execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
265
+ execFileSync(
266
+ "git",
267
+ ["commit", "-m", `chore(migrate): import ${count} entries from project dual-root`],
268
+ { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] }
269
+ );
270
+ return true;
271
+ } catch {
272
+ return false;
273
+ }
274
+ }
275
+
276
+ // src/commands/store.ts
277
+ var listCommand = defineCommand({
278
+ meta: { name: "list", description: "List mounted knowledge stores" },
279
+ run() {
280
+ const t = getProjectTranslator();
281
+ const stores = storeList();
282
+ if (stores.length === 0) {
283
+ console.log(t("cli.store.none-mounted"));
284
+ return;
285
+ }
286
+ const localOnly = t("cli.shared.local-only");
287
+ for (const store of stores) {
288
+ const realRemote = storeGitRemote(store.store_uuid);
289
+ console.log(`${store.alias} ${store.store_uuid} ${realRemote ?? localOnly}`);
290
+ }
291
+ }
292
+ });
293
+ var addCommand = defineCommand({
294
+ meta: { name: "add", description: "Mount a knowledge store into the global registry" },
295
+ args: {
296
+ uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
297
+ alias: { type: "string", required: true, description: "Local alias for this store" },
298
+ remote: { type: "string", description: "Git remote locator (omit for local-only)" }
299
+ },
300
+ run({ args }) {
301
+ assertStoreMountable(args.uuid);
302
+ const store = args.remote === void 0 ? { store_uuid: args.uuid, alias: args.alias } : { store_uuid: args.uuid, alias: args.alias, remote: args.remote };
303
+ const next = storeAdd(store);
304
+ const t = getProjectTranslator();
305
+ console.log(
306
+ t("cli.store.mounted", {
307
+ alias: args.alias,
308
+ count: String(next.stores.length)
309
+ })
310
+ );
311
+ }
312
+ });
313
+ var createCommand = defineCommand({
314
+ meta: { name: "create", description: "Create a brand-new local knowledge store and mount it" },
315
+ args: {
316
+ alias: { type: "string", required: true, description: "Local alias for the new store" },
317
+ remote: { type: "string", description: "Git remote to associate (push target; optional)" }
318
+ },
319
+ run({ args }) {
320
+ const result = storeCreate(args.alias, (/* @__PURE__ */ new Date()).toISOString(), {
321
+ ...args.remote === void 0 ? {} : { remote: args.remote }
322
+ });
323
+ const t = getProjectTranslator();
324
+ console.log(
325
+ t("cli.store.created", { alias: args.alias, uuid: result.store_uuid, dir: result.storeDir }) + (args.remote === void 0 ? `
326
+ ${t("cli.store.created-local-hint")}` : "")
327
+ );
328
+ }
329
+ });
330
+ var removeCommand = defineCommand({
331
+ meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
332
+ args: {
333
+ alias: { type: "positional", required: true, description: "Alias to detach" }
334
+ },
335
+ run({ args }) {
336
+ const { detached } = storeRemove(args.alias);
337
+ const t = getProjectTranslator();
338
+ console.log(
339
+ detached === null ? t("cli.store.no-alias", { alias: args.alias }) : t("cli.store.detached", { alias: args.alias })
340
+ );
341
+ }
342
+ });
343
+ var explainCommand = defineCommand({
344
+ meta: { name: "explain", description: "Explain how a store alias resolves" },
345
+ args: {
346
+ alias: { type: "positional", required: true, description: "Alias to explain" }
347
+ },
348
+ run({ args }) {
349
+ const explanation = storeExplain(args.alias);
350
+ console.log(
351
+ explanation === null ? getProjectTranslator()("cli.store.no-alias", { alias: args.alias }) : JSON.stringify(explanation, null, 2)
352
+ );
353
+ }
354
+ });
355
+ var bindCommand = defineCommand({
356
+ meta: { name: "bind", description: "Declare a required store on this project's config" },
357
+ args: {
358
+ id: { type: "positional", required: true, description: "Store alias/UUID to require" },
359
+ remote: { type: "string", description: "Suggested remote for clone onboarding" }
360
+ },
361
+ run({ args }) {
362
+ const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
363
+ const projectRoot = process.cwd();
364
+ const next = storeBind(projectRoot, entry);
365
+ console.log(
366
+ getProjectTranslator(projectRoot)("cli.store.bound", {
367
+ id: args.id,
368
+ count: String(next.required_stores?.length ?? 0)
369
+ })
370
+ );
371
+ regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
372
+ }
373
+ });
374
+ var switchWriteCommand = defineCommand({
375
+ meta: { name: "switch-write", description: "Set the active write store for non-personal scopes" },
376
+ args: {
377
+ alias: { type: "positional", required: true, description: "Alias of the store to write to" }
378
+ },
379
+ run({ args }) {
380
+ const projectRoot = process.cwd();
381
+ storeSwitchWrite(projectRoot, args.alias);
382
+ console.log(getProjectTranslator(projectRoot)("cli.store.switch-write", { alias: args.alias }));
383
+ }
384
+ });
385
+ var migrateCommand = defineCommand({
386
+ meta: {
387
+ name: "migrate",
388
+ description: "Move project-local (dual-root) knowledge into the resolved write-target stores"
389
+ },
390
+ args: {
391
+ "dry-run": {
392
+ type: "boolean",
393
+ description: "Preview the move without writing anything"
394
+ }
395
+ },
396
+ run({ args }) {
397
+ const projectRoot = process.cwd();
398
+ const t = getProjectTranslator(projectRoot);
399
+ const dryRun = args["dry-run"] === true;
400
+ const report = migrateProjectKnowledge(projectRoot, { dryRun });
401
+ if (report.items.length === 0 && report.skips.length === 0) {
402
+ console.log(t("cli.store.migrate.none"));
403
+ return;
404
+ }
405
+ console.log(
406
+ dryRun ? t("cli.store.migrate.dry-run-header") : t("cli.store.migrate.applied-header", { count: String(report.items.length) })
407
+ );
408
+ for (const item of report.items) {
409
+ const id = item.newId ?? item.oldId ?? "(draft)";
410
+ console.log(` ${item.layer}/${item.type} ${id} \u2192 ${item.alias}`);
411
+ if (item.newId !== null && item.oldId !== null) {
412
+ console.log(
413
+ t("cli.store.migrate.remap-note", { oldId: item.oldId, newId: item.newId })
414
+ );
415
+ }
416
+ }
417
+ if (report.skips.length > 0) {
418
+ console.log(t("cli.store.migrate.skips-header", { count: String(report.skips.length) }));
419
+ for (const skip of report.skips) {
420
+ console.log(` ${skip.source}: ${skip.reason}`);
421
+ }
422
+ }
423
+ if (report.committed) {
424
+ console.log(t("cli.store.migrate.committed"));
425
+ }
426
+ }
427
+ });
428
+ var store_default = defineCommand({
429
+ meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
430
+ subCommands: {
431
+ list: listCommand,
432
+ create: createCommand,
433
+ add: addCommand,
434
+ remove: removeCommand,
435
+ explain: explainCommand,
436
+ bind: bindCommand,
437
+ "switch-write": switchWriteCommand,
438
+ migrate: migrateCommand
439
+ }
440
+ });
441
+ export {
442
+ store_default as default
443
+ };