@fenglimg/fabric-cli 2.0.0-rc.36 → 2.0.0-rc.37

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{chunk-XVS4F3P6.js → chunk-D25XJ4BC.js} +49 -5
  3. package/dist/{chunk-G2CIOLD4.js → chunk-WWNXR34K.js} +1 -16
  4. package/dist/{doctor-2FCRAWDZ.js → doctor-764NFF3X.js} +112 -16
  5. package/dist/index.js +7 -6
  6. package/dist/{install-XSUIX6AD.js → install-U7MGIJ2L.js} +50 -22
  7. package/dist/metrics-ACEQFPDU.js +122 -0
  8. package/dist/{uninstall-BIJ5GLEU.js → uninstall-MH7ZIB6M.js} +6 -18
  9. package/package.json +30 -4
  10. package/templates/hooks/cite-policy-evict.cjs +80 -91
  11. package/templates/hooks/configs/README.md +19 -0
  12. package/templates/hooks/configs/codex-hooks.json +3 -0
  13. package/templates/hooks/configs/cursor-hooks.json +2 -1
  14. package/templates/hooks/fabric-hint.cjs +146 -8
  15. package/templates/hooks/knowledge-hint-broad.cjs +65 -104
  16. package/templates/hooks/knowledge-hint-narrow.cjs +122 -5
  17. package/templates/hooks/lib/cite-line-parser.cjs +7 -1
  18. package/templates/hooks/lib/client-adapter.cjs +106 -0
  19. package/templates/hooks/lib/config-cache.cjs +107 -0
  20. package/templates/hooks/lib/state-store.cjs +84 -0
  21. package/templates/skills/fabric-archive/SKILL.md +29 -7
  22. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  23. package/templates/skills/fabric-archive/ref/i18n-policy.md +6 -0
  24. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +1 -1
  25. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +2 -0
  26. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +25 -11
  27. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +43 -15
  28. package/templates/skills/fabric-import/SKILL.md +3 -3
  29. package/templates/skills/fabric-import/ref/i18n-policy.md +6 -0
  30. package/templates/skills/fabric-import/ref/phase-2-mining.md +2 -2
  31. package/templates/skills/fabric-review/SKILL.md +31 -25
  32. package/templates/skills/fabric-review/ref/i18n-policy.md +6 -0
  33. package/templates/skills/fabric-review/ref/modify-flow.md +9 -1
  34. package/templates/skills/fabric-review/ref/per-mode-flows.md +1 -1
  35. package/templates/skills/lib/shared-policy.md +69 -0
  36. package/dist/serve-43JTEM3U.js +0 -142
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wangzhichao (fenglimg)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -96,11 +96,19 @@ var HOOK_SCRIPT_DESTINATIONS = {
96
96
  ".codex/hooks/knowledge-hint-narrow.cjs",
97
97
  ".cursor/hooks/knowledge-hint-narrow.cjs"
98
98
  ],
99
- // v2.0.0-rc.34 TASK-06: Claude Code only — UserPromptSubmit cite-policy
100
- // long-session evict sidecar. Codex / Cursor don't have an equivalent
101
- // event registration; cite-coverage telemetry there relies on the existing
102
- // Stop / SessionStart hooks (knowledge-hint-broad rc.33 W2 channel).
103
- citePolicyEvict: [".claude/hooks/cite-policy-evict.cjs"]
99
+ // v2.0.0-rc.34 TASK-06: Claude Code — UserPromptSubmit cite-policy long-
100
+ // session evict sidecar.
101
+ // v2.0.0-rc.37 NEW-21: extended to Codex / Cursor SessionStart slots.
102
+ // Those clients don't have an equivalent per-prompt event, so cite-policy-
103
+ // evict.cjs runs in "SessionStart mode" (one-shot stderr emit per session
104
+ // boot, no turn-counter). Cadence is lower than Claude Code's per-prompt
105
+ // window but strictly better than 0 (rc.32 baseline measured Codex/Cursor
106
+ // at 3.1% cite coverage when no cite-reminder surface existed).
107
+ citePolicyEvict: [
108
+ ".claude/hooks/cite-policy-evict.cjs",
109
+ ".codex/hooks/cite-policy-evict.cjs",
110
+ ".cursor/hooks/cite-policy-evict.cjs"
111
+ ]
104
112
  };
