@fenglimg/fabric-cli 2.0.0-rc.13 → 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.
Files changed (29) hide show
  1. package/README.md +4 -2
  2. package/dist/{chunk-X7QPY5KH.js → chunk-4HC5ZK7H.js} +296 -301
  3. package/dist/{chunk-FDRLV5PL.js → chunk-FNO7CQDG.js} +5 -213
  4. package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
  5. package/dist/chunk-KZ2YITOS.js +225 -0
  6. package/dist/{chunk-OHWQNSLH.js → chunk-MF3OTILQ.js} +267 -44
  7. package/dist/{chunk-OBQU6NHO.js → chunk-ZSESMG6L.js} +0 -6
  8. package/dist/config-AYP5F72E.js +13 -0
  9. package/dist/doctor-L6TIXXIX.js +425 -0
  10. package/dist/index.js +11 -9
  11. package/dist/{install-SLS5W27W.js → install-DNZXGFHJ.js} +344 -359
  12. package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-CFDGXHCA.js} +10 -5
  13. package/dist/{serve-NGLXHDYC.js → serve-6PPQX7AW.js} +16 -11
  14. package/dist/{uninstall-JHUSFENL.js → uninstall-L2HEEOU3.js} +200 -215
  15. package/package.json +3 -3
  16. package/templates/hooks/configs/README.md +9 -5
  17. package/templates/hooks/configs/cursor-hooks.json +7 -10
  18. package/templates/hooks/fabric-hint.cjs +350 -21
  19. package/templates/hooks/knowledge-hint-broad.cjs +39 -14
  20. package/templates/hooks/knowledge-hint-narrow.cjs +31 -7
  21. package/templates/hooks/lib/banner-i18n.cjs +252 -0
  22. package/dist/chunk-Q72D24BG.js +0 -186
  23. package/dist/doctor-RILCO5OG.js +0 -282
  24. package/dist/hooks-HIWYI3VG.js +0 -13
  25. package/dist/scan-VHKZPT2W.js +0 -24
  26. package/templates/agents-md/AGENTS.md.template +0 -59
  27. package/templates/bootstrap/CLAUDE.md +0 -8
  28. package/templates/bootstrap/codex-AGENTS-header.md +0 -6
  29. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runInitScan
