@fenglimg/fabric-cli 2.2.0-rc.1 → 2.2.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5LQIHYFC.js +64 -0
- package/dist/chunk-5ZUMLCD5.js +248 -0
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/{chunk-AOE6AYI7.js → chunk-F6ITRM7T.js} +2 -2
- package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
- package/dist/chunk-XCBVSGCS.js +25 -0
- package/dist/{chunk-2R55HNVD.js → chunk-XHHCRDIR.js} +71 -6
- package/dist/{config-XYRBZJDU.js → config-VJMXCLXW.js} +1 -1
- package/dist/{doctor-YONYXDX6.js → doctor-J4O3X54I.js} +118 -7
- package/dist/index.js +13 -12
- package/dist/{install-74ANPCCP.js → install-BULNDUIM.js} +159 -80
- package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
- package/dist/{scope-explain-CDIZESP5.js → scope-explain-BWRWBCCP.js} +14 -4
- package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
- package/dist/store-66NK2FTQ.js +443 -0
- package/dist/{sync-UJ4BBCZJ.js → sync-EA5HZMXM.js} +165 -21
- package/dist/{uninstall-C3QXKOO6.js → uninstall-F75MPKQC.js} +27 -1
- package/dist/{whoami-2MLO4Y37.js → whoami-66YKY5DZ.js} +16 -5
- package/package.json +3 -3
- package/templates/hooks/cite-policy-evict.cjs +412 -160
- package/templates/hooks/configs/claude-code.json +17 -2
- package/templates/hooks/configs/codex-hooks.json +14 -2
- package/templates/hooks/configs/cursor-hooks.json +14 -2
- package/templates/hooks/fabric-hint.cjs +151 -15
- package/templates/hooks/knowledge-hint-broad.cjs +12 -1
- package/templates/hooks/knowledge-hint-narrow.cjs +54 -1
- package/templates/hooks/post-tooluse-mutation.cjs +285 -0
- package/templates/hooks/session-end-marker.cjs +140 -0
- package/templates/skills/fabric-archive/SKILL.md +7 -1
- package/dist/chunk-4R2CYEA4.js +0 -116
- package/dist/chunk-L4Q55UC4.js +0 -52
- package/dist/chunk-LFIKMVY7.js +0 -27
- package/dist/chunk-RYAFBNES.js +0 -33
- package/dist/chunk-T5RPGCCM.js +0 -40
- package/dist/store-XB3ADT65.js +0 -144
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
installHookLibs,
|
|
14
14
|
installKnowledgeHintBroadHook,
|
|
15
15
|
installKnowledgeHintNarrowHook,
|
|
16
|
+
installPostTooluseMutationHook,
|
|
17
|
+
installSessionEndMarkerHook,
|
|
16
18
|
installSharedSkillLib,
|
|
17
19
|
mergeClaudeCodeHookConfig,
|
|
18
20
|
mergeCodexHookConfig,
|
|
@@ -22,7 +24,7 @@ import {
|
|
|
22
24
|
writeCodexBootstrapManagedBlock,
|
|
23
25
|
writeCursorBootstrapManagedBlock,
|
|
24
26
|
writeFabricAgentsSnapshot
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-XHHCRDIR.js";
|
|
26
28
|
import {
|
|
27
29
|
displayWidth,
|
|
28
30
|
padEnd,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
} from "./chunk-COI5VDFU.js";
|
|
35
37
|
import {
|
|
36
38
|
installMcpClients
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-F6ITRM7T.js";
|
|
38
40
|
import {
|
|
39
41
|
detectClientSupports
|
|
40
42
|
} from "./chunk-XC5RUHLK.js";
|
|
@@ -42,19 +44,23 @@ import {
|
|
|
42
44
|
getProjectTranslator,
|
|
43
45
|
t
|
|
44
46
|
} from "./chunk-2CY4BMTH.js";
|
|
47
|
+
import {
|
|
48
|
+
syncStoreAliasLinks,
|
|
49
|
+
unboundAvailableStores
|
|
50
|
+
} from "./chunk-5ZUMLCD5.js";
|
|
45
51
|
import {
|
|
46
52
|
globalConfigPath,
|
|
47
53
|
loadGlobalConfig,
|
|
48
54
|
resolveGlobalRoot,
|
|
49
55
|
saveGlobalConfig
|
|
50
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-XCBVSGCS.js";
|
|
51
57
|
|
|
52
58
|
// src/commands/install.ts
|
|
53
59
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
54
60
|
import { homedir } from "os";
|
|
55
61
|
import * as childProcess from "child_process";
|
|
56
|
-
import { appendFileSync, existsSync as
|
|
57
|
-
import { dirname, isAbsolute as isAbsolute3, join as
|
|
62
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
63
|
+
import { dirname, isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
|
|
58
64
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
59
65
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
60
66
|
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
@@ -79,6 +85,8 @@ async function installHooks(target, _options = {}) {
|
|
|
79
85
|
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
80
86
|
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
81
87
|
results.push(...await runStep(() => installCitePolicyEvictHook(normalizedTarget)));
|
|
88
|
+
results.push(...await runStep(() => installSessionEndMarkerHook(normalizedTarget)));
|
|
89
|
+
results.push(...await runStep(() => installPostTooluseMutationHook(normalizedTarget)));
|
|
82
90
|
results.push(...await runStep(() => installHookLibs(normalizedTarget)));
|
|
83
91
|
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
84
92
|
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
@@ -94,7 +102,10 @@ function validateHookPaths(projectRoot) {
|
|
|
94
102
|
const scripts = [
|
|
95
103
|
{ stepSuffix: "", hookFile: "fabric-hint.cjs" },
|
|
96
104
|
{ stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
|
|
97
|
-
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
|
|
105
|
+
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" },
|
|
106
|
+
// lifecycle-refactor W2-T2/T3: SessionEnd + PostToolUse marker hooks.
|
|
107
|
+
{ stepSuffix: "-session-end", hookFile: "session-end-marker.cjs" },
|
|
108
|
+
{ stepSuffix: "-post-tooluse", hookFile: "post-tooluse-mutation.cjs" }
|
|
98
109
|
];
|
|
99
110
|
const clients = [
|
|
100
111
|
{
|
|
@@ -197,12 +208,50 @@ function assertExistingDirectory(target) {
|
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
|
|
211
|
+
// src/install/semantic-search.ts
|
|
212
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
213
|
+
import { join as join2 } from "path";
|
|
214
|
+
var DEFAULT_EMBED_MODEL_PIN = "fast-bge-small-zh-v1.5";
|
|
215
|
+
function enableSemanticSearch(projectRoot, opts = {}) {
|
|
216
|
+
const model = typeof opts.model === "string" && opts.model.length > 0 ? opts.model : DEFAULT_EMBED_MODEL_PIN;
|
|
217
|
+
const configPath = join2(projectRoot, "fabric.config.json");
|
|
218
|
+
let existing = {};
|
|
219
|
+
if (existsSync2(configPath)) {
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
222
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
223
|
+
existing = parsed;
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
existing = {};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const alreadyEnabled = existing.embed_enabled === true && existing.embed_model === model;
|
|
230
|
+
if (alreadyEnabled) {
|
|
231
|
+
return { configPath, model, alreadyEnabled: true, changed: false };
|
|
232
|
+
}
|
|
233
|
+
const merged = { ...existing, embed_enabled: true, embed_model: model };
|
|
234
|
+
writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
235
|
+
return { configPath, model, alreadyEnabled: false, changed: true };
|
|
236
|
+
}
|
|
237
|
+
function renderSemanticSearchInstructions(model) {
|
|
238
|
+
return [
|
|
239
|
+
"\u8BED\u4E49\u641C\u7D22\u5DF2\u542F\u7528 (embed_enabled=true, embed_model=" + model + ")\u3002\u8FD8\u9700\u4E24\u6B65 (\u4E00\u6B21\u6027):",
|
|
240
|
+
" 1. \u5B89\u88C5\u53EF\u9009 embedder (\u88C5\u5230 MCP server \u89E3\u6790\u6A21\u5757\u7684\u4F4D\u7F6E \u2014 \u5168\u5C40\u5B89\u88C5\u5373\u5168\u5C40):",
|
|
241
|
+
" npm i -g fastembed",
|
|
242
|
+
" 2. \u9884\u70ED\u6A21\u578B\u7F13\u5B58 (\u9996\u8DD1\u4F1A\u8054\u7F51\u4E0B\u8F7D\u6A21\u578B\u6743\u91CD ~\u6570\u5341-\u6570\u767E MB, \u4E0D\u4E0A\u4F20\u4EFB\u4F55 KB \u6570\u636E):",
|
|
243
|
+
" export FABRIC_EMBED_CACHE_DIR=~/.cache/fabric-embed # \u4E25\u683C\u79BB\u7EBF\u8005\u9884\u5148\u653E\u597D\u6743\u91CD",
|
|
244
|
+
" \u6CE8: \u5207\u6362 embed_model \u540E\u5DF2\u6709\u5411\u91CF\u7EF4\u5EA6/\u8BED\u4E49\u53D8\u5316, \u4E0B\u6B21 recall \u4F1A\u6309\u65B0\u6A21\u578B\u91CD\u65B0\u5D4C\u5165 (doc \u5411\u91CF\u6309\u6587\u672C\u7F13\u5B58, \u81EA\u52A8\u5931\u914D\u91CD\u7B97)\u3002",
|
|
245
|
+
" \u5173\u95ED: \u7F16\u8F91 fabric.config.json \u8BBE embed_enabled=false\u3002"
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
|
|
200
249
|
// src/install/run-global-install.ts
|
|
201
250
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
202
251
|
import { randomUUID } from "crypto";
|
|
203
252
|
import { mkdirSync, mkdtempSync, renameSync } from "fs";
|
|
204
253
|
import { tmpdir } from "os";
|
|
205
|
-
import { join as
|
|
254
|
+
import { join as join4 } from "path";
|
|
206
255
|
import { STORES_ROOT_DIR as STORES_ROOT_DIR2, addMountedStore, readStoreIdentity } from "@fenglimg/fabric-shared";
|
|
207
256
|
import { GenericIOError } from "@fenglimg/fabric-shared/errors";
|
|
208
257
|
|
|
@@ -229,7 +278,7 @@ function deriveUid(opts = {}) {
|
|
|
229
278
|
|
|
230
279
|
// src/install/install-global.ts
|
|
231
280
|
import { rmSync } from "fs";
|
|
232
|
-
import { join as
|
|
281
|
+
import { join as join3 } from "path";
|
|
233
282
|
import {
|
|
234
283
|
STORES_ROOT_DIR,
|
|
235
284
|
globalConfigSchema,
|
|
@@ -288,7 +337,7 @@ async function installGlobalCore(options) {
|
|
|
288
337
|
};
|
|
289
338
|
}
|
|
290
339
|
const alias = options.personalAlias ?? "personal";
|
|
291
|
-
const personalDir =
|
|
340
|
+
const personalDir = join3(options.globalRoot, STORES_ROOT_DIR, options.personalStoreUuid);
|
|
292
341
|
let config = null;
|
|
293
342
|
const receipt = await runInstallTransaction([
|
|
294
343
|
{
|
|
@@ -339,10 +388,10 @@ function gitClone(url, dest) {
|
|
|
339
388
|
}
|
|
340
389
|
}
|
|
341
390
|
function mountStoreFromRemote(url, globalRoot) {
|
|
342
|
-
const storesRoot =
|
|
391
|
+
const storesRoot = join4(globalRoot, STORES_ROOT_DIR2);
|
|
343
392
|
mkdirSync(storesRoot, { recursive: true });
|
|
344
|
-
const tmp = mkdtempSync(
|
|
345
|
-
const cloneDest =
|
|
393
|
+
const tmp = mkdtempSync(join4(tmpdir(), "fabric-clone-"));
|
|
394
|
+
const cloneDest = join4(tmp, "store");
|
|
346
395
|
gitClone(url, cloneDest);
|
|
347
396
|
const identity = readStoreIdentity(cloneDest);
|
|
348
397
|
if (identity === null) {
|
|
@@ -350,7 +399,7 @@ function mountStoreFromRemote(url, globalRoot) {
|
|
|
350
399
|
actionHint: "verify the url points to a repository created by `fabric` (it must contain a store.json at its root); if you meant to mount a different store, re-run with the correct url"
|
|
351
400
|
});
|
|
352
401
|
}
|
|
353
|
-
const finalDir =
|
|
402
|
+
const finalDir = join4(storesRoot, identity.store_uuid);
|
|
354
403
|
renameSync(cloneDest, finalDir);
|
|
355
404
|
const config = loadGlobalConfig(globalRoot);
|
|
356
405
|
if (config === null) {
|
|
@@ -384,23 +433,24 @@ async function runGlobalInstall(options = {}, globalRoot = resolveGlobalRoot())
|
|
|
384
433
|
if (options.url !== void 0) {
|
|
385
434
|
mountStoreFromRemote(options.url, globalRoot);
|
|
386
435
|
}
|
|
436
|
+
syncStoreAliasLinks(globalRoot);
|
|
387
437
|
}
|
|
388
438
|
|
|
389
439
|
// src/lib/detect-language.ts
|
|
390
|
-
import { existsSync as
|
|
391
|
-
import { join as
|
|
440
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
441
|
+
import { join as join5 } from "path";
|
|
392
442
|
function detectExistingLanguage(target) {
|
|
393
443
|
const ZH_CN_RATIO_THRESHOLD = 0.3;
|
|
394
444
|
const samples = [];
|
|
395
|
-
const readmePath =
|
|
396
|
-
if (
|
|
445
|
+
const readmePath = join5(target, "README.md");
|
|
446
|
+
if (existsSync3(readmePath)) {
|
|
397
447
|
try {
|
|
398
|
-
samples.push(
|
|
448
|
+
samples.push(readFileSync2(readmePath, "utf8"));
|
|
399
449
|
} catch {
|
|
400
450
|
}
|
|
401
451
|
}
|
|
402
|
-
const docsDir =
|
|
403
|
-
if (
|
|
452
|
+
const docsDir = join5(target, "docs");
|
|
453
|
+
if (existsSync3(docsDir)) {
|
|
404
454
|
try {
|
|
405
455
|
const stat = statSync2(docsDir);
|
|
406
456
|
if (stat.isDirectory()) {
|
|
@@ -408,7 +458,7 @@ function detectExistingLanguage(target) {
|
|
|
408
458
|
if (!entry.isFile()) continue;
|
|
409
459
|
if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
|
|
410
460
|
try {
|
|
411
|
-
samples.push(
|
|
461
|
+
samples.push(readFileSync2(join5(docsDir, entry.name), "utf8"));
|
|
412
462
|
} catch {
|
|
413
463
|
}
|
|
414
464
|
}
|
|
@@ -441,9 +491,9 @@ function detectExistingLanguage(target) {
|
|
|
441
491
|
|
|
442
492
|
// src/scanner/forensic.ts
|
|
443
493
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
444
|
-
import { existsSync as
|
|
494
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
445
495
|
import { createRequire } from "module";
|
|
446
|
-
import { basename, extname, isAbsolute as isAbsolute2, join as
|
|
496
|
+
import { basename, extname, isAbsolute as isAbsolute2, join as join6, posix, relative, resolve as resolve2, sep } from "path";
|
|
447
497
|
import {
|
|
448
498
|
buildScanRecommendations,
|
|
449
499
|
forensicReportSchema
|
|
@@ -588,7 +638,7 @@ function buildTopology(root) {
|
|
|
588
638
|
continue;
|
|
589
639
|
}
|
|
590
640
|
for (const entry of readdirSync2(current, { withFileTypes: true })) {
|
|
591
|
-
const absolutePath =
|
|
641
|
+
const absolutePath = join6(current, entry.name);
|
|
592
642
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
593
643
|
if (relativePath.length === 0) {
|
|
594
644
|
continue;
|
|
@@ -627,7 +677,7 @@ function buildTopology(root) {
|
|
|
627
677
|
};
|
|
628
678
|
}
|
|
629
679
|
function assertExistingDirectory2(target) {
|
|
630
|
-
if (!
|
|
680
|
+
if (!existsSync4(target) || !statSync3(target).isDirectory()) {
|
|
631
681
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
632
682
|
}
|
|
633
683
|
}
|
|
@@ -676,7 +726,7 @@ function getEntryPointReason(relativePath) {
|
|
|
676
726
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
677
727
|
const samples = [];
|
|
678
728
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
679
|
-
const absolutePath =
|
|
729
|
+
const absolutePath = join6(target, ...entryPoint.path.split("/"));
|
|
680
730
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
681
731
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
682
732
|
frameworkKind,
|
|
@@ -696,7 +746,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
696
746
|
}
|
|
697
747
|
function readFirstLines(path, lineLimit) {
|
|
698
748
|
try {
|
|
699
|
-
const lines =
|
|
749
|
+
const lines = readFileSync3(path, "utf8").split(/\r?\n/);
|
|
700
750
|
if (lines.at(-1) === "") {
|
|
701
751
|
lines.pop();
|
|
702
752
|
}
|
|
@@ -713,12 +763,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
713
763
|
}
|
|
714
764
|
}
|
|
715
765
|
function readPackageDependencies(target) {
|
|
716
|
-
const packageJsonPath =
|
|
717
|
-
if (!
|
|
766
|
+
const packageJsonPath = join6(target, "package.json");
|
|
767
|
+
if (!existsSync4(packageJsonPath)) {
|
|
718
768
|
return /* @__PURE__ */ new Map();
|
|
719
769
|
}
|
|
720
770
|
try {
|
|
721
|
-
const packageJson = JSON.parse(
|
|
771
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
722
772
|
return new Map([
|
|
723
773
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
724
774
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -1054,16 +1104,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
1054
1104
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1055
1105
|
}
|
|
1056
1106
|
function readReadmeInfo(target) {
|
|
1057
|
-
const readmePath =
|
|
1058
|
-
const hasContributing =
|
|
1059
|
-
if (!
|
|
1107
|
+
const readmePath = join6(target, "README.md");
|
|
1108
|
+
const hasContributing = existsSync4(join6(target, "CONTRIBUTING.md"));
|
|
1109
|
+
if (!existsSync4(readmePath)) {
|
|
1060
1110
|
return {
|
|
1061
1111
|
quality: "missing",
|
|
1062
1112
|
line_count: 0,
|
|
1063
1113
|
has_contributing: hasContributing
|
|
1064
1114
|
};
|
|
1065
1115
|
}
|
|
1066
|
-
const readme =
|
|
1116
|
+
const readme = readFileSync3(readmePath, "utf8");
|
|
1067
1117
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1068
1118
|
return {
|
|
1069
1119
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -1529,10 +1579,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme, projectRoot)
|
|
|
1529
1579
|
);
|
|
1530
1580
|
}
|
|
1531
1581
|
function readProjectName(target) {
|
|
1532
|
-
const packageJsonPath =
|
|
1533
|
-
if (
|
|
1582
|
+
const packageJsonPath = join6(target, "package.json");
|
|
1583
|
+
if (existsSync4(packageJsonPath)) {
|
|
1534
1584
|
try {
|
|
1535
|
-
const packageJson = JSON.parse(
|
|
1585
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
1536
1586
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1537
1587
|
return packageJson.name;
|
|
1538
1588
|
}
|
|
@@ -1543,7 +1593,7 @@ function readProjectName(target) {
|
|
|
1543
1593
|
return basename(target);
|
|
1544
1594
|
}
|
|
1545
1595
|
function getCliVersion() {
|
|
1546
|
-
return true ? "2.2.0-rc.
|
|
1596
|
+
return true ? "2.2.0-rc.3" : "unknown";
|
|
1547
1597
|
}
|
|
1548
1598
|
function sortRecord(record) {
|
|
1549
1599
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1553,7 +1603,7 @@ function toPosixPath(path) {
|
|
|
1553
1603
|
}
|
|
1554
1604
|
|
|
1555
1605
|
// src/commands/install.ts
|
|
1556
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
1606
|
+
var LOCAL_FABRIC_SERVER_PATH = join7("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1557
1607
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1558
1608
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1559
1609
|
var installCommand = defineCommand({
|
|
@@ -1599,6 +1649,15 @@ var installCommand = defineCommand({
|
|
|
1599
1649
|
url: {
|
|
1600
1650
|
type: "string",
|
|
1601
1651
|
description: "With --global: clone + mount this shared store remote"
|
|
1652
|
+
},
|
|
1653
|
+
"enable-embed": {
|
|
1654
|
+
type: "boolean",
|
|
1655
|
+
description: t("cli.install.args.enable-embed.description"),
|
|
1656
|
+
default: false
|
|
1657
|
+
},
|
|
1658
|
+
"embed-model": {
|
|
1659
|
+
type: "string",
|
|
1660
|
+
description: t("cli.install.args.embed-model.description")
|
|
1602
1661
|
}
|
|
1603
1662
|
},
|
|
1604
1663
|
async run({ args }) {
|
|
@@ -1608,8 +1667,8 @@ var installCommand = defineCommand({
|
|
|
1608
1667
|
var install_default = installCommand;
|
|
1609
1668
|
async function runSkillsOnlyRefresh(targetInput) {
|
|
1610
1669
|
const target = normalizeTarget3(targetInput);
|
|
1611
|
-
const metaPath =
|
|
1612
|
-
if (!
|
|
1670
|
+
const metaPath = join7(target, ".fabric", "agents.meta.json");
|
|
1671
|
+
if (!existsSync5(metaPath)) {
|
|
1613
1672
|
const message = t("cli.install.force-skills-only.uninitialised.message");
|
|
1614
1673
|
const hint = t("cli.install.force-skills-only.uninitialised.hint");
|
|
1615
1674
|
process.stderr.write(`${message}
|
|
@@ -1656,8 +1715,8 @@ ${hint}
|
|
|
1656
1715
|
}
|
|
1657
1716
|
async function runHooksOnlyRefresh(targetInput) {
|
|
1658
1717
|
const target = normalizeTarget3(targetInput);
|
|
1659
|
-
const metaPath =
|
|
1660
|
-
if (!
|
|
1718
|
+
const metaPath = join7(target, ".fabric", "agents.meta.json");
|
|
1719
|
+
if (!existsSync5(metaPath)) {
|
|
1661
1720
|
const message = t("cli.install.force-hooks-only.uninitialised.message");
|
|
1662
1721
|
const hint = t("cli.install.force-hooks-only.uninitialised.hint");
|
|
1663
1722
|
process.stderr.write(`${message}
|
|
@@ -1703,6 +1762,10 @@ async function runInitCommand(args) {
|
|
|
1703
1762
|
for (const step of resolution.chain) {
|
|
1704
1763
|
logger(step);
|
|
1705
1764
|
}
|
|
1765
|
+
if (loadGlobalConfig() === null) {
|
|
1766
|
+
logger("no global Fabric config found \u2014 minting ~/.fabric (uid + personal store)");
|
|
1767
|
+
await runGlobalInstall({});
|
|
1768
|
+
}
|
|
1706
1769
|
const supports = detectClientSupports(intent.target);
|
|
1707
1770
|
const basePlan = await buildInitExecutionPlan({
|
|
1708
1771
|
target: intent.target,
|
|
@@ -1723,6 +1786,27 @@ async function runInitCommand(args) {
|
|
|
1723
1786
|
console.log(t("cli.install.next-steps"));
|
|
1724
1787
|
console.log("");
|
|
1725
1788
|
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1789
|
+
const unboundStores = unboundAvailableStores(resolution.target);
|
|
1790
|
+
if (unboundStores.length > 0) {
|
|
1791
|
+
console.log("");
|
|
1792
|
+
console.log(
|
|
1793
|
+
t("cli.install.store-bind-nudge", {
|
|
1794
|
+
aliases: unboundStores.map((s) => `'${s.alias}'`).join(", "),
|
|
1795
|
+
first: unboundStores[0].alias
|
|
1796
|
+
})
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
if (args["enable-embed"] === true) {
|
|
1800
|
+
const enabled = enableSemanticSearch(resolution.target, { model: args["embed-model"] });
|
|
1801
|
+
console.log("");
|
|
1802
|
+
if (enabled.alreadyEnabled) {
|
|
1803
|
+
console.log(paint.muted(`\u8BED\u4E49\u641C\u7D22\u5DF2\u662F\u542F\u7528\u72B6\u6001 (embed_model=${enabled.model})\uFF0C\u672A\u6539\u52A8 ${enabled.configPath}\u3002`));
|
|
1804
|
+
} else {
|
|
1805
|
+
for (const line of renderSemanticSearchInstructions(enabled.model)) {
|
|
1806
|
+
console.log(line);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1726
1810
|
}
|
|
1727
1811
|
return result;
|
|
1728
1812
|
}
|
|
@@ -1739,14 +1823,14 @@ var FABRIC_GITIGNORE_CONTENT = [
|
|
|
1739
1823
|
""
|
|
1740
1824
|
].join("\n");
|
|
1741
1825
|
function writeDefaultGitignore(fabricDir) {
|
|
1742
|
-
const target =
|
|
1743
|
-
if (
|
|
1826
|
+
const target = join7(fabricDir, ".gitignore");
|
|
1827
|
+
if (existsSync5(target)) return;
|
|
1744
1828
|
mkdirSync2(fabricDir, { recursive: true });
|
|
1745
|
-
|
|
1829
|
+
writeFileSync2(target, FABRIC_GITIGNORE_CONTENT, "utf8");
|
|
1746
1830
|
}
|
|
1747
1831
|
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1748
|
-
const target =
|
|
1749
|
-
if (
|
|
1832
|
+
const target = join7(fabricDir, "fabric-config.json");
|
|
1833
|
+
if (existsSync5(target)) return;
|
|
1750
1834
|
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1751
1835
|
const FABRIC_CONFIG_DEFAULTS = {
|
|
1752
1836
|
// Scan/import language policy. Fixated at init time by probing
|
|
@@ -1808,7 +1892,7 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
|
1808
1892
|
review_stale_pending_days: 14
|
|
1809
1893
|
};
|
|
1810
1894
|
mkdirSync2(fabricDir, { recursive: true });
|
|
1811
|
-
|
|
1895
|
+
writeFileSync2(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
1812
1896
|
log.info(
|
|
1813
1897
|
`Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
|
|
1814
1898
|
);
|
|
@@ -1885,7 +1969,7 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1885
1969
|
finalSupports: plan.supports
|
|
1886
1970
|
};
|
|
1887
1971
|
}
|
|
1888
|
-
if (
|
|
1972
|
+
if (existsSync5(plan.scaffold.fabricDir) && !statSync4(plan.scaffold.fabricDir).isDirectory()) {
|
|
1889
1973
|
throw new Error(
|
|
1890
1974
|
t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
|
|
1891
1975
|
);
|
|
@@ -1938,16 +2022,16 @@ function resolvePersonalFabricRoot() {
|
|
|
1938
2022
|
}
|
|
1939
2023
|
async function buildInitFabricPlan(target, options) {
|
|
1940
2024
|
assertExistingDirectory3(target);
|
|
1941
|
-
const fabricDir =
|
|
1942
|
-
const agentsMdPath =
|
|
1943
|
-
const agentsMdAction =
|
|
1944
|
-
const knowledgeDir =
|
|
1945
|
-
const personalKnowledgeDir =
|
|
1946
|
-
const forensicPath =
|
|
1947
|
-
const eventsPath =
|
|
1948
|
-
const metaPath =
|
|
2025
|
+
const fabricDir = join7(target, ".fabric");
|
|
2026
|
+
const agentsMdPath = join7(target, "AGENTS.md");
|
|
2027
|
+
const agentsMdAction = existsSync5(agentsMdPath) ? "preserved" : "created";
|
|
2028
|
+
const knowledgeDir = join7(fabricDir, "knowledge");
|
|
2029
|
+
const personalKnowledgeDir = join7(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
2030
|
+
const forensicPath = join7(fabricDir, "forensic.json");
|
|
2031
|
+
const eventsPath = join7(fabricDir, "events.jsonl");
|
|
2032
|
+
const metaPath = join7(fabricDir, "agents.meta.json");
|
|
1949
2033
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1950
|
-
const knowledgeDirAction =
|
|
2034
|
+
const knowledgeDirAction = existsSync5(knowledgeDir) ? "overwritten" : "created";
|
|
1951
2035
|
const metaClassification = classifyFreshPath(metaPath, "structural");
|
|
1952
2036
|
const eventsClassification = classifyFreshPath(eventsPath, "presence");
|
|
1953
2037
|
const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
|
|
@@ -1997,20 +2081,13 @@ async function executeInitFabricPlan(plan) {
|
|
|
1997
2081
|
writeDefaultGitignore(plan.fabricDir);
|
|
1998
2082
|
mkdirSync2(plan.knowledgeDir, { recursive: true });
|
|
1999
2083
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
2000
|
-
const teamSubDir =
|
|
2084
|
+
const teamSubDir = join7(plan.knowledgeDir, sub);
|
|
2001
2085
|
mkdirSync2(teamSubDir, { recursive: true });
|
|
2002
|
-
const teamGitkeep =
|
|
2003
|
-
if (!
|
|
2004
|
-
|
|
2086
|
+
const teamGitkeep = join7(teamSubDir, ".gitkeep");
|
|
2087
|
+
if (!existsSync5(teamGitkeep)) {
|
|
2088
|
+
writeFileSync2(teamGitkeep, "", "utf8");
|
|
2005
2089
|
}
|
|
2006
2090
|
}
|
|
2007
|
-
try {
|
|
2008
|
-
mkdirSync2(plan.personalKnowledgeDir, { recursive: true });
|
|
2009
|
-
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
2010
|
-
mkdirSync2(join6(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
2011
|
-
}
|
|
2012
|
-
} catch {
|
|
2013
|
-
}
|
|
2014
2091
|
if (plan.metaState === "missing") {
|
|
2015
2092
|
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
2016
2093
|
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
@@ -2018,11 +2095,11 @@ async function executeInitFabricPlan(plan) {
|
|
|
2018
2095
|
if (plan.eventsState === "missing") {
|
|
2019
2096
|
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
2020
2097
|
mkdirSync2(dirname(plan.eventsPath), { recursive: true });
|
|
2021
|
-
|
|
2098
|
+
writeFileSync2(plan.eventsPath, "", "utf8");
|
|
2022
2099
|
}
|
|
2023
2100
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2024
2101
|
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
2025
|
-
if (
|
|
2102
|
+
if (existsSync5(plan.eventsPath)) {
|
|
2026
2103
|
const applied = [];
|
|
2027
2104
|
const canonical = [];
|
|
2028
2105
|
const drifted = [];
|
|
@@ -2183,6 +2260,8 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2183
2260
|
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
2184
2261
|
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
2185
2262
|
installResults.push(...await runBestEffort("hook-cite-policy-evict-script", () => installCitePolicyEvictHook(plan.target)));
|
|
2263
|
+
installResults.push(...await runBestEffort("hook-session-end-script", () => installSessionEndMarkerHook(plan.target)));
|
|
2264
|
+
installResults.push(...await runBestEffort("hook-post-tooluse-script", () => installPostTooluseMutationHook(plan.target)));
|
|
2186
2265
|
installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
|
|
2187
2266
|
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
2188
2267
|
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
@@ -2238,7 +2317,7 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2238
2317
|
}
|
|
2239
2318
|
}
|
|
2240
2319
|
function shouldReplaceWritableDirectory(path, _options) {
|
|
2241
|
-
if (!
|
|
2320
|
+
if (!existsSync5(path)) {
|
|
2242
2321
|
return false;
|
|
2243
2322
|
}
|
|
2244
2323
|
if (statSync4(path).isDirectory()) {
|
|
@@ -2247,7 +2326,7 @@ function shouldReplaceWritableDirectory(path, _options) {
|
|
|
2247
2326
|
return false;
|
|
2248
2327
|
}
|
|
2249
2328
|
function classifyFreshPath(path, strategy) {
|
|
2250
|
-
if (!
|
|
2329
|
+
if (!existsSync5(path)) {
|
|
2251
2330
|
return { path, state: "missing" };
|
|
2252
2331
|
}
|
|
2253
2332
|
let stat;
|
|
@@ -2267,7 +2346,7 @@ function classifyFreshPath(path, strategy) {
|
|
|
2267
2346
|
return { path, state: "present-canonical" };
|
|
2268
2347
|
}
|
|
2269
2348
|
try {
|
|
2270
|
-
const raw =
|
|
2349
|
+
const raw = readFileSync4(path, "utf8");
|
|
2271
2350
|
const parsed = JSON.parse(raw);
|
|
2272
2351
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2273
2352
|
return { path, state: "user-modified", reason: "not a JSON object" };
|
|
@@ -2296,7 +2375,7 @@ function formatDiffFileState(state) {
|
|
|
2296
2375
|
}
|
|
2297
2376
|
function preparePlannedPath(path, action) {
|
|
2298
2377
|
mkdirSync2(dirname(path), { recursive: true });
|
|
2299
|
-
if (action === "overwritten" &&
|
|
2378
|
+
if (action === "overwritten" && existsSync5(path)) {
|
|
2300
2379
|
rmSync2(path, { recursive: true, force: true });
|
|
2301
2380
|
}
|
|
2302
2381
|
}
|
|
@@ -2450,19 +2529,19 @@ function normalizeTarget3(targetInput) {
|
|
|
2450
2529
|
return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2451
2530
|
}
|
|
2452
2531
|
function assertExistingDirectory3(target) {
|
|
2453
|
-
if (!
|
|
2532
|
+
if (!existsSync5(target) || !statSync4(target).isDirectory()) {
|
|
2454
2533
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2455
2534
|
}
|
|
2456
2535
|
}
|
|
2457
2536
|
function detectPackageManager(cwd) {
|
|
2458
2537
|
const workspaceRoot = resolve3(cwd);
|
|
2459
|
-
if (
|
|
2538
|
+
if (existsSync5(join7(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2460
2539
|
return "pnpm";
|
|
2461
2540
|
}
|
|
2462
|
-
if (
|
|
2541
|
+
if (existsSync5(join7(workspaceRoot, "yarn.lock"))) {
|
|
2463
2542
|
return "yarn";
|
|
2464
2543
|
}
|
|
2465
|
-
if (
|
|
2544
|
+
if (existsSync5(join7(workspaceRoot, "package-lock.json"))) {
|
|
2466
2545
|
return "npm";
|
|
2467
2546
|
}
|
|
2468
2547
|
return "npm";
|
|
@@ -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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scopeExplain
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EOT63RDH.js";
|
|
5
5
|
import {
|
|
6
6
|
getProjectTranslator
|
|
7
7
|
} from "./chunk-2CY4BMTH.js";
|
|
8
|
-
import "./chunk-LFIKMVY7.js";
|
|
9
|
-
import "./chunk-RYAFBNES.js";
|
|
10
8
|
|
|
11
9
|
// src/commands/scope-explain.ts
|
|
12
10
|
import { defineCommand } from "citty";
|
|
11
|
+
import { FabricError } from "@fenglimg/fabric-shared/errors";
|
|
13
12
|
var scope_explain_default = defineCommand({
|
|
14
13
|
meta: {
|
|
15
14
|
name: "scope-explain",
|
|
@@ -24,7 +23,18 @@ var scope_explain_default = defineCommand({
|
|
|
24
23
|
},
|
|
25
24
|
run({ args }) {
|
|
26
25
|
const projectRoot = process.cwd();
|
|
27
|
-
|
|
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
|
+
}
|
|
28
38
|
if (result === null) {
|
|
29
39
|
console.log(getProjectTranslator(projectRoot)("cli.cmd.no-global-config"));
|
|
30
40
|
return;
|