@fenglimg/fabric-shared 2.1.0-rc.2 → 2.2.0-rc.1

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.js CHANGED
@@ -1,12 +1,4 @@
1
- import {
2
- BOOTSTRAP_CANONICAL,
3
- BOOTSTRAP_MARKER_BEGIN,
4
- BOOTSTRAP_MARKER_END,
5
- BOOTSTRAP_REGEX,
6
- LEGACY_KB_MARKER_BEGIN,
7
- LEGACY_KB_MARKER_END,
8
- LEGACY_KB_REGEX
9
- } from "./chunk-MDWTGOAY.js";
1
+ import "./chunk-LXNCAKJZ.js";
10
2
  import {
11
3
  PROTECTED_TOKENS,
12
4
  createTranslator,
@@ -16,7 +8,7 @@ import {
16
8
  normalizeLocale,
17
9
  resolveFabricLocale,
18
10
  zhCNMessages
19
- } from "./chunk-R2J7DAED.js";
11
+ } from "./chunk-7TZ2PMVH.js";
20
12
  import {
21
13
  FabExtractKnowledgeInputSchema,
22
14
  FabExtractKnowledgeInputShape,
@@ -64,8 +56,16 @@ import {
64
56
  recallInputSchema,
65
57
  recallOutputSchema,
66
58
  structuredWarningSchema
67
- } from "./chunk-WVPDH4BF.js";
68
- import "./chunk-LXNCAKJZ.js";
59
+ } from "./chunk-JEXTOQVV.js";
60
+ import {
61
+ BOOTSTRAP_CANONICAL,
62
+ BOOTSTRAP_MARKER_BEGIN,
63
+ BOOTSTRAP_MARKER_END,
64
+ BOOTSTRAP_REGEX,
65
+ LEGACY_KB_MARKER_BEGIN,
66
+ LEGACY_KB_MARKER_END,
67
+ LEGACY_KB_REGEX
68
+ } from "./chunk-TX2XZ7AW.js";
69
69
 
70
70
  // src/schemas/agents-meta.ts
71
71
  import { z } from "zod";
@@ -112,7 +112,15 @@ var ruleDescriptionSchema = z.object({
112
112
  // relevance_scope → 'broad' (always-surface, safe default)
113
113
  // relevance_paths → [] (no path anchors)
114
114
  relevance_scope: z.enum(["narrow", "broad"]).default("broad"),
115
- relevance_paths: z.array(z.string()).default([])
115
+ relevance_paths: z.array(z.string()).default([]),
116
+ // v2.2 H2-related (W1-T7): explicit graph edges to related KB entries by
117
+ // stable_id. Authored in frontmatter (`related: [KT-DEC-0001, KT-PIT-0002]`)
118
+ // or written by the fabric-connect skill (SK2); read by fab_recall's
119
+ // include_related packaging (MC1). Optional + default [] so the field is a
120
+ // pure additive — every pre-v2.2 entry parses unchanged. The schema is
121
+ // .strict(), so this MUST be declared or `related:` frontmatter would be
122
+ // rejected at parse time.
123
+ related: z.array(z.string()).default([]).optional()
116
124
  }).strict();
