@fenglimg/fabric-cli 2.0.0-rc.11 → 2.0.0-rc.15
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 +6 -4
- package/dist/chunk-AIB54QRT.js +82 -0
- package/dist/{chunk-5MQ52F42.js → chunk-AXIFEVAS.js} +16 -219
- package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
- package/dist/{chunk-HQLEHH4O.js → chunk-SKSYUHKK.js} +267 -40
- package/dist/chunk-UTF4YBDN.js +366 -0
- package/dist/config-7YD365I3.js +13 -0
- package/dist/{doctor-RILCO5OG.js → doctor-6XHLQJXB.js} +67 -50
- package/dist/index.js +8 -9
- package/dist/{init-C56PWHID.js → install-JLDCHAXV.js} +409 -416
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-73U4FGKO.js} +6 -1
- package/dist/{serve-NGLXHDYC.js → serve-L3X5UHG2.js} +15 -10
- package/dist/{uninstall-DBAR2JBS.js → uninstall-DD6FIFCI.js} +81 -179
- package/package.json +3 -3
- package/templates/hooks/configs/README.md +10 -6
- package/templates/hooks/configs/cursor-hooks.json +7 -10
- package/templates/hooks/knowledge-hint-broad.cjs +28 -107
- package/templates/skills/fabric-archive/SKILL.md +15 -15
- package/templates/skills/fabric-import/SKILL.md +26 -26
- package/templates/skills/fabric-review/SKILL.md +19 -19
- package/dist/chunk-AW3G7ZH5.js +0 -576
- package/dist/chunk-WPTA74BY.js +0 -184
- package/dist/hooks-NX32PPEN.js +0 -13
- package/dist/scan-66EKMNAY.js +0 -24
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
deepMerge
|
|
4
|
+
} from "./chunk-SKSYUHKK.js";
|
|
5
|
+
|
|
6
|
+
// src/install/skills-and-hooks.ts
|
|
7
|
+
import { chmodSync, existsSync, readFileSync } from "fs";
|
|
8
|
+
import { mkdir, readFile } from "fs/promises";
|
|
9
|
+
import { dirname, join, parse, resolve } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
12
|
+
var SKILL_TEMPLATE_REL = "skills/fabric-archive/SKILL.md";
|
|
13
|
+
var SKILL_REVIEW_TEMPLATE_REL = "skills/fabric-review/SKILL.md";
|
|
14
|
+
var SKILL_IMPORT_TEMPLATE_REL = "skills/fabric-import/SKILL.md";
|
|
15
|
+
var HOOK_SCRIPT_TEMPLATE_REL = "hooks/fabric-hint.cjs";
|
|
16
|
+
var HOOK_BROAD_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-broad.cjs";
|
|
17
|
+
var HOOK_NARROW_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-narrow.cjs";
|
|
18
|
+
var CLAUDE_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/claude-code.json";
|
|
19
|
+
var CODEX_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/codex-hooks.json";
|
|
20
|
+
var CURSOR_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/cursor-hooks.json";
|
|
21
|
+
var SKILL_DESTINATIONS = {
|
|
22
|
+
fabricArchive: [
|
|
23
|
+
".claude/skills/fabric-archive/SKILL.md",
|
|
24
|
+
".codex/skills/fabric-archive/SKILL.md"
|
|
25
|
+
],
|
|
26
|
+
fabricReview: [
|
|
27
|
+
".claude/skills/fabric-review/SKILL.md",
|
|
28
|
+
".codex/skills/fabric-review/SKILL.md"
|
|
29
|
+
],
|
|
30
|
+
fabricImport: [
|
|
31
|
+
".claude/skills/fabric-import/SKILL.md",
|
|
32
|
+
".codex/skills/fabric-import/SKILL.md"
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
var HOOK_SCRIPT_DESTINATIONS = {
|
|
36
|
+
fabricHint: [
|
|
37
|
+
".claude/hooks/fabric-hint.cjs",
|
|
38
|
+
".codex/hooks/fabric-hint.cjs",
|
|
39
|
+
".cursor/hooks/fabric-hint.cjs"
|
|
40
|
+
],
|
|
41
|
+
knowledgeHintBroad: [
|
|
42
|
+
".claude/hooks/knowledge-hint-broad.cjs",
|
|
43
|
+
".codex/hooks/knowledge-hint-broad.cjs",
|
|
44
|
+
".cursor/hooks/knowledge-hint-broad.cjs"
|
|
45
|
+
],
|
|
46
|
+
knowledgeHintNarrow: [
|
|
47
|
+
".claude/hooks/knowledge-hint-narrow.cjs",
|
|
48
|
+
".codex/hooks/knowledge-hint-narrow.cjs",
|
|
49
|
+
".cursor/hooks/knowledge-hint-narrow.cjs"
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
var HOOK_CONFIG_TARGETS = {
|
|
53
|
+
claudeCode: ".claude/settings.json",
|
|
54
|
+
codex: ".codex/hooks.json",
|
|
55
|
+
cursor: ".cursor/hooks.json"
|
|
56
|
+
};
|
|
57
|
+
var HOOK_CONFIG_ARRAY_PATHS = {
|
|
58
|
+
claudeCode: ["hooks.Stop", "hooks.SessionStart", "hooks.PreToolUse"],
|
|
59
|
+
codex: ["events.Stop", "events.SessionStart", "events.PreToolUse"],
|
|
60
|
+
cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse"]
|
|
61
|
+
};
|
|
62
|
+
var FABRIC_HOOK_COMMAND_PATHS = {
|
|
63
|
+
claudeCode: {
|
|
64
|
+
fabricHint: ".claude/hooks/fabric-hint.cjs",
|
|
65
|
+
knowledgeHintBroad: ".claude/hooks/knowledge-hint-broad.cjs",
|
|
66
|
+
knowledgeHintNarrow: ".claude/hooks/knowledge-hint-narrow.cjs"
|
|
67
|
+
},
|
|
68
|
+
codex: {
|
|
69
|
+
fabricHint: ".codex/hooks/fabric-hint.cjs",
|
|
70
|
+
knowledgeHintBroad: ".codex/hooks/knowledge-hint-broad.cjs",
|
|
71
|
+
knowledgeHintNarrow: ".codex/hooks/knowledge-hint-narrow.cjs"
|
|
72
|
+
},
|
|
73
|
+
cursor: {
|
|
74
|
+
fabricHint: ".cursor/hooks/fabric-hint.cjs",
|
|
75
|
+
knowledgeHintBroad: ".cursor/hooks/knowledge-hint-broad.cjs",
|
|
76
|
+
knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs"
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var SECTION_TARGETS = ["CLAUDE.md", "AGENTS.md", join(".cursor", "rules")];
|
|
80
|
+
var FABRIC_SECTION_BEGIN_MARKER = "<!-- fabric:knowledge-base:begin -->";
|
|
81
|
+
var FABRIC_SECTION_END_MARKER = "<!-- fabric:knowledge-base:end -->";
|
|
82
|
+
var FABRIC_SECTION_REGEX = /(?:\r?\n){0,2}<!-- fabric:knowledge-base:begin -->[\s\S]*?<!-- fabric:knowledge-base:end -->/;
|
|
83
|
+
function readFabricLanguagePreference(projectRoot) {
|
|
84
|
+
const configPath = join(projectRoot, ".fabric", "fabric-config.json");
|
|
85
|
+
if (!existsSync(configPath)) {
|
|
86
|
+
return "match-existing";
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const raw = readFileSync(configPath, "utf8");
|
|
90
|
+
const parsed = JSON.parse(raw);
|
|
91
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
92
|
+
return "match-existing";
|
|
93
|
+
}
|
|
94
|
+
const value = parsed["fabric_language"];
|
|
95
|
+
return typeof value === "string" && value.length > 0 ? value : "match-existing";
|
|
96
|
+
} catch {
|
|
97
|
+
return "match-existing";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function buildFabricKnowledgeBaseSection(fabricLanguage) {
|
|
101
|
+
return `${FABRIC_SECTION_BEGIN_MARKER}
|
|
102
|
+
|
|
103
|
+
## Fabric Knowledge Base
|
|
104
|
+
|
|
105
|
+
This project uses Fabric for persistent project knowledge under \`.fabric/knowledge/\`.
|
|
106
|
+
|
|
107
|
+
- **Discovery**: SessionStart lists available entries (broad menu); editing files may surface narrow hints
|
|
108
|
+
- **Usage**: call \`fab_get_knowledge_sections\` to fetch full content of any entry by id
|
|
109
|
+
- **Write flows**: see fabric-archive (record), fabric-review (validate), fabric-import (backfill) Skills
|
|
110
|
+
- **Language**: rendered per \`fabric_language\` in \`.fabric/fabric-config.json\` (current: \`${fabricLanguage}\`)
|
|
111
|
+
|
|
112
|
+
${FABRIC_SECTION_END_MARKER}`;
|
|
113
|
+
}
|
|
114
|
+
async function installFabricArchiveSkill(projectRoot, _options = {}) {
|
|
115
|
+
const source = await readTemplate(SKILL_TEMPLATE_REL);
|
|
116
|
+
const targets = SKILL_DESTINATIONS.fabricArchive.map((rel) => join(projectRoot, rel));
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const target of targets) {
|
|
119
|
+
results.push(await copyTextIdempotent("skill", source, target));
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
124
|
+
const source = await readTemplate(SKILL_REVIEW_TEMPLATE_REL);
|
|
125
|
+
const targets = SKILL_DESTINATIONS.fabricReview.map((rel) => join(projectRoot, rel));
|
|
126
|
+
const results = [];
|
|
127
|
+
for (const target of targets) {
|
|
128
|
+
results.push(await copyTextIdempotent("skill-review", source, target));
|
|
129
|
+
}
|
|
130
|
+
return results;
|
|
131
|
+
}
|
|
132
|
+
async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
133
|
+
const source = await readTemplate(SKILL_IMPORT_TEMPLATE_REL);
|
|
134
|
+
const targets = SKILL_DESTINATIONS.fabricImport.map((rel) => join(projectRoot, rel));
|
|
135
|
+
const results = [];
|
|
136
|
+
for (const target of targets) {
|
|
137
|
+
results.push(await copyTextIdempotent("skill-import", source, target));
|
|
138
|
+
}
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
async function installArchiveHintHook(projectRoot, _options = {}) {
|
|
142
|
+
const source = await readTemplate(HOOK_SCRIPT_TEMPLATE_REL);
|
|
143
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.fabricHint.map((rel) => join(projectRoot, rel));
|
|
144
|
+
const results = [];
|
|
145
|
+
for (const target of targets) {
|
|
146
|
+
const result = await copyTextIdempotent("hook-script", source, target);
|
|
147
|
+
if (result.status === "written" && process.platform !== "win32") {
|
|
148
|
+
try {
|
|
149
|
+
chmodSync(target, 493);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
results.push(result);
|
|
154
|
+
}
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
async function installKnowledgeHintBroadHook(projectRoot, _options = {}) {
|
|
158
|
+
const source = await readTemplate(HOOK_BROAD_SCRIPT_TEMPLATE_REL);
|
|
159
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintBroad.map((rel) => join(projectRoot, rel));
|
|
160
|
+
const results = [];
|
|
161
|
+
for (const target of targets) {
|
|
162
|
+
const result = await copyTextIdempotent("hook-broad-script", source, target);
|
|
163
|
+
if (result.status === "written" && process.platform !== "win32") {
|
|
164
|
+
try {
|
|
165
|
+
chmodSync(target, 493);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
results.push(result);
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
async function installKnowledgeHintNarrowHook(projectRoot, _options = {}) {
|
|
174
|
+
const source = await readTemplate(HOOK_NARROW_SCRIPT_TEMPLATE_REL);
|
|
175
|
+
const targets = HOOK_SCRIPT_DESTINATIONS.knowledgeHintNarrow.map((rel) => join(projectRoot, rel));
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const target of targets) {
|
|
178
|
+
const result = await copyTextIdempotent("hook-narrow-script", source, target);
|
|
179
|
+
if (result.status === "written" && process.platform !== "win32") {
|
|
180
|
+
try {
|
|
181
|
+
chmodSync(target, 493);
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
results.push(result);
|
|
186
|
+
}
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
async function mergeClaudeCodeHookConfig(projectRoot, _options = {}) {
|
|
190
|
+
const fragment = await readJsonTemplate(CLAUDE_HOOK_CONFIG_TEMPLATE_REL);
|
|
191
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.claudeCode);
|
|
192
|
+
return mergeJsonIdempotent(
|
|
193
|
+
"claude-hook-config",
|
|
194
|
+
targetPath,
|
|
195
|
+
fragment,
|
|
196
|
+
[...HOOK_CONFIG_ARRAY_PATHS.claudeCode]
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
async function mergeCodexHookConfig(projectRoot, _options = {}) {
|
|
200
|
+
const fragment = await readJsonTemplate(CODEX_HOOK_CONFIG_TEMPLATE_REL);
|
|
201
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.codex);
|
|
202
|
+
return mergeJsonIdempotent(
|
|
203
|
+
"codex-hook-config",
|
|
204
|
+
targetPath,
|
|
205
|
+
fragment,
|
|
206
|
+
[...HOOK_CONFIG_ARRAY_PATHS.codex]
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
async function mergeCursorHookConfig(projectRoot, _options = {}) {
|
|
210
|
+
const fragment = await readJsonTemplate(CURSOR_HOOK_CONFIG_TEMPLATE_REL);
|
|
211
|
+
const targetPath = join(projectRoot, HOOK_CONFIG_TARGETS.cursor);
|
|
212
|
+
return mergeJsonIdempotent(
|
|
213
|
+
"cursor-hook-config",
|
|
214
|
+
targetPath,
|
|
215
|
+
fragment,
|
|
216
|
+
[...HOOK_CONFIG_ARRAY_PATHS.cursor]
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
async function addFabricKnowledgeBaseSection(projectRoot, fabricLanguage, _options = {}) {
|
|
220
|
+
const sectionBody = buildFabricKnowledgeBaseSection(fabricLanguage);
|
|
221
|
+
const results = [];
|
|
222
|
+
for (const rel of SECTION_TARGETS) {
|
|
223
|
+
const target = join(projectRoot, rel);
|
|
224
|
+
if (!existsSync(target)) {
|
|
225
|
+
results.push({ step: "section", path: target, status: "skipped", message: "absent" });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
let existing;
|
|
229
|
+
try {
|
|
230
|
+
existing = await readFile(target, "utf8");
|
|
231
|
+
} catch (error) {
|
|
232
|
+
results.push({
|
|
233
|
+
step: "section",
|
|
234
|
+
path: target,
|
|
235
|
+
status: "error",
|
|
236
|
+
message: error instanceof Error ? error.message : String(error)
|
|
237
|
+
});
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let next;
|
|
241
|
+
const match = existing.match(FABRIC_SECTION_REGEX);
|
|
242
|
+
if (match !== null) {
|
|
243
|
+
const before = existing.slice(0, match.index ?? 0);
|
|
244
|
+
const after = existing.slice((match.index ?? 0) + match[0].length);
|
|
245
|
+
const stripped = `${before}${after.replace(/^\r?\n/, "")}`;
|
|
246
|
+
const trailingNewline = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
|
|
247
|
+
next = `${stripped}${trailingNewline}
|
|
248
|
+
${sectionBody}
|
|
249
|
+
`;
|
|
250
|
+
} else {
|
|
251
|
+
const trailingNewline = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
252
|
+
next = `${existing}${trailingNewline}
|
|
253
|
+
${sectionBody}
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
if (next === existing) {
|
|
257
|
+
results.push({ step: "section", path: target, status: "skipped", message: "up-to-date" });
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
await atomicWriteText(target, next);
|
|
262
|
+
results.push({ step: "section", path: target, status: "written" });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
results.push({
|
|
265
|
+
step: "section",
|
|
266
|
+
path: target,
|
|
267
|
+
status: "error",
|
|
268
|
+
message: error instanceof Error ? error.message : String(error)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
async function copyTextIdempotent(step, source, target) {
|
|
275
|
+
if (existsSync(target)) {
|
|
276
|
+
try {
|
|
277
|
+
const existing = readFileSync(target, "utf8");
|
|
278
|
+
if (existing === source) {
|
|
279
|
+
return { step, path: target, status: "skipped", message: "up-to-date" };
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await mkdir(dirname(target), { recursive: true });
|
|
285
|
+
await atomicWriteText(target, source);
|
|
286
|
+
return { step, path: target, status: "written" };
|
|
287
|
+
}
|
|
288
|
+
async function mergeJsonIdempotent(step, target, fragment, arrayAppendPaths) {
|
|
289
|
+
const existing = await readJsonObjectOrEmpty(target);
|
|
290
|
+
const merged = deepMerge(existing, fragment, { arrayAppendPaths });
|
|
291
|
+
if (jsonEqual(existing, merged)) {
|
|
292
|
+
return { step, path: target, status: "skipped", message: "up-to-date" };
|
|
293
|
+
}
|
|
294
|
+
await mkdir(dirname(target), { recursive: true });
|
|
295
|
+
await atomicWriteJson(target, merged, { indent: 2 });
|
|
296
|
+
return { step, path: target, status: "written" };
|
|
297
|
+
}
|
|
298
|
+
async function readJsonObjectOrEmpty(path) {
|
|
299
|
+
try {
|
|
300
|
+
const raw = await readFile(path, "utf8");
|
|
301
|
+
if (raw.trim().length === 0) {
|
|
302
|
+
return {};
|
|
303
|
+
}
|
|
304
|
+
const parsed = JSON.parse(raw);
|
|
305
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
306
|
+
return {};
|
|
307
|
+
}
|
|
308
|
+
return parsed;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function jsonEqual(a, b) {
|
|
317
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
318
|
+
}
|
|
319
|
+
async function readTemplate(relativePath) {
|
|
320
|
+
const path = findTemplatePath(relativePath);
|
|
321
|
+
return readFile(path, "utf8");
|
|
322
|
+
}
|
|
323
|
+
async function readJsonTemplate(relativePath) {
|
|
324
|
+
const raw = await readTemplate(relativePath);
|
|
325
|
+
const parsed = JSON.parse(raw);
|
|
326
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
327
|
+
throw new Error(`Template at ${relativePath} is not a JSON object`);
|
|
328
|
+
}
|
|
329
|
+
return parsed;
|
|
330
|
+
}
|
|
331
|
+
function findTemplatePath(relativePath) {
|
|
332
|
+
const startDir = dirname(fileURLToPath(import.meta.url));
|
|
333
|
+
let current = resolve(startDir);
|
|
334
|
+
while (true) {
|
|
335
|
+
const candidate = join(current, "templates", relativePath);
|
|
336
|
+
if (existsSync(candidate)) {
|
|
337
|
+
return candidate;
|
|
338
|
+
}
|
|
339
|
+
const parent = dirname(current);
|
|
340
|
+
if (parent === current || parse(current).root === current) {
|
|
341
|
+
throw new Error(`Template not found: templates/${relativePath} (searched up from ${startDir})`);
|
|
342
|
+
}
|
|
343
|
+
current = parent;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export {
|
|
348
|
+
SKILL_DESTINATIONS,
|
|
349
|
+
HOOK_SCRIPT_DESTINATIONS,
|
|
350
|
+
HOOK_CONFIG_TARGETS,
|
|
351
|
+
HOOK_CONFIG_ARRAY_PATHS,
|
|
352
|
+
FABRIC_HOOK_COMMAND_PATHS,
|
|
353
|
+
SECTION_TARGETS,
|
|
354
|
+
FABRIC_SECTION_REGEX,
|
|
355
|
+
readFabricLanguagePreference,
|
|
356
|
+
installFabricArchiveSkill,
|
|
357
|
+
installFabricReviewSkill,
|
|
358
|
+
installFabricImportSkill,
|
|
359
|
+
installArchiveHintHook,
|
|
360
|
+
installKnowledgeHintBroadHook,
|
|
361
|
+
installKnowledgeHintNarrowHook,
|
|
362
|
+
mergeClaudeCodeHookConfig,
|
|
363
|
+
mergeCodexHookConfig,
|
|
364
|
+
mergeCursorHookConfig,
|
|
365
|
+
addFabricKnowledgeBaseSection
|
|
366
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configCmd,
|
|
4
|
+
config_default,
|
|
5
|
+
installMcpClients
|
|
6
|
+
} from "./chunk-AIB54QRT.js";
|
|
7
|
+
import "./chunk-SKSYUHKK.js";
|
|
8
|
+
import "./chunk-6ICJICVU.js";
|
|
9
|
+
export {
|
|
10
|
+
configCmd,
|
|
11
|
+
config_default as default,
|
|
12
|
+
installMcpClients
|
|
13
|
+
};
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
runInitScan
|
|
4
|
+
} from "./chunk-AXIFEVAS.js";
|
|
5
|
+
import {
|
|
6
|
+
hasActionHint,
|
|
3
7
|
paint,
|
|
8
|
+
renderFabricError,
|
|
4
9
|
symbol
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
resolveDevMode
|
|
8
|
-
} from "./chunk-OBQU6NHO.js";
|
|
10
|
+
} from "./chunk-G2CIOLD4.js";
|
|
9
11
|
import {
|
|
10
12
|
t
|
|
11
13
|
} from "./chunk-6ICJICVU.js";
|
|
14
|
+
import {
|
|
15
|
+
resolveDevMode
|
|
16
|
+
} from "./chunk-OBQU6NHO.js";
|
|
12
17
|
|
|
13
18
|
// src/commands/doctor.ts
|
|
14
19
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -16,11 +21,11 @@ import { defineCommand } from "citty";
|
|
|
16
21
|
import {
|
|
17
22
|
appendEventLedgerEvent,
|
|
18
23
|
checkLockOrThrow,
|
|
19
|
-
runDoctorApplyLint,
|
|
24
|
+
runDoctorApplyLint as runDoctorFixKnowledge,
|
|
20
25
|
runDoctorFix,
|
|
21
26
|
runDoctorReport
|
|
22
27
|
} from "@fenglimg/fabric-server";
|
|
23
|
-
var
|
|
28
|
+
var FIX_KNOWLEDGE_CODE_LABELS = {
|
|
24
29
|
knowledge_orphan_demote_required: "demote (maturity)",
|
|
25
30
|
knowledge_stale_archive_required: "archive (git mv)",
|
|
26
31
|
knowledge_pending_auto_archive: "archive (git mv, pending)",
|
|
@@ -43,28 +48,28 @@ var doctorCommand = defineCommand({
|
|
|
43
48
|
description: t("cli.doctor.args.fix.description"),
|
|
44
49
|
default: false
|
|
45
50
|
},
|
|
46
|
-
|
|
51
|
+
"fix-knowledge": {
|
|
47
52
|
type: "boolean",
|
|
48
|
-
description: t("cli.doctor.args.
|
|
53
|
+
description: t("cli.doctor.args.fix-knowledge.description"),
|
|
49
54
|
default: false
|
|
50
55
|
},
|
|
51
|
-
|
|
56
|
+
json: {
|
|
52
57
|
type: "boolean",
|
|
53
|
-
description: t("cli.doctor.args.
|
|
58
|
+
description: t("cli.doctor.args.json.description"),
|
|
54
59
|
default: false
|
|
55
60
|
},
|
|
56
|
-
|
|
61
|
+
rescan: {
|
|
57
62
|
type: "boolean",
|
|
58
|
-
description: t("cli.doctor.args.
|
|
63
|
+
description: t("cli.doctor.args.rescan.description"),
|
|
59
64
|
default: false
|
|
60
65
|
},
|
|
61
|
-
|
|
66
|
+
strict: {
|
|
62
67
|
type: "boolean",
|
|
63
|
-
description: t("cli.doctor.args.
|
|
68
|
+
description: t("cli.doctor.args.strict.description"),
|
|
64
69
|
default: false
|
|
65
70
|
},
|
|
66
71
|
// rc.7 T11: skip the safety confirm before mutations. Required for any
|
|
67
|
-
// non-tty invocation that wants to run --
|
|
72
|
+
// non-tty invocation that wants to run --fix-knowledge without setting
|
|
68
73
|
// FABRIC_NONINTERACTIVE=1 in the environment.
|
|
69
74
|
yes: {
|
|
70
75
|
type: "boolean",
|
|
@@ -75,26 +80,38 @@ var doctorCommand = defineCommand({
|
|
|
75
80
|
async run({ args }) {
|
|
76
81
|
const workspaceRoot = process.cwd();
|
|
77
82
|
const resolution = resolveDevMode(args.target, workspaceRoot);
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
try {
|
|
84
|
+
checkLockOrThrow(resolution.target);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (hasActionHint(err)) {
|
|
87
|
+
renderFabricError(err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
const fixKnowledge = args["fix-knowledge"] === true;
|
|
80
93
|
const fix = args.fix === true;
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
const rescan = args.rescan === true;
|
|
95
|
+
if (fixKnowledge && fix) {
|
|
96
|
+
writeStderr(t("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
|
|
83
97
|
process.exitCode = 1;
|
|
84
98
|
return;
|
|
85
99
|
}
|
|
86
|
-
|
|
100
|
+
if (rescan) {
|
|
101
|
+
await runInitScan(resolution.target, { source: "doctor-rescan" });
|
|
102
|
+
}
|
|
103
|
+
let fixKnowledgeReport = null;
|
|
87
104
|
let fixReport = null;
|
|
88
105
|
let report;
|
|
89
|
-
if (
|
|
106
|
+
if (fixKnowledge) {
|
|
90
107
|
const preReport = await runDoctorReport(resolution.target);
|
|
91
|
-
const plan =
|
|
108
|
+
const plan = computeFixKnowledgePlan(preReport);
|
|
92
109
|
const yesFlag = args.yes === true;
|
|
93
110
|
const envBypass = process.env.FABRIC_NONINTERACTIVE === "1";
|
|
94
111
|
if (plan.totalCount === 0) {
|
|
95
112
|
} else {
|
|
96
|
-
|
|
97
|
-
const decision = await
|
|
113
|
+
renderFixKnowledgePlan(plan);
|
|
114
|
+
const decision = await resolveFixKnowledgeConsent({
|
|
98
115
|
yesFlag,
|
|
99
116
|
envBypass,
|
|
100
117
|
plan
|
|
@@ -104,8 +121,8 @@ var doctorCommand = defineCommand({
|
|
|
104
121
|
return;
|
|
105
122
|
}
|
|
106
123
|
}
|
|
107
|
-
|
|
108
|
-
report =
|
|
124
|
+
fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
|
|
125
|
+
report = fixKnowledgeReport.report;
|
|
109
126
|
} else if (fix) {
|
|
110
127
|
fixReport = await runDoctorFix(resolution.target);
|
|
111
128
|
report = fixReport.report;
|
|
@@ -113,30 +130,30 @@ var doctorCommand = defineCommand({
|
|
|
113
130
|
report = await runDoctorReport(resolution.target);
|
|
114
131
|
}
|
|
115
132
|
if (args.json === true) {
|
|
116
|
-
writeStdout(JSON.stringify(
|
|
133
|
+
writeStdout(JSON.stringify(fixKnowledgeReport ?? fixReport ?? report, null, 2));
|
|
117
134
|
} else {
|
|
118
|
-
if (
|
|
119
|
-
writeStdout(
|
|
120
|
-
if (
|
|
121
|
-
writeStderr(
|
|
135
|
+
if (fixKnowledgeReport !== null) {
|
|
136
|
+
writeStdout(fixKnowledgeReport.message);
|
|
137
|
+
if (fixKnowledgeReport.aborted && fixKnowledgeReport.abort_reason !== void 0) {
|
|
138
|
+
writeStderr(fixKnowledgeReport.abort_reason);
|
|
122
139
|
}
|
|
123
|
-
|
|
140
|
+
renderFixKnowledgeMutations(fixKnowledgeReport);
|
|
124
141
|
} else if (fixReport !== null) {
|
|
125
142
|
writeStdout(fixReport.message);
|
|
126
143
|
}
|
|
127
144
|
renderHumanReport(report);
|
|
128
145
|
}
|
|
129
146
|
await emitDoctorRunEventBestEffort(resolution.target, {
|
|
130
|
-
mode:
|
|
147
|
+
mode: fixKnowledge ? "fix-knowledge" : "lint",
|
|
131
148
|
issues: report.fixable_errors.length + report.manual_errors.length + report.warnings.length,
|
|
132
|
-
mutations:
|
|
149
|
+
mutations: fixKnowledgeReport !== null ? fixKnowledgeReport.mutations.filter((m) => m.applied).length : void 0
|
|
133
150
|
});
|
|
134
|
-
if (
|
|
135
|
-
if (
|
|
151
|
+
if (fixKnowledgeReport !== null) {
|
|
152
|
+
if (fixKnowledgeReport.aborted) {
|
|
136
153
|
process.exitCode = 1;
|
|
137
154
|
return;
|
|
138
155
|
}
|
|
139
|
-
if (
|
|
156
|
+
if (fixKnowledgeReport.mutations.some((m) => !m.applied)) {
|
|
140
157
|
process.exitCode = 1;
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
@@ -156,13 +173,13 @@ function renderHumanReport(report) {
|
|
|
156
173
|
writeIssueSection(t("doctor.section.manual"), report.manual_errors);
|
|
157
174
|
writeIssueSection(t("doctor.section.warnings"), report.warnings);
|
|
158
175
|
}
|
|
159
|
-
function
|
|
160
|
-
if (
|
|
176
|
+
function renderFixKnowledgeMutations(fixKnowledgeReport) {
|
|
177
|
+
if (fixKnowledgeReport.mutations.length === 0) {
|
|
161
178
|
return;
|
|
162
179
|
}
|
|
163
180
|
writeStdout("");
|
|
164
|
-
writeStdout(t("doctor.section.
|
|
165
|
-
for (const mutation of
|
|
181
|
+
writeStdout(t("doctor.section.fix-knowledge-mutations"));
|
|
182
|
+
for (const mutation of fixKnowledgeReport.mutations) {
|
|
166
183
|
const marker = mutation.applied ? symbol.ok : symbol.error;
|
|
167
184
|
const errSuffix = mutation.applied || mutation.error === void 0 ? "" : ` (${mutation.error})`;
|
|
168
185
|
writeStdout(`${marker} ${mutation.kind}: ${mutation.path} [${mutation.detail}]${errSuffix}`);
|
|
@@ -207,28 +224,28 @@ function writeStderr(message) {
|
|
|
207
224
|
process.stderr.write(`${message}
|
|
208
225
|
`);
|
|
209
226
|
}
|
|
210
|
-
function
|
|
227
|
+
function computeFixKnowledgePlan(report) {
|
|
211
228
|
const buckets = {};
|
|
212
229
|
const sources = [
|
|
213
230
|
...report.fixable_errors,
|
|
214
231
|
...report.warnings
|
|
215
232
|
];
|
|
216
233
|
for (const issue of sources) {
|
|
217
|
-
if (
|
|
234
|
+
if (FIX_KNOWLEDGE_CODE_LABELS[issue.code] === void 0) continue;
|
|
218
235
|
if (!Array.isArray(buckets[issue.code])) {
|
|
219
236
|
buckets[issue.code] = [];
|
|
220
237
|
}
|
|
221
238
|
buckets[issue.code].push(issue);
|
|
222
239
|
}
|
|
223
240
|
const codes = Object.keys(buckets).sort(
|
|
224
|
-
(a, b) =>
|
|
241
|
+
(a, b) => FIX_KNOWLEDGE_CODE_LABELS[a].localeCompare(FIX_KNOWLEDGE_CODE_LABELS[b])
|
|
225
242
|
);
|
|
226
243
|
const perCodeLines = [];
|
|
227
244
|
let totalCount = 0;
|
|
228
245
|
for (const code of codes) {
|
|
229
246
|
const items = buckets[code];
|
|
230
247
|
totalCount += items.length;
|
|
231
|
-
perCodeLines.push(` - ${
|
|
248
|
+
perCodeLines.push(` - ${FIX_KNOWLEDGE_CODE_LABELS[code]}: ${items.length}`);
|
|
232
249
|
}
|
|
233
250
|
const previewLines = [];
|
|
234
251
|
const flattened = codes.flatMap((c) => buckets[c]);
|
|
@@ -241,9 +258,9 @@ function computeApplyLintPlan(report) {
|
|
|
241
258
|
}
|
|
242
259
|
return { totalCount, perCodeLines, previewLines };
|
|
243
260
|
}
|
|
244
|
-
function
|
|
261
|
+
function renderFixKnowledgePlan(plan) {
|
|
245
262
|
writeStdout("");
|
|
246
|
-
writeStdout(`${paint.warn("
|
|
263
|
+
writeStdout(`${paint.warn("fix-knowledge mutation plan")} (${plan.totalCount} total)`);
|
|
247
264
|
for (const line of plan.perCodeLines) {
|
|
248
265
|
writeStdout(line);
|
|
249
266
|
}
|
|
@@ -255,13 +272,13 @@ function renderApplyLintPlan(plan) {
|
|
|
255
272
|
}
|
|
256
273
|
}
|
|
257
274
|
}
|
|
258
|
-
async function
|
|
275
|
+
async function resolveFixKnowledgeConsent(options) {
|
|
259
276
|
if (options.yesFlag || options.envBypass) {
|
|
260
277
|
return "proceed";
|
|
261
278
|
}
|
|
262
279
|
if (process.stdin.isTTY !== true) {
|
|
263
280
|
writeStderr(
|
|
264
|
-
"doctor --
|
|
281
|
+
"doctor --fix-knowledge: stdin is not a TTY and neither --yes nor FABRIC_NONINTERACTIVE=1 is set. Refusing to mutate."
|
|
265
282
|
);
|
|
266
283
|
return "abort";
|
|
267
284
|
}
|
|
@@ -271,7 +288,7 @@ async function resolveApplyLintConsent(options) {
|
|
|
271
288
|
initialValue: false
|
|
272
289
|
});
|
|
273
290
|
if (isCancel(answer) || answer !== true) {
|
|
274
|
-
writeStderr("doctor --
|
|
291
|
+
writeStderr("doctor --fix-knowledge: aborted by user.");
|
|
275
292
|
return "abort";
|
|
276
293
|
}
|
|
277
294
|
return "proceed";
|
package/dist/index.js
CHANGED
|
@@ -8,21 +8,20 @@ import { defineCommand, runMain } from "citty";
|
|
|
8
8
|
|
|
9
9
|
// src/commands/index.ts
|
|
10
10
|
var allCommands = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
serve: () => import("./serve-
|
|
14
|
-
uninstall: () => import("./uninstall-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"plan-context-hint": () => import("./plan-context-hint-QMUPAXIB.js").then((module) => module.default)
|
|
11
|
+
install: () => import("./install-JLDCHAXV.js").then((module) => module.default),
|
|
12
|
+
doctor: () => import("./doctor-6XHLQJXB.js").then((module) => module.default),
|
|
13
|
+
serve: () => import("./serve-L3X5UHG2.js").then((module) => module.default),
|
|
14
|
+
uninstall: () => import("./uninstall-DD6FIFCI.js").then((module) => module.default),
|
|
15
|
+
config: () => import("./config-7YD365I3.js").then((module) => module.default),
|
|
16
|
+
"plan-context-hint": () => import("./plan-context-hint-73U4FGKO.js").then((module) => module.default)
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
// src/index.ts
|
|
21
20
|
var main = defineCommand({
|
|
22
21
|
meta: {
|
|
23
22
|
name: "fabric",
|
|
24
|
-
version: "2.0.0-rc.
|
|
25
|
-
description: 'Initialize and manage Fabric projects. Use "fabric
|
|
23
|
+
version: "2.0.0-rc.15",
|
|
24
|
+
description: 'Initialize and manage Fabric projects. Use "fabric install" for one-shot setup.'
|
|
26
25
|
},
|
|
27
26
|
subCommands: allCommands
|
|
28
27
|
});
|