@fenglimg/fabric-cli 2.2.0-rc.1 → 2.2.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/chunk-27HK6H5Y.js +69 -0
- package/dist/{chunk-AOE6AYI7.js → chunk-2KBCTMID.js} +31 -8
- package/dist/chunk-3D7B2UAZ.js +149 -0
- package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
- package/dist/{plan-context-hint-FC6P3WFE.js → chunk-722JU5BP.js} +52 -12
- package/dist/{chunk-2R55HNVD.js → chunk-7ZDXBOOU.js} +234 -206
- package/dist/{doctor-YONYXDX6.js → chunk-E7HJUU34.js} +215 -52
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/chunk-FNHDQTPC.js +16 -0
- package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
- package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
- package/dist/{chunk-WU6GAPKH.js → chunk-PTGQAZEW.js} +12 -4
- 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/{config-XYRBZJDU.js → config-A3LTECAY.js} +4 -3
- package/dist/context-UJCGYOT6.js +117 -0
- package/dist/doctor-MDTZWKBK.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +133 -22
- package/dist/info-7FKBTMVO.js +139 -0
- package/dist/install-v2-WLEJ5XHT.js +3279 -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-5TNGH3R4.js +12 -0
- package/dist/{scope-explain-CDIZESP5.js → scope-explain-HLJZ2M33.js} +17 -6
- package/dist/status-4R3TM4FJ.js +37 -0
- package/dist/store-HOCORVL3.js +563 -0
- package/dist/{sync-UJ4BBCZJ.js → sync-DT5UJMMR.js} +197 -30
- package/dist/{uninstall-C3QXKOO6.js → uninstall-IFN2KYBK.js} +97 -140
- package/dist/whoami-ITGEFWH4.js +49 -0
- package/package.json +7 -5
- package/templates/hooks/cite-policy-evict.cjs +412 -160
- package/templates/hooks/configs/README.md +14 -27
- package/templates/hooks/configs/claude-code.json +17 -2
- package/templates/hooks/configs/codex-hooks.json +15 -3
- package/templates/hooks/fabric-hint.cjs +742 -259
- package/templates/hooks/knowledge-hint-broad.cjs +577 -274
- package/templates/hooks/knowledge-hint-narrow.cjs +113 -73
- package/templates/hooks/lib/banner-i18n.cjs +50 -1
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +118 -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/post-tooluse-mutation.cjs +386 -0
- package/templates/hooks/session-end-marker.cjs +140 -0
- package/templates/skills/fabric/SKILL.md +100 -0
- package/templates/skills/fabric-archive/SKILL.md +47 -24
- 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 +14 -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-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/install-74ANPCCP.js +0 -2737
- package/dist/status-GLQWLWH6.js +0 -23
- package/dist/store-XB3ADT65.js +0 -144
- package/dist/whoami-2MLO4Y37.js +0 -36
- package/templates/hooks/configs/cursor-hooks.json +0 -18
- package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
- package/templates/hooks/lib/summary-fallback.cjs +0 -210
package/README.md
CHANGED
|
@@ -7,19 +7,22 @@
|
|
|
7
7
|
1. 在 monorepo 根目录运行 `pnpm install`。
|
|
8
8
|
2. 用 `pnpm --filter @fenglimg/fabric-cli build` 构建 CLI。
|
|
9
9
|
3. 在目标项目运行 `fabric install`,完成一站式安装。
|
|
10
|
-
4.
|
|
10
|
+
4. 重启 Claude Code / Codex CLI,在客户端里验证 `fab_recall`。
|
|
11
11
|
|
|
12
|
-
`fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks
|
|
12
|
+
`fabric install` 会自动准备 bootstrap、MCP stdio 配置和 git hooks。当前公共命令面包括 `install`、`store`、`sync`、`info`、`doctor`、`uninstall`、`config`;`metrics`、`plan-context-hint`、`onboard-coverage` 是 hidden/internal 命令;`whoami` / `status` / `scope-explain` 作为 deprecated alias 保留到 v3。`fabric serve` 已 quarantine 到 `packages/server-http-experimental/`,主线不再注册。
|
|
13
13
|
|
|
14
14
|
## 常用命令
|
|
15
15
|
|
|
16
16
|
- `fabric install`
|
|
17
17
|
- `fabric doctor`
|
|
18
18
|
- `fabric doctor --json`
|
|
19
|
-
- `fabric doctor --strict`
|
|
20
19
|
- `fabric doctor --fix`
|
|
21
|
-
- `fabric
|
|
20
|
+
- `fabric doctor --fix-knowledge`
|
|
21
|
+
- `fabric store list`
|
|
22
|
+
- `fabric sync`
|
|
23
|
+
- `fabric info`
|
|
24
|
+
- `fabric metrics`(hidden/internal)
|
|
22
25
|
- `fabric uninstall`
|
|
23
26
|
- `fabric config`(rc.16 起将提供配置面板;当前为占位提示)
|
|
24
27
|
|
|
25
|
-
`fabric doctor --fix` 只修复确定性的派生状态,例如 `.fabric/agents.meta.json`、`.fabric/.cache/knowledge-test.index.json`、缺失的 `.fabric/events.jsonl` 和 stale hashes
|
|
28
|
+
`fabric doctor --fix` 只修复确定性的派生状态,例如 `.fabric/agents.meta.json`、`.fabric/.cache/knowledge-test.index.json`、缺失的 `.fabric/events.jsonl` 和 stale hashes。知识条目的 demote/archive/default backfill 走 `fabric doctor --fix-knowledge` 或 `fabric-review`;语义冲突、未完成的初始化确认和本地客户端配置问题仍需要人工处理。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
storeGitRemote
|
|
4
|
+
} from "./chunk-QPAW6IYT.js";
|
|
5
|
+
import {
|
|
6
|
+
loadProjectConfig
|
|
7
|
+
} from "./chunk-QFIVFZRH.js";
|
|
8
|
+
import {
|
|
9
|
+
loadGlobalConfig,
|
|
10
|
+
resolveGlobalRoot
|
|
11
|
+
} from "./chunk-FNHDQTPC.js";
|
|
12
|
+
|
|
13
|
+
// src/lib/unknown-flags.ts
|
|
14
|
+
function warnUnknownFlags(known) {
|
|
15
|
+
const knownSet = /* @__PURE__ */ new Set([...known, "help", "version"]);
|
|
16
|
+
const unknown = [];
|
|
17
|
+
for (const tok of process.argv.slice(2)) {
|
|
18
|
+
if (!tok.startsWith("--")) continue;
|
|
19
|
+
const name = tok.slice(2).split("=")[0].replace(/^no-/, "");
|
|
20
|
+
if (name.length === 0) continue;
|
|
21
|
+
if (!knownSet.has(name)) unknown.push(tok.split("=")[0]);
|
|
22
|
+
}
|
|
23
|
+
if (unknown.length > 0) {
|
|
24
|
+
process.stderr.write(`[fabric] ignored unknown flag(s): ${unknown.join(", ")}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/store/info-ops.ts
|
|
30
|
+
function whoami(globalRoot = resolveGlobalRoot()) {
|
|
31
|
+
const config = loadGlobalConfig(globalRoot);
|
|
32
|
+
if (config === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
uid: config.uid,
|
|
37
|
+
stores: config.stores.map((s) => ({
|
|
38
|
+
alias: s.alias,
|
|
39
|
+
mount_name: s.mount_name ?? null,
|
|
40
|
+
store_uuid: s.store_uuid,
|
|
41
|
+
// F4: parity with `fabric store list` — local-only reflects the store
|
|
42
|
+
// repo's TRUE git remote (what sync actually pushes to), not the registry
|
|
43
|
+
// metadata. A store with a physical `origin` but no registry `remote`
|
|
44
|
+
// (e.g. the personal store) was misreported as local-only by whoami while
|
|
45
|
+
// `store list` honestly showed its remote. Both now read the same source.
|
|
46
|
+
local_only: storeGitRemote(s.alias, globalRoot) === void 0
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function projectStatus(projectRoot, globalRoot = resolveGlobalRoot()) {
|
|
51
|
+
const global = loadGlobalConfig(globalRoot);
|
|
52
|
+
const project = loadProjectConfig(projectRoot);
|
|
53
|
+
return {
|
|
54
|
+
uid: global?.uid ?? null,
|
|
55
|
+
mounted: (global?.stores ?? []).map((s) => s.alias),
|
|
56
|
+
project_id: project?.project_id ?? null,
|
|
57
|
+
is_fabric_project: project !== null,
|
|
58
|
+
required: (project?.required_stores ?? []).map((r) => r.id),
|
|
59
|
+
active_write_store: project?.active_write_store ?? null,
|
|
60
|
+
default_write_store: project?.default_write_store ?? null,
|
|
61
|
+
write_routes: project?.write_routes ?? []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
warnUnknownFlags,
|
|
67
|
+
whoami,
|
|
68
|
+
projectStatus
|
|
69
|
+
};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveClients
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3IOLS5EK.js";
|
|
5
|
+
import {
|
|
6
|
+
loadGlobalConfig,
|
|
7
|
+
resolveGlobalRoot,
|
|
8
|
+
saveGlobalConfig
|
|
9
|
+
} from "./chunk-FNHDQTPC.js";
|
|
5
10
|
import {
|
|
6
11
|
t
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HORSMSZL.js";
|
|
8
13
|
|
|
9
14
|
// src/commands/config.ts
|
|
10
15
|
import { existsSync, statSync } from "fs";
|
|
@@ -18,8 +23,9 @@ import {
|
|
|
18
23
|
} from "@fenglimg/fabric-shared";
|
|
19
24
|
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
20
25
|
import { defineCommand } from "citty";
|
|
26
|
+
var LANGUAGE_FIELD_KEY = "fabric_language";
|
|
21
27
|
async function loadFabricConfig(workspaceRoot) {
|
|
22
|
-
const configPath = resolve(workspaceRoot, "fabric
|
|
28
|
+
const configPath = resolve(workspaceRoot, ".fabric", "fabric-config.json");
|
|
23
29
|
if (!existsSync(configPath)) {
|
|
24
30
|
return {};
|
|
25
31
|
}
|
|
@@ -178,8 +184,8 @@ var configCmd = defineCommand({
|
|
|
178
184
|
"onboard-reset": onboardResetCmd
|
|
179
185
|
},
|
|
180
186
|
async run({ args }) {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
187
|
+
const argvAfterConfig = process.argv.slice(3);
|
|
188
|
+
if (argvAfterConfig.includes("dismiss-slot") || argvAfterConfig.includes("onboard-reset")) {
|
|
183
189
|
return;
|
|
184
190
|
}
|
|
185
191
|
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
@@ -201,6 +207,10 @@ var configCmd = defineCommand({
|
|
|
201
207
|
let edited = false;
|
|
202
208
|
while (true) {
|
|
203
209
|
const current = await readPanelConfig(configPath);
|
|
210
|
+
const globalLanguage = loadGlobalConfig(resolveGlobalRoot())?.language;
|
|
211
|
+
if (globalLanguage !== void 0) {
|
|
212
|
+
current[LANGUAGE_FIELD_KEY] = globalLanguage;
|
|
213
|
+
}
|
|
204
214
|
const fields = getPanelFields();
|
|
205
215
|
const fieldChoice = await select({
|
|
206
216
|
message: t("cli.config.menu.field-select"),
|
|
@@ -234,9 +244,22 @@ var configCmd = defineCommand({
|
|
|
234
244
|
continue;
|
|
235
245
|
}
|
|
236
246
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
247
|
+
if (field.key === LANGUAGE_FIELD_KEY) {
|
|
248
|
+
const globalRoot = resolveGlobalRoot();
|
|
249
|
+
const globalConfig = loadGlobalConfig(globalRoot);
|
|
250
|
+
if (globalConfig === null) {
|
|
251
|
+
log.error(t("cli.config.errors.uninit-workspace.message"));
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
saveGlobalConfig(
|
|
255
|
+
{ ...globalConfig, language: newValue },
|
|
256
|
+
globalRoot
|
|
257
|
+
);
|
|
258
|
+
} else {
|
|
259
|
+
const refreshed = await readPanelConfig(configPath);
|
|
260
|
+
const merged = { ...refreshed, [field.key]: newValue };
|
|
261
|
+
await atomicWriteJson(configPath, merged);
|
|
262
|
+
}
|
|
240
263
|
edited = true;
|
|
241
264
|
log.success(
|
|
242
265
|
t("cli.config.write.success", {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
regenerateBindingsSnapshot
|
|
4
|
+
} from "./chunk-PTGQAZEW.js";
|
|
5
|
+
import {
|
|
6
|
+
storeBind,
|
|
7
|
+
storeProjectCreate,
|
|
8
|
+
storeProjectList,
|
|
9
|
+
storeSetWriteRoute,
|
|
10
|
+
storeSwitchWrite
|
|
11
|
+
} from "./chunk-QPAW6IYT.js";
|
|
12
|
+
import {
|
|
13
|
+
loadProjectConfig,
|
|
14
|
+
saveProjectConfig
|
|
15
|
+
} from "./chunk-QFIVFZRH.js";
|
|
16
|
+
|
|
17
|
+
// src/install/migrate-root-config.ts
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
19
|
+
import { dirname, join } from "path";
|
|
20
|
+
var ROOT_AUTHORITATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
21
|
+
"embed_enabled",
|
|
22
|
+
"embed_model",
|
|
23
|
+
"embed_weight",
|
|
24
|
+
"plan_context_top_k",
|
|
25
|
+
"recall_relevance_ratio",
|
|
26
|
+
"mcpPayloadLimits",
|
|
27
|
+
"selection_token_ttl_ms",
|
|
28
|
+
"orphan_demote_proven_days",
|
|
29
|
+
"orphan_demote_verified_days",
|
|
30
|
+
"orphan_demote_draft_days",
|
|
31
|
+
"clientPaths"
|
|
32
|
+
]);
|
|
33
|
+
function readJsonObject(path) {
|
|
34
|
+
if (!existsSync(path)) return null;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
37
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function migrateRootConfig(projectRoot) {
|
|
46
|
+
const rootPath = join(projectRoot, "fabric.config.json");
|
|
47
|
+
const fabricPath = join(projectRoot, ".fabric", "fabric-config.json");
|
|
48
|
+
const result = {
|
|
49
|
+
migrated: false,
|
|
50
|
+
mergedKeys: [],
|
|
51
|
+
rootPath,
|
|
52
|
+
fabricPath
|
|
53
|
+
};
|
|
54
|
+
const rootConfig = readJsonObject(rootPath);
|
|
55
|
+
if (rootConfig === null) {
|
|
56
|
+
if (existsSync(rootPath)) {
|
|
57
|
+
rmSync(rootPath, { force: true });
|
|
58
|
+
result.migrated = true;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
const fabricConfig = readJsonObject(fabricPath) ?? {};
|
|
63
|
+
const merged = { ...fabricConfig };
|
|
64
|
+
const mergedKeys = [];
|
|
65
|
+
for (const [key, value] of Object.entries(rootConfig)) {
|
|
66
|
+
const fabricHasKey = Object.prototype.hasOwnProperty.call(merged, key);
|
|
67
|
+
if (ROOT_AUTHORITATIVE_KEYS.has(key) || !fabricHasKey) {
|
|
68
|
+
if (!fabricHasKey || merged[key] !== value) {
|
|
69
|
+
mergedKeys.push(key);
|
|
70
|
+
}
|
|
71
|
+
merged[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(dirname(fabricPath), { recursive: true });
|
|
75
|
+
writeFileSync(fabricPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
76
|
+
rmSync(rootPath, { force: true });
|
|
77
|
+
result.migrated = true;
|
|
78
|
+
result.mergedKeys = mergedKeys;
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/install/store-project-onboarding.ts
|
|
83
|
+
import { execFileSync } from "child_process";
|
|
84
|
+
import { randomUUID } from "crypto";
|
|
85
|
+
import { basename } from "path";
|
|
86
|
+
function ensureProjectId(projectRoot, uuid = randomUUID()) {
|
|
87
|
+
const config = loadProjectConfig(projectRoot) ?? {};
|
|
88
|
+
if (typeof config.project_id === "string" && config.project_id.length > 0) {
|
|
89
|
+
return config.project_id;
|
|
90
|
+
}
|
|
91
|
+
saveProjectConfig({ ...config, project_id: uuid }, projectRoot);
|
|
92
|
+
return uuid;
|
|
93
|
+
}
|
|
94
|
+
function suggestStoreProjectId(projectRoot) {
|
|
95
|
+
return normalizeStoreProjectId(readGitRemoteName(projectRoot) ?? basename(projectRoot));
|
|
96
|
+
}
|
|
97
|
+
function normalizeStoreProjectId(value) {
|
|
98
|
+
const normalized = value.trim().replace(/\.git$/iu, "").toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/-+/gu, "-").replace(/^[-_]+|[-_]+$/gu, "");
|
|
99
|
+
return normalized.length > 0 ? normalized : "project";
|
|
100
|
+
}
|
|
101
|
+
async function ensureStoreProjectBinding(projectRoot, storeAlias, options) {
|
|
102
|
+
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
103
|
+
const project_id = ensureProjectId(projectRoot, options.uuid);
|
|
104
|
+
const currentConfig = loadProjectConfig(projectRoot);
|
|
105
|
+
const requested = options.requestedProjectId ?? currentConfig?.active_project ?? suggestStoreProjectId(projectRoot);
|
|
106
|
+
const active_project = normalizeStoreProjectId(requested);
|
|
107
|
+
const projects = await storeProjectList(storeAlias, options.globalRoot);
|
|
108
|
+
const project_created = !projects.some((project) => project.id === active_project);
|
|
109
|
+
if (project_created) {
|
|
110
|
+
await storeProjectCreate(storeAlias, active_project, now, {
|
|
111
|
+
name: active_project,
|
|
112
|
+
globalRoot: options.globalRoot
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const entry = options.suggestedRemote === void 0 ? { id: storeAlias } : { id: storeAlias, suggested_remote: options.suggestedRemote };
|
|
116
|
+
await storeBind(projectRoot, entry, { project: active_project, globalRoot: options.globalRoot });
|
|
117
|
+
storeSwitchWrite(projectRoot, storeAlias, { globalRoot: options.globalRoot });
|
|
118
|
+
storeSetWriteRoute(projectRoot, `project:${active_project}`, storeAlias, {
|
|
119
|
+
globalRoot: options.globalRoot
|
|
120
|
+
});
|
|
121
|
+
regenerateBindingsSnapshot(projectRoot, {
|
|
122
|
+
now,
|
|
123
|
+
globalRoot: options.globalRoot
|
|
124
|
+
});
|
|
125
|
+
return { project_id, active_project, project_created };
|
|
126
|
+
}
|
|
127
|
+
function readGitRemoteName(projectRoot) {
|
|
128
|
+
try {
|
|
129
|
+
const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
130
|
+
cwd: projectRoot,
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
133
|
+
}).trim();
|
|
134
|
+
if (remote.length === 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const lastSegment = remote.split(/[/:\\]/u).filter(Boolean).at(-1);
|
|
138
|
+
return lastSegment === void 0 ? null : lastSegment.replace(/\.git$/iu, "");
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
migrateRootConfig,
|
|
146
|
+
suggestStoreProjectId,
|
|
147
|
+
normalizeStoreProjectId,
|
|
148
|
+
ensureStoreProjectBinding
|
|
149
|
+
};
|
|
@@ -231,16 +231,6 @@ var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
|
231
231
|
return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
|
|
232
232
|
}
|
|
233
233
|
};
|
|
234
|
-
var CursorWriter = class extends JsonClientConfigWriter {
|
|
235
|
-
clientKind = "Cursor";
|
|
236
|
-
constructor(configuredPath) {
|
|
237
|
-
super(configuredPath);
|
|
238
|
-
}
|
|
239
|
-
defaultPath(workspaceRoot) {
|
|
240
|
-
const cursorDir = join(workspaceRoot, ".cursor");
|
|
241
|
-
return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
234
|
|
|
245
235
|
// src/config/claude-code.ts
|
|
246
236
|
function getClaudeDesktopConfigPath() {
|
|
@@ -342,16 +332,11 @@ function trimTrailingBlankLines(value) {
|
|
|
342
332
|
function removeCodexServerBlock(rawConfig, serverName) {
|
|
343
333
|
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
344
334
|
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
345
|
-
const legacyPattern = new RegExp(
|
|
346
|
-
String.raw`\n?\[mcp\.servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
347
|
-
"g"
|
|
348
|
-
);
|
|
349
335
|
const currentPattern = new RegExp(
|
|
350
336
|
String.raw`\n?\[mcp_servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
351
337
|
"g"
|
|
352
338
|
);
|
|
353
|
-
const
|
|
354
|
-
const withoutCurrent = withoutLegacy.replace(currentPattern, "");
|
|
339
|
+
const withoutCurrent = normalized.replace(currentPattern, "");
|
|
355
340
|
const changed = withoutCurrent !== normalized;
|
|
356
341
|
const text = changed ? `${trimTrailingBlankLines(withoutCurrent)}
|
|
357
342
|
` : rawConfig;
|
|
@@ -360,7 +345,6 @@ function removeCodexServerBlock(rawConfig, serverName) {
|
|
|
360
345
|
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
361
346
|
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
362
347
|
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
363
|
-
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
364
348
|
const currentPattern = new RegExp(
|
|
365
349
|
String.raw`\n?\[mcp_servers\.${escaped}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
366
350
|
"g"
|
|
@@ -368,8 +352,7 @@ function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
|
368
352
|
const existingMatch = normalized.match(currentPattern);
|
|
369
353
|
const preservedUserLines = existingMatch !== null && existingMatch.length > 0 ? extractCodexBlockUserLines(existingMatch[0]) : [];
|
|
370
354
|
const block = serializeCodexServerBlock(serverName, serverEntry, preservedUserLines);
|
|
371
|
-
const
|
|
372
|
-
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
355
|
+
const withoutExisting = normalized.replace(currentPattern, "");
|
|
373
356
|
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
374
357
|
if (trimmed.length === 0) {
|
|
375
358
|
return block;
|
|
@@ -474,12 +457,6 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
474
457
|
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
475
458
|
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
476
459
|
);
|
|
477
|
-
addIfDetected(
|
|
478
|
-
writers,
|
|
479
|
-
existsSync4(join4(workspaceRoot, ".cursor")),
|
|
480
|
-
(configuredPath) => new CursorWriter(configuredPath),
|
|
481
|
-
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
482
|
-
);
|
|
483
460
|
addIfDetected(
|
|
484
461
|
writers,
|
|
485
462
|
existsSync4(join4(homedir4(), ".codex")),
|
|
@@ -492,7 +469,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
492
469
|
const clientPaths = fabricConfig.clientPaths;
|
|
493
470
|
const claudeDetected = existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude"));
|
|
494
471
|
const claudeDesktopDetected = existsSync4(getClaudeDesktopConfigPath());
|
|
495
|
-
const cursorDetected = existsSync4(join4(workspaceRoot, ".cursor"));
|
|
496
472
|
const codexDetected = existsSync4(join4(homedir4(), ".codex"));
|
|
497
473
|
return [
|
|
498
474
|
{
|
|
@@ -523,18 +499,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
523
499
|
skill: false
|
|
524
500
|
}
|
|
525
501
|
},
|
|
526
|
-
{
|
|
527
|
-
clientKind: "Cursor",
|
|
528
|
-
label: "Cursor",
|
|
529
|
-
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
530
|
-
configPath: ".cursor/mcp.json",
|
|
531
|
-
capabilities: {
|
|
532
|
-
bootstrap: true,
|
|
533
|
-
mcp: true,
|
|
534
|
-
hook: false,
|
|
535
|
-
skill: false
|
|
536
|
-
}
|
|
537
|
-
},
|
|
538
502
|
{
|
|
539
503
|
clientKind: "CodexCLI",
|
|
540
504
|
label: "Codex CLI",
|
|
@@ -554,6 +518,27 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
554
518
|
// skills as uninstalled even right after installing them).
|
|
555
519
|
skill: existsSync4(join4(workspaceRoot, ".codex", "skills"))
|
|
556
520
|
}
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
clientKind: "CodexDesktop",
|
|
524
|
+
label: "Codex Desktop",
|
|
525
|
+
// Codex Desktop shares the same ~/.codex config as Codex CLI — there is no
|
|
526
|
+
// separate adapter work: installing the Codex CLI assets (MCP config /
|
|
527
|
+
// hooks / skills) makes Desktop ready too. Display-only row mirroring Codex
|
|
528
|
+
// CLI's detection + installed state; no dedicated writer (CodexTOMLConfigWriter
|
|
529
|
+
// already targets the shared config, so removing/adding it once covers both).
|
|
530
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
531
|
+
configPath: "~/.codex/config.toml (shared with Codex CLI)",
|
|
532
|
+
capabilities: {
|
|
533
|
+
bootstrap: true,
|
|
534
|
+
mcp: true,
|
|
535
|
+
hook: true,
|
|
536
|
+
skill: true
|
|
537
|
+
},
|
|
538
|
+
installedCapabilities: {
|
|
539
|
+
hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
|
|
540
|
+
skill: existsSync4(join4(workspaceRoot, ".codex", "skills"))
|
|
541
|
+
}
|
|
557
542
|
}
|
|
558
543
|
];
|
|
559
544
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveDevMode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WA3DYGSY.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/plan-context-hint.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
buildAlwaysActiveBodies,
|
|
10
|
+
buildKnowledgeCensus,
|
|
11
|
+
planContext
|
|
12
|
+
} from "@fenglimg/fabric-server";
|
|
9
13
|
var ALL_PATHS_SENTINEL = "**";
|
|
10
14
|
var planContextHintCommand = defineCommand({
|
|
11
15
|
meta: {
|
|
@@ -56,22 +60,49 @@ async function runPlanContextHint(opts) {
|
|
|
56
60
|
const targetPaths = all ? [ALL_PATHS_SENTINEL] : explicitPaths.length > 0 ? explicitPaths : [ALL_PATHS_SENTINEL];
|
|
57
61
|
const resolution = resolveDevMode(opts.target, process.cwd());
|
|
58
62
|
const result = await planContext(resolution.target, {
|
|
59
|
-
paths: targetPaths
|
|
63
|
+
paths: targetPaths,
|
|
64
|
+
// lifecycle-refactor W3-T2 (§7 图谱消费 / §5): default-enable graph二阶召回 for
|
|
65
|
+
// the hint path. planContext appends the one-hop `related` neighbours that
|
|
66
|
+
// ranked outside top_k of the surfaced set and reports them in
|
|
67
|
+
// `related_appended` (appended id → source id). Honest no-op when the
|
|
68
|
+
// surfaced set declares no in-corpus related edge.
|
|
69
|
+
include_related: true
|
|
60
70
|
});
|
|
61
71
|
const candidates = result.candidates;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
const relatedAppended = result.related_appended ?? {};
|
|
73
|
+
const entries = candidates.map((item) => {
|
|
74
|
+
const relatedTo = relatedAppended[item.stable_id];
|
|
75
|
+
return {
|
|
76
|
+
id: item.stable_id,
|
|
77
|
+
type: item.description.knowledge_type ?? "",
|
|
78
|
+
maturity: item.description.maturity ?? "",
|
|
79
|
+
summary: item.description.summary,
|
|
80
|
+
relevance_scope: item.description.relevance_scope ?? "broad",
|
|
81
|
+
// W2-2 (KT-DEC-0027): forward the must_read_if trigger hook for the
|
|
82
|
+
// SessionStart REFERENCE rendering. Omitted when absent/empty.
|
|
83
|
+
...typeof item.description.must_read_if === "string" && item.description.must_read_if.length > 0 ? { must_read_if: item.description.must_read_if } : {},
|
|
84
|
+
// Only set when this entry was pulled in via a graph edge — its presence
|
|
85
|
+
// is the honest signal, never synthesized for ordinarily-ranked entries.
|
|
86
|
+
...typeof relatedTo === "string" ? { related_to: relatedTo } : {}
|
|
87
|
+
};
|
|
88
|
+
});
|
|
69
89
|
let narrow_count = 0;
|
|
70
90
|
let broad_only_count = 0;
|
|
71
91
|
for (const e of entries) {
|
|
72
92
|
if (e.relevance_scope === "narrow") narrow_count += 1;
|
|
73
93
|
else broad_only_count += 1;
|
|
74
94
|
}
|
|
95
|
+
const alwaysBodies = await buildAlwaysActiveBodies(resolution.target).catch(() => []);
|
|
96
|
+
const census = await buildKnowledgeCensus(resolution.target).catch(
|
|
97
|
+
() => ({
|
|
98
|
+
by_type: {},
|
|
99
|
+
by_layer: { team: 0, personal: 0, project: 0 },
|
|
100
|
+
broad_by_type: {},
|
|
101
|
+
narrow_total: 0,
|
|
102
|
+
dropped_other_project: 0,
|
|
103
|
+
total: 0
|
|
104
|
+
})
|
|
105
|
+
);
|
|
75
106
|
const output = {
|
|
76
107
|
version: 2,
|
|
77
108
|
revision_hash: result.revision_hash,
|
|
@@ -81,7 +112,15 @@ async function runPlanContextHint(opts) {
|
|
|
81
112
|
// semantics unchanged from rc.18 (total candidate count).
|
|
82
113
|
broad_count: candidates.length,
|
|
83
114
|
narrow_count,
|
|
84
|
-
broad_only_count
|
|
115
|
+
broad_only_count,
|
|
116
|
+
always_bodies: alwaysBodies.map((b) => ({
|
|
117
|
+
id: b.stable_id,
|
|
118
|
+
type: b.type,
|
|
119
|
+
layer: b.layer,
|
|
120
|
+
summary: b.summary,
|
|
121
|
+
body: b.body
|
|
122
|
+
})),
|
|
123
|
+
census
|
|
85
124
|
};
|
|
86
125
|
if (result.auto_healed === true) {
|
|
87
126
|
output.auto_healed = true;
|
|
@@ -97,8 +136,9 @@ function parsePathsArg(raw) {
|
|
|
97
136
|
}
|
|
98
137
|
return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
99
138
|
}
|
|
139
|
+
|
|
100
140
|
export {
|
|
101
|
-
plan_context_hint_default as default,
|
|
102
141
|
planContextHintCommand,
|
|
142
|
+
plan_context_hint_default,
|
|
103
143
|
runPlanContextHint
|
|
104
144
|
};
|