@fenglimg/fabric-server 2.2.0-rc.4 → 2.2.0-rc.9

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 CHANGED
@@ -1,12 +1,11 @@
1
1
  # @fenglimg/fabric-server
2
2
 
3
- Fabric MCP knowledge server. Runs over stdio transport and serves Claude Code, Cursor, and Codex CLI from a single `.fabric/` directory.
3
+ Fabric MCP knowledge server. Runs over stdio transport and serves Claude Code and Codex CLI from a single `.fabric/` directory.
4
4
 
5
5
  ## Tools exposed
6
6
 
7
- - `fab_plan_context` — neutral rule description index + selection token
8
- - `fab_get_knowledge_sections` — fetch full markdown bodies by stable_id
9
- - `fab_recall` — combined one-call recall (plan + sections), the rc.37+ default
7
+ - `fab_recall` — single-step recall: returns candidate descriptions + native read paths (no body delivery over MCP; read a body on demand via a native Read of the returned path)
8
+ - `fab_archive_scan` — scan recent work for archive-worthy knowledge candidates
10
9
  - `fab_extract_knowledge` — persist a pending knowledge entry
11
10
  - `fab_review` — list / approve / reject / modify / defer pending entries
12
11
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { FabExtractKnowledgeInput, FabExtractKnowledgeOutput, FabReviewInput, FabReviewOutput } from '@fenglimg/fabric-shared/schemas/api-contracts';
3
3
  import { EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem, LedgerEntry, AgentsMeta } from '@fenglimg/fabric-shared';
4
+ import { PayloadGuardOptions } from '@fenglimg/fabric-shared/node/mcp-payload-guard';
4
5
 
