@fenglimg/fabric-server 2.2.0-rc.1 → 2.2.0-rc.11

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/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { AgentsMeta, KnowledgeTestIndex, AgentsLayer, AgentsTopologyType, KnowledgeType, Layer, StableId, AgentsMetaCounters, EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem, LedgerEntry } from '@fenglimg/fabric-shared';
3
2
  import { FabExtractKnowledgeInput, FabExtractKnowledgeOutput, FabReviewInput, FabReviewOutput } from '@fenglimg/fabric-shared/schemas/api-contracts';
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,70 @@ 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
+ broad_by_type: Record<string, number>;
66
+ /** count of narrow-scope kept entries (file-specific; only合计, not per-type). */
67
+ narrow_total: number;
68
+ /** entries专属 to OTHER projects that filterByActiveProject removed. */
69
+ dropped_other_project: number;
70
+ /** kept (post-filter) total. */
71
+ total: number;
72
+ }
73
+ /**
74
+ * Build the read-set census (project-filtered counts + dropped-other-project).
75
+ * Reuses the cached read-set walk, so calling alongside buildAlwaysActiveBodies
76
+ * in one SessionStart fire costs a single walk. Never throws — degrades to an
77
+ * all-zero census so the banner stays renderable.
78
+ */
79
+ declare function buildKnowledgeCensus(projectRoot: string): Promise<KnowledgeCensus>;
80
+ declare function buildAlwaysActiveBodies(projectRoot: string): Promise<AlwaysActiveBody[]>;
81
+
82
+ interface UnboundProjectViolation {
83
+ /** The store already bound as the active write target. */
84
+ alias: string;
85
+ /** Which project-scope fields are absent: `project_id` and/or `active_project`. */
86
+ missing: string[];
87
+ }
88
+ declare function detectUnboundProject(projectRoot: string): UnboundProjectViolation | null;
89
+
25
90
  /**
26
91
  * O(1) in-memory increment for a named counter. Safe to call from any MCP
27
92
  * tool handler; no I/O happens until the next `flushMetrics()` call.
@@ -81,11 +146,15 @@ type MetricsRow = {
81
146
  counters: Record<string, number>;
82
147
  };
83
148
  /**
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.
149
+ * Canonical metric counter names. rc.37 Wave B added a metrics.jsonl counter
150
+ * rollup for each (the forward-compatible signal), but contrary to the
151
+ * original "these left events.jsonl" plan every one is ALSO still appended to
152
+ * the audit ledger as a structured event, because a doctor lint consumes its
153
+ * per-id / per-path payload (see LEDGER_DUAL_WRITE_METRIC_NAMES below). The
154
+ * promised counter-only cutover (rc.38+; tracked by the G11 invariant test) has
155
+ * not happened. Centralized here so the B5 hard-gate (`metric_event_in_jsonl`)
156
+ * can grep for these exact strings; the gate only flags names NOT in the
157
+ * dual-write allowlist below.
89
158
  */