105
113
  var HOOK_LIB_DESTINATIONS = [
106
114
  ".claude/hooks/lib",
@@ -314,6 +322,41 @@ async function installSkillRefFiles(projectRoot, skillSlug) {
314
322
  }
315
323
  return results;
316
324
  }
325
+ async function installSharedSkillLib(projectRoot, _options = {}) {
326
+ let libTemplateDir;
327
+ try {
328
+ libTemplateDir = findTemplatePath("skills/lib");
329
+ } catch {
330
+ return [{ step: "skill-lib", path: "skills/lib", status: "skipped", message: "no-lib-dir" }];
331
+ }
332
+ let libFiles;
333
+ try {
334
+ libFiles = readdirSync(libTemplateDir).filter((name) => name.endsWith(".md"));
335
+ } catch {
336
+ return [{ step: "skill-lib", path: libTemplateDir, status: "skipped", message: "no-lib-files" }];
337
+ }
338
+ const clientPrefixes = [".claude", ".codex"];
339
+ const results = [];
340
+ for (const libFile of libFiles) {
341
+ let source;
342
+ try {
343
+ source = readFileSync2(join2(libTemplateDir, libFile), "utf8");
344
+ } catch (error) {
345
+ results.push({
346
+ step: "skill-lib",
347
+ path: join2(libTemplateDir, libFile),
348
+ status: "error",
349
+ message: error instanceof Error ? error.message : String(error)
350
+ });
351
+ continue;
352
+ }
353
+ for (const prefix of clientPrefixes) {
354
+ const target = join2(projectRoot, prefix, "skills", "lib", libFile);
355
+ results.push(await copyTextIdempotent("skill-lib", source, target));
356
+ }
357
+ }
358
+ return results;
359
+ }
317
360
  async function installArchiveHintHook(projectRoot, _options = {}) {
318
361
  const source = await readTemplate(HOOK_SCRIPT_TEMPLATE_REL);
319
362
  const targets = HOOK_SCRIPT_DESTINATIONS.fabricHint.map((rel) => join2(projectRoot, rel));
@@ -822,6 +865,7 @@ export {
822
865
  installFabricReviewSkill,
823
866
  installFabricImportSkill,
824
867
  cleanupDeprecatedSkills,
868
+ installSharedSkillLib,
825
869
  installArchiveHintHook,
826
870
  installKnowledgeHintBroadHook,
827
871
  installKnowledgeHintNarrowHook,
@@ -41,24 +41,9 @@ function padEnd(value, width, char = " ") {
41
41
  return result;
42
42
  }
43
43
 
44
- // src/lib/error-render.ts
45
- function hasActionHint(err) {
46
- if (err === null || typeof err !== "object") return false;
47
- const candidate = err;
48
- return typeof candidate.message === "string" && candidate.message.length > 0 && typeof candidate.actionHint === "string" && candidate.actionHint.length > 0;
49
- }
50
- function renderFabricError(err, stream = process.stderr) {
51
- stream.write(`${err.message}
52
- `);
53
- stream.write(` -> ${err.actionHint}
54
- `);
55
- }
56
-
57
44
  export {
58
45
  paint,
59
46
  symbol,
60
47
  displayWidth,
61
- padEnd,
62
- hasActionHint,
63
- renderFabricError
48
+ padEnd
64
49
  };
@@ -1,29 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- hasActionHint,
4
3
  paint,
5
- renderFabricError,
6
4
  symbol
7
- } from "./chunk-G2CIOLD4.js";
5
+ } from "./chunk-WWNXR34K.js";
6
+ import {
7
+ resolveDevMode
8
+ } from "./chunk-COI5VDFU.js";
8
9
  import {
9
10
  getDoctorTranslator,
10
11
  t
11
12
  } from "./chunk-PWLW3B57.js";
12
- import {
13
- resolveDevMode
14
- } from "./chunk-COI5VDFU.js";
15
13
 
