@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.
- package/README.md +8 -5
- package/dist/{chunk-5LQIHYFC.js → chunk-27HK6H5Y.js} +10 -5
- package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
- package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
- package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
- package/dist/chunk-FEOPLBGA.js +150 -0
- package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
- package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
- package/dist/{doctor-J4O3X54I.js → chunk-JTHWLUD3.js} +103 -51
- package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
- package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
- package/dist/chunk-QFIVFZRH.js +13 -0
- package/dist/chunk-QPAW6IYT.js +387 -0
- package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
- package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
- package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
- package/dist/context-7NUKXDB6.js +117 -0
- package/dist/doctor-REZDNH4A.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +131 -21
- package/dist/info-7FKBTMVO.js +139 -0
- package/dist/install-v2-2COC3DO3.js +3277 -0
- package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
- package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
- package/dist/plan-context-hint-G75R4P4J.js +12 -0
- package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
- package/dist/{status-PANEGKU2.js → status-4R3TM4FJ.js} +8 -5
- package/dist/store-HOCORVL3.js +563 -0
- package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
- package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
- package/dist/{whoami-66YKY5DZ.js → whoami-ITGEFWH4.js} +9 -7
- package/package.json +7 -5
- package/templates/hooks/cite-policy-evict.cjs +5 -5
- package/templates/hooks/configs/README.md +14 -27
- package/templates/hooks/configs/claude-code.json +1 -1
- package/templates/hooks/configs/codex-hooks.json +3 -3
- package/templates/hooks/fabric-hint.cjs +301 -161
- package/templates/hooks/knowledge-hint-broad.cjs +426 -207
- package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
- package/templates/hooks/lib/banner-i18n.cjs +31 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -7
- package/templates/hooks/lib/cite-line-parser.cjs +12 -20
- package/templates/hooks/lib/client-adapter.cjs +66 -7
- package/templates/hooks/lib/nudge-policy.cjs +117 -0
- package/templates/hooks/lib/state-store.cjs +60 -0
- package/templates/hooks/lib/summary-fallback.cjs +82 -19
- package/templates/hooks/post-tooluse-mutation.cjs +112 -11
- package/templates/skills/fabric/SKILL.md +94 -0
- package/templates/skills/fabric-archive/SKILL.md +29 -26
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
- package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
- package/templates/skills/fabric-audit/SKILL.md +13 -3
- package/templates/skills/fabric-connect/SKILL.md +3 -3
- package/templates/skills/fabric-import/SKILL.md +7 -7
- package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
- package/templates/skills/fabric-review/SKILL.md +5 -5
- package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
- package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-review/ref/output-contract.md +1 -1
- package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
- package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-store/SKILL.md +1 -1
- package/templates/skills/fabric-sync/SKILL.md +1 -1
- package/templates/skills/lib/shared-policy.md +2 -2
- package/dist/chunk-5ZUMLCD5.js +0 -248
- package/dist/install-BULNDUIM.js +0 -2816
- package/dist/store-66NK2FTQ.js +0 -443
- package/templates/hooks/configs/cursor-hooks.json +0 -30
- 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-
|
|
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-
|
|
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,
|
|
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 = (
|
|
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
|
|
321
|
+
const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
|
|
322
|
+
const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
|
|
308
323
|
(options.rebaseContinue ?? defaultRebaseContinue)(storeDirOf(conflicted));
|
|
309
|
-
const pushableAliases = pushableAliasesOf(
|
|
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
|
|
361
|
+
const resumeConfig = loadGlobalConfig(globalRoot) ?? { stores: [] };
|
|
362
|
+
const storeDirOf = makeStoreDirResolver(globalRoot, resumeConfig.stores);
|
|
347
363
|
(options.rebaseAbort ?? defaultRebaseAbort)(storeDirOf(conflicted));
|
|
348
|
-
const pushableAliases = pushableAliasesOf(
|
|
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
|
|
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
|
};
|