90
159
  declare const METRIC_COUNTER_NAMES: {
91
160
  readonly knowledge_consumed: "knowledge_consumed";
@@ -113,7 +182,7 @@ type CiteCoverageReport = {
113
182
  marker_ts: number;
114
183
  marker_emitted_now: boolean;
115
184
  since_ts: number;
116
- client_filter: "cc" | "codex" | "cursor" | "all";
185
+ client_filter: "cc" | "codex" | "all";
117
186
  layer_filter?: "team" | "personal" | "all";
118
187
  metrics: {
119
188
  edits_touched: number;
@@ -125,6 +194,25 @@ type CiteCoverageReport = {
125
194
  compliant_cites?: number;
126
195
  noncompliant_cites?: number;
127
196
  uncorrelatable_edits?: number;
197
+ recall_backed_edits?: number;
198
+ recall_coverage_rate?: number | null;
199
+ exposed_and_mutated?: {
200
+ count: number;
201
+ ids?: string[];
202
+ };
203
+ mutations_observed?: {
204
+ count: number;
205
+ };
206
+ mutation_pool?: {
207
+ attributed: number;
208
+ unattributed_workspace_dirty: number;
209
+ };
210
+ sessions_closed?: {
211
+ count: number;
212
+ };
213
+ by_store?: Record<string, {
214
+ qualifying_cites: number;
215
+ }>;
128
216
  };
129
217
  per_client?: Record<string, Partial<CiteCoverageReport["metrics"]>>;
130
218
  dismissed_reason_histogram?: Record<string, number>;
@@ -143,9 +231,10 @@ type CiteCoverageReport = {
143
231
  };
144
232
  declare function runDoctorCiteCoverage(projectRoot: string, options: {
145
233
  since: number;
146
- client: "cc" | "codex" | "cursor" | "all";
234
+ client: "cc" | "codex" | "all";
147
235
  layer?: "team" | "personal" | "all";
148
236
  until?: number;
237
+ recallWindowMs?: number;
149
238
  }): Promise<CiteCoverageReport>;
150
239
  type ArchiveHistoryEntry = {
151
240
  session_id_short: string;
@@ -230,15 +319,6 @@ type DoctorSummary = {
230
319
  payload_limits: DoctorPayloadLimits;
231
320
  health: DoctorHealth;
232
321
  };
233
- type DoctorHealth = {
234
- score: number;
235
- grade: "A" | "B" | "C" | "D" | "F";
236
- penalties: {
237
- manual_errors: number;
238
- fixable_errors: number;
239
- warnings: number;
240
- };
241
- };
242
322
  type DoctorReport = {
243
323
  status: DoctorStatus;
244
324
  checks: DoctorCheck[];
@@ -267,6 +347,7 @@ type DoctorApplyLintMutation = {
267
347
  type DoctorApplyLintReport = {
268
348
  changed: boolean;
269
349
  mutations: DoctorApplyLintMutation[];
350
+ warnings: DoctorIssue[];
270
351
  manual_errors: DoctorIssue[];
271
352
  aborted: boolean;
272
353
  abort_reason?: string;
@@ -276,6 +357,7 @@ type DoctorApplyLintReport = {
276
357
  declare function runDoctorReport(target: string): Promise<DoctorReport>;
277
358
  declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
278
359
  declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
360
+
279
361
  type EnrichDescriptionsMode = "auto" | "preview" | "readonly" | "interactive";
280
362
  type EnrichDescriptionsCandidate = {
281
363
  path: string;
@@ -297,84 +379,131 @@ declare function enrichDescriptions(projectRoot: string, opts?: {
297
379
  dryRun?: boolean;
298
380
  }): Promise<EnrichDescriptionsReport>;
299
381
 
300
- type KnowledgeMetaBuildSource = "doctor_fix" | "sync_meta";
301
- type KnowledgeMetaBuildResult = {
302
- meta: AgentsMeta;
303
- knowledgeTestIndex: KnowledgeTestIndex;
304
- changed: boolean;
305
- };
306
- type WriteKnowledgeMetaOptions = {
307
- source: KnowledgeMetaBuildSource;
308
- };
382
+ interface Bm25Document {
383
+ id: string;
384
+ tokens: string[];
385
+ }
386
+ interface Bm25Model {
387
+ /**
388
+ * BM25 score of document `id` against the (pre-tokenized) query terms.
389
+ * Returns 0 for an unknown id, an empty document, no query terms, or no
390
+ * term overlap. Query-term duplicates are collapsed — repeating a term in
391
+ * the query does not inflate the score (term frequency is a document
392
+ * property, not a query property).
393
+ */
394
+ scoreDoc(id: string, queryTerms: string[]): number;
395
+ }
309
396
  /**
310
- * v2.0-rc.24 TASK-07: Load a Map<stable_id, knowledge_type> from the
311
- * project's `.fabric/agents.meta.json`. Consumed by the doctor cite-coverage
312
- * routing (TASK-08) to dispatch cites to the correct policy bucket
313
- * (decision/pitfall = strict contract / model = reference-only /
314
- * guideline+process = deferred to rc.25 LLM-judge). Cited ids absent from
315
- * this map fall into the `cite_id_unresolved` bucket.
316
- *
317
- * **Plural knowledge_type contract (rc.29 BUG-C1 unification):** the returned
318
- * map values are the PLURAL `KnowledgeType` enum (`"models" | "decisions" |
319
- * "guidelines" | "pitfalls" | "processes"`) — matching disk frontmatter,
320
- * filesystem layout, MCP I/O surface, and the canonical `KnowledgeTypeSchema`
321
- * exported from `@fenglimg/fabric-shared`. Legacy singular frontmatter is
322
- * normalized at parse time (see `SINGULAR_TO_PLURAL` in `parseFrontmatter`);
323
- * downstream callers (TASK-08 doctor) match against the plural enum.
324
- *
325
- * Both team (KT-*) and personal (KP-*) entries are included — they live in
326
- * the same `meta.nodes` map.
327
- *
328
- * Graceful on failure: a missing meta file, malformed JSON, or schema
329
- * validation failure all yield an empty Map (no throw). The doctor will then
330
- * surface every cite as `cite_id_unresolved`, which is the safe degraded
331
- * mode.
397
+ * Build a BM25 model over `docs`. The corpus statistics (document frequency,
398
+ * average document length) are computed once here; `scoreDoc` is then O(query
399
+ * terms) per call.
332
400
  */
333
- declare function loadKbIdTypeMap(projectRootInput: string): Promise<Map<string, KnowledgeType>>;
334
- declare function buildKnowledgeMeta(projectRootInput: string): Promise<KnowledgeMetaBuildResult>;
335
- declare function writeKnowledgeMeta(projectRootInput: string, options: WriteKnowledgeMetaOptions): Promise<KnowledgeMetaBuildResult>;
336
- declare function computeKnowledgeBasedAgentsMeta(projectRootInput: string, existingMeta?: AgentsMeta): Promise<AgentsMeta>;
337
- declare function computeKnowledgeTestIndex(projectRootInput: string, computedMeta: AgentsMeta, previousIndex?: KnowledgeTestIndex): Promise<KnowledgeTestIndex>;
338
- declare function deriveKnowledgeMetaLayer(relativePath: string): AgentsLayer;
339
- declare function deriveKnowledgeMetaTopologyType(relativePath: string): AgentsTopologyType;
340
- declare function isSameKnowledgeTestIndex(left: KnowledgeTestIndex, right: KnowledgeTestIndex): boolean;
341
- declare function stableStringify(value: unknown): string;
401
+ declare function buildBm25Model(docs: Bm25Document[]): Bm25Model;
342
402
 
403
+ interface ConflictEntry {
404
+ stable_id: string;
405
+ /** Plural knowledge_type ("decisions" | "pitfalls" | "guidelines" | "models" | "processes"). */
406
+ knowledge_type: string;
407
+ /** "team" | "personal". Conflicts are only meaningful within one layer. */
408
+ layer: string;
409
+ /** Title + body (or any text used for similarity). */
410
+ text: string;
411
+ }
412
+ type ConflictVerdict = "conflict" | "similar" | "unknown";
413
+ interface ConflictPair {
414
+ a: string;
415
+ b: string;
416
+ knowledge_type: string;
417
+ layer: string;
418
+ /** Symmetric normalized bm25 similarity in [0,1]. */
419
+ similarity: number;
420
+ /**
421
+ * "unknown" until a judge classifies it (cheap pass leaves it unknown =
422
+ * needs-human-review). "conflict" = the judge ruled a real contradiction
423
+ * (escalates to error). "similar" = judged not-a-conflict (stays a warn /
424
+ * possible duplicate).
425
+ */
426
+ verdict: ConflictVerdict;
427
+ rationale?: string;
428
+ }
429
+ type ConflictJudge = (a: ConflictEntry, b: ConflictEntry) => Promise<{
430
+ isConflict: boolean;
431
+ rationale: string;
432
+ }>;
433
+ declare const DEFAULT_CONFLICT_SIMILARITY_THRESHOLD = 0.5;
343
434
  /**
344
- * v2.0 KnowledgeIdAllocator
345
- *
346
- * Wraps the pure `allocateKnowledgeId` allocator with persistence: it reads
347
- * `agents.meta.json`, advances the counter for the requested (layer, type)
348
- * pair, and writes the updated meta atomically (write-to-tmp + rename) so
349
- * concurrent readers always see a consistent file.
435
+ * Symmetric, [0,1]-normalized bm25 similarity between two docs sharing a model.
436
+ * For each direction we measure how much of the target doc's self-relevance the
437
+ * other doc's terms recover (scoreDoc(target, otherTokens) / scoreDoc(target,
438
+ * targetTokens)), then take the MIN of both directions conservative: both
439
+ * entries must strongly overlap, so a short entry incidentally contained in a
440
+ * long one does not over-fire.
441
+ */
442
+ declare function pairSimilarity(model: ReturnType<typeof buildBm25Model>, a: {
443
+ id: string;
444
+ tokens: string[];
445
+ }, b: {
446
+ id: string;
447
+ tokens: string[];
448
+ }): number;
449
+ /**
450
+ * Cheap deterministic pass: candidate conflicting/duplicate pairs. Compares
451
+ * only entries sharing (type, layer), via bm25 similarity ≥ threshold. Returns
452
+ * pairs sorted by similarity descending (stable_id-tie-broken for determinism).
453
+ * Every returned pair has verdict "unknown" — the cheap pass cannot tell a
454
+ * conflict from a benign duplicate; that is the judge's job.
350
455
  *
351
- * Counters are MONOTONIC across the lifetime of the meta file: deleting a
352
- * knowledge entry does NOT free its counter slot, so previously-allocated
353
- * stable_ids remain unique even after their files are removed.
456
+ * O(G · n_g²) where G = groups and n_g = entries per group. Conflicts only make
457
+ * sense within one (type, layer), so the quadratic stays bounded to small
458
+ * same-bucket sets, not the whole corpus.
459
+ */
460
+ declare function findConflictCandidates(entries: ConflictEntry[], opts?: {
461
+ threshold?: number;
462
+ }): ConflictPair[];
463
+ /**
464
+ * Full lint. Always runs the cheap pass; when `judge` is supplied (deep mode)
465
+ * each candidate is classified into conflict/similar. A judge throw leaves the
466
+ * pair as "unknown" (degrade-safe — a flaky judge never crashes doctor).
354
467
  *
355
- * Key invariants:
356
- * - Counters envelope is initialized to zeros if absent (v1.x meta compat).
357
- * - Each allocate() call performs read → mutate → atomic-write in sequence.
358
- * - The returned id is guaranteed to differ from every previously-returned
359
- * id for the same meta path.
468
+ * Entry lookup for the judge is by stable_id from the same `entries` array.
360
469
  */
361
- declare class KnowledgeIdAllocator {
362
- private readonly metaPath;
363
- constructor(metaPath: string);
364
- /**
365
- * Allocate the next stable_id for the given (layer, type) pair and persist
366
- * the advanced counter to `agents.meta.json`.
367
- */
368
- allocate(layer: Layer, type: KnowledgeType): Promise<StableId>;
369
- /**
370
- * Returns the current counters envelope, defaulting to all-zero slots when
371
- * the meta file is absent or pre-v2.0 (counters key missing).
372
- */
373
- getCounters(): Promise<AgentsMetaCounters>;
374
- private readMeta;
375
- private normalizeCounters;
376
- private writeMetaAtomic;
470
+ declare function lintConflicts(entries: ConflictEntry[], opts?: {
471
+ threshold?: number;
472
+ judge?: ConflictJudge;
473
+ }): Promise<ConflictPair[]>;
474
+
475
+ interface ConflictLintReport {
476
+ status: "ok";
477
+ threshold: number;
478
+ deep: boolean;
479
+ /** Total candidate pairs (similarity threshold). */
480
+ candidate_count: number;
481
+ /** Subset judged a real contradiction (deep mode only). */
482
+ conflict_count: number;
483
+ pairs: ConflictPair[];
377
484
  }
485
+ /**
486
+ * v2.2 W5 R6 (读侧 cutover): load canonical knowledge entries with bodies from
487
+ * the read-set STORES (cross-store on-the-fly) instead of the retired
488
+ * co-location agents.meta node index. Skips pending/draft staging entries
489
+ * (conflict detection targets the curated corpus) and any entry missing a
490
+ * knowledge_type. collectStoreCanonicalEntries already reads each store entry's
491
+ * body + parsed frontmatter and degrades to [] when no store is in the read-set.
492
+ */
493
+ declare function loadConflictEntries(projectRoot: string): Promise<ConflictEntry[]>;
494
+ /**
495
+ * Run the knowledge-conflict lint. Always runs the cheap bm25 candidate pass;
496
+ * when `deep` is set AND a judge is supplied, escalates candidates to
497
+ * conflict/similar verdicts via the injected LLM judge.
498
+ *
499
+ * threshold resolution: opts.threshold → fabric-config
500
+ * conflict_lint_similarity_threshold → DEFAULT_CONFLICT_SIMILARITY_THRESHOLD.
501
+ */
502
+ declare function runDoctorConflictLint(projectRoot: string, opts?: {
503
+ threshold?: number;
504
+ deep?: boolean;
505
+ judge?: ConflictJudge;
506
+ }): Promise<ConflictLintReport>;
378
507
 
379
508
  /**
380
509
  * Append-evidence-on-collision service for fab_extract_knowledge.
@@ -412,6 +541,37 @@ declare function extractKnowledge(projectRoot: string, input: FabExtractKnowledg
412
541
  */
413
542
  declare function reviewKnowledge(projectRoot: string, input: FabReviewInput): Promise<FabReviewOutput>;
414
543
 
544
+ /** A summary to be cold-judged, keyed by its stable_id. */
545
+ interface ColdEvalCandidate {
546
+ stable_id: string;
547
+ summary: string;
548
+ }
549
+ /** The verdict the external cold-eval judge returns per candidate. */
550
+ interface ColdEvalVerdict {
551
+ stable_id: string;
552
+ /** true when the summary alone is act-on sufficient without the body. */
553
+ self_sufficient: boolean;
554
+ /** When not self-sufficient, the judge's suggested act-on rewrite. */
555
+ suggested_summary?: string;
556
+ /** Short rationale (pointer-vs-thesis) for the verdict. */
557
+ reason?: string;
558
+ }
559
+ /** The batch request handed to the external (maestro delegate) cold-eval judge. */
560
+ interface ColdEvalBatch {
561
+ rubric: string;
562
+ candidates: ColdEvalCandidate[];
563
+ }
564
+ declare const COLD_EVAL_RUBRIC: string;
565
+ /**
566
+ * Build the cold-eval batch request for the external judge. Pure + deterministic:
567
+ * drops blank summaries (nothing to judge) and pairs the candidates with the
568
+ * zero-context rubric. The fabric-review skill hands the result to
569
+ * `maestro delegate` and applies the returned {@link ColdEvalVerdict}[] via
570
+ * fab_review modify. Returns a batch with an empty candidate list when nothing is
571
+ * judgeable, so callers can short-circuit without a delegate round-trip.
572
+ */
573
+ declare function buildColdEvalBatch(candidates: ColdEvalCandidate[]): ColdEvalBatch;
574
+
415
575
  type StoredEventLedgerEvent = EventLedgerEvent;
416
576
  type ReadEventLedgerOptions = {
417
577
  event_type?: EventLedgerEvent["event_type"];
@@ -460,11 +620,28 @@ type PlanContextInput = {
460
620
  correlation_id?: string;
461
621
  session_id?: string;
462
622
  target_paths?: string[];
623
+ layer_filter?: "team" | "personal" | "both";
624
+ include_related?: boolean;
625
+ /**
626
+ * Internal MCP presentation budget. When supplied by the tool layer,
627
+ * candidates are byte-trimmed before the selection token is cached so the
628
+ * token cannot reference candidates omitted from the response.
629
+ */
630
+ payload_budget?: PlanContextPayloadBudget;
631
+ };
632
+ type PlanContextPayloadWarning = {
633
+ code: string;
634
+ file?: string;
635
+ action_hint?: string;
636
+ };
637
+ type PlanContextPayloadBudget = {
638
+ limits?: PayloadGuardOptions;
639
+ warnings?: PlanContextPayloadWarning[];
640
+ trim_warning?: PlanContextPayloadWarning;
463
641
  };
464
642
  type RequirementProfile = {
465
643
  target_path: string;
466
644
  known_tech: string[];
467
- user_intent: string;
468
645
  detected_entities: string[];
469
646
  };
470
647
  type PlanContextEntry = {
@@ -483,12 +660,18 @@ type PlanContextResult = {
483
660
  stale: boolean;
484
661
  selection_token: string;
485
662
  entries: PlanContextEntry[];
663
+ intent?: string;
486
664
  candidates: RuleDescriptionIndexItem[];
487
665
  omitted_candidate_count?: number;
488
666
  preflight_diagnostics: PreflightDiagnostic[];
489
667
  auto_healed?: boolean;
490
668
  previous_revision_hash?: string;
491
669
  redirects?: Record<string, string>;
670
+ related_appended?: Record<string, string>;
671
+ /** Internal service→tool signal; stripped before MCP output. */
672
+ payload_trimmed?: boolean;
673
+ /** Internal service→tool signal; stripped before MCP output. */
674
+ payload_over_budget?: boolean;
492
675
  };
493
676
  type SelectionTokenState = {
494
677
  token: string;
@@ -504,42 +687,33 @@ declare function readSelectionToken(token: string, now?: number): SelectionToken
504
687
 
505
688
  type RecallInput = PlanContextInput & {
506
689
  /**
507
- * Optional explicit set of stable_ids to fetch bodies for. When omitted,
508
- * fab_recall picks up every stable_id surfaced in the shared description
509
- * index (the common case after rc.37 selectable-filter removal). When
510
- * provided, filters the fetched body set to this intersection.
690
+ * Optional explicit set of stable_ids to SCOPE the returned read paths to.
691
+ * When omitted, `paths` carries one entry per surfaced candidate. The candidate
692
+ * DESCRIPTION index is always returned in full for discovery — `ids` only narrows
693
+ * which read paths are surfaced (e.g. `recall(ids)` when the agent already knows
694
+ * which entries it wants to Read). Stale (pre layer-flip) ids are redirect-rewritten
695
+ * before the match.
511
696
  */
512
697
  ids?: string[];
513
698
  /**
514
- * v2.2 MC1-recall-pack (W2-T4): when true, expand the fetched set with the
515
- * one-hop `related` graph neighbours (H2) of the selected entries that are
516
- * also present in the candidate index. Lets a scoped `ids` recall pull in the
517
- * connected knowledge without a second round-trip. No-op when `ids` is omitted
518
- * (every candidate is already fetched) or no related edges resolve in-corpus.
699
+ * When true, forwarded to planContext, which appends the one-hop `related` graph
700
+ * neighbours (H2) of the surfaced set to the candidate index (descriptions only —
701
+ * NO body). Their read paths are included in `paths` like any other candidate
702
+ * (W1-3 / KT-DEC-0031: surface the related id, do not fetch its body).
519
703
  */
520
704
  include_related?: boolean;
521
705
  };
522
- type RecallTruncation = {
523
- omitted_candidate_count: number;
524
- returned_candidate_count: number;
706
+ type RecallPath = {
707
+ stable_id: string;
708
+ path: string;
709
+ store?: {
710
+ alias: string;
711
+ };
525
712
  };
526
- type RecallResult = PlanContextResult & {
527
- rules: Array<{
528
- stable_id: string;
529
- level: "L0" | "L1" | "L2";
530
- path: string;
531
- body: string;
532
- }>;
533
- selected_stable_ids: string[];
534
- diagnostics: Array<{
535
- code: "missing_knowledge_metadata";
536
- severity: "warn";
537
- stable_id: string;
538
- message: string;
539
- }>;
713
+ type RecallResult = Omit<PlanContextResult, "selection_token" | "payload_trimmed" | "payload_over_budget"> & {
714
+ paths: RecallPath[];
540
715
  directive: string;
541
716
  next_steps?: string[];
542
- truncation?: RecallTruncation;
543
717
  };
544
718
  declare function recall(projectRoot: string, input: RecallInput): Promise<RecallResult>;
545
719
 
@@ -607,92 +781,6 @@ declare class ContextCache {
607
781
  }
608
782
  declare const contextCache: ContextCache;
609
783
 
610
- interface KnowledgeSyncOptions {
611
- mode?: "incremental" | "full";
612
- /** When true, invalid frontmatter throws RuleValidationError (default: false — collect as warning). */
613
- throwOnInvalidFrontmatter?: boolean;
614
- /**
615
- * v2.0.0-rc.29 TASK-005 (BUG-G1): when true, `ensureKnowledgeFresh`
616
- * synchronously follows a drift detection with a `reconcileKnowledge`
617
- * call to materialize the auto-heal (rewrite agents.meta.json + emit a
618
- * paired `knowledge_meta_auto_healed` event). Default false preserves
619
- * the rc.28 hot-path semantics where drift detection never blocks the
620
- * MCP read on a meta rebuild. Opt-in is intended for callers that can
621
- * tolerate ~tens-of-ms extra latency in exchange for the invariant
622
- * "every knowledge_drift_detected has a paired heal event in the same
623
- * tail window." Audit (BUG-G1) found 5/72 drifts healed on this repo
624
- * (~7%) because the hot path emitted detect-only events.
625
- */
626
- autoHealOnDrift?: boolean;
627
- }
628
- interface StructuredWarning {
629
- code: string;
630
- file: string;
631
- line?: number;
632
- action_hint: string;
633
- }
634
- /**
635
- * Granular ledger event shape for knowledge-sync operations.
636
- * These are returned in KnowledgeSyncReport and also appended to the event ledger
637
- * using the nearest available ledger event type (knowledge_drift_detected).
638
- * The shape below is what callers receive in `.events`.
639
- */
640
- interface KnowledgeSyncLedgerEvent {
641
- type: "rule_content_changed" | "rule_added" | "rule_removed";
642
- stable_id: string;
643
- path: string;
644
- prev_hash: string | null;
645
- new_hash: string | null;
646
- changed_fields: string[];
647
- source: "ensureKnowledgeFresh" | "reconcileKnowledge";
648
- }
649
- /** Alias so the public API says LedgerEvent (as documented). */
650
- type LedgerEvent = KnowledgeSyncLedgerEvent;
651
- interface KnowledgeSyncReport {
652
- status: "fresh" | "reconciled" | "errors";
653
- events: LedgerEvent[];
654
- warnings: StructuredWarning[];
655
- reconciled_files?: string[];
656
- }
657
- /**
658
- * Clear the knowledge-sync cooldown for a projectRoot so the next ensureKnowledgeFresh
659
- * call performs a real I/O scan. Called by the chokidar watcher when a rule
660
- * file changes (see http.ts handleCacheWatcherEvent).
661
- */
662
- declare function invalidateKnowledgeSyncCooldown(projectRoot: string): void;
663
- /**
664
- * Detects drift between disk and agents.meta.json, emits ledger events, and
665
- * invalidates the cache. Does NOT rewrite agents.meta.json. Optimised for
666
- * hot-path consumers (MCP tools).
667
- */
668
- declare function ensureKnowledgeFresh(projectRoot: string, opts?: KnowledgeSyncOptions): Promise<KnowledgeSyncReport>;
669
- interface ReconcileKnowledgeOptions {
670
- /**
671
- * Identifies who triggered the reconcile; controls which summary ledger event is written.
672
- *
673
- * v2.0.0-rc.23 TASK-005 (a-B): `auto-heal-description` added so plan_context
674
- * can drive a full reconcile when it detects nodes with `description === undefined`
675
- * (legacy meta drift the revision-hash gate cannot detect).
676
- *
677
- * v2.0.0-rc.27 TASK-001 (§2.9 root): `post-approve` / `post-modify` added so
678
- * `fab_review` approve/modify-layer-flip can drive an immediate meta rebuild
679
- * — without this the new entry's `nodes[id]` stays empty until the next
680
- * plan_context call's auto-heal, which leaves the entry undiscoverable in
681
- * the description_index window between approve and the next hint call.
682
- */
683
- trigger?: "startup" | "doctor" | "manual" | "auto-heal-description" | "auto-heal-after-drift" | "post-approve" | "post-modify";
684
- }
685
- /**
686
- * Full scan + rewrites agents.meta.json with ground-truth disk state + emits
687
- * ledger events. Used by startup (TASK-022) and doctor repair (TASK-023).
688
- * Returns reconciled_files listing all paths whose meta was updated.
689
- *
690
- * When `opts.trigger` is `'startup'`, a `meta_reconciled_on_startup` summary
691
- * ledger event is appended after per-file drift events. Other trigger values
692
- * append a `meta_reconciled` event. Omitting the trigger skips the summary.
693
- */
694
- declare function reconcileKnowledge(projectRoot: string, opts?: ReconcileKnowledgeOptions): Promise<KnowledgeSyncReport>;
695
-
696
784
  type LedgerSourceFilter = "ai" | "human";
697
785
  type StoredLedgerEntry = LedgerEntry & {
698
786
  id: string;
@@ -727,40 +815,6 @@ type RehydratedAgentsMetaSnapshot = {
727
815
  };
728
816
  declare function rehydrateAgentsMetaAt(projectRoot: string, target: RehydrateTarget): Promise<RehydratedAgentsMetaSnapshot>;
729
817
 
730
- declare function readAgentsMeta(projectRoot: string): Promise<AgentsMeta>;
731
-
732
- type KnowledgeEntryItem = {
733
- path: string;
734
- content: string;
735
- };
736
- type DescriptionStub = {
737
- path: string;
738
- description: string;
739
- };
740
- type HumanLockedNearby = {
741
- file: string;
742
- excerpt: string;
743
- };
744
- type KnowledgePayload = {
745
- L0: string;
746
- L1: KnowledgeEntryItem[];
747
- L2: KnowledgeEntryItem[];
748
- human_locked_nearby: HumanLockedNearby[];
749
- description_stubs?: DescriptionStub[];
750
- };
751
- type GetKnowledgeInput = {
752
- path: string;
753
- client_hash?: string;
754
- correlation_id?: string;
755
- session_id?: string;
756
- };
757
- type GetKnowledgeResult = {
758
- revision_hash: string;
759
- stale: boolean;
760
- rules: KnowledgePayload;
761
- };
762
- declare function getKnowledge(projectRoot: string, input: GetKnowledgeInput): Promise<GetKnowledgeResult>;
763
-
764
818
  /**
765
819
  * Shared constants used across the server package.
766
820
  */
@@ -803,4 +857,4 @@ interface ShutdownHandlerDeps {
803
857
  */
804
858
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
805
859
 
806
- 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, 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, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getKnowledge, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, invalidateKnowledgeSyncCooldown, isSameKnowledgeTestIndex, loadKbIdTypeMap, planContext, readAgentsMeta, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, reconcileKnowledge, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorFix, runDoctorHistoryAll, runDoctorReport, stableStringify, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick, writeKnowledgeMeta };
860
+ 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 };