117
125
  var ruleDescriptionIndexItemSchema = z.object({
118
126
  stable_id: z.string(),
@@ -417,6 +425,7 @@ var mcpPayloadLimitsSchema = z6.object({
417
425
  hardBytes: z6.number().int().positive().optional()
418
426
  }).optional();
419
427
  var selectionTokenTtlMsSchema = z6.number().int().min(3e4).max(36e5);
428
+ var planContextTopKSchema = z6.number().int().min(1).max(200);
420
429
  var fabricLanguageSchema = z6.enum([
421
430
  "match-existing",
422
431
  "zh-CN",
@@ -634,12 +643,32 @@ var fabricConfigSchema = z6.object({
634
643
  // TRUNCATION_THRESHOLD=12 grouped-render kicks in. Mirrors the rc.7 T7 +
635
644
  // archive_max_* pattern of externalizing previously-hardcoded thresholds.
636
645
  hint_broad_top_k: z6.number().int().min(1).max(50).optional().default(8),
646
+ // v2.2 HK2-degrade (W2-T2): char budget for the rendered SessionStart broad-menu
647
+ // body — the final rung of the degradation ladder after the hint_broad_top_k
648
+ // count slice. Once the rendered entry/group lines exceed this, the tail
649
+ // collapses to a single "N more omitted" marker so a large corpus cannot blow
650
+ // the agent's working memory. Default 2000 (~one screenful); 0 disables the
651
+ // budget. Read by knowledge-hint-broad.cjs via readConfigNumber. Range 0..20000.
652
+ hint_broad_budget_chars: z6.number().int().min(0).max(2e4).optional().default(2e3),
637
653
  // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint Stop
638
654
  // hook nudges. Any signal type listed here is suppressed at emit time across
639
655
  // all sessions (the session-scoped sibling lives in a .fabric/.cache sidecar
640
656
  // written on request). Mirrors the cite_evict_interval=0 opt-out convention —
641
657
  // a knob for an existing surface, not a new feature. Unknown types ignored.
642
658
  hint_dismiss_signals: z6.array(z6.enum(["archive", "review", "import", "maintenance"])).optional(),
659
+ // v2.1 ADJ-NEWN-4: user-override escape hatches for the two strong behavioral
660
+ // policies (cite-before-edit + self-archive). The strong policies can make an
661
+ // agent feel like a "stubborn parrot" (D2 user-in-control red line); these
662
+ // flags let a user durably turn either off via fabric-config.json (or the
663
+ // `fabric config` panel) without editing bootstrap/AGENTS.md. Default true
664
+ // preserves rc.x behavior (policies ON); set false to opt a project out.
665
+ // The bootstrap behavior layer references these so the AGENTS.md rules degrade
666
+ // from "MUST" to "optional" when disabled — a config knob for an existing
667
+ // surface, mirroring the cite_evict_interval=0 / hint_dismiss_signals opt-out
668
+ // convention, NOT a new feature. Wave3 J32 will quantify the friction these
669
+ // relieve; until then they ship as inert-safe opt-outs.
670
+ cite_policy_enabled: z6.boolean().optional().default(true),
671
+ self_archive_policy_enabled: z6.boolean().optional().default(true),
643
672
  // v2.0.0-rc.33 W2-1 (P0-9): TopK upper bound for the narrow PreToolUse hint
644
673
  // emitted by knowledge-hint-narrow.cjs. After filtering to entries whose
645
674
  // `relevance_scope === "narrow"` (rc.27 TASK-005 audit §2.5 fix), the hook
@@ -713,7 +742,31 @@ var fabricConfigSchema = z6.object({
713
742
  // so the server-side per-field reader can validate without re-running the
714
743
  // whole fabricConfigSchema on every plan_context call — that lets a corrupt
715
744
  // unrelated field stay isolated from the hot read path.
716
- selection_token_ttl_ms: selectionTokenTtlMsSchema.optional()
745
+ selection_token_ttl_ms: selectionTokenTtlMsSchema.optional(),
746
+ // v2.2 A-INFRA-3 (W1-T3-TOPK): bound on `fab_plan_context` candidate count,
747
+ // applied after BM25 ranking. Absent → library default (24). See
748
+ // planContextTopKSchema for the range/calibration rationale.
749
+ plan_context_top_k: planContextTopKSchema.optional(),
750
+ // v2.2 C5-budget (W2-T3): layered retrieval budget profile. A single coherent
751
+ // strategy across the injection + MCP layers — `balanced` (default) reproduces
752
+ // the historical per-knob defaults exactly, `conservative` / `generous` scale
753
+ // the whole truncation chain (top_k + payload bytes + injection chars) down /
754
+ // up together. Per-field knobs (plan_context_top_k, mcpPayloadLimits.*,
755
+ // hint_broad_budget_chars) still override the profile when set. See
756
+ // retrieval-budget.ts (resolveRetrievalBudget) for the resolution order.
757
+ retrieval_budget_profile: z6.enum(["conservative", "balanced", "generous"]).optional(),
758
+ // v2.2 C2-vector (W2-T7): OPTIONAL dense-embedding semantic retrieval, layered
759
+ // as a recall supplement after BM25. Default OFF (`--no-embed` is the baseline);
760
+ // requires the operator to install the optional `fastembed` package — absent →
761
+ // text-only fallback. Never grows the default install footprint.
762
+ embed_enabled: z6.boolean().optional().default(false),
763
+ // Weight applied to the 0..1 cosine similarity before it joins the additive
764
+ // score. Capped at 49 — strictly BELOW BM25_WEIGHT (50) — so a perfect vector
765
+ // match (weight × 1) can never outscore a single strong BM25 term match. This
766
+ // ENFORCES the "vectors supplement, never override lexical relevance"
767
+ // invariant in the schema rather than leaving it to a comment (W2-REVIEW codex
768
+ // MED-4). Range 0..49; default 30.
769
+ embed_weight: z6.number().int().min(0).max(49).optional().default(30)
717
770
  });
718
771
 
719
772
  // src/schemas/fabric-config-introspect.ts
@@ -1226,9 +1279,23 @@ function hasScriptExtension(name) {
1226
1279
  const dot = name.lastIndexOf(".");
1227
1280
  return dot !== -1 && SCRIPT_EXTENSIONS.has(name.slice(dot).toLowerCase());
1228
1281
  }
1229
- function findStoreExecutableViolations(absDir) {
1282
+ var STORE_SCAN_MAX_DEPTH = 32;
1283
+ var STORE_SCAN_MAX_ENTRIES = 1e5;
1284
+ function findStoreExecutableViolations(absDir, options = {}) {
1285
+ const maxDepth = options.maxDepth ?? STORE_SCAN_MAX_DEPTH;
1286
+ const maxEntries = options.maxEntries ?? STORE_SCAN_MAX_ENTRIES;
1230
1287
  const violations = [];
1231
- const walk = (dir, rel) => {
1288
+ let entriesScanned = 0;
1289
+ let bounded = false;
1290
+ const walk = (dir, rel, depth) => {
1291
+ if (bounded) {
1292
+ return;
1293
+ }
1294
+ if (depth > maxDepth) {
1295
+ violations.push(`<scan-bounded: depth > ${maxDepth} at ${rel === "" ? "." : rel}>`);
1296
+ bounded = true;
1297
+ return;
1298
+ }
1232
1299
  let entries;
1233
1300
  try {
1234
1301
  entries = readdirSync(dir);
@@ -1236,9 +1303,18 @@ function findStoreExecutableViolations(absDir) {
1236
1303
  return;
1237
1304
  }
1238
1305
  for (const entry of entries) {
1306
+ if (bounded) {
1307
+ return;
1308
+ }
1239
1309
  if (rel === "" && entry === ".git") {
1240
1310
  continue;
1241
1311
  }
1312
+ entriesScanned += 1;
1313
+ if (entriesScanned > maxEntries) {
1314
+ violations.push(`<scan-bounded: entries > ${maxEntries}>`);
1315
+ bounded = true;
1316
+ return;
1317
+ }
1242
1318
  const abs = join(dir, entry);
1243
1319
  const relPath = rel === "" ? entry : `${rel}/${entry}`;
1244
1320
  let stat;
@@ -1248,7 +1324,7 @@ function findStoreExecutableViolations(absDir) {
1248
1324
  continue;
1249
1325
  }
1250
1326
  if (stat.isDirectory()) {
1251
- walk(abs, relPath);
1327
+ walk(abs, relPath, depth + 1);
1252
1328
  continue;
1253
1329
  }
1254
1330
  if ((stat.mode & 73) !== 0 || hasScriptExtension(entry)) {
@@ -1256,7 +1332,7 @@ function findStoreExecutableViolations(absDir) {
1256
1332
  }
1257
1333
  }
1258
1334
  };
1259
- walk(absDir, "");
1335
+ walk(absDir, "", 0);
1260
1336
  return violations;
1261
1337
  }
1262
1338
 
@@ -1463,6 +1539,47 @@ function redactSecrets(content) {
1463
1539
  }
1464
1540
  return out;
1465
1541
  }
1542
+ function scrubRemoteUrl(remote) {
1543
+ return remote.replace(
1544
+ /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/@]*:[^/@]*@/,
1545
+ "$1"
1546
+ );
1547
+ }
1548
+
1549
+ // src/scanner/scan-recommendations.ts
1550
+ function buildScanRecommendations(input, t) {
1551
+ const recs = [];
1552
+ if (input.hasExistingFabric === false) {
1553
+ recs.push(t("scan.rec.install"));
1554
+ }
1555
+ if (input.readmeOk === false) {
1556
+ recs.push(t("scan.rec.readme"));
1557
+ }
1558
+ if (input.hasContributing === false) {
1559
+ recs.push(t("scan.rec.contributing"));
1560
+ }
1561
+ switch (input.frameworkKind) {
1562
+ case "cocos-creator":
1563
+ recs.push(t("scan.rec.cocos.lifecycle"));
1564
+ recs.push(t("scan.rec.cocos.human-protect"));
1565
+ if (input.hasMeta === true) {
1566
+ recs.push(t("scan.rec.cocos.meta-lock"));
1567
+ }
1568
+ break;
1569
+ case "next":
1570
+ recs.push(t("scan.rec.next"));
1571
+ break;
1572
+ case "vite":
1573
+ recs.push(t("scan.rec.vite"));
1574
+ break;
1575
+ case "unknown":
1576
+ recs.push(t("scan.rec.unknown"));
1577
+ break;
1578
+ default:
1579
+ recs.push(t("scan.rec.generic", { kind: input.frameworkKind }));
1580
+ }
1581
+ return recs;
1582
+ }
1466
1583
 
1467
1584
  // src/store/cross-store-lint.ts
1468
1585
  function lintCrossStoreReferences(input) {
@@ -1650,6 +1767,8 @@ function addMountedStore(config, store) {
1650
1767
  `alias '${store.alias}' already mounts store ${aliasClash.store_uuid}; choose another alias`
1651
1768
  );
1652
1769
  }
1770
+ const sanitized = store.remote === void 0 ? store : { ...store, remote: scrubRemoteUrl(store.remote) };
1771
+ store = sanitized;
1653
1772
  const existing = config.stores.find((s) => s.store_uuid === store.store_uuid);
1654
1773
  const stores = existing === void 0 ? [...config.stores, store] : config.stores.map((s) => s.store_uuid === store.store_uuid ? store : s);
1655
1774
  return { ...config, stores };
@@ -1665,7 +1784,8 @@ function detachMountedStore(config, alias) {
1665
1784
  };
1666
1785
  }
1667
1786
  function bindRequiredStore(required, entry) {
1668
- return required.some((r) => r.id === entry.id) ? required.map((r) => r.id === entry.id ? entry : r) : [...required, entry];
1787
+ const safeEntry = entry.suggested_remote === void 0 ? entry : { ...entry, suggested_remote: scrubRemoteUrl(entry.suggested_remote) };
1788
+ return required.some((r) => r.id === safeEntry.id) ? required.map((r) => r.id === safeEntry.id ? safeEntry : r) : [...required, safeEntry];
1669
1789
  }
1670
1790
  function explainStore(config, alias) {
1671
1791
  const store = findMountedStore(config, alias);
@@ -2372,6 +2492,94 @@ var sessionArchiveAttemptedEventSchema = z18.object({
2372
2492
  candidates_proposed: z18.number().int().nonnegative().default(0),
2373
2493
  knowledge_proposed_ids: z18.array(z18.string()).default([])
2374
2494
  });
2495
+ var hookSurfaceEmittedEventSchema = z18.object({
2496
+ ...eventLedgerEnvelopeSchema,
2497
+ event_type: z18.literal("hook_surface_emitted"),
2498
+ hook_name: z18.string(),
2499
+ client: z18.enum(["cc", "codex", "cursor"]),
2500
+ target_channel: z18.string(),
2501
+ rendered_ids: z18.array(z18.string()),
2502
+ delivery_status: z18.enum(["delivered", "suppressed", "error"]),
2503
+ suppression_reason: z18.string().optional()
2504
+ });
2505
+ var hookSignalEmittedEventSchema = z18.object({
2506
+ ...eventLedgerEnvelopeSchema,
2507
+ event_type: z18.literal("hook_signal_emitted"),
2508
+ signal_type: z18.enum(["archive", "review", "maintenance", "other"]),
2509
+ threshold: z18.number(),
2510
+ actual_value: z18.number(),
2511
+ fired: z18.boolean()
2512
+ });
2513
+ var mcpStdioTraceEventSchema = z18.object({
2514
+ ...eventLedgerEnvelopeSchema,
2515
+ event_type: z18.literal("mcp_stdio_trace"),
2516
+ tool_name: z18.string(),
2517
+ request_id: z18.string(),
2518
+ duration_ms: z18.number().nonnegative(),
2519
+ status: z18.enum(["ok", "error"]),
2520
+ payload_bytes_in: z18.number().int().nonnegative(),
2521
+ payload_bytes_out: z18.number().int().nonnegative(),
2522
+ error_code: z18.string().optional()
2523
+ });
2524
+ var payloadGuardObservedEventSchema = z18.object({
2525
+ ...eventLedgerEnvelopeSchema,
2526
+ event_type: z18.literal("payload_guard_observed"),
2527
+ tool_name: z18.string(),
2528
+ path_count: z18.number().int().nonnegative(),
2529
+ tokens_estimated: z18.number().int().nonnegative(),
2530
+ truncated: z18.boolean(),
2531
+ cap: z18.number().int().positive()
2532
+ });
2533
+ var skillInvocationStartedEventSchema = z18.object({
2534
+ ...eventLedgerEnvelopeSchema,
2535
+ event_type: z18.literal("skill_invocation_started"),
2536
+ skill_name: z18.string(),
2537
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2538
+ entry_point: z18.string()
2539
+ });
2540
+ var skillInvocationCompletedEventSchema = z18.object({
2541
+ ...eventLedgerEnvelopeSchema,
2542
+ event_type: z18.literal("skill_invocation_completed"),
2543
+ skill_name: z18.string(),
2544
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2545
+ entry_point: z18.string(),
2546
+ outcome: z18.enum(["completed", "aborted", "error", "no_op"]),
2547
+ elapsed_ms: z18.number().nonnegative().optional()
2548
+ });
2549
+ var skillPhaseTransitionEventSchema = z18.object({
2550
+ ...eventLedgerEnvelopeSchema,
2551
+ event_type: z18.literal("skill_phase_transition"),
2552
+ skill_name: z18.string(),
2553
+ phase: z18.string(),
2554
+ status: z18.enum(["entered", "completed", "skipped", "failed"]),
2555
+ checkpoint: z18.string().optional(),
2556
+ elapsed_ms: z18.number().nonnegative().optional()
2557
+ });
2558
+ var skillTriggerCandidateEventSchema = z18.object({
2559
+ ...eventLedgerEnvelopeSchema,
2560
+ event_type: z18.literal("skill_trigger_candidate"),
2561
+ skill_name: z18.string(),
2562
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2563
+ signal: z18.string(),
2564
+ invoked: z18.boolean()
2565
+ });
2566
+ var llmJudgeRunEventSchema = z18.object({
2567
+ ...eventLedgerEnvelopeSchema,
2568
+ event_type: z18.literal("llm_judge_run"),
2569
+ prompt: z18.string(),
2570
+ version: z18.string(),
2571
+ model: z18.string(),
2572
+ input_trace_id: z18.string(),
2573
+ score: z18.number(),
2574
+ rationale: z18.string()
2575
+ });
2576
+ var clientCapabilitySnapshotEventSchema = z18.object({
2577
+ ...eventLedgerEnvelopeSchema,
2578
+ event_type: z18.literal("client_capability_snapshot"),
2579
+ client: z18.enum(["cc", "codex", "cursor"]),
2580
+ capabilities: z18.array(z18.string()),
2581
+ version: z18.string()
2582
+ });
2375
2583
  var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2376
2584
  knowledgeContextPlannedEventSchema,
2377
2585
  knowledgeSelectionEventSchema,
@@ -2453,8 +2661,83 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2453
2661
  // fabric-archive skill at the end of every invocation. Drives Phase 0.0
2454
2662
  // cross-session digest, outcome-based rescan filter (skips user_dismissed),
2455
2663
  // covered_through_ts watermark, and `fabric doctor --archive-history`.
2456
- sessionArchiveAttemptedEventSchema
2664
+ sessionArchiveAttemptedEventSchema,
2665
+ // v2.1 GATE-INSTR (NEW-N-3): 9 interaction-axis instrumentation events.
2666
+ hookSurfaceEmittedEventSchema,
2667
+ hookSignalEmittedEventSchema,
2668
+ mcpStdioTraceEventSchema,
2669
+ payloadGuardObservedEventSchema,
2670
+ skillInvocationStartedEventSchema,
2671
+ skillInvocationCompletedEventSchema,
2672
+ skillPhaseTransitionEventSchema,
2673
+ skillTriggerCandidateEventSchema,
2674
+ llmJudgeRunEventSchema,
2675
+ clientCapabilitySnapshotEventSchema
2457
2676
  ]);
2677
+
2678
+ // src/text-tokenize.ts
2679
+ var CJK_CLASS = "\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af";
2680
+ var RUN_RE = new RegExp(`[a-z0-9]+|[${CJK_CLASS}]+`, "gu");
2681
+ var CJK_FIRST_RE = new RegExp(`[${CJK_CLASS}]`, "u");
2682
+ function tokenize(text) {
2683
+ if (text.length === 0) {
2684
+ return [];
2685
+ }
2686
+ const tokens = [];
2687
+ const lowered = text.toLowerCase();
2688
+ RUN_RE.lastIndex = 0;
2689
+ let match;
2690
+ while ((match = RUN_RE.exec(lowered)) !== null) {
2691
+ const run = match[0];
2692
+ if (CJK_FIRST_RE.test(run[0])) {
2693
+ if (run.length === 1) {
2694
+ tokens.push(run);
2695
+ } else {
2696
+ for (let i = 0; i < run.length - 1; i += 1) {
2697
+ tokens.push(run.slice(i, i + 2));
2698
+ }
2699
+ }
2700
+ } else {
2701
+ tokens.push(run);
2702
+ }
2703
+ }
2704
+ return tokens;
2705
+ }
2706
+
2707
+ // src/retrieval-budget.ts
2708
+ var PROFILES = {
2709
+ conservative: {
2710
+ topK: 12,
2711
+ payloadWarnBytes: 8192,
2712
+ payloadHardBytes: 32768,
2713
+ injectionChars: 1e3
2714
+ },
2715
+ balanced: {
2716
+ topK: 24,
2717
+ payloadWarnBytes: 16384,
2718
+ payloadHardBytes: 65536,
2719
+ injectionChars: 2e3
2720
+ },
2721
+ generous: {
2722
+ topK: 48,
2723
+ payloadWarnBytes: 32768,
2724
+ payloadHardBytes: 131072,
2725
+ injectionChars: 4e3
2726
+ }
2727
+ };
2728
+ var DEFAULT_RETRIEVAL_BUDGET_PROFILE = "balanced";
2729
+ function resolveRetrievalBudget(overrides) {
2730
+ const base = PROFILES[overrides?.profile ?? DEFAULT_RETRIEVAL_BUDGET_PROFILE];
2731
+ return {
2732
+ topK: overrides?.topK ?? base.topK,
2733
+ payloadWarnBytes: overrides?.payloadWarnBytes ?? base.payloadWarnBytes,
2734
+ payloadHardBytes: overrides?.payloadHardBytes ?? base.payloadHardBytes,
2735
+ injectionChars: overrides?.injectionChars ?? base.injectionChars
2736
+ };
2737
+ }
2738
+ function retrievalBudgetProfile(profile) {
2739
+ return PROFILES[profile];
2740
+ }
2458
2741
  export {
2459
2742
  AGENTS_META_IDENTITY_SOURCES,
2460
2743
  AGENTS_META_LAYERS,
@@ -2464,6 +2747,7 @@ export {
2464
2747
  BOOTSTRAP_MARKER_BEGIN,
2465
2748
  BOOTSTRAP_MARKER_END,
2466
2749
  BOOTSTRAP_REGEX,
2750
+ DEFAULT_RETRIEVAL_BUDGET_PROFILE,
2467
2751
  FabExtractKnowledgeInputSchema,
2468
2752
  FabExtractKnowledgeInputShape,
2469
2753
  FabExtractKnowledgeOutputSchema,
@@ -2528,6 +2812,7 @@ export {
2528
2812
  bootstrapMarkerMigratedEventSchema,
2529
2813
  buildDebugBundle,
2530
2814
  buildFailureTrace,
2815
+ buildScanRecommendations,
2531
2816
  candidateFileEntrySchema,
2532
2817
  citeContractMetricsSchema,
2533
2818
  citeContractPolicyActivatedEventSchema,
@@ -2536,6 +2821,7 @@ export {
2536
2821
  citePolicyActivatedEventSchema,
2537
2822
  claudeHookPathMigratedEventSchema,
2538
2823
  claudeSkillPathMigratedEventSchema,
2824
+ clientCapabilitySnapshotEventSchema,
2539
2825
  clientPathsSchema,
2540
2826
  codexSkillPathMigratedEventSchema,
2541
2827
  createProjectRootResolver,
@@ -2584,6 +2870,8 @@ export {
2584
2870
  globalRefSchema,
2585
2871
  hasSecrets,
2586
2872
  historyStateQuerySchema,
2873
+ hookSignalEmittedEventSchema,
2874
+ hookSurfaceEmittedEventSchema,
2587
2875
  humanLedgerEntrySchema,
2588
2876
  humanLockApproveRequestSchema,
2589
2877
  humanLockEntrySchema,
@@ -2637,12 +2925,14 @@ export {
2637
2925
  ledgerSourceSchema,
2638
2926
  lintCrossStoreReferences,
2639
2927
  listStoreKnowledge,
2928
+ llmJudgeRunEventSchema,
2640
2929
  localKnowledgeIdSchema,
2641
2930
  lockApprovedEventSchema,
2642
2931
  lockDriftEventSchema,
2643
2932
  mcpConfigMigratedEventSchema,
2644
2933
  mcpEventLedgerEventSchema,
2645
2934
  mcpPayloadLimitsSchema,
2935
+ mcpStdioTraceEventSchema,
2646
2936
  metaReconciledEventSchema,
2647
2937
  metaReconciledOnStartupEventSchema,
2648
2938
  metaUpdatedEventSchema,
@@ -2658,12 +2948,14 @@ export {
2658
2948
  parseCiteLine,
2659
2949
  parseGlobalRef,
2660
2950
  parseKnowledgeId,
2951
+ payloadGuardObservedEventSchema,
2661
2952
  pendingAutoArchivedEventSchema,
2662
2953
  planContextAnnotations,
2663
2954
  planContextHintNarrowEntrySchema,
2664
2955
  planContextHintOutputSchema,
2665
2956
  planContextInputSchema,
2666
2957
  planContextOutputSchema,
2958
+ planContextTopKSchema,
2667
2959
  projectRootGoldenCaseSchema,
2668
2960
  projectRootGoldenFileSchema,
2669
2961
  projectRootResolutionSchema,
@@ -2685,16 +2977,23 @@ export {
2685
2977
  requiredStoreEntrySchema,
2686
2978
  resolveCandidates,
2687
2979
  resolveFabricLocale,
2980
+ resolveRetrievalBudget,
2688
2981
  resolveStoreQualifiedId,
2689
2982
  resolvedBindingsSnapshotSchema,
2983
+ retrievalBudgetProfile,
2690
2984
  ruleDescriptionIndexItemSchema,
2691
2985
  ruleDescriptionSchema,
2692
2986
  scanForSecrets,
2693
2987
  scopeCoordinateSchema,
2694
2988
  scopeRoot,
2989
+ scrubRemoteUrl,
2695
2990
  selectionTokenTtlMsSchema,
2696
2991
  serveLockClearedEventSchema,
2697
2992
  sessionArchiveAttemptedEventSchema,
2993
+ skillInvocationCompletedEventSchema,
2994
+ skillInvocationStartedEventSchema,
2995
+ skillPhaseTransitionEventSchema,
2996
+ skillTriggerCandidateEventSchema,
2698
2997
  storeAwareEntrySchema,
2699
2998
  storeCountersSchema,
2700
2999
  storeIdentitySchema,
@@ -2706,6 +3005,7 @@ export {
2706
3005
  storeResolverWarningSchema,
2707
3006
  storeUuidSchema,
2708
3007
  structuredWarningSchema,
3008
+ tokenize,
2709
3009
  uidSchema,
2710
3010
  withDerivedAgentsMetaNodeDefaults,
2711
3011
  writeBindingsSnapshot,
@@ -6,6 +6,31 @@ interface AtomicWriteJsonOptions extends AtomicWriteOptions {
6
6
  }
7
7
  declare function atomicWriteText(path: string, content: string, opts?: AtomicWriteOptions): Promise<void>;
8
8
  declare function atomicWriteJson(path: string, value: unknown, opts?: AtomicWriteJsonOptions): Promise<void>;
9
+ interface FileLockOptions {
10
+ /** A held lock older than this (ms, by lock-file mtime) is presumed stale —
11
+ * left by a crashed holder — and reclaimed. Default 10s. */
12
+ staleMs?: number;
13
+ /** Poll interval (ms) between acquire attempts while contended. Default 20ms. */
14
+ retryDelayMs?: number;
15
+ /** Give up acquiring after this long (ms) and throw. Default 10s. */
16
+ maxWaitMs?: number;
17
+ }
18
+ /**
19
+ * Run `fn` while holding a cross-process advisory lock at `lockPath`.
20
+ *
21
+ * Unlike the hook-side `appendLockedLine` (which DROPS on contention, fine for
22
+ * best-effort telemetry), this WAITS for the lock — the critical section it
23
+ * guards (e.g. a read-modify-write of a shared counter file) must not be
24
+ * skipped. The lock is a `wx` (O_CREAT|O_EXCL) lock file, so acquisition is
25
+ * atomic across processes; a crashed holder leaves the file behind, so any
26
+ * holder older than `staleMs` is reclaimed. The lock is always released in a
27
+ * `finally`, even if `fn` throws.
28
+ *
29
+ * Scope: cross-process AND in-process. Two concurrent callers on the same
30
+ * `lockPath` (same process or not) serialize, because both race the same
31
+ * O_EXCL create.
32
+ */
33
+ declare function withFileLock<T>(lockPath: string, fn: () => Promise<T>, opts?: FileLockOptions): Promise<T>;
9
34
  interface LedgerWriteQueue {
10
35
  append(path: string, line: string): Promise<void>;
11
36
  /**
@@ -27,4 +52,4 @@ interface LedgerWriteQueue {
27
52
  }
28
53
  declare function createLedgerWriteQueue(): LedgerWriteQueue;
29
54
 
30
- export { type AtomicWriteJsonOptions, type AtomicWriteOptions, type LedgerWriteQueue, atomicWriteJson, atomicWriteText, createLedgerWriteQueue };
55
+ export { type AtomicWriteJsonOptions, type AtomicWriteOptions, type FileLockOptions, type LedgerWriteQueue, atomicWriteJson, atomicWriteText, createLedgerWriteQueue, withFileLock };
@@ -1,5 +1,6 @@
1
1
  // src/node/atomic-write.ts
2
- import { appendFile, open, rename, unlink, writeFile } from "fs/promises";
2
+ import { appendFile, mkdir, open, rename, stat, unlink, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
3
4
  function makeTmpSuffix() {
4
5
  const rand = Math.floor(Math.random() * 65535).toString(16).padStart(4, "0");
5
6
  return `.${process.pid}.${Date.now()}.${rand}.tmp`;
@@ -32,6 +33,47 @@ async function atomicWriteJson(path, value, opts) {
32
33
  const content = JSON.stringify(value, null, indent) + "\n";
33
34
  await atomicWriteText(path, content, { fsync: opts?.fsync });
34
35
  }
36
+ function isErrnoException(err) {
37
+ return err instanceof Error && typeof err.code === "string";
38
+ }
39
+ function sleep(ms) {
40
+ return new Promise((resolve) => setTimeout(resolve, ms));
41
+ }
42
+ async function withFileLock(lockPath, fn, opts = {}) {
43
+ const staleMs = opts.staleMs ?? 1e4;
44
+ const retryDelayMs = opts.retryDelayMs ?? 20;
45
+ const maxWaitMs = opts.maxWaitMs ?? 1e4;
46
+ await mkdir(dirname(lockPath), { recursive: true });
47
+ const start = Date.now();
48
+ for (; ; ) {
49
+ let handle;
50
+ try {
51
+ handle = await open(lockPath, "wx");
52
+ } catch (err) {
53
+ if (!isErrnoException(err) || err.code !== "EEXIST") throw err;
54
+ try {
55
+ const st = await stat(lockPath);
56
+ if (Date.now() - st.mtimeMs > staleMs) {
57
+ await unlink(lockPath).catch(() => void 0);
58
+ continue;
59
+ }
60
+ } catch {
61
+ continue;
62
+ }
63
+ if (Date.now() - start > maxWaitMs) {
64
+ throw new Error(`withFileLock: timed out acquiring ${lockPath} after ${maxWaitMs}ms`);
65
+ }
66
+ await sleep(retryDelayMs);
67
+ continue;
68
+ }
69
+ try {
70
+ await handle.close();
71
+ return await fn();
72
+ } finally {
73
+ await unlink(lockPath).catch(() => void 0);
74
+ }
75
+ }
76
+ }
35
77
  function createLedgerWriteQueue() {
36
78
  const chains = /* @__PURE__ */ new Map();
37
79
  async function doAppend(path, line) {
@@ -65,5 +107,6 @@ function createLedgerWriteQueue() {
65
107
  export {
66
108
  atomicWriteJson,
67
109
  atomicWriteText,
68
- createLedgerWriteQueue
110
+ createLedgerWriteQueue,
111
+ withFileLock
69
112
  };
@@ -14,5 +14,36 @@ interface PayloadGuardResult {
14
14
  declare const PAYLOAD_LIMIT_DEFAULT_WARN_BYTES = 16384;
15
15
  declare const PAYLOAD_LIMIT_DEFAULT_HARD_BYTES = 65536;
16
16
  declare function enforcePayloadLimit(serializedPayload: string, opts?: PayloadGuardOptions): PayloadGuardResult;
17
+ interface PayloadBudgetTrimResult<T> {
18
+ /** The retained head of `items` (the ranked tail was dropped to fit). */
19
+ items: T[];
20
+ /** How many trailing items were dropped to fit the hard budget. */
21
+ dropped: number;
22
+ /** Serialized byte size of the envelope built from the retained items. */
23
+ bytes: number;
24
+ /**
25
+ * True when even `minKeep` items still overflow the hard budget — the caller
26
+ * must surface this (a single oversized entry) rather than assume it fit.
27
+ */
28
+ overBudget: boolean;
29
+ }
30
+ /**
31
+ * v2.2 MC4-payload-budget (W1-T4): the byte-budget tail of the unified
32
+ * truncation chain (CJK → BM25 → top_k → payload). Rather than hard-throwing
33
+ * when a response overflows the hard limit, callers trim the LEAST-relevant
34
+ * items off the tail of an already-ranked list until the serialized envelope
35
+ * fits — turning a 413 crash into graceful degradation.
36
+ *
37
+ * `serialize` builds the FULL response envelope from a candidate slice (so the
38
+ * byte count includes warnings / metadata, not just the list). Trimming drops
39
+ * from the END, which is correct ONLY when the list is pre-ranked best-first
40
+ * (plan_context sorts by BM25 before calling this). `minKeep` (default 1)
41
+ * guarantees a non-empty result even under a pathological oversized head; in
42
+ * that case `overBudget` is returned true so the caller can warn instead of
43
+ * silently shipping an over-limit payload.
44
+ */
45
+ declare function trimToPayloadBudget<T>(items: T[], serialize: (items: T[]) => string, opts?: PayloadGuardOptions & {
46
+ minKeep?: number;
47
+ }): PayloadBudgetTrimResult<T>;
17
48
 
18
- export { PAYLOAD_LIMIT_DEFAULT_HARD_BYTES, PAYLOAD_LIMIT_DEFAULT_WARN_BYTES, type PayloadGuardOptions, type PayloadGuardResult, enforcePayloadLimit };
49
+ export { PAYLOAD_LIMIT_DEFAULT_HARD_BYTES, PAYLOAD_LIMIT_DEFAULT_WARN_BYTES, type PayloadBudgetTrimResult, type PayloadGuardOptions, type PayloadGuardResult, enforcePayloadLimit, trimToPayloadBudget };