5
6
  interface InFlightTracker {
6
7
  enter(requestId: string): void;
@@ -22,6 +23,67 @@ declare function getLegacyLedgerPath(projectRoot: string): string;
22
23
  declare function getEventLedgerPath(projectRoot: string): string;
23
24
  declare function getMetricsLedgerPath(projectRoot: string): string;
24
25
 
26
+ type DoctorHealth = {
27
+ score: number;
28
+ grade: "A" | "B" | "C" | "D" | "F";
29
+ penalties: {
30
+ manual_errors: number;
31
+ fixable_errors: number;
32
+ warnings: number;
33
+ };
34
+ };
35
+
36
+ interface AlwaysActiveBody {
37
+ /** store-qualified id (`<alias>:<stableId>`). */
38
+ stable_id: string;
39
+ /** knowledge_type — one of ALWAYS_ACTIVE_TYPES. */
40
+ type: string;
41
+ layer: "team" | "personal";
42
+ /** description.summary — the overflow-degrade fallback when the body cannot
43
+ * fit the injection char budget (the budget is enforced hook-side, D10). */
44
+ summary: string;
45
+ /** frontmatter-stripped markdown body. */
46
+ body: string;
47
+ }
48
+ /**
49
+ * Collect the always-active (guidelines + models) entries from the project's
50
+ * read-set, project-filtered identically to recall (filterByActiveProject), with
51
+ * their frontmatter-stripped bodies. The SessionStart hook injects these bodies
52
+ * into the AI context and renders category counts for the on-demand remainder.
53
+ *
54
+ * Returns [] (never throws) on any read-set resolution failure — the SessionStart
55
+ * banner must degrade gracefully, never crash session start.
56
+ */
57
+ interface KnowledgeCensus {
58
+ /** knowledge_type → count (decisions/pitfalls/guidelines/models/processes). */
59
+ by_type: Record<string, number>;
60
+ by_layer: {
61
+ team: number;
62
+ personal: number;
63
+ project: number;
64
+ };
65
+ /** entries专属 to OTHER projects that filterByActiveProject removed. */
66
+ dropped_other_project: number;
67
+ /** kept (post-filter) total. */
68
+ total: number;
69
+ }
70
+ /**
71
+ * Build the read-set census (project-filtered counts + dropped-other-project).
72
+ * Reuses the cached read-set walk, so calling alongside buildAlwaysActiveBodies
73
+ * in one SessionStart fire costs a single walk. Never throws — degrades to an
74
+ * all-zero census so the banner stays renderable.
75
+ */
76
+ declare function buildKnowledgeCensus(projectRoot: string): Promise<KnowledgeCensus>;
77
+ declare function buildAlwaysActiveBodies(projectRoot: string): Promise<AlwaysActiveBody[]>;
78
+
79
+ interface UnboundProjectViolation {
80
+ /** The store already bound as the active write target. */
81
+ alias: string;
82
+ /** Which project-scope fields are absent: `project_id` and/or `active_project`. */
83
+ missing: string[];
84
+ }
85
+ declare function detectUnboundProject(projectRoot: string): UnboundProjectViolation | null;
86
+
25
87
  /**
26
88
  * O(1) in-memory increment for a named counter. Safe to call from any MCP
27
89
  * tool handler; no I/O happens until the next `flushMetrics()` call.
@@ -81,11 +143,15 @@ type MetricsRow = {
81
143
  counters: Record<string, number>;
82
144
  };
83
145
  /**
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.
146
+ * Canonical metric counter names. rc.37 Wave B added a metrics.jsonl counter
147
+ * rollup for each (the forward-compatible signal), but contrary to the
148
+ * original "these left events.jsonl" plan every one is ALSO still appended to
149
+ * the audit ledger as a structured event, because a doctor lint consumes its
150
+ * per-id / per-path payload (see LEDGER_DUAL_WRITE_METRIC_NAMES below). The
151
+ * promised counter-only cutover (rc.38+; tracked by the G11 invariant test) has
152
+ * not happened. Centralized here so the B5 hard-gate (`metric_event_in_jsonl`)
153
+ * can grep for these exact strings; the gate only flags names NOT in the
154
+ * dual-write allowlist below.
89
155
  */
90
156
  declare const METRIC_COUNTER_NAMES: {
91
157
  readonly knowledge_consumed: "knowledge_consumed";
@@ -113,7 +179,7 @@ type CiteCoverageReport = {
113
179
  marker_ts: number;
114
180
  marker_emitted_now: boolean;
115
181
  since_ts: number;
116
- client_filter: "cc" | "codex" | "cursor" | "all";
182
+ client_filter: "cc" | "codex" | "all";
117
183
  layer_filter?: "team" | "personal" | "all";
118
184
  metrics: {
119
185
  edits_touched: number;
@@ -162,7 +228,7 @@ type CiteCoverageReport = {
162
228
  };
163
229
  declare function runDoctorCiteCoverage(projectRoot: string, options: {
164
230
  since: number;
165
- client: "cc" | "codex" | "cursor" | "all";
231
+ client: "cc" | "codex" | "all";
166
232
  layer?: "team" | "personal" | "all";
167
233
  until?: number;
168
234
  recallWindowMs?: number;
@@ -250,15 +316,6 @@ type DoctorSummary = {
250
316
  payload_limits: DoctorPayloadLimits;
251
317
  health: DoctorHealth;
252
318
  };
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
319
  type DoctorReport = {
263
320
  status: DoctorStatus;
264
321
  checks: DoctorCheck[];
@@ -287,6 +344,7 @@ type DoctorApplyLintMutation = {
287
344
  type DoctorApplyLintReport = {
288
345
  changed: boolean;
289
346
  mutations: DoctorApplyLintMutation[];
347
+ warnings: DoctorIssue[];
290
348
  manual_errors: DoctorIssue[];
291
349
  aborted: boolean;
292
350
  abort_reason?: string;
@@ -296,6 +354,7 @@ type DoctorApplyLintReport = {
296
354
  declare function runDoctorReport(target: string): Promise<DoctorReport>;
297
355
  declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
298
356
  declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
357
+
299
358
  type EnrichDescriptionsMode = "auto" | "preview" | "readonly" | "interactive";
300
359
  type EnrichDescriptionsCandidate = {
301
360
  path: string;
@@ -479,6 +538,37 @@ declare function extractKnowledge(projectRoot: string, input: FabExtractKnowledg
479
538
  */
480
539
  declare function reviewKnowledge(projectRoot: string, input: FabReviewInput): Promise<FabReviewOutput>;
481
540
 
541
+ /** A summary to be cold-judged, keyed by its stable_id. */
542
+ interface ColdEvalCandidate {
543
+ stable_id: string;
544
+ summary: string;
545
+ }
546
+ /** The verdict the external cold-eval judge returns per candidate. */
547
+ interface ColdEvalVerdict {
548
+ stable_id: string;
549
+ /** true when the summary alone is act-on sufficient without the body. */
550
+ self_sufficient: boolean;
551
+ /** When not self-sufficient, the judge's suggested act-on rewrite. */
552
+ suggested_summary?: string;
553
+ /** Short rationale (pointer-vs-thesis) for the verdict. */
554
+ reason?: string;
555
+ }
556
+ /** The batch request handed to the external (maestro delegate) cold-eval judge. */
557
+ interface ColdEvalBatch {
558
+ rubric: string;
559
+ candidates: ColdEvalCandidate[];
560
+ }
561
+ declare const COLD_EVAL_RUBRIC: string;
562
+ /**
563
+ * Build the cold-eval batch request for the external judge. Pure + deterministic:
564
+ * drops blank summaries (nothing to judge) and pairs the candidates with the
565
+ * zero-context rubric. The fabric-review skill hands the result to
566
+ * `maestro delegate` and applies the returned {@link ColdEvalVerdict}[] via
567
+ * fab_review modify. Returns a batch with an empty candidate list when nothing is
568
+ * judgeable, so callers can short-circuit without a delegate round-trip.
569
+ */
570
+ declare function buildColdEvalBatch(candidates: ColdEvalCandidate[]): ColdEvalBatch;
571
+
482
572
  type StoredEventLedgerEvent = EventLedgerEvent;
483
573
  type ReadEventLedgerOptions = {
484
574
  event_type?: EventLedgerEvent["event_type"];
@@ -529,11 +619,26 @@ type PlanContextInput = {
529
619
  target_paths?: string[];
530
620
  layer_filter?: "team" | "personal" | "both";
531
621
  include_related?: boolean;
622
+ /**
623
+ * Internal MCP presentation budget. When supplied by the tool layer,
624
+ * candidates are byte-trimmed before the selection token is cached so the
625
+ * token cannot reference candidates omitted from the response.
626
+ */
627
+ payload_budget?: PlanContextPayloadBudget;
628
+ };
629
+ type PlanContextPayloadWarning = {
630
+ code: string;
631
+ file?: string;
632
+ action_hint?: string;
633
+ };
634
+ type PlanContextPayloadBudget = {
635
+ limits?: PayloadGuardOptions;
636
+ warnings?: PlanContextPayloadWarning[];
637
+ trim_warning?: PlanContextPayloadWarning;
532
638
  };
533
639
  type RequirementProfile = {
534
640
  target_path: string;
535
641
  known_tech: string[];
536
- user_intent: string;
537
642
  detected_entities: string[];
538
643
  };
539
644
  type PlanContextEntry = {
@@ -552,6 +657,7 @@ type PlanContextResult = {
552
657
  stale: boolean;
553
658
  selection_token: string;
554
659
  entries: PlanContextEntry[];
660
+ intent?: string;
555
661
  candidates: RuleDescriptionIndexItem[];
556
662
  omitted_candidate_count?: number;
557
663
  preflight_diagnostics: PreflightDiagnostic[];
@@ -559,6 +665,10 @@ type PlanContextResult = {
559
665
  previous_revision_hash?: string;
560
666
  redirects?: Record<string, string>;
561
667
  related_appended?: Record<string, string>;
668
+ /** Internal service→tool signal; stripped before MCP output. */
669
+ payload_trimmed?: boolean;
670
+ /** Internal service→tool signal; stripped before MCP output. */
671
+ payload_over_budget?: boolean;
562
672
  };
563
673
  type SelectionTokenState = {
564
674
  token: string;
@@ -574,45 +684,33 @@ declare function readSelectionToken(token: string, now?: number): SelectionToken
574
684
 
575
685
  type RecallInput = PlanContextInput & {
576
686
  /**
577
- * Optional explicit set of stable_ids to fetch bodies for. When omitted,
578
- * fab_recall picks up every stable_id surfaced in the shared description
579
- * index (the common case after rc.37 selectable-filter removal). When
580
- * provided, filters the fetched body set to this intersection.
687
+ * Optional explicit set of stable_ids to SCOPE the returned read paths to.
688
+ * When omitted, `paths` carries one entry per surfaced candidate. The candidate
689
+ * DESCRIPTION index is always returned in full for discovery — `ids` only narrows
690
+ * which read paths are surfaced (e.g. `recall(ids)` when the agent already knows
691
+ * which entries it wants to Read). Stale (pre layer-flip) ids are redirect-rewritten
692
+ * before the match.
581
693
  */
582
694
  ids?: string[];
583
695
  /**
584
- * v2.2 MC1-recall-pack (W2-T4): when true, expand the fetched set with the
585
- * one-hop `related` graph neighbours (H2) of the selected entries that are
586
- * also present in the candidate index. Lets a scoped `ids` recall pull in the
587
- * connected knowledge without a second round-trip. No-op when `ids` is omitted
588
- * (every candidate is already fetched) or no related edges resolve in-corpus.
696
+ * When true, forwarded to planContext, which appends the one-hop `related` graph
697
+ * neighbours (H2) of the surfaced set to the candidate index (descriptions only —
698
+ * NO body). Their read paths are included in `paths` like any other candidate
699
+ * (W1-3 / KT-DEC-0031: surface the related id, do not fetch its body).
589
700
  */
590
701
  include_related?: boolean;
591
702
  };
592
- type RecallTruncation = {
593
- omitted_candidate_count: number;
594
- returned_candidate_count: number;
703
+ type RecallPath = {
704
+ stable_id: string;
705
+ path: string;
706
+ store?: {
707
+ alias: string;
708
+ };
595
709
  };
596
- type RecallResult = PlanContextResult & {
597
- rules: Array<{
598
- stable_id: string;
599
- level: "L0" | "L1" | "L2";
600
- path: string;
601
- body: string;
602
- store?: {
603
- alias: string;
604
- };
605
- }>;
606
- selected_stable_ids: string[];
607
- diagnostics: Array<{
608
- code: "missing_knowledge_metadata" | "unresolved_selected_id";
609
- severity: "warn";
610
- stable_id: string;
611
- message: string;
612
- }>;
710
+ type RecallResult = Omit<PlanContextResult, "selection_token" | "payload_trimmed" | "payload_over_budget"> & {
711
+ paths: RecallPath[];
613
712
  directive: string;
614
713
  next_steps?: string[];
615
- truncation?: RecallTruncation;
616
714
  };
617
715
  declare function recall(projectRoot: string, input: RecallInput): Promise<RecallResult>;
618
716
 
@@ -680,65 +778,6 @@ declare class ContextCache {
680
778
  }
681
779
  declare const contextCache: ContextCache;
682
780
 
683
- interface KnowledgeSyncOptions {
684
- mode?: "incremental" | "full";
685
- /** When true, invalid frontmatter throws RuleValidationError (default: false — collect as warning). */
686
- throwOnInvalidFrontmatter?: boolean;
687
- /**
688
- * v2.0.0-rc.29 TASK-005 (BUG-G1): originally opted the hot path into a
689
- * follow-up `reconcileKnowledge` (rewrite agents.meta.json) so every
690
- * detected drift got a paired heal event.
691
- *
692
- * v2.2 W5 R2 (agents.meta decolo): retained as a NO-OP for backward
693
- * compatibility — the co-location agents.meta.json it used to rebuild no
694
- * longer exists (knowledge lives in stores; read paths cut over to the
695
- * cross-store model). MCP tool call sites still pass `autoHealOnDrift: true`;
696
- * it is now ignored.
697
- */
698
- autoHealOnDrift?: boolean;
699
- }
700
- interface StructuredWarning {
701
- code: string;
702
- file: string;
703
- line?: number;
704
- action_hint: string;
705
- }
706
- /**
707
- * Granular ledger event shape for knowledge-sync operations.
708
- * These are returned in KnowledgeSyncReport and also appended to the event ledger
709
- * using the nearest available ledger event type (knowledge_drift_detected).
710
- * The shape below is what callers receive in `.events`.
711
- */
712
- interface KnowledgeSyncLedgerEvent {
713
- type: "rule_content_changed" | "rule_added" | "rule_removed";
714
- stable_id: string;
715
- path: string;
716
- prev_hash: string | null;
717
- new_hash: string | null;
718
- changed_fields: string[];
719
- source: "ensureKnowledgeFresh" | "reconcileKnowledge";
720
- }
721
- /** Alias so the public API says LedgerEvent (as documented). */
722
- type LedgerEvent = KnowledgeSyncLedgerEvent;
723
- interface KnowledgeSyncReport {
724
- status: "fresh" | "reconciled" | "errors";
725
- events: LedgerEvent[];
726
- warnings: StructuredWarning[];
727
- reconciled_files?: string[];
728
- }
729
- /**
730
- * Clear the knowledge-sync cooldown for a projectRoot so the next ensureKnowledgeFresh
731
- * call performs a real I/O scan. Called by the chokidar watcher when a rule
732
- * file changes (see http.ts handleCacheWatcherEvent).
733
- */
734
- declare function invalidateKnowledgeSyncCooldown(projectRoot: string): void;
735
- /**
736
- * Detects drift between disk and agents.meta.json, emits ledger events, and
737
- * invalidates the cache. Does NOT rewrite agents.meta.json. Optimised for
738
- * hot-path consumers (MCP tools).
739
- */
740
- declare function ensureKnowledgeFresh(projectRoot: string, opts?: KnowledgeSyncOptions): Promise<KnowledgeSyncReport>;
741
-
742
781
  type LedgerSourceFilter = "ai" | "human";
743
782
  type StoredLedgerEntry = LedgerEntry & {
744
783
  id: string;
@@ -815,4 +854,4 @@ interface ShutdownHandlerDeps {
815
854
  */
816
855
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
817
856
 
818
- 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, 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 RequirementProfile, type SelectionTokenState, type ShutdownHandlerDeps, type StructuredWarning, appendEventLedgerEvent, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, drainCounters, enrichDescriptions, ensureKnowledgeFresh, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, invalidateKnowledgeSyncCooldown, lintConflicts, loadConflictEntries, pairSimilarity, planContext, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
857
+ export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, 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, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, type PlanContextInput, type PlanContextResult, type RecallInput, type RecallResult, type RequirementProfile, type SelectionTokenState, type ShutdownHandlerDeps, type UnboundProjectViolation, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, detectUnboundProject, drainCounters, enrichDescriptions, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, lintConflicts, loadConflictEntries, pairSimilarity, planContext, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };