@fenglimg/fabric-server 2.1.0-rc.2 → 2.2.0-rc.3

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 (3) hide show
  1. package/dist/index.d.ts +483 -161
  2. package/dist/index.js +5380 -3603
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { AgentsMeta, KnowledgeTestIndex, AgentsLayer, AgentsTopologyType, KnowledgeType, Layer, StableId, AgentsMetaCounters, EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem } from '@fenglimg/fabric-shared';
2
+ import { AgentsMeta, KnowledgeTestIndex, AgentsLayer, AgentsTopologyType, KnowledgeType, Layer, StableId, AgentsMetaCounters, EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem, LedgerEntry } from '@fenglimg/fabric-shared';
3
3
  import { FabExtractKnowledgeInput, FabExtractKnowledgeOutput, FabReviewInput, FabReviewOutput } from '@fenglimg/fabric-shared/schemas/api-contracts';
4
4
 
5
5
  interface InFlightTracker {
@@ -22,90 +22,79 @@ declare function getLegacyLedgerPath(projectRoot: string): string;
22
22
  declare function getEventLedgerPath(projectRoot: string): string;
23
23
  declare function getMetricsLedgerPath(projectRoot: string): string;
24
24
 
25
- type DoctorStatus = "ok" | "warn" | "error";
26
- type DoctorIssueKind = "fixable_error" | "manual_error" | "warning" | "info";
27
- type DoctorCheck = {
28
- name: string;
29
- status: DoctorStatus;
30
- message: string;
31
- kind?: DoctorIssueKind;
32
- code?: string;
33
- fixable?: boolean;
34
- actionHint?: string;
35
- audience?: "user" | "maintainer";
36
- };
37
- type DoctorIssue = {
38
- code: string;
39
- name: string;
40
- message: string;
41
- path?: string;
42
- actionHint?: string;
43
- audience?: "user" | "maintainer";
44
- };
45
- type DoctorPayloadLimits = {
46
- warn_bytes: number;
47
- hard_bytes: number;
48
- source: "default" | "config";
49
- };
50
- type DoctorSummary = {
51
- target: string;
52
- framework: {
53
- kind: string;
54
- version: string;
55
- subkind: string;
56
- };
57
- entryPoints: Array<{
58
- path: string;
59
- reason: string;
60
- }>;
61
- metaRevision: string | null;
62
- computedMetaRevision: string | null;
63
- ruleCount: number;
64
- eventLedgerPath: string;
65
- fixableErrorCount: number;
66
- manualErrorCount: number;
67
- warningCount: number;
68
- infoCount: number;
69
- targetFiles: Record<string, boolean>;
70
- payload_limits: DoctorPayloadLimits;
71
- };
72
- type DoctorReport = {
73
- status: DoctorStatus;
74
- checks: DoctorCheck[];
75
- fixable_errors: DoctorIssue[];
76
- manual_errors: DoctorIssue[];
77
- warnings: DoctorIssue[];
78
- infos: DoctorIssue[];
79
- summary: DoctorSummary;
80
- };
81
- type DoctorFixReport = {
82
- changed: boolean;
83
- fixed: DoctorIssue[];
84
- remaining_manual_errors: DoctorIssue[];
85
- warnings: DoctorIssue[];
86
- message: string;
87
- report: DoctorReport;
88
- };
89
- type DoctorApplyLintMutationKind = "knowledge_orphan_demote_required" | "knowledge_stale_archive_required" | "knowledge_index_drift" | "knowledge_pending_auto_archive" | "knowledge_session_hints_stale_cleanup" | "knowledge_relevance_fields_missing";
90
- type DoctorApplyLintMutation = {
91
- kind: DoctorApplyLintMutationKind;
92
- path: string;
93
- detail: string;
94
- applied: boolean;
95
- error?: string;
25
+ /**
26
+ * O(1) in-memory increment for a named counter. Safe to call from any MCP
27
+ * tool handler; no I/O happens until the next `flushMetrics()` call.
28
+ *
29
+ * `delta` defaults to 1; callers can pass a positive integer (e.g. fetched
30
+ * N stable_ids in a single sections call) to fold N bumps into one.
31
+ */
32
+ declare function bumpCounter(projectRoot: string, name: string, delta?: number): void;
33
+ /**
34
+ * Snapshot the current counter accumulator and reset it. Returned map is a
35
+ * frozen copy; the live accumulator starts fresh from zero. Exposed so
36
+ * flushMetrics + tests + a future fab_metrics manual-flush CLI hook can all
37
+ * use the same primitive without racing.
38
+ */
39
+ declare function drainCounters(projectRoot: string): Record<string, number>;
40
+ /**
41
+ * Drain the current accumulator and append one JSONL row to
42
+ * `.fabric/metrics.jsonl`. Returns the appended row (or `null` when the
43
+ * accumulator was empty — no spurious zero rows). fs failures degrade
44
+ * silently; the next flush will carry the union of the failed-write
45
+ * interval + the current one.
46
+ */
47
+ declare function flushMetrics(projectRoot: string, options?: {
48
+ windowMs?: number;
49
+ now?: Date;
50
+ }): Promise<MetricsRow | null>;
51
+ /**
52
+ * Start the background flush timer for a project root. Idempotent — calling
53
+ * twice on the same root replaces the prior interval. Returns a stop handle
54
+ * the caller can invoke at shutdown to flush + clear the timer.
55
+ *
56
+ * The flush is fire-and-forget (no await on the setInterval callback) so
57
+ * the timer cadence stays accurate even when fs is slow.
58
+ */
59
+ declare function startMetricsFlush(projectRoot: string, options?: {
60
+ intervalMs?: number;
61
+ }): () => Promise<void>;
62
+ /**
63
+ * Cancel the background flush timer for a project root if one is running.
64
+ * Does NOT drain the accumulator — callers that want a final flush should
65
+ * await flushMetrics(projectRoot) afterward.
66
+ */
67
+ declare function stopMetricsFlush(projectRoot: string): void;
68
+ /**
69
+ * Read accumulated metrics rows from `.fabric/metrics.jsonl`. Missing file
70
+ * returns []. Malformed rows are dropped silently (the sidecar is best-
71
+ * effort observability; a corrupt row never blocks a reader).
72
+ *
73
+ * Exposed for the NEW-34 `fab metrics` CLI dashboard + future doctor lints
74
+ * (e.g. cite-goodhart pattern replay) that need counter trends without
75
+ * walking events.jsonl.
76
+ */
77
+ declare function readMetrics(projectRoot: string): Promise<MetricsRow[]>;
78
+ type MetricsRow = {
79
+ timestamp: string;
80
+ window: string;
81
+ counters: Record<string, number>;
96
82
  };
97
- type DoctorApplyLintReport = {
98
- changed: boolean;
99
- mutations: DoctorApplyLintMutation[];
100
- manual_errors: DoctorIssue[];
101
- aborted: boolean;
102
- abort_reason?: string;
103
- message: string;
104
- report: DoctorReport;
83
+ /**
84
+ * Canonical metric counter names used by the high-frequency emitters that
85
+ * left events.jsonl as part of the rc.37 Wave B clean-slate. Centralized
86
+ * here so the B5 hard-gate (`metric_event_in_jsonl`) can grep for these
87
+ * exact strings and fail when one accidentally re-appears in the audit
88
+ * ledger emit path.
89
+ */
90
+ declare const METRIC_COUNTER_NAMES: {
91
+ readonly knowledge_consumed: "knowledge_consumed";
92
+ readonly edit_intent_checked: "edit_intent_checked";
93
+ readonly knowledge_context_planned: "knowledge_context_planned";
94
+ readonly knowledge_sections_fetched: "knowledge_sections_fetched";
105
95
  };
106
- declare function runDoctorReport(target: string): Promise<DoctorReport>;
107
- declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
108
- declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
96
+ type MetricCounterName = (typeof METRIC_COUNTER_NAMES)[keyof typeof METRIC_COUNTER_NAMES];
97
+
109
98
  type CiteContractMetrics = {
110
99
  decisions_cited: number;
111
100
  pitfalls_cited: number;
@@ -136,6 +125,25 @@ type CiteCoverageReport = {
136
125
  compliant_cites?: number;
137
126
  noncompliant_cites?: number;
138
127
  uncorrelatable_edits?: number;
128
+ recall_backed_edits?: number;
129
+ recall_coverage_rate?: number | null;
130
+ exposed_and_mutated?: {
131
+ count: number;
132
+ ids?: string[];
133
+ };
134
+ mutations_observed?: {
135
+ count: number;
136
+ };
137
+ mutation_pool?: {
138
+ attributed: number;
139
+ unattributed_workspace_dirty: number;
140
+ };
141
+ sessions_closed?: {
142
+ count: number;
143
+ };
144
+ by_store?: Record<string, {
145
+ qualifying_cites: number;
146
+ }>;
139
147
  };
140
148
  per_client?: Record<string, Partial<CiteCoverageReport["metrics"]>>;
141
149
  dismissed_reason_histogram?: Record<string, number>;
@@ -157,6 +165,7 @@ declare function runDoctorCiteCoverage(projectRoot: string, options: {
157
165
  client: "cc" | "codex" | "cursor" | "all";
158
166
  layer?: "team" | "personal" | "all";
159
167
  until?: number;
168
+ recallWindowMs?: number;
160
169
  }): Promise<CiteCoverageReport>;
161
170
  type ArchiveHistoryEntry = {
162
171
  session_id_short: string;
@@ -192,6 +201,101 @@ type HistoryAllReport = {
192
201
  declare function runDoctorHistoryAll(projectRoot: string, options: {
193
202
  since: number;
194
203
  }): Promise<HistoryAllReport>;
204
+
205
+ type DoctorStatus = "ok" | "warn" | "error";
206
+ type DoctorIssueKind = "fixable_error" | "manual_error" | "warning" | "info";
207
+ type DoctorCheck = {
208
+ name: string;
209
+ status: DoctorStatus;
210
+ message: string;
211
+ kind?: DoctorIssueKind;
212
+ code?: string;
213
+ fixable?: boolean;
214
+ actionHint?: string;
215
+ audience?: "user" | "maintainer";
216
+ };
217
+ type DoctorIssue = {
218
+ code: string;
219
+ name: string;
220
+ message: string;
221
+ path?: string;
222
+ actionHint?: string;
223
+ audience?: "user" | "maintainer";
224
+ };
225
+ type DoctorPayloadLimits = {
226
+ warn_bytes: number;
227
+ hard_bytes: number;
228
+ source: "default" | "config";
229
+ };
230
+ type DoctorSummary = {
231
+ target: string;
232
+ framework: {
233
+ kind: string;
234
+ version: string;
235
+ subkind: string;
236
+ };
237
+ entryPoints: Array<{
238
+ path: string;
239
+ reason: string;
240
+ }>;
241
+ metaRevision: string | null;
242
+ computedMetaRevision: string | null;
243
+ ruleCount: number;
244
+ eventLedgerPath: string;
245
+ fixableErrorCount: number;
246
+ manualErrorCount: number;
247
+ warningCount: number;
248
+ infoCount: number;
249
+ targetFiles: Record<string, boolean>;
250
+ payload_limits: DoctorPayloadLimits;
251
+ health: DoctorHealth;
252
+ };
253
+ type DoctorHealth = {
254
+ score: number;
255
+ grade: "A" | "B" | "C" | "D" | "F";
256
+ penalties: {
257
+ manual_errors: number;
258
+ fixable_errors: number;
259
+ warnings: number;
260
+ };
261
+ };
262
+ type DoctorReport = {
263
+ status: DoctorStatus;
264
+ checks: DoctorCheck[];
265
+ fixable_errors: DoctorIssue[];
266
+ manual_errors: DoctorIssue[];
267
+ warnings: DoctorIssue[];
268
+ infos: DoctorIssue[];
269
+ summary: DoctorSummary;
270
+ };
271
+ type DoctorFixReport = {
272
+ changed: boolean;
273
+ fixed: DoctorIssue[];
274
+ remaining_manual_errors: DoctorIssue[];
275
+ warnings: DoctorIssue[];
276
+ message: string;
277
+ report: DoctorReport;
278
+ };
279
+ type DoctorApplyLintMutationKind = "knowledge_orphan_demote_required" | "knowledge_stale_archive_required" | "knowledge_index_drift" | "knowledge_pending_auto_archive" | "knowledge_session_hints_stale_cleanup" | "knowledge_relevance_fields_missing";
280
+ type DoctorApplyLintMutation = {
281
+ kind: DoctorApplyLintMutationKind;
282
+ path: string;
283
+ detail: string;
284
+ applied: boolean;
285
+ error?: string;
286
+ };
287
+ type DoctorApplyLintReport = {
288
+ changed: boolean;
289
+ mutations: DoctorApplyLintMutation[];
290
+ manual_errors: DoctorIssue[];
291
+ aborted: boolean;
292
+ abort_reason?: string;
293
+ message: string;
294
+ report: DoctorReport;
295
+ };
296
+ declare function runDoctorReport(target: string): Promise<DoctorReport>;
297
+ declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
298
+ declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
195
299
  type EnrichDescriptionsMode = "auto" | "preview" | "readonly" | "interactive";
196
300
  type EnrichDescriptionsCandidate = {
197
301
  path: string;
@@ -213,6 +317,130 @@ declare function enrichDescriptions(projectRoot: string, opts?: {
213
317
  dryRun?: boolean;
214
318
  }): Promise<EnrichDescriptionsReport>;
215
319
 
320
+ interface Bm25Document {
321
+ id: string;
322
+ tokens: string[];
323
+ }
324
+ interface Bm25Model {
325
+ /**
326
+ * BM25 score of document `id` against the (pre-tokenized) query terms.
327
+ * Returns 0 for an unknown id, an empty document, no query terms, or no
328
+ * term overlap. Query-term duplicates are collapsed — repeating a term in
329
+ * the query does not inflate the score (term frequency is a document
330
+ * property, not a query property).
331
+ */
332
+ scoreDoc(id: string, queryTerms: string[]): number;
333
+ }
334
+ /**
335
+ * Build a BM25 model over `docs`. The corpus statistics (document frequency,
336
+ * average document length) are computed once here; `scoreDoc` is then O(query
337
+ * terms) per call.
338
+ */
339
+ declare function buildBm25Model(docs: Bm25Document[]): Bm25Model;
340
+
341
+ interface ConflictEntry {
342
+ stable_id: string;
343
+ /** Plural knowledge_type ("decisions" | "pitfalls" | "guidelines" | "models" | "processes"). */
344
+ knowledge_type: string;
345
+ /** "team" | "personal". Conflicts are only meaningful within one layer. */
346
+ layer: string;
347
+ /** Title + body (or any text used for similarity). */
348
+ text: string;
349
+ }
350
+ type ConflictVerdict = "conflict" | "similar" | "unknown";
351
+ interface ConflictPair {
352
+ a: string;
353
+ b: string;
354
+ knowledge_type: string;
355
+ layer: string;
356
+ /** Symmetric normalized bm25 similarity in [0,1]. */
357
+ similarity: number;
358
+ /**
359
+ * "unknown" until a judge classifies it (cheap pass leaves it unknown =
360
+ * needs-human-review). "conflict" = the judge ruled a real contradiction
361
+ * (escalates to error). "similar" = judged not-a-conflict (stays a warn /
362
+ * possible duplicate).
363
+ */
364
+ verdict: ConflictVerdict;
365
+ rationale?: string;
366
+ }
367
+ type ConflictJudge = (a: ConflictEntry, b: ConflictEntry) => Promise<{
368
+ isConflict: boolean;
369
+ rationale: string;
370
+ }>;
371
+ declare const DEFAULT_CONFLICT_SIMILARITY_THRESHOLD = 0.5;
372
+ /**
373
+ * Symmetric, [0,1]-normalized bm25 similarity between two docs sharing a model.
374
+ * For each direction we measure how much of the target doc's self-relevance the
375
+ * other doc's terms recover (scoreDoc(target, otherTokens) / scoreDoc(target,
376
+ * targetTokens)), then take the MIN of both directions — conservative: both
377
+ * entries must strongly overlap, so a short entry incidentally contained in a
378
+ * long one does not over-fire.
379
+ */
380
+ declare function pairSimilarity(model: ReturnType<typeof buildBm25Model>, a: {
381
+ id: string;
382
+ tokens: string[];
383
+ }, b: {
384
+ id: string;
385
+ tokens: string[];
386
+ }): number;
387
+ /**
388
+ * Cheap deterministic pass: candidate conflicting/duplicate pairs. Compares
389
+ * only entries sharing (type, layer), via bm25 similarity ≥ threshold. Returns
390
+ * pairs sorted by similarity descending (stable_id-tie-broken for determinism).
391
+ * Every returned pair has verdict "unknown" — the cheap pass cannot tell a
392
+ * conflict from a benign duplicate; that is the judge's job.
393
+ *
394
+ * O(G · n_g²) where G = groups and n_g = entries per group. Conflicts only make
395
+ * sense within one (type, layer), so the quadratic stays bounded to small
396
+ * same-bucket sets, not the whole corpus.
397
+ */
398
+ declare function findConflictCandidates(entries: ConflictEntry[], opts?: {
399
+ threshold?: number;
400
+ }): ConflictPair[];
401
+ /**
402
+ * Full lint. Always runs the cheap pass; when `judge` is supplied (deep mode)
403
+ * each candidate is classified into conflict/similar. A judge throw leaves the
404
+ * pair as "unknown" (degrade-safe — a flaky judge never crashes doctor).
405
+ *
406
+ * Entry lookup for the judge is by stable_id from the same `entries` array.
407
+ */
408
+ declare function lintConflicts(entries: ConflictEntry[], opts?: {
409
+ threshold?: number;
410
+ judge?: ConflictJudge;
411
+ }): Promise<ConflictPair[]>;
412
+
413
+ interface ConflictLintReport {
414
+ status: "ok";
415
+ threshold: number;
416
+ deep: boolean;
417
+ /** Total candidate pairs (similarity ≥ threshold). */
418
+ candidate_count: number;
419
+ /** Subset judged a real contradiction (deep mode only). */
420
+ conflict_count: number;
421
+ pairs: ConflictPair[];
422
+ }
423
+ /**
424
+ * Load canonical knowledge entries with bodies from the agents.meta node index.
425
+ * Skips pending/draft staging entries (conflict detection targets the curated
426
+ * corpus) and any node missing a stable_id or knowledge_type. Bodies are read
427
+ * best-effort — an unreadable file contributes no entry.
428
+ */
429
+ declare function loadConflictEntries(projectRoot: string): Promise<ConflictEntry[]>;
430
+ /**
431
+ * Run the knowledge-conflict lint. Always runs the cheap bm25 candidate pass;
432
+ * when `deep` is set AND a judge is supplied, escalates candidates to
433
+ * conflict/similar verdicts via the injected LLM judge.
434
+ *
435
+ * threshold resolution: opts.threshold → fabric-config
436
+ * conflict_lint_similarity_threshold → DEFAULT_CONFLICT_SIMILARITY_THRESHOLD.
437
+ */
438
+ declare function runDoctorConflictLint(projectRoot: string, opts?: {
439
+ threshold?: number;
440
+ deep?: boolean;
441
+ judge?: ConflictJudge;
442
+ }): Promise<ConflictLintReport>;
443
+
216
444
  type KnowledgeMetaBuildSource = "doctor_fix" | "sync_meta";
217
445
  type KnowledgeMetaBuildResult = {
218
446
  meta: AgentsMeta;
@@ -329,7 +557,34 @@ declare function extractKnowledge(projectRoot: string, input: FabExtractKnowledg
329
557
  declare function reviewKnowledge(projectRoot: string, input: FabReviewInput): Promise<FabReviewOutput>;
330
558
 
331
559
  type StoredEventLedgerEvent = EventLedgerEvent;
560
+ type ReadEventLedgerOptions = {
561
+ event_type?: EventLedgerEvent["event_type"];
562
+ since?: number;
563
+ correlation_id?: string;
564
+ session_id?: string;
565
+ };
566
+ type LedgerWarning = {
567
+ kind: "partial_write_at_tail";
568
+ byte_offset: number;
569
+ byte_length: number;
570
+ snippet_first_120: string;
571
+ } | {
572
+ kind: "schema_version_unsupported";
573
+ line_index: number;
574
+ schema_version: unknown;
575
+ snippet_first_120: string;
576
+ } | {
577
+ kind: "event_type_unknown";
578
+ line_index: number;
579
+ event_type: unknown;
580
+ snippet_first_120: string;
581
+ };
582
+ type ReadEventLedgerResult = {
583
+ events: StoredEventLedgerEvent[];
584
+ warnings: LedgerWarning[];
585
+ };
332
586
  declare function appendEventLedgerEvent(projectRoot: string, event: EventLedgerEventInput): Promise<StoredEventLedgerEvent>;
587
+ declare function readEventLedger(projectRoot: string, options?: ReadEventLedgerOptions): Promise<ReadEventLedgerResult>;
333
588
  /**
334
589
  * Synchronously fsync the event ledger file to ensure OS page-cache buffers are
335
590
  * flushed to durable storage. Must be called AFTER in-flight drain but BEFORE
@@ -349,6 +604,8 @@ type PlanContextInput = {
349
604
  correlation_id?: string;
350
605
  session_id?: string;
351
606
  target_paths?: string[];
607
+ layer_filter?: "team" | "personal" | "both";
608
+ include_related?: boolean;
352
609
  };
353
610
  type RequirementProfile = {
354
611
  target_path: string;
@@ -373,10 +630,12 @@ type PlanContextResult = {
373
630
  selection_token: string;
374
631
  entries: PlanContextEntry[];
375
632
  candidates: RuleDescriptionIndexItem[];
633
+ omitted_candidate_count?: number;
376
634
  preflight_diagnostics: PreflightDiagnostic[];
377
635
  auto_healed?: boolean;
378
636
  previous_revision_hash?: string;
379
637
  redirects?: Record<string, string>;
638
+ related_appended?: Record<string, string>;
380
639
  };
381
640
  type SelectionTokenState = {
382
641
  token: string;
@@ -398,6 +657,18 @@ type RecallInput = PlanContextInput & {
398
657
  * provided, filters the fetched body set to this intersection.
399
658
  */
400
659
  ids?: string[];
660
+ /**
661
+ * v2.2 MC1-recall-pack (W2-T4): when true, expand the fetched set with the
662
+ * one-hop `related` graph neighbours (H2) of the selected entries that are
663
+ * also present in the candidate index. Lets a scoped `ids` recall pull in the
664
+ * connected knowledge without a second round-trip. No-op when `ids` is omitted
665
+ * (every candidate is already fetched) or no related edges resolve in-corpus.
666
+ */
667
+ include_related?: boolean;
668
+ };
669
+ type RecallTruncation = {
670
+ omitted_candidate_count: number;
671
+ returned_candidate_count: number;
401
672
  };
402
673
  type RecallResult = PlanContextResult & {
403
674
  rules: Array<{
@@ -405,90 +676,23 @@ type RecallResult = PlanContextResult & {
405
676
  level: "L0" | "L1" | "L2";
406
677
  path: string;
407
678
  body: string;
679
+ store?: {
680
+ alias: string;
681
+ };
408
682
  }>;
409
683
  selected_stable_ids: string[];
410
684
  diagnostics: Array<{
411
- code: "missing_knowledge_metadata";
685
+ code: "missing_knowledge_metadata" | "unresolved_selected_id";
412
686
  severity: "warn";
413
687
  stable_id: string;
414
688
  message: string;
415
689
  }>;
690
+ directive: string;
691
+ next_steps?: string[];
692
+ truncation?: RecallTruncation;
416
693
  };
417
694
  declare function recall(projectRoot: string, input: RecallInput): Promise<RecallResult>;
418
695
 
419
- /**
420
- * O(1) in-memory increment for a named counter. Safe to call from any MCP
421
- * tool handler; no I/O happens until the next `flushMetrics()` call.
422
- *
423
- * `delta` defaults to 1; callers can pass a positive integer (e.g. fetched
424
- * N stable_ids in a single sections call) to fold N bumps into one.
425
- */
426
- declare function bumpCounter(projectRoot: string, name: string, delta?: number): void;
427
- /**
428
- * Snapshot the current counter accumulator and reset it. Returned map is a
429
- * frozen copy; the live accumulator starts fresh from zero. Exposed so
430
- * flushMetrics + tests + a future fab_metrics manual-flush CLI hook can all
431
- * use the same primitive without racing.
432
- */
433
- declare function drainCounters(projectRoot: string): Record<string, number>;
434
- /**
435
- * Drain the current accumulator and append one JSONL row to
436
- * `.fabric/metrics.jsonl`. Returns the appended row (or `null` when the
437
- * accumulator was empty — no spurious zero rows). fs failures degrade
438
- * silently; the next flush will carry the union of the failed-write
439
- * interval + the current one.
440
- */
441
- declare function flushMetrics(projectRoot: string, options?: {
442
- windowMs?: number;
443
- now?: Date;
444
- }): Promise<MetricsRow | null>;
445
- /**
446
- * Start the background flush timer for a project root. Idempotent — calling
447
- * twice on the same root replaces the prior interval. Returns a stop handle
448
- * the caller can invoke at shutdown to flush + clear the timer.
449
- *
450
- * The flush is fire-and-forget (no await on the setInterval callback) so
451
- * the timer cadence stays accurate even when fs is slow.
452
- */
453
- declare function startMetricsFlush(projectRoot: string, options?: {
454
- intervalMs?: number;
455
- }): () => Promise<void>;
456
- /**
457
- * Cancel the background flush timer for a project root if one is running.
458
- * Does NOT drain the accumulator — callers that want a final flush should
459
- * await flushMetrics(projectRoot) afterward.
460
- */
461
- declare function stopMetricsFlush(projectRoot: string): void;
462
- /**
463
- * Read accumulated metrics rows from `.fabric/metrics.jsonl`. Missing file
464
- * returns []. Malformed rows are dropped silently (the sidecar is best-
465
- * effort observability; a corrupt row never blocks a reader).
466
- *
467
- * Exposed for the NEW-34 `fab metrics` CLI dashboard + future doctor lints
468
- * (e.g. cite-goodhart pattern replay) that need counter trends without
469
- * walking events.jsonl.
470
- */
471
- declare function readMetrics(projectRoot: string): Promise<MetricsRow[]>;
472
- type MetricsRow = {
473
- timestamp: string;
474
- window: string;
475
- counters: Record<string, number>;
476
- };
477
- /**
478
- * Canonical metric counter names used by the high-frequency emitters that
479
- * left events.jsonl as part of the rc.37 Wave B clean-slate. Centralized
480
- * here so the B5 hard-gate (`metric_event_in_jsonl`) can grep for these
481
- * exact strings and fail when one accidentally re-appears in the audit
482
- * ledger emit path.
483
- */
484
- declare const METRIC_COUNTER_NAMES: {
485
- readonly knowledge_consumed: "knowledge_consumed";
486
- readonly edit_intent_checked: "edit_intent_checked";
487
- readonly knowledge_context_planned: "knowledge_context_planned";
488
- readonly knowledge_sections_fetched: "knowledge_sections_fetched";
489
- };
490
- type MetricCounterName = (typeof METRIC_COUNTER_NAMES)[keyof typeof METRIC_COUNTER_NAMES];
491
-
492
696
  /**
493
697
  * Start the background rotation timer for a project root. Idempotent —
494
698
  * calling twice on the same root replaces the prior interval. Returns a
@@ -511,10 +715,47 @@ declare function startRotationTick(projectRoot: string, options?: {
511
715
  declare function stopRotationTick(projectRoot: string): void;
512
716
 
513
717
  /**
514
- * Shared constants used across the server package.
718
+ * ContextCache unified hot-path cache for the Fabric server.
719
+ *
720
+ * Three logical slots:
721
+ * 1. "meta" — agents.meta.json content (TTL-based, default 5 s)
722
+ * 2. "context" — GetKnowledgeContext per projectRoot (TTL-based, default 5 s)
723
+ * 3. "audit" — sliding-window byte-offset cursor for audit.jsonl reads
724
+ *
725
+ * Invalidation reasons:
726
+ * - "meta_write" — eager invalidation when a write service mutates agents.meta.json
727
+ * - "file_watch" — chokidar detected an on-disk change
515
728
  */
516
- /** MCP resource URI for the project's bootstrap README (L0 rules) file. */
517
- declare const AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
729
+ type InvalidationReason = "meta_write" | "file_watch";
730
+ type AuditCursor = {
731
+ offset: number;
732
+ remainder: string;
733
+ windowEntries: Array<{
734
+ ts: number;
735
+ }>;
736
+ };
737
+ declare class ContextCache {
738
+ private readonly defaultTtlMs;
739
+ private readonly metaSlot;
740
+ private readonly contextSlot;
741
+ private readonly auditSlot;
742
+ constructor(defaultTtlMs?: number);
743
+ get<T>(slot: "meta" | "context", key: string): T | undefined;
744
+ set<T>(slot: "meta" | "context", key: string, value: T, ttlMs?: number): void;
745
+ getAuditCursor(projectRoot: string): AuditCursor | undefined;
746
+ setAuditCursor(projectRoot: string, cursor: AuditCursor): void;
747
+ resetAuditCursor(projectRoot: string): void;
748
+ /**
749
+ * Invalidate cache slots based on what changed.
750
+ *
751
+ * @param reason "meta_write" — only the meta slot for this projectRoot
752
+ * "file_watch" — meta + context slots (AGENTS.md may have changed)
753
+ * @param projectRoot Optional; if omitted, clears ALL keys in affected slots.
754
+ */
755
+ invalidate(reason: InvalidationReason, projectRoot?: string): void;
756
+ private slotStore;
757
+ }
758
+ declare const contextCache: ContextCache;
518
759
 
519
760
  interface KnowledgeSyncOptions {
520
761
  mode?: "incremental" | "full";
@@ -563,6 +804,12 @@ interface KnowledgeSyncReport {
563
804
  warnings: StructuredWarning[];
564
805
  reconciled_files?: string[];
565
806
  }
807
+ /**
808
+ * Clear the knowledge-sync cooldown for a projectRoot so the next ensureKnowledgeFresh
809
+ * call performs a real I/O scan. Called by the chokidar watcher when a rule
810
+ * file changes (see http.ts handleCacheWatcherEvent).
811
+ */
812
+ declare function invalidateKnowledgeSyncCooldown(projectRoot: string): void;
566
813
  /**
567
814
  * Detects drift between disk and agents.meta.json, emits ledger events, and
568
815
  * invalidates the cache. Does NOT rewrite agents.meta.json. Optimised for
@@ -596,6 +843,80 @@ interface ReconcileKnowledgeOptions {
596
843
  */
597
844
  declare function reconcileKnowledge(projectRoot: string, opts?: ReconcileKnowledgeOptions): Promise<KnowledgeSyncReport>;
598
845
 
846
+ type LedgerSourceFilter = "ai" | "human";
847
+ type StoredLedgerEntry = LedgerEntry & {
848
+ id: string;
849
+ };
850
+ type ReadLedgerOptions = {
851
+ source?: LedgerSourceFilter;
852
+ since?: number;
853
+ };
854
+ type ResolvedLedgerPaths = {
855
+ primaryPath: string;
856
+ legacyPath: string;
857
+ readPath: string;
858
+ usingLegacy: boolean;
859
+ };
860
+ declare function resolveLedgerPaths(projectRoot: string): Promise<ResolvedLedgerPaths>;
861
+ declare function readLedger(projectRoot: string, options?: ReadLedgerOptions): Promise<StoredLedgerEntry[]>;
862
+
863
+ type RehydrateTarget = {
864
+ ledgerEntryId: string;
865
+ } | {
866
+ timestamp: number;
867
+ };
868
+ type RehydratedAgentsMetaSnapshot = {
869
+ meta: AgentsMeta;
870
+ metadata: {
871
+ at_ledger_id: string;
872
+ at_commit: string | null;
873
+ replayed_count: number;
874
+ mode: "git-show" | "ledger-fallback";
875
+ };
876
+ entries: StoredLedgerEntry[];
877
+ };
878
+ declare function rehydrateAgentsMetaAt(projectRoot: string, target: RehydrateTarget): Promise<RehydratedAgentsMetaSnapshot>;
879
+
880
+ declare function readAgentsMeta(projectRoot: string): Promise<AgentsMeta>;
881
+
882
+ type KnowledgeEntryItem = {
883
+ path: string;
884
+ content: string;
885
+ };
886
+ type DescriptionStub = {
887
+ path: string;
888
+ description: string;
889
+ };
890
+ type HumanLockedNearby = {
891
+ file: string;
892
+ excerpt: string;
893
+ };
894
+ type KnowledgePayload = {
895
+ L0: string;
896
+ L1: KnowledgeEntryItem[];
897
+ L2: KnowledgeEntryItem[];
898
+ human_locked_nearby: HumanLockedNearby[];
899
+ description_stubs?: DescriptionStub[];
900
+ };
901
+ type GetKnowledgeInput = {
902
+ path: string;
903
+ client_hash?: string;
904
+ correlation_id?: string;
905
+ session_id?: string;
906
+ };
907
+ type GetKnowledgeResult = {
908
+ revision_hash: string;
909
+ stale: boolean;
910
+ rules: KnowledgePayload;
911
+ };
912
+ declare function getKnowledge(projectRoot: string, input: GetKnowledgeInput): Promise<GetKnowledgeResult>;
913
+
914
+ /**
915
+ * Shared constants used across the server package.
916
+ */
917
+ /** MCP resource URI for the project's bootstrap README (L0 rules) file. */
918
+ declare const AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
919
+
599
920
  /**
600
921
  * Returns an info-level startup message when CLAUDE.md or AGENTS.md exist at
601
922
  * the project root, or null when neither is present.
@@ -605,6 +926,7 @@ declare function reconcileKnowledge(projectRoot: string, opts?: ReconcileKnowled
605
926
  */
606
927
  declare function formatPreexistingRootMessage(projectRoot: string): string | null;
607
928
 
929
+ declare const FABRIC_SERVER_INSTRUCTIONS: string;
608
930
  declare function createFabricServer(tracker?: InFlightTracker): McpServer;
609
931
  declare function startStdioServer(): Promise<void>;
610
932
  /**
@@ -631,4 +953,4 @@ interface ShutdownHandlerDeps {
631
953
  */
632
954
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
633
955
 
634
- export { AGENTS_MD_RESOURCE_URI, type ArchiveHistoryEntry, type ArchiveHistoryReport, type CiteCoverageReport, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, KnowledgeIdAllocator, type KnowledgeMetaBuildResult, type KnowledgeMetaBuildSource, type KnowledgeSyncLedgerEvent, type KnowledgeSyncOptions, type KnowledgeSyncReport, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, type PlanContextInput, type PlanContextResult, type RecallInput, type RecallResult, type ReconcileKnowledgeOptions, type RequirementProfile, type SelectionTokenState, type ShutdownHandlerDeps, type StructuredWarning, type WriteKnowledgeMetaOptions, appendEventLedgerEvent, buildKnowledgeMeta, bumpCounter, computeKnowledgeBasedAgentsMeta, computeKnowledgeTestIndex, createFabricServer, createInFlightTracker, createShutdownHandler, deriveKnowledgeMetaLayer, deriveKnowledgeMetaTopologyType, drainCounters, enrichDescriptions, ensureKnowledgeFresh, extractKnowledge, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, isSameKnowledgeTestIndex, loadKbIdTypeMap, planContext, readMetrics, readSelectionToken, recall, reconcileKnowledge, reviewKnowledge, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorFix, runDoctorHistoryAll, runDoctorReport, stableStringify, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick, writeKnowledgeMeta };
956
+ export { AGENTS_MD_RESOURCE_URI, type ArchiveHistoryEntry, type ArchiveHistoryReport, type CiteCoverageReport, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, KnowledgeIdAllocator, type KnowledgeMetaBuildResult, type KnowledgeMetaBuildSource, type KnowledgeSyncLedgerEvent, type KnowledgeSyncOptions, type KnowledgeSyncReport, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, type PlanContextInput, type PlanContextResult, type RecallInput, type RecallResult, type ReconcileKnowledgeOptions, type RequirementProfile, type SelectionTokenState, type ShutdownHandlerDeps, type StructuredWarning, type WriteKnowledgeMetaOptions, appendEventLedgerEvent, buildKnowledgeMeta, bumpCounter, computeKnowledgeBasedAgentsMeta, computeKnowledgeTestIndex, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, deriveKnowledgeMetaLayer, deriveKnowledgeMetaTopologyType, drainCounters, enrichDescriptions, ensureKnowledgeFresh, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getKnowledge, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, invalidateKnowledgeSyncCooldown, isSameKnowledgeTestIndex, lintConflicts, loadConflictEntries, loadKbIdTypeMap, pairSimilarity, planContext, readAgentsMeta, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, reconcileKnowledge, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, stableStringify, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick, writeKnowledgeMeta };