@fenglimg/fabric-cli 2.0.0-rc.15 → 2.0.0-rc.21
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-4HC5ZK7H.js +598 -0
- package/dist/{chunk-AXIFEVAS.js → chunk-FNO7CQDG.js} +1 -1
- package/dist/chunk-KZ2YITOS.js +225 -0
- package/dist/{chunk-SKSYUHKK.js → chunk-MF3OTILQ.js} +0 -4
- package/dist/{chunk-OBQU6NHO.js → chunk-ZSESMG6L.js} +0 -6
- package/dist/{config-7YD365I3.js → config-AYP5F72E.js} +2 -2
- package/dist/{doctor-6XHLQJXB.js → doctor-L6TIXXIX.js} +129 -3
- package/dist/index.js +11 -8
- package/dist/{install-JLDCHAXV.js → install-DNZXGFHJ.js} +23 -25
- package/dist/{plan-context-hint-73U4FGKO.js → plan-context-hint-CFDGXHCA.js} +4 -4
- package/dist/{serve-L3X5UHG2.js → serve-6PPQX7AW.js} +1 -1
- package/dist/{uninstall-DD6FIFCI.js → uninstall-L2HEEOU3.js} +147 -55
- package/package.json +3 -3
- package/templates/hooks/fabric-hint.cjs +350 -21
- package/templates/hooks/knowledge-hint-broad.cjs +39 -14
- package/templates/hooks/knowledge-hint-narrow.cjs +31 -7
- package/templates/hooks/lib/banner-i18n.cjs +252 -0
- package/dist/chunk-AIB54QRT.js +0 -82
- package/dist/chunk-UTF4YBDN.js +0 -366
- package/templates/agents-md/AGENTS.md.template +0 -59
- package/templates/bootstrap/CLAUDE.md +0 -8
- package/templates/bootstrap/codex-AGENTS-header.md +0 -6
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveClients
|
|
4
|
+
} from "./chunk-MF3OTILQ.js";
|
|
5
|
+
import {
|
|
6
|
+
t
|
|
7
|
+
} from "./chunk-6ICJICVU.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/config.ts
|
|
10
|
+
import { existsSync, statSync } from "fs";
|
|
11
|
+
import { readFile } from "fs/promises";
|
|
12
|
+
import { join, resolve } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
|
|
15
|
+
import { getPanelFields } from "@fenglimg/fabric-shared";
|
|
16
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
17
|
+
import { defineCommand } from "citty";
|
|
18
|
+
async function loadFabricConfig(workspaceRoot) {
|
|
19
|
+
const configPath = resolve(workspaceRoot, "fabric.config.json");
|
|
20
|
+
if (!existsSync(configPath)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const parsed = JSON.parse(await readFile(configPath, "utf8"));
|
|
24
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
25
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function resolveServerPath(override) {
|
|
30
|
+
if (override) return override;
|
|
31
|
+
if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
|
|
32
|
+
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
33
|
+
}
|
|
34
|
+
var PANEL_CONFIG_RELATIVE_PATH = [".fabric", "fabric-config.json"];
|
|
35
|
+
var EXIT_CHOICE = "__exit__";
|
|
36
|
+
var configCmd = defineCommand({
|
|
37
|
+
meta: {
|
|
38
|
+
name: "config",
|
|
39
|
+
description: t("cli.config.description")
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
target: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: t("cli.config.args.target.description"),
|
|
45
|
+
valueHint: "path"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async run({ args }) {
|
|
49
|
+
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
50
|
+
const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
|
|
51
|
+
const fabricDir = join(workspaceRoot, ".fabric");
|
|
52
|
+
const fabricDirOk = existsSync(fabricDir) && statSync(fabricDir).isDirectory();
|
|
53
|
+
const configOk = fabricDirOk && existsSync(configPath);
|
|
54
|
+
if (!configOk) {
|
|
55
|
+
console.error(t("cli.config.errors.uninit-workspace.message"));
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!isInteractiveConfig()) {
|
|
60
|
+
console.log(t("cli.config.intro"));
|
|
61
|
+
console.log(t("cli.config.non-tty-notice"));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
intro(t("cli.config.intro"));
|
|
65
|
+
let edited = false;
|
|
66
|
+
while (true) {
|
|
67
|
+
const current = await readPanelConfig(configPath);
|
|
68
|
+
const fields = getPanelFields();
|
|
69
|
+
const fieldChoice = await select({
|
|
70
|
+
message: t("cli.config.menu.field-select"),
|
|
71
|
+
options: [
|
|
72
|
+
...fields.map((field2) => ({
|
|
73
|
+
value: field2.key,
|
|
74
|
+
label: formatFieldMenuLabel(field2, current)
|
|
75
|
+
})),
|
|
76
|
+
{ value: EXIT_CHOICE, label: t("cli.config.menu.exit") }
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
if (isCancel(fieldChoice)) {
|
|
80
|
+
cancel(t("cli.config.cancel"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (fieldChoice === EXIT_CHOICE) {
|
|
84
|
+
outro(edited ? t("cli.config.outro") : t("cli.config.outro-no-changes"));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const field = fields.find((f) => f.key === fieldChoice);
|
|
88
|
+
if (!field) {
|
|
89
|
+
log.warn(t("cli.config.errors.unknown-field"));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const newValue = await promptFieldValue(field, current);
|
|
93
|
+
if (newValue === CANCELLED) {
|
|
94
|
+
cancel(t("cli.config.cancel"));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (newValue === SKIPPED) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const refreshed = await readPanelConfig(configPath);
|
|
102
|
+
const merged = { ...refreshed, [field.key]: newValue };
|
|
103
|
+
await atomicWriteJson(configPath, merged);
|
|
104
|
+
edited = true;
|
|
105
|
+
log.success(
|
|
106
|
+
t("cli.config.write.success", {
|
|
107
|
+
key: field.key,
|
|
108
|
+
value: field.format_for_display(newValue)
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
log.error(t("cli.config.write.failure", { message }));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
var config_default = configCmd;
|
|
119
|
+
var CANCELLED = /* @__PURE__ */ Symbol("config-cancelled");
|
|
120
|
+
var SKIPPED = /* @__PURE__ */ Symbol("config-skipped");
|
|
121
|
+
async function promptFieldValue(field, current) {
|
|
122
|
+
const currentValue = current[field.key];
|
|
123
|
+
const currentDisplay = field.format_for_display(currentValue);
|
|
124
|
+
if (field.widget === "select") {
|
|
125
|
+
const enumValues = field.enum_values ?? [];
|
|
126
|
+
if (enumValues.length === 0) {
|
|
127
|
+
log.warn(t("cli.config.errors.no-enum-options"));
|
|
128
|
+
return SKIPPED;
|
|
129
|
+
}
|
|
130
|
+
const initialValue = enumValues.includes(String(currentValue)) ? String(currentValue) : enumValues.includes(String(field.default)) ? String(field.default) : enumValues[0];
|
|
131
|
+
const picked = await select({
|
|
132
|
+
message: t("cli.config.prompt.select", {
|
|
133
|
+
key: field.key,
|
|
134
|
+
current: currentDisplay
|
|
135
|
+
}),
|
|
136
|
+
options: enumValues.map((value) => ({ value, label: value })),
|
|
137
|
+
initialValue
|
|
138
|
+
});
|
|
139
|
+
if (isCancel(picked)) {
|
|
140
|
+
return CANCELLED;
|
|
141
|
+
}
|
|
142
|
+
const result = field.validate(String(picked));
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
log.error(result.error);
|
|
145
|
+
return SKIPPED;
|
|
146
|
+
}
|
|
147
|
+
return result.value;
|
|
148
|
+
}
|
|
149
|
+
const entered = await text({
|
|
150
|
+
message: t("cli.config.prompt.text", {
|
|
151
|
+
key: field.key,
|
|
152
|
+
current: currentDisplay
|
|
153
|
+
}),
|
|
154
|
+
placeholder: currentDisplay,
|
|
155
|
+
initialValue: currentDisplay,
|
|
156
|
+
validate(raw) {
|
|
157
|
+
const result = field.validate(raw ?? "");
|
|
158
|
+
return result.ok ? void 0 : result.error;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (isCancel(entered)) {
|
|
162
|
+
return CANCELLED;
|
|
163
|
+
}
|
|
164
|
+
const finalResult = field.validate(String(entered));
|
|
165
|
+
if (!finalResult.ok) {
|
|
166
|
+
log.error(finalResult.error);
|
|
167
|
+
return SKIPPED;
|
|
168
|
+
}
|
|
169
|
+
return finalResult.value;
|
|
170
|
+
}
|
|
171
|
+
function formatFieldMenuLabel(field, current) {
|
|
172
|
+
const key = field.key;
|
|
173
|
+
const rawValue = current[key];
|
|
174
|
+
const display = field.format_for_display(rawValue);
|
|
175
|
+
const isDefault = rawValue === void 0 || rawValue === null;
|
|
176
|
+
const labelText = t(field.label_i18n_key);
|
|
177
|
+
const valueLabel = isDefault ? `${display} ${t("cli.config.value.default-marker")}` : display;
|
|
178
|
+
return `[${field.group}] ${key} (${labelText}) \u2014 ${t("cli.config.value.current", { value: valueLabel })}`;
|
|
179
|
+
}
|
|
180
|
+
async function readPanelConfig(configPath) {
|
|
181
|
+
const raw = await readFile(configPath, "utf8");
|
|
182
|
+
const parsed = JSON.parse(raw);
|
|
183
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
184
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
185
|
+
}
|
|
186
|
+
return parsed;
|
|
187
|
+
}
|
|
188
|
+
function isInteractiveConfig() {
|
|
189
|
+
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
190
|
+
}
|
|
191
|
+
async function installMcpClients(target, options = {}) {
|
|
192
|
+
const workspaceRoot = resolve(target);
|
|
193
|
+
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
194
|
+
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
195
|
+
const serverPath = resolveServerPath(options.localServerPath);
|
|
196
|
+
const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
|
|
197
|
+
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
198
|
+
);
|
|
199
|
+
const installed = [];
|
|
200
|
+
const skipped = [];
|
|
201
|
+
const details = [];
|
|
202
|
+
for (const writer of writers) {
|
|
203
|
+
const configPath = await writer.detect(workspaceRoot);
|
|
204
|
+
if (configPath === null) {
|
|
205
|
+
skipped.push(writer.clientKind);
|
|
206
|
+
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (options.dryRun) {
|
|
210
|
+
skipped.push(writer.clientKind);
|
|
211
|
+
details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
await writer.write(serverPath, workspaceRoot);
|
|
215
|
+
installed.push(writer.clientKind);
|
|
216
|
+
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
217
|
+
}
|
|
218
|
+
return { installed, skipped, details };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export {
|
|
222
|
+
configCmd,
|
|
223
|
+
config_default,
|
|
224
|
+
installMcpClients
|
|
225
|
+
};
|
|
@@ -479,7 +479,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
479
479
|
clientKind: "ClaudeCodeCLI",
|
|
480
480
|
label: "Claude Code CLI",
|
|
481
481
|
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
482
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
483
482
|
configPath: "project .claude/settings.json",
|
|
484
483
|
capabilities: {
|
|
485
484
|
bootstrap: true,
|
|
@@ -496,7 +495,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
496
495
|
clientKind: "ClaudeCodeDesktop",
|
|
497
496
|
label: "Claude Code Desktop",
|
|
498
497
|
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
499
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
500
498
|
configPath: "desktop Claude config",
|
|
501
499
|
capabilities: {
|
|
502
500
|
bootstrap: true,
|
|
@@ -509,7 +507,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
509
507
|
clientKind: "Cursor",
|
|
510
508
|
label: "Cursor",
|
|
511
509
|
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
512
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
513
510
|
configPath: ".cursor/mcp.json",
|
|
514
511
|
capabilities: {
|
|
515
512
|
bootstrap: true,
|
|
@@ -522,7 +519,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
522
519
|
clientKind: "CodexCLI",
|
|
523
520
|
label: "Codex CLI",
|
|
524
521
|
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
525
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
526
522
|
configPath: "~/.codex/config.toml",
|
|
527
523
|
capabilities: {
|
|
528
524
|
bootstrap: true,
|
|
@@ -16,13 +16,10 @@ function readFabricConfig(workspaceRoot = process.cwd()) {
|
|
|
16
16
|
}
|
|
17
17
|
function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
|
|
18
18
|
const envTarget = normalizeTarget(process.env.EXTERNAL_FIXTURE_PATH, workspaceRoot);
|
|
19
|
-
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
20
|
-
const configTarget = normalizeTarget(fabricConfig.externalFixturePath, workspaceRoot);
|
|
21
19
|
const directTarget = normalizeTarget(cliTarget, workspaceRoot);
|
|
22
20
|
const chain = [
|
|
23
21
|
formatResolutionStep("cliTarget", directTarget),
|
|
24
22
|
formatResolutionStep("EXTERNAL_FIXTURE_PATH", envTarget),
|
|
25
|
-
formatResolutionStep("fabric.config.json.externalFixturePath", configTarget),
|
|
26
23
|
formatResolutionStep("process.cwd()", workspaceRoot)
|
|
27
24
|
];
|
|
28
25
|
if (directTarget !== void 0) {
|
|
@@ -31,9 +28,6 @@ function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
|
|
|
31
28
|
if (envTarget !== void 0) {
|
|
32
29
|
return { target: envTarget, source: "env", chain };
|
|
33
30
|
}
|
|
34
|
-
if (configTarget !== void 0) {
|
|
35
|
-
return { target: configTarget, source: "config", chain };
|
|
36
|
-
}
|
|
37
31
|
return { target: workspaceRoot, source: "cwd", chain };
|
|
38
32
|
}
|
|
39
33
|
function createDebugLogger(debug) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runInitScan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FNO7CQDG.js";
|
|
5
5
|
import {
|
|
6
6
|
hasActionHint,
|
|
7
7
|
paint,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-6ICJICVU.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveDevMode
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ZSESMG6L.js";
|
|
17
17
|
|
|
18
18
|
// src/commands/doctor.ts
|
|
19
19
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
appendEventLedgerEvent,
|
|
23
23
|
checkLockOrThrow,
|
|
24
24
|
runDoctorApplyLint as runDoctorFixKnowledge,
|
|
25
|
+
runDoctorCiteCoverage,
|
|
25
26
|
runDoctorFix,
|
|
26
27
|
runDoctorReport
|
|
27
28
|
} from "@fenglimg/fabric-server";
|
|
@@ -75,6 +76,25 @@ var doctorCommand = defineCommand({
|
|
|
75
76
|
type: "boolean",
|
|
76
77
|
description: t("cli.doctor.args.yes.description"),
|
|
77
78
|
default: false
|
|
79
|
+
},
|
|
80
|
+
// rc.20 TASK-05: cite policy adherence report (read-only). Skips standard
|
|
81
|
+
// inspections entirely — different output surface. Mutually exclusive
|
|
82
|
+
// with --fix / --fix-knowledge (enforced in run()).
|
|
83
|
+
"cite-coverage": {
|
|
84
|
+
type: "boolean",
|
|
85
|
+
description: t("cli.doctor.args.cite-coverage.description"),
|
|
86
|
+
default: false
|
|
87
|
+
},
|
|
88
|
+
since: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: t("cli.doctor.args.since.description"),
|
|
91
|
+
default: "7d"
|
|
92
|
+
},
|
|
93
|
+
client: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: t("cli.doctor.args.client.description"),
|
|
96
|
+
default: "all",
|
|
97
|
+
valueHint: "cc|codex|cursor|all"
|
|
78
98
|
}
|
|
79
99
|
},
|
|
80
100
|
async run({ args }) {
|
|
@@ -92,6 +112,34 @@ var doctorCommand = defineCommand({
|
|
|
92
112
|
const fixKnowledge = args["fix-knowledge"] === true;
|
|
93
113
|
const fix = args.fix === true;
|
|
94
114
|
const rescan = args.rescan === true;
|
|
115
|
+
const citeCoverage = args["cite-coverage"] === true;
|
|
116
|
+
if (citeCoverage) {
|
|
117
|
+
if (fix || fixKnowledge) {
|
|
118
|
+
writeStderr(t("cli.doctor.errors.cite-coverage-mutex"));
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let sinceMs;
|
|
123
|
+
try {
|
|
124
|
+
sinceMs = parseSinceDuration(args.since ?? "7d");
|
|
125
|
+
} catch {
|
|
126
|
+
writeStderr(t("cli.doctor.errors.invalid-since", { input: args.since ?? "7d" }));
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const clientFilter = args.client ?? "all";
|
|
131
|
+
if (!isValidClientFilter(clientFilter)) {
|
|
132
|
+
writeStderr(t("cli.doctor.errors.invalid-client", { input: clientFilter }));
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const report2 = await runDoctorCiteCoverage(resolution.target, {
|
|
137
|
+
since: sinceMs,
|
|
138
|
+
client: clientFilter
|
|
139
|
+
});
|
|
140
|
+
renderCiteCoverageReport(report2, args.json === true);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
95
143
|
if (fixKnowledge && fix) {
|
|
96
144
|
writeStderr(t("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
|
|
97
145
|
process.exitCode = 1;
|
|
@@ -293,7 +341,85 @@ async function resolveFixKnowledgeConsent(options) {
|
|
|
293
341
|
}
|
|
294
342
|
return "proceed";
|
|
295
343
|
}
|
|
344
|
+
var CITE_COVERAGE_CLIENT_FILTERS = /* @__PURE__ */ new Set([
|
|
345
|
+
"cc",
|
|
346
|
+
"codex",
|
|
347
|
+
"cursor",
|
|
348
|
+
"all"
|
|
349
|
+
]);
|
|
350
|
+
function isValidClientFilter(input) {
|
|
351
|
+
return CITE_COVERAGE_CLIENT_FILTERS.has(input);
|
|
352
|
+
}
|
|
353
|
+
function renderCiteCoverageReport(report, jsonMode) {
|
|
354
|
+
if (jsonMode) {
|
|
355
|
+
writeStdout(JSON.stringify(report, null, 2));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (report.status === "skipped") {
|
|
359
|
+
writeStdout(t("doctor.cite.status.skipped"));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const lines = [];
|
|
363
|
+
lines.push(t("doctor.section.cite-coverage"));
|
|
364
|
+
lines.push(
|
|
365
|
+
t("doctor.cite.header", {
|
|
366
|
+
since: new Date(report.since_ts).toISOString(),
|
|
367
|
+
marker: new Date(report.marker_ts).toISOString()
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
if (report.marker_emitted_now) {
|
|
371
|
+
lines.push(t("doctor.cite.warning.justActivated"));
|
|
372
|
+
}
|
|
373
|
+
lines.push("");
|
|
374
|
+
lines.push(` ${t("doctor.cite.metric.editsTouched")}: ${report.metrics.edits_touched}`);
|
|
375
|
+
lines.push(` ${t("doctor.cite.metric.qualifyingCites")}: ${report.metrics.qualifying_cites}`);
|
|
376
|
+
lines.push(` ${t("doctor.cite.metric.recalledUnverified")}: ${report.metrics.recalled_unverified}`);
|
|
377
|
+
lines.push(` ${t("doctor.cite.metric.expectedButMissed")}: ${report.metrics.expected_but_missed}`);
|
|
378
|
+
lines.push(` ${t("doctor.cite.metric.totalTurns")}: ${report.metrics.total_turns}`);
|
|
379
|
+
if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
|
|
380
|
+
lines.push("");
|
|
381
|
+
lines.push(`### ${t("doctor.cite.section.perClient")}`);
|
|
382
|
+
for (const [client, metrics] of Object.entries(report.per_client)) {
|
|
383
|
+
const summary = Object.entries(metrics).map(([k, v]) => `${k}=${v}`).join(" / ");
|
|
384
|
+
lines.push(` ${client}: ${summary}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (report.dismissed_reason_histogram !== void 0 && Object.keys(report.dismissed_reason_histogram).length > 0) {
|
|
388
|
+
lines.push("");
|
|
389
|
+
lines.push(`### ${t("doctor.cite.section.dismissedReasons")}`);
|
|
390
|
+
for (const [reason, count] of Object.entries(report.dismissed_reason_histogram)) {
|
|
391
|
+
const label = t(`doctor.cite.dismissed.${reason}`);
|
|
392
|
+
lines.push(` ${label}: ${count}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
writeStdout(lines.join("\n"));
|
|
396
|
+
}
|
|
397
|
+
function parseSinceDuration(input) {
|
|
398
|
+
const trimmed = input.trim();
|
|
399
|
+
if (trimmed.length === 0) {
|
|
400
|
+
throw new Error(`invalid --since value: ${input}`);
|
|
401
|
+
}
|
|
402
|
+
const durationMatch = /^(\d+)([dhm])$/.exec(trimmed);
|
|
403
|
+
if (durationMatch !== null) {
|
|
404
|
+
const value = Number.parseInt(durationMatch[1], 10);
|
|
405
|
+
const unit = durationMatch[2];
|
|
406
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
407
|
+
throw new Error(`invalid --since value: ${input}`);
|
|
408
|
+
}
|
|
409
|
+
const unitMs = unit === "d" ? 864e5 : unit === "h" ? 36e5 : 6e4;
|
|
410
|
+
return Date.now() - value * unitMs;
|
|
411
|
+
}
|
|
412
|
+
if (/^\d+$/.test(trimmed)) {
|
|
413
|
+
const value = Number.parseInt(trimmed, 10);
|
|
414
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
415
|
+
throw new Error(`invalid --since value: ${input}`);
|
|
416
|
+
}
|
|
417
|
+
return value;
|
|
418
|
+
}
|
|
419
|
+
throw new Error(`invalid --since value: ${input}`);
|
|
420
|
+
}
|
|
296
421
|
export {
|
|
297
422
|
doctor_default as default,
|
|
298
|
-
doctorCommand
|
|
423
|
+
doctorCommand,
|
|
424
|
+
parseSinceDuration
|
|
299
425
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
t
|
|
4
|
+
} from "./chunk-6ICJICVU.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
7
|
import { realpathSync } from "fs";
|
|
@@ -8,20 +11,20 @@ import { defineCommand, runMain } from "citty";
|
|
|
8
11
|
|
|
9
12
|
// src/commands/index.ts
|
|
10
13
|
var allCommands = {
|
|
11
|
-
install: () => import("./install-
|
|
12
|
-
doctor: () => import("./doctor-
|
|
13
|
-
serve: () => import("./serve-
|
|
14
|
-
uninstall: () => import("./uninstall-
|
|
15
|
-
config: () => import("./config-
|
|
16
|
-
"plan-context-hint": () => import("./plan-context-hint-
|
|
14
|
+
install: () => import("./install-DNZXGFHJ.js").then((module) => module.default),
|
|
15
|
+
doctor: () => import("./doctor-L6TIXXIX.js").then((module) => module.default),
|
|
16
|
+
serve: () => import("./serve-6PPQX7AW.js").then((module) => module.default),
|
|
17
|
+
uninstall: () => import("./uninstall-L2HEEOU3.js").then((module) => module.default),
|
|
18
|
+
config: () => import("./config-AYP5F72E.js").then((module) => module.default),
|
|
19
|
+
"plan-context-hint": () => import("./plan-context-hint-CFDGXHCA.js").then((module) => module.default)
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
// src/index.ts
|
|
20
23
|
var main = defineCommand({
|
|
21
24
|
meta: {
|
|
22
25
|
name: "fabric",
|
|
23
|
-
version: "2.0.0-rc.
|
|
24
|
-
description:
|
|
26
|
+
version: "2.0.0-rc.21",
|
|
27
|
+
description: t("cli.main.description")
|
|
25
28
|
},
|
|
26
29
|
subCommands: allCommands
|
|
27
30
|
});
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
installMcpClients
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KZ2YITOS.js";
|
|
5
5
|
import {
|
|
6
6
|
detectExistingLanguage,
|
|
7
7
|
runInitScan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FNO7CQDG.js";
|
|
9
9
|
import {
|
|
10
|
-
addFabricKnowledgeBaseSection,
|
|
11
10
|
installArchiveHintHook,
|
|
12
11
|
installFabricArchiveSkill,
|
|
13
12
|
installFabricImportSkill,
|
|
14
13
|
installFabricReviewSkill,
|
|
14
|
+
installHookLibs,
|
|
15
15
|
installKnowledgeHintBroadHook,
|
|
16
16
|
installKnowledgeHintNarrowHook,
|
|
17
17
|
mergeClaudeCodeHookConfig,
|
|
18
18
|
mergeCodexHookConfig,
|
|
19
19
|
mergeCursorHookConfig,
|
|
20
|
-
readFabricLanguagePreference
|
|
21
|
-
|
|
20
|
+
readFabricLanguagePreference,
|
|
21
|
+
writeClaudeBootstrapThinShell,
|
|
22
|
+
writeCodexBootstrapManagedBlock,
|
|
23
|
+
writeCursorBootstrapManagedBlock,
|
|
24
|
+
writeFabricAgentsSnapshot
|
|
25
|
+
} from "./chunk-4HC5ZK7H.js";
|
|
22
26
|
import {
|
|
23
27
|
detectClientSupports
|
|
24
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-MF3OTILQ.js";
|
|
25
29
|
import {
|
|
26
30
|
displayWidth,
|
|
27
31
|
hasActionHint,
|
|
@@ -35,7 +39,7 @@ import {
|
|
|
35
39
|
import {
|
|
36
40
|
createDebugLogger,
|
|
37
41
|
resolveDevMode
|
|
38
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-ZSESMG6L.js";
|
|
39
43
|
|
|
40
44
|
// src/commands/install.ts
|
|
41
45
|
import { randomUUID } from "crypto";
|
|
@@ -45,7 +49,7 @@ import { appendFileSync, existsSync as existsSync3, mkdirSync, readFileSync as r
|
|
|
45
49
|
import { dirname, isAbsolute as isAbsolute3, join as join3, resolve as resolve3 } from "path";
|
|
46
50
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
47
51
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
48
|
-
import { atomicWriteJson
|
|
52
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
49
53
|
import { defineCommand } from "citty";
|
|
50
54
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
51
55
|
|
|
@@ -62,11 +66,14 @@ async function installHooks(target, _options = {}) {
|
|
|
62
66
|
results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
|
|
63
67
|
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
64
68
|
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
69
|
+
results.push(...await runStep(() => installHookLibs(normalizedTarget)));
|
|
65
70
|
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
66
71
|
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
67
72
|
results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
|
|
68
|
-
|
|
69
|
-
results.push(
|
|
73
|
+
results.push(await runSingleStep("bootstrap-snapshot", () => writeFabricAgentsSnapshot(normalizedTarget)));
|
|
74
|
+
results.push(await runSingleStep("bootstrap-claude", () => writeClaudeBootstrapThinShell(normalizedTarget)));
|
|
75
|
+
results.push(await runSingleStep("bootstrap-codex", () => writeCodexBootstrapManagedBlock(normalizedTarget)));
|
|
76
|
+
results.push(await runSingleStep("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(normalizedTarget)));
|
|
70
77
|
results.push(...validateHookPaths(normalizedTarget));
|
|
71
78
|
return summarizeResults(results);
|
|
72
79
|
}
|
|
@@ -1292,7 +1299,7 @@ function readProjectName(target) {
|
|
|
1292
1299
|
return basename(target);
|
|
1293
1300
|
}
|
|
1294
1301
|
function getCliVersion() {
|
|
1295
|
-
return true ? "2.0.0-rc.
|
|
1302
|
+
return true ? "2.0.0-rc.21" : "unknown";
|
|
1296
1303
|
}
|
|
1297
1304
|
function sortRecord(record) {
|
|
1298
1305
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1302,15 +1309,6 @@ function toPosixPath(path) {
|
|
|
1302
1309
|
}
|
|
1303
1310
|
|
|
1304
1311
|
// src/commands/install.ts
|
|
1305
|
-
var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
|
|
1306
|
-
|
|
1307
|
-
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1308
|
-
|
|
1309
|
-
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1310
|
-
Run \`fabric doctor\` to verify state.
|
|
1311
|
-
|
|
1312
|
-
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1313
|
-
`;
|
|
1314
1312
|
var LOCAL_FABRIC_SERVER_PATH = join3("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1315
1313
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1316
1314
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
@@ -1626,9 +1624,6 @@ async function executeInitFabricPlan(plan) {
|
|
|
1626
1624
|
}
|
|
1627
1625
|
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1628
1626
|
writeDefaultFabricConfig(plan.fabricDir, plan.target);
|
|
1629
|
-
if (plan.agentsMdAction === "created" && !existsSync3(plan.agentsMdPath)) {
|
|
1630
|
-
await atomicWriteText(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1631
|
-
}
|
|
1632
1627
|
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1633
1628
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1634
1629
|
const teamSubDir = join3(plan.knowledgeDir, sub);
|
|
@@ -1820,11 +1815,14 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1820
1815
|
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
1821
1816
|
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
1822
1817
|
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
1818
|
+
installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
|
|
1823
1819
|
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
1824
1820
|
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
1825
1821
|
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
1826
|
-
|
|
1827
|
-
installResults.push(
|
|
1822
|
+
installResults.push(await runBestEffortSingle("bootstrap-snapshot", () => writeFabricAgentsSnapshot(plan.target)));
|
|
1823
|
+
installResults.push(await runBestEffortSingle("bootstrap-claude", () => writeClaudeBootstrapThinShell(plan.target)));
|
|
1824
|
+
installResults.push(await runBestEffortSingle("bootstrap-codex", () => writeCodexBootstrapManagedBlock(plan.target)));
|
|
1825
|
+
installResults.push(await runBestEffortSingle("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(plan.target)));
|
|
1828
1826
|
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
1829
1827
|
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
1830
1828
|
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveDevMode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZSESMG6L.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/plan-context-hint.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -66,17 +66,17 @@ async function runPlanContextHint(opts) {
|
|
|
66
66
|
// scope_glob matches the requested path.
|
|
67
67
|
dedupeByStableId(result.entries.flatMap((entry) => entry.description_index))
|
|
68
68
|
);
|
|
69
|
-
const
|
|
69
|
+
const entries = narrowSource.map((item) => ({
|
|
70
70
|
id: item.stable_id,
|
|
71
71
|
type: item.type ?? item.description.knowledge_type ?? "",
|
|
72
72
|
maturity: item.maturity ?? item.description.maturity ?? "",
|
|
73
73
|
summary: item.description.summary
|
|
74
74
|
}));
|
|
75
75
|
return {
|
|
76
|
-
version:
|
|
76
|
+
version: 2,
|
|
77
77
|
revision_hash: result.revision_hash,
|
|
78
78
|
target_paths: targetPaths,
|
|
79
|
-
|
|
79
|
+
entries,
|
|
80
80
|
broad_count: sharedIndex.length
|
|
81
81
|
};
|
|
82
82
|
}
|