4
+ } from "./chunk-FNO7CQDG.js";
5
+ import {
6
+ hasActionHint,
7
+ paint,
8
+ renderFabricError,
9
+ symbol
10
+ } from "./chunk-G2CIOLD4.js";
11
+ import {
12
+ t
13
+ } from "./chunk-6ICJICVU.js";
14
+ import {
15
+ resolveDevMode
16
+ } from "./chunk-ZSESMG6L.js";
17
+
18
+ // src/commands/doctor.ts
19
+ import { confirm, isCancel } from "@clack/prompts";
20
+ import { defineCommand } from "citty";
21
+ import {
22
+ appendEventLedgerEvent,
23
+ checkLockOrThrow,
24
+ runDoctorApplyLint as runDoctorFixKnowledge,
25
+ runDoctorCiteCoverage,
26
+ runDoctorFix,
27
+ runDoctorReport
28
+ } from "@fenglimg/fabric-server";
29
+ var FIX_KNOWLEDGE_CODE_LABELS = {
30
+ knowledge_orphan_demote_required: "demote (maturity)",
31
+ knowledge_stale_archive_required: "archive (git mv)",
32
+ knowledge_pending_auto_archive: "archive (git mv, pending)",
33
+ knowledge_index_drift: "counter bump (agents.meta)",
34
+ knowledge_session_hints_stale: "cache delete"
35
+ };
36
+ var PLAN_PREVIEW_LIMIT = 12;
37
+ var doctorCommand = defineCommand({
38
+ meta: {
39
+ name: "doctor",
40
+ description: t("cli.doctor.description")
41
+ },
42
+ args: {
43
+ target: {
44
+ type: "string",
45
+ description: t("cli.doctor.args.target.description")
46
+ },
47
+ fix: {
48
+ type: "boolean",
49
+ description: t("cli.doctor.args.fix.description"),
50
+ default: false
51
+ },
52
+ "fix-knowledge": {
53
+ type: "boolean",
54
+ description: t("cli.doctor.args.fix-knowledge.description"),
55
+ default: false
56
+ },
57
+ json: {
58
+ type: "boolean",
59
+ description: t("cli.doctor.args.json.description"),
60
+ default: false
61
+ },
62
+ rescan: {
63
+ type: "boolean",
64
+ description: t("cli.doctor.args.rescan.description"),
65
+ default: false
66
+ },
67
+ strict: {
68
+ type: "boolean",
69
+ description: t("cli.doctor.args.strict.description"),
70
+ default: false
71
+ },
72
+ // rc.7 T11: skip the safety confirm before mutations. Required for any
73
+ // non-tty invocation that wants to run --fix-knowledge without setting
74
+ // FABRIC_NONINTERACTIVE=1 in the environment.
75
+ yes: {
76
+ type: "boolean",
77
+ description: t("cli.doctor.args.yes.description"),
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"
98
+ }
99
+ },
100
+ async run({ args }) {
101
+ const workspaceRoot = process.cwd();
102
+ const resolution = resolveDevMode(args.target, workspaceRoot);
103
+ try {
104
+ checkLockOrThrow(resolution.target);
105
+ } catch (err) {
106
+ if (hasActionHint(err)) {
107
+ renderFabricError(err);
108
+ process.exit(1);
109
+ }
110
+ throw err;
111
+ }
112
+ const fixKnowledge = args["fix-knowledge"] === true;
113
+ const fix = args.fix === true;
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
+ }
143
+ if (fixKnowledge && fix) {
144
+ writeStderr(t("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ if (rescan) {
149
+ await runInitScan(resolution.target, { source: "doctor-rescan" });
150
+ }
151
+ let fixKnowledgeReport = null;
152
+ let fixReport = null;
153
+ let report;
154
+ if (fixKnowledge) {
155
+ const preReport = await runDoctorReport(resolution.target);
156
+ const plan = computeFixKnowledgePlan(preReport);
157
+ const yesFlag = args.yes === true;
158
+ const envBypass = process.env.FABRIC_NONINTERACTIVE === "1";
159
+ if (plan.totalCount === 0) {
160
+ } else {
161
+ renderFixKnowledgePlan(plan);
162
+ const decision = await resolveFixKnowledgeConsent({
163
+ yesFlag,
164
+ envBypass,
165
+ plan
166
+ });
167
+ if (decision === "abort") {
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ }
172
+ fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
173
+ report = fixKnowledgeReport.report;
174
+ } else if (fix) {
175
+ fixReport = await runDoctorFix(resolution.target);
176
+ report = fixReport.report;
177
+ } else {
178
+ report = await runDoctorReport(resolution.target);
179
+ }
180
+ if (args.json === true) {
181
+ writeStdout(JSON.stringify(fixKnowledgeReport ?? fixReport ?? report, null, 2));
182
+ } else {
183
+ if (fixKnowledgeReport !== null) {
184
+ writeStdout(fixKnowledgeReport.message);
185
+ if (fixKnowledgeReport.aborted && fixKnowledgeReport.abort_reason !== void 0) {
186
+ writeStderr(fixKnowledgeReport.abort_reason);
187
+ }
188
+ renderFixKnowledgeMutations(fixKnowledgeReport);
189
+ } else if (fixReport !== null) {
190
+ writeStdout(fixReport.message);
191
+ }
192
+ renderHumanReport(report);
193
+ }
194
+ await emitDoctorRunEventBestEffort(resolution.target, {
195
+ mode: fixKnowledge ? "fix-knowledge" : "lint",
196
+ issues: report.fixable_errors.length + report.manual_errors.length + report.warnings.length,
197
+ mutations: fixKnowledgeReport !== null ? fixKnowledgeReport.mutations.filter((m) => m.applied).length : void 0
198
+ });
199
+ if (fixKnowledgeReport !== null) {
200
+ if (fixKnowledgeReport.aborted) {
201
+ process.exitCode = 1;
202
+ return;
203
+ }
204
+ if (fixKnowledgeReport.mutations.some((m) => !m.applied)) {
205
+ process.exitCode = 1;
206
+ return;
207
+ }
208
+ }
209
+ if (report.status === "error" || args.strict === true && (report.status === "warn" || report.warnings.length > 0)) {
210
+ process.exitCode = 1;
211
+ }
212
+ }
213
+ });
214
+ var doctor_default = doctorCommand;
215
+ function renderHumanReport(report) {
216
+ writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
217
+ for (const check of report.checks) {
218
+ writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
219
+ }
220
+ writeIssueSection(t("doctor.section.fixable"), report.fixable_errors);
221
+ writeIssueSection(t("doctor.section.manual"), report.manual_errors);
222
+ writeIssueSection(t("doctor.section.warnings"), report.warnings);
223
+ }
224
+ function renderFixKnowledgeMutations(fixKnowledgeReport) {
225
+ if (fixKnowledgeReport.mutations.length === 0) {
226
+ return;
227
+ }
228
+ writeStdout("");
229
+ writeStdout(t("doctor.section.fix-knowledge-mutations"));
230
+ for (const mutation of fixKnowledgeReport.mutations) {
231
+ const marker = mutation.applied ? symbol.ok : symbol.error;
232
+ const errSuffix = mutation.applied || mutation.error === void 0 ? "" : ` (${mutation.error})`;
233
+ writeStdout(`${marker} ${mutation.kind}: ${mutation.path} [${mutation.detail}]${errSuffix}`);
234
+ }
235
+ }
236
+ function writeIssueSection(title, issues) {
237
+ if (issues.length === 0) {
238
+ return;
239
+ }
240
+ writeStdout("");
241
+ writeStdout(title);
242
+ for (const issue of issues) {
243
+ writeStdout(`- ${issue.code}: ${issue.message}`);
244
+ }
245
+ }
246
+ function renderStatus(status) {
247
+ if (status === "ok") {
248
+ return symbol.ok;
249
+ }
250
+ if (status === "warn") {
251
+ return symbol.warn;
252
+ }
253
+ return symbol.error;
254
+ }
255
+ function writeStdout(message) {
256
+ process.stdout.write(`${message}
257
+ `);
258
+ }
259
+ async function emitDoctorRunEventBestEffort(projectRoot, payload) {
260
+ try {
261
+ await appendEventLedgerEvent(projectRoot, {
262
+ event_type: "doctor_run",
263
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
264
+ mode: payload.mode,
265
+ issues: payload.issues,
266
+ ...payload.mutations !== void 0 ? { mutations: payload.mutations } : {}
267
+ });
268
+ } catch {
269
+ }
270
+ }
271
+ function writeStderr(message) {
272
+ process.stderr.write(`${message}
273
+ `);
274
+ }
275
+ function computeFixKnowledgePlan(report) {
276
+ const buckets = {};
277
+ const sources = [
278
+ ...report.fixable_errors,
279
+ ...report.warnings
280
+ ];
281
+ for (const issue of sources) {
282
+ if (FIX_KNOWLEDGE_CODE_LABELS[issue.code] === void 0) continue;
283
+ if (!Array.isArray(buckets[issue.code])) {
284
+ buckets[issue.code] = [];
285
+ }
286
+ buckets[issue.code].push(issue);
287
+ }
288
+ const codes = Object.keys(buckets).sort(
289
+ (a, b) => FIX_KNOWLEDGE_CODE_LABELS[a].localeCompare(FIX_KNOWLEDGE_CODE_LABELS[b])
290
+ );
291
+ const perCodeLines = [];
292
+ let totalCount = 0;
293
+ for (const code of codes) {
294
+ const items = buckets[code];
295
+ totalCount += items.length;
296
+ perCodeLines.push(` - ${FIX_KNOWLEDGE_CODE_LABELS[code]}: ${items.length}`);
297
+ }
298
+ const previewLines = [];
299
+ const flattened = codes.flatMap((c) => buckets[c]);
300
+ for (const item of flattened.slice(0, PLAN_PREVIEW_LIMIT)) {
301
+ const where = item.path !== void 0 && item.path.length > 0 ? `${item.path}` : "(no path)";
302
+ previewLines.push(` \u2022 ${where} \u2014 ${item.message}`);
303
+ }
304
+ if (flattened.length > PLAN_PREVIEW_LIMIT) {
305
+ previewLines.push(` \u2022 ... and ${flattened.length - PLAN_PREVIEW_LIMIT} more`);
306
+ }
307
+ return { totalCount, perCodeLines, previewLines };
308
+ }
309
+ function renderFixKnowledgePlan(plan) {
310
+ writeStdout("");
311
+ writeStdout(`${paint.warn("fix-knowledge mutation plan")} (${plan.totalCount} total)`);
312
+ for (const line of plan.perCodeLines) {
313
+ writeStdout(line);
314
+ }
315
+ if (plan.previewLines.length > 0) {
316
+ writeStdout("");
317
+ writeStdout(" preview:");
318
+ for (const line of plan.previewLines) {
319
+ writeStdout(line);
320
+ }
321
+ }
322
+ }
323
+ async function resolveFixKnowledgeConsent(options) {
324
+ if (options.yesFlag || options.envBypass) {
325
+ return "proceed";
326
+ }
327
+ if (process.stdin.isTTY !== true) {
328
+ writeStderr(
329
+ "doctor --fix-knowledge: stdin is not a TTY and neither --yes nor FABRIC_NONINTERACTIVE=1 is set. Refusing to mutate."
330
+ );
331
+ return "abort";
332
+ }
333
+ const message = `About to apply ${options.plan.totalCount} mutation(s) to knowledge entries (frontmatter writes + git mv + cache deletes). Proceed?`;
334
+ const answer = await confirm({
335
+ message,
336
+ initialValue: false
337
+ });
338
+ if (isCancel(answer) || answer !== true) {
339
+ writeStderr("doctor --fix-knowledge: aborted by user.");
340
+ return "abort";
341
+ }
342
+ return "proceed";
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
+ }
421
+ export {
422
+ doctor_default as default,
423
+ doctorCommand,
424
+ parseSinceDuration
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,21 +11,20 @@ import { defineCommand, runMain } from "citty";
8
11
 
9
12
  // src/commands/index.ts
10
13
  var allCommands = {
11
- install: () => import("./install-SLS5W27W.js").then((module) => module.default),
12
- scan: () => import("./scan-VHKZPT2W.js").then((module) => module.default),
13
- serve: () => import("./serve-NGLXHDYC.js").then((module) => module.default),
14
- uninstall: () => import("./uninstall-JHUSFENL.js").then((module) => module.default),
15
- doctor: () => import("./doctor-RILCO5OG.js").then((module) => module.default),
16
- hooks: () => import("./hooks-HIWYI3VG.js").then((module) => module.default),
17
- "plan-context-hint": () => import("./plan-context-hint-QMUPAXIB.js").then((module) => module.default)
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)
18
20
  };
19
21
 
20
22
  // src/index.ts
21
23
  var main = defineCommand({
22
24
  meta: {
23
25
  name: "fabric",
24
- version: "2.0.0-rc.13",
25
- description: 'Initialize and manage Fabric projects. Use "fabric install" for one-shot setup.'
26
+ version: "2.0.0-rc.21",
27
+ description: t("cli.main.description")
26
28
  },
27
29
  subCommands: allCommands
28
30
  });