16
14
  // src/commands/doctor.ts
17
15
  import { confirm, isCancel } from "@clack/prompts";
18
16
  import { defineCommand } from "citty";
19
17
  import {
20
18
  appendEventLedgerEvent,
21
- checkLockOrThrow,
22
19
  enrichDescriptions,
23
20
  runDoctorApplyLint as runDoctorFixKnowledge,
24
21
  runDoctorArchiveHistory,
25
22
  runDoctorCiteCoverage,
26
23
  runDoctorFix,
24
+ runDoctorHistoryAll,
27
25
  runDoctorReport
28
26
  } from "@fenglimg/fabric-server";
29
27
  var FIX_KNOWLEDGE_CODE_LABELS = {
@@ -135,21 +133,22 @@ var doctorCommand = defineCommand({
135
133
  type: "boolean",
136
134
  description: t("cli.doctor.args.archive-history.description"),
137
135
  default: false
136
+ },
137
+ // rc.37 NEW-33: unified history view across archive / fix / all surfaces.
138
+ // Mode = `archive | fix | all` (the `archive` mode delegates to the
139
+ // existing runDoctorArchiveHistory; `fix` aggregates doctor_run events;
140
+ // `all` rolls up both into a per-day count table). Read-only; mutex
141
+ // with the mutation arms.
142
+ history: {
143
+ type: "string",
144
+ description: t("cli.doctor.args.history.description"),
145
+ valueHint: "archive|fix|all"
138
146
  }
139
147
  },
140
148
  async run({ args }) {
141
149
  const workspaceRoot = process.cwd();
142
150
  const resolution = resolveDevMode(args.target, workspaceRoot);
143
151
  const dt = getDoctorTranslator(resolution.target);
144
- try {
145
- checkLockOrThrow(resolution.target);
146
- } catch (err) {
147
- if (hasActionHint(err)) {
148
- renderFabricError(err);
149
- process.exit(1);
150
- }
151
- throw err;
152
- }
153
152
  const fixKnowledge = args["fix-knowledge"] === true;
154
153
  const fix = args.fix === true;
155
154
  const citeCoverage = args["cite-coverage"] === true;
@@ -164,6 +163,44 @@ var doctorCommand = defineCommand({
164
163
  return;
165
164
  }
166
165
  }
166
+ const historyMode = args.history;
167
+ if (typeof historyMode === "string" && historyMode.length > 0) {
168
+ if (fix || fixKnowledge || citeCoverage || enrichDesc || archiveHistory) {
169
+ writeStderr(dt("cli.doctor.errors.history-mutex"));
170
+ process.exitCode = 1;
171
+ return;
172
+ }
173
+ if (historyMode !== "archive" && historyMode !== "fix" && historyMode !== "all") {
174
+ writeStderr(dt("cli.doctor.errors.invalid-history-mode", { input: historyMode }));
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ const sinceInput = args.since ?? "7d";
179
+ let sinceMs;
180
+ try {
181
+ sinceMs = parseSinceDuration(sinceInput);
182
+ } catch {
183
+ writeStderr(dt("cli.doctor.errors.invalid-since", { input: sinceInput }));
184
+ process.exitCode = 1;
185
+ return;
186
+ }
187
+ if (historyMode === "archive") {
188
+ const report3 = await runDoctorArchiveHistory(resolution.target, { since: sinceMs });
189
+ if (args.json === true) {
190
+ writeStdout(JSON.stringify(report3, null, 2));
191
+ } else {
192
+ renderArchiveHistoryReport(report3, sinceInput, dt);
193
+ }
194
+ return;
195
+ }
196
+ const report2 = await runDoctorHistoryAll(resolution.target, { since: sinceMs });
197
+ if (args.json === true) {
198
+ writeStdout(JSON.stringify(report2, null, 2));
199
+ } else {
200
+ renderHistoryAllReport(report2, sinceInput, historyMode, dt);
201
+ }
202
+ return;
203
+ }
167
204
  if (archiveHistory) {
168
205
  if (fix || fixKnowledge || citeCoverage || enrichDesc) {
169
206
  writeStderr(dt("cli.doctor.errors.archive-history-mutex"));
@@ -319,6 +356,7 @@ var doctorCommand = defineCommand({
319
356
  var doctor_default = doctorCommand;
320
357
  function renderHumanReport(report, dt, verbose) {
321
358
  writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
359
+ renderTldrHeader(report);
322
360
  for (const check of report.checks) {
323
361
  writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
324
362
  }
@@ -372,6 +410,31 @@ function writeIssueSection(title, issues, options) {
372
410
  }
373
411
  }
374
412
  }
413
+ function renderTldrHeader(report) {
414
+ const ranked = [];
415
+ for (const issue of report.fixable_errors) {
416
+ ranked.push({ severity: "fixable", code: issue.code, message: issue.message });
417
+ }
418
+ for (const issue of report.manual_errors) {
419
+ ranked.push({ severity: "manual", code: issue.code, message: issue.message });
420
+ }
421
+ for (const issue of report.warnings) {
422
+ ranked.push({ severity: "warn", code: issue.code, message: issue.message });
423
+ }
424
+ if (ranked.length === 0) {
425
+ writeStdout(`${symbol.ok} TL;DR: all 48 checks green \u2014 nothing to fix.`);
426
+ return;
427
+ }
428
+ const top3 = ranked.slice(0, 3);
429
+ writeStdout(
430
+ `TL;DR (top ${top3.length} of ${ranked.length}, severity order: fixable\u2192manual\u2192warn):`
431
+ );
432
+ for (const item of top3) {
433
+ const marker = item.severity === "fixable" ? symbol.error : item.severity === "manual" ? symbol.error : symbol.warn;
434
+ const truncated = item.message.length > 140 ? `${item.message.slice(0, 137)}...` : item.message;
435
+ writeStdout(` ${marker} ${item.code}: ${truncated}`);
436
+ }
437
+ }
375
438
  function renderStatus(status) {
376
439
  if (status === "ok") {
377
440
  return symbol.ok;
@@ -692,6 +755,39 @@ function renderArchiveHistoryReport(report, sinceLabel, dt) {
692
755
  }
693
756
  writeStdout(lines.join("\n"));
694
757
  }
758
+ function renderHistoryAllReport(report, sinceLabel, mode, dt) {
759
+ if (report.rows.length === 0) {
760
+ writeStdout(dt("doctor.history.empty", { sinceLabel, mode }));
761
+ return;
762
+ }
763
+ const lines = [];
764
+ lines.push(
765
+ dt("doctor.history.header", {
766
+ sinceLabel,
767
+ mode,
768
+ days: String(report.rows.length)
769
+ })
770
+ );
771
+ lines.push("");
772
+ if (mode === "fix") {
773
+ lines.push("| date | lint | fix | issues | mutations |");
774
+ lines.push("| ---------- | ---- | --- | ------ | --------- |");
775
+ for (const row of report.rows) {
776
+ lines.push(
777
+ `| ${row.date} | ${row.doctor_runs_lint} | ${row.doctor_runs_fix} | ${row.doctor_total_issues} | ${row.doctor_total_mutations} |`
778
+ );
779
+ }
780
+ } else {
781
+ lines.push("| date | lint | fix | issues | mutations | archive | proposed |");
782
+ lines.push("| ---------- | ---- | --- | ------ | --------- | ------- | -------- |");
783
+ for (const row of report.rows) {
784
+ lines.push(
785
+ `| ${row.date} | ${row.doctor_runs_lint} | ${row.doctor_runs_fix} | ${row.doctor_total_issues} | ${row.doctor_total_mutations} | ${row.archive_attempts} | ${row.archive_proposed} |`
786
+ );
787
+ }
788
+ }
789
+ writeStdout(lines.join("\n"));
790
+ }
695
791
  function formatTimestampForTable(iso) {
696
792
  const d = new Date(iso);
697
793
  if (Number.isNaN(d.getTime())) return iso;
package/dist/index.js CHANGED
@@ -11,22 +11,23 @@ import { defineCommand, runMain } from "citty";
11
11
 
12
12
  // src/commands/index.ts
13
13
  var allCommands = {
14
- install: () => import("./install-XSUIX6AD.js").then((module) => module.default),
15
- doctor: () => import("./doctor-2FCRAWDZ.js").then((module) => module.default),
16
- serve: () => import("./serve-43JTEM3U.js").then((module) => module.default),
17
- uninstall: () => import("./uninstall-BIJ5GLEU.js").then((module) => module.default),
14
+ install: () => import("./install-U7MGIJ2L.js").then((module) => module.default),
15
+ doctor: () => import("./doctor-764NFF3X.js").then((module) => module.default),
16
+ uninstall: () => import("./uninstall-MH7ZIB6M.js").then((module) => module.default),
18
17
  config: () => import("./config-XJIPZNUP.js").then((module) => module.default),
19
18
  "plan-context-hint": () => import("./plan-context-hint-UQLRKGBZ.js").then((module) => module.default),
20
19
  // v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
21
20
  // fabric-archive Skill's first-run phase to detect unclaimed slots.
22
- "onboard-coverage": () => import("./onboard-coverage-MFCAEBDO.js").then((module) => module.default)
21
+ "onboard-coverage": () => import("./onboard-coverage-MFCAEBDO.js").then((module) => module.default),
22
+ // v2.0.0-rc.37 NEW-34: text dashboard over .fabric/metrics.jsonl.
23
+ metrics: () => import("./metrics-ACEQFPDU.js").then((module) => module.default)
23
24
  };
24
25
 
25
26
  // src/index.ts
26
27
  var main = defineCommand({
27
28
  meta: {
28
29
  name: "fabric",
29
- version: "2.0.0-rc.36",
30
+ version: "2.0.0-rc.37",
30
31
  description: t("cli.main.description")
31
32
  },
32
33
  subCommands: allCommands
@@ -12,6 +12,7 @@ import {
12
12
  installHookLibs,
13
13
  installKnowledgeHintBroadHook,
14
14
  installKnowledgeHintNarrowHook,
15
+ installSharedSkillLib,
15
16
  mergeClaudeCodeHookConfig,
16
17
  mergeCodexHookConfig,
17
18
  mergeCursorHookConfig,
@@ -20,24 +21,22 @@ import {
20
21
  writeCodexBootstrapManagedBlock,
21
22
  writeCursorBootstrapManagedBlock,
22
23
  writeFabricAgentsSnapshot
23
- } from "./chunk-XVS4F3P6.js";
24
+ } from "./chunk-D25XJ4BC.js";
24
25
  import {
25
26
  detectClientSupports
26
27
  } from "./chunk-MF3OTILQ.js";
27
28
  import {
28
29
  displayWidth,
29
- hasActionHint,
30
30
  padEnd,
31
- paint,
32
- renderFabricError
33
- } from "./chunk-G2CIOLD4.js";
34
- import {
35
- t
36
- } from "./chunk-PWLW3B57.js";
31
+ paint
32
+ } from "./chunk-WWNXR34K.js";
37
33
  import {
38
34
  createDebugLogger,
39
35
  resolveDevMode
40
36
  } from "./chunk-COI5VDFU.js";
37
+ import {
38
+ t
39
+ } from "./chunk-PWLW3B57.js";
41
40
 
42
41
  // src/commands/install.ts
43
42
  import { randomUUID } from "crypto";
@@ -49,7 +48,6 @@ import { cancel, confirm, group, intro, isCancel, log, note, outro, select } fro
49
48
  import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
50
49
  import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
51
50
  import { defineCommand } from "citty";
52
- import { checkLockOrThrow } from "@fenglimg/fabric-server";
53
51
 
54
52
  // src/install/hooks-orchestrator.ts
55
53
  import { existsSync, statSync } from "fs";
@@ -61,6 +59,7 @@ async function installHooks(target, _options = {}) {
61
59
  results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
62
60
  results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
63
61
  results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
62
+ results.push(...await runStep(() => installSharedSkillLib(normalizedTarget)));
64
63
  results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
65
64
  results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
66
65
  results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
@@ -1351,7 +1350,7 @@ function readProjectName(target) {
1351
1350
  return basename(target);
1352
1351
  }
1353
1352
  function getCliVersion() {
1354
- return true ? "2.0.0-rc.36" : "unknown";
1353
+ return true ? "2.0.0-rc.37" : "unknown";
1355
1354
  }
1356
1355
  function sortRecord(record) {
1357
1356
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1393,6 +1392,11 @@ var installCommand = defineCommand({
1393
1392
  type: "boolean",
1394
1393
  description: t("cli.install.args.force-skills-only.description"),
1395
1394
  default: false
1395
+ },
1396
+ "force-hooks-only": {
1397
+ type: "boolean",
1398
+ description: t("cli.install.args.force-hooks-only.description"),
1399
+ default: false
1396
1400
  }
1397
1401
  },
1398
1402
  async run({ args }) {
@@ -1443,6 +1447,35 @@ ${hint}
1443
1447
  process.exitCode = 1;
1444
1448
  }
1445
1449
  }
1450
+ async function runHooksOnlyRefresh(targetInput) {
1451
+ const target = normalizeTarget3(targetInput);
1452
+ const metaPath = join4(target, ".fabric", "agents.meta.json");
1453
+ if (!existsSync4(metaPath)) {
1454
+ const message = t("cli.install.force-hooks-only.uninitialised.message");
1455
+ const hint = t("cli.install.force-hooks-only.uninitialised.hint");
1456
+ process.stderr.write(`${message}
1457
+ ${hint}
1458
+ `);
1459
+ process.exitCode = 1;
1460
+ return;
1461
+ }
1462
+ console.log(formatInitStageHeader(t("cli.install.force-hooks-only.banner")));
1463
+ const result = await installHooks(target);
1464
+ console.log(
1465
+ t("cli.install.force-hooks-only.summary", {
1466
+ written: String(result.installed.length),
1467
+ skipped: String(result.skipped.length),
1468
+ errors: String(result.errors.length)
1469
+ })
1470
+ );
1471
+ if (result.errors.length > 0) {
1472
+ for (const err of result.errors) {
1473
+ process.stderr.write(` ${err}
1474
+ `);
1475
+ }
1476
+ process.exitCode = 1;
1477
+ }
1478
+ }
1446
1479
  async function runInitCommand(args) {
1447
1480
  const logger = createDebugLogger(args.debug);
1448
1481
  const resolution = resolveDevMode(args.target, process.cwd());
@@ -1450,19 +1483,11 @@ async function runInitCommand(args) {
1450
1483
  await runSkillsOnlyRefresh(resolution.target);
1451
1484
  return;
1452
1485
  }
1453
- const intent = resolveInitCliIntent(args, resolution.target);
1454
- const fabricInitialized = existsSync4(join4(intent.target, ".fabric", "events.jsonl"));
1455
- if (fabricInitialized) {
1456
- try {
1457
- checkLockOrThrow(intent.target);
1458
- } catch (err) {
1459
- if (hasActionHint(err)) {
1460
- renderFabricError(err);
1461
- process.exit(1);
1462
- }
1463
- throw err;
1464
- }
1486
+ if (args["force-hooks-only"] === true) {
1487
+ await runHooksOnlyRefresh(resolution.target);
1488
+ return;
1465
1489
  }
1490
+ const intent = resolveInitCliIntent(args, resolution.target);
1466
1491
  logger(`init target source: ${resolution.source}`);
1467
1492
  for (const step of resolution.chain) {
1468
1493
  logger(step);
@@ -2335,6 +2360,8 @@ function printInitCapabilitySummary(supports, stageResults, options) {
2335
2360
  for (const row of rows) {
2336
2361
  console.log(formatCapabilityTableRow(row, widths));
2337
2362
  }
2363
+ console.log("");
2364
+ console.log(t("cli.install.restart-banner"));
2338
2365
  }
2339
2366
  function toCapabilityRow(support, stageResults, options) {
2340
2367
  const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
@@ -2455,6 +2482,7 @@ export {
2455
2482
  initFabric,
2456
2483
  installCommand,
2457
2484
  resolveInitExecutionPlanWithWizard,
2485
+ runHooksOnlyRefresh,
2458
2486
  runInitCommand,
2459
2487
  runSkillsOnlyRefresh,
2460
2488
  shouldUseInitWizard
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/metrics.ts
4
+ import { resolve } from "path";
5
+ import { defineCommand } from "citty";
6
+ import { readMetrics } from "@fenglimg/fabric-server";
7
+ function parseSinceArg(raw) {
8
+ if (raw === void 0 || raw.length === 0) return 0;
9
+ const match = /^(\d+)([smhd]?)$/u.exec(raw);
10
+ if (match === null) {
11
+ throw new Error(`--since: invalid duration "${raw}" (expected e.g. 24h, 7d, 30m)`);
12
+ }
13
+ const n = Number.parseInt(match[1], 10);
14
+ const unit = match[2] ?? "s";
15
+ const multipliers = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
16
+ return n * (multipliers[unit] ?? 1e3);
17
+ }
18
+ function aggregate(rows, sinceMs, now) {
19
+ const cutoff = sinceMs > 0 ? now.getTime() - sinceMs : 0;
20
+ const filtered = rows.filter((r) => {
21
+ if (cutoff === 0) return true;
22
+ const ts = Date.parse(r.timestamp);
23
+ return Number.isFinite(ts) && ts >= cutoff;
24
+ });
25
+ const totals = {};
26
+ const perEntryConsumed = {};
27
+ for (const row of filtered) {
28
+ for (const [name, count] of Object.entries(row.counters)) {
29
+ if (name.startsWith("knowledge_consumed:")) {
30
+ const id = name.slice("knowledge_consumed:".length);
31
+ perEntryConsumed[id] = (perEntryConsumed[id] ?? 0) + count;
32
+ totals["knowledge_consumed"] = (totals["knowledge_consumed"] ?? 0) + count;
33
+ continue;
34
+ }
35
+ totals[name] = (totals[name] ?? 0) + count;
36
+ }
37
+ }
38
+ return {
39
+ windowDescription: sinceMs > 0 ? formatDuration(sinceMs) : "all-time",
40
+ rowCount: filtered.length,
41
+ totals,
42
+ perEntryConsumed,
43
+ rangeStart: filtered[0]?.timestamp ?? null,
44
+ rangeEnd: filtered[filtered.length - 1]?.timestamp ?? null
45
+ };
46
+ }
47
+ function formatDuration(ms) {
48
+ if (ms >= 864e5) return `${Math.round(ms / 864e5)}d`;
49
+ if (ms >= 36e5) return `${Math.round(ms / 36e5)}h`;
50
+ if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
51
+ return `${Math.round(ms / 1e3)}s`;
52
+ }
53
+ function renderText(agg) {
54
+ const lines = [];
55
+ lines.push(`Fabric metrics \u2014 window: ${agg.windowDescription}`);
56
+ if (agg.rangeStart && agg.rangeEnd) {
57
+ lines.push(` rows: ${agg.rowCount} (${agg.rangeStart} \u2192 ${agg.rangeEnd})`);
58
+ } else {
59
+ lines.push(` rows: ${agg.rowCount}`);
60
+ }
61
+ lines.push("");
62
+ if (Object.keys(agg.totals).length === 0) {
63
+ lines.push(" (no counter activity in window \u2014 server may be idle or just started)");
64
+ return lines.join("\n");
65
+ }
66
+ lines.push(" counter total");
67
+ lines.push(" ------------------------------------ ----------");
68
+ const sorted = Object.entries(agg.totals).sort((a, b) => b[1] - a[1]);
69
+ for (const [name, count] of sorted) {
70
+ lines.push(` ${name.padEnd(36)} ${String(count).padStart(10)}`);
71
+ }
72
+ const perEntrySorted = Object.entries(agg.perEntryConsumed).sort((a, b) => b[1] - a[1]).slice(0, 10);
73
+ if (perEntrySorted.length > 0) {
74
+ lines.push("");
75
+ lines.push(" Top per-entry consumed (knowledge_consumed:<id>)");
76
+ lines.push(" ------------------------------------ ----------");
77
+ for (const [id, count] of perEntrySorted) {
78
+ lines.push(` ${id.padEnd(36)} ${String(count).padStart(10)}`);
79
+ }
80
+ }
81
+ return lines.join("\n");
82
+ }
83
+ var metricsCommand = defineCommand({
84
+ meta: {
85
+ name: "metrics",
86
+ description: "Print a text dashboard of Fabric counter activity from .fabric/metrics.jsonl",
87
+ hidden: true
88
+ },
89
+ args: {
90
+ json: {
91
+ type: "boolean",
92
+ description: "Emit machine-readable JSON instead of the text table.",
93
+ default: false
94
+ },
95
+ target: {
96
+ type: "string",
97
+ description: "Project root (defaults to the current working directory)."
98
+ },
99
+ since: {
100
+ type: "string",
101
+ description: "Limit to rows within a recent window. Examples: 24h, 7d, 30m, 90s. Omit for all-time."
102
+ }
103
+ },
104
+ async run({ args }) {
105
+ const projectRoot = resolve(args.target ?? process.cwd());
106
+ const sinceMs = parseSinceArg(args.since);
107
+ const rows = await readMetrics(projectRoot);
108
+ const aggregated = aggregate(rows, sinceMs, /* @__PURE__ */ new Date());
109
+ if (args.json === true) {
110
+ process.stdout.write(`${JSON.stringify(aggregated)}
111
+ `);
112
+ } else {
113
+ process.stdout.write(`${renderText(aggregated)}
114
+ `);
115
+ }
116
+ }
117
+ });
118
+ var metrics_default = metricsCommand;
119
+ export {
120
+ metrics_default as default,
121
+ metricsCommand
122
+ };
@@ -7,23 +7,21 @@ import {
7
7
  HOOK_SCRIPT_DESTINATIONS,
8
8
  SKILL_DESTINATIONS,
9
9
  fabricAgentsSnapshotPath
10
- } from "./chunk-XVS4F3P6.js";
10
+ } from "./chunk-D25XJ4BC.js";
11
11
  import {
12
12
  detectClientSupports,
13
13
  resolveClients
14
14
  } from "./chunk-MF3OTILQ.js";
15
15
  import {
16
- hasActionHint,
17
- paint,
18
- renderFabricError
19
- } from "./chunk-G2CIOLD4.js";
20
- import {
21
- t
22
- } from "./chunk-PWLW3B57.js";
16
+ paint
17
+ } from "./chunk-WWNXR34K.js";
23
18
  import {
24
19
  createDebugLogger,
25
20
  resolveDevMode
26
21
  } from "./chunk-COI5VDFU.js";
22
+ import {
23
+ t
24
+ } from "./chunk-PWLW3B57.js";
27
25
 
28
26
  // src/commands/uninstall.ts
29
27
  import { existsSync as existsSync2, statSync } from "fs";
@@ -32,7 +30,6 @@ import { homedir } from "os";
32
30
  import { isAbsolute, join as join2, relative, resolve, sep } from "path";
33
31
  import { cancel, confirm, group, intro, isCancel, log, note, outro } from "@clack/prompts";
34
32
  import { defineCommand } from "citty";
35
- import { checkLockOrThrow } from "@fenglimg/fabric-server";
36
33
 
37
34
  // src/install/uninstall-skills-and-hooks.ts
38
35
  import { existsSync } from "fs";
@@ -570,15 +567,6 @@ async function runUninstallCommand(args) {
570
567
  for (const step of resolution.chain) {
571
568
  logger(step);
572
569
  }
573
- try {
574
- checkLockOrThrow(intent.target);
575
- } catch (err) {
576
- if (hasActionHint(err)) {
577
- renderFabricError(err);
578
- process.exit(1);
579
- }
580
- throw err;
581
- }
582
570
  const supports = detectClientSupports(intent.target);
583
571
  const basePlan = await buildUninstallExecutionPlan(intent.target, {
584
572
  ...intent.options