@fenglimg/fabric-shared 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.
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-QSD4PN4W.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-355LUDLW.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-AFT7DB4P.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",
@@ -592,6 +601,30 @@ var fabricConfigSchema = z6.object({
592
601
  // high-contract-criticality projects. Other strategies (time-based,
593
602
  // token-budget) deferred to rc.35 per plan locked-decisions 2026-05-26.
594
603
  cite_evict_interval: z6.number().int().min(0).optional().default(0),
604
+ // v2.1 ⑤ cite-redesign (P5): recall-based cite-accounting hook config. The
605
+ // rc.34 cite_evict_interval turn-counter above is superseded by the
606
+ // PreToolUse(Edit/Write) recall-aware nudge in cite-policy-evict.cjs; the old
607
+ // key is retained for back-compat (inert now that the hook moved off
608
+ // UserPromptSubmit). `cite_recall_nudge` is the master switch (default true =
609
+ // ON); set false to silence the "改前先 fab_recall" nudge entirely (mirrors
610
+ // the cite_evict_interval=0 opt-out convention). `cite_recall_window_minutes`
611
+ // bounds how far back an in-session fab_recall counts as "informing" the edit
612
+ // (default 30; 0 = unbounded).
613
+ cite_recall_nudge: z6.boolean().optional().default(true),
614
+ cite_recall_window_minutes: z6.number().int().min(0).optional().default(30),
615
+ // F2: glob exemptions for the cite nudge (cite-policy-evict.cjs). Edit paths
616
+ // matching any glob skip the "改前先 fab_recall" nudge — meta/orchestration
617
+ // files (e.g. `.workflow/` scratchpads) are not source the cite policy
618
+ // governs. MERGED with the hook's built-in [".workflow/**"] default; an
619
+ // omitted/empty value keeps just that default. `*` = within a path segment,
620
+ // `**` = across segments.
621
+ cite_nudge_ignore_globs: z6.array(z6.string()).optional(),
622
+ // v2.1 ④ conflict-detection (P4): bm25 content-similarity threshold (0..1)
623
+ // for the knowledge-conflict lint (`fabric doctor --lint-conflicts`). A
624
+ // same-(type,layer) pair whose normalized bm25 similarity reaches this floor
625
+ // is surfaced as a candidate (possible duplicate OR conflict). Conservative
626
+ // default 0.5 — raise to reduce noise, lower to catch looser pairs.
627
+ conflict_lint_similarity_threshold: z6.number().min(0).max(1).optional().default(0.5),
595
628
  // v2.0.0-rc.22 Scope A T3: sliding-window retention (in days) for the
596
629
  // event ledger rotation primitive (`rotateEventLedgerIfNeeded`). Lines
597
630
  // whose `ts` is older than `now - fabric_event_retention_days * 86_400_000`
@@ -634,12 +667,32 @@ var fabricConfigSchema = z6.object({
634
667
  // TRUNCATION_THRESHOLD=12 grouped-render kicks in. Mirrors the rc.7 T7 +
635
668
  // archive_max_* pattern of externalizing previously-hardcoded thresholds.
636
669
  hint_broad_top_k: z6.number().int().min(1).max(50).optional().default(8),
670
+ // v2.2 HK2-degrade (W2-T2): char budget for the rendered SessionStart broad-menu
671
+ // body — the final rung of the degradation ladder after the hint_broad_top_k
672
+ // count slice. Once the rendered entry/group lines exceed this, the tail
673
+ // collapses to a single "N more omitted" marker so a large corpus cannot blow
674
+ // the agent's working memory. Default 2000 (~one screenful); 0 disables the
675
+ // budget. Read by knowledge-hint-broad.cjs via readConfigNumber. Range 0..20000.
676
+ hint_broad_budget_chars: z6.number().int().min(0).max(2e4).optional().default(2e3),
637
677
  // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint Stop
638
678
  // hook nudges. Any signal type listed here is suppressed at emit time across
639
679
  // all sessions (the session-scoped sibling lives in a .fabric/.cache sidecar
640
680
  // written on request). Mirrors the cite_evict_interval=0 opt-out convention —
641
681
  // a knob for an existing surface, not a new feature. Unknown types ignored.
642
682
  hint_dismiss_signals: z6.array(z6.enum(["archive", "review", "import", "maintenance"])).optional(),
683
+ // v2.1 ADJ-NEWN-4: user-override escape hatches for the two strong behavioral
684
+ // policies (cite-before-edit + self-archive). The strong policies can make an
685
+ // agent feel like a "stubborn parrot" (D2 user-in-control red line); these
686
+ // flags let a user durably turn either off via fabric-config.json (or the
687
+ // `fabric config` panel) without editing bootstrap/AGENTS.md. Default true
688
+ // preserves rc.x behavior (policies ON); set false to opt a project out.
689
+ // The bootstrap behavior layer references these so the AGENTS.md rules degrade
690
+ // from "MUST" to "optional" when disabled — a config knob for an existing
691
+ // surface, mirroring the cite_evict_interval=0 / hint_dismiss_signals opt-out
692
+ // convention, NOT a new feature. Wave3 J32 will quantify the friction these
693
+ // relieve; until then they ship as inert-safe opt-outs.
694
+ cite_policy_enabled: z6.boolean().optional().default(true),
695
+ self_archive_policy_enabled: z6.boolean().optional().default(true),
643
696
  // v2.0.0-rc.33 W2-1 (P0-9): TopK upper bound for the narrow PreToolUse hint
644
697
  // emitted by knowledge-hint-narrow.cjs. After filtering to entries whose
645
698
  // `relevance_scope === "narrow"` (rc.27 TASK-005 audit §2.5 fix), the hook
@@ -674,13 +727,25 @@ var fabricConfigSchema = z6.object({
674
727
  // 0..168 (one week).
675
728
  hint_narrow_cooldown_hours: z6.number().int().min(0).max(168).optional().default(0),
676
729
  // v2.0.0-rc.33 W4-B3 (T5 P2): per-maturity inactivity thresholds (days)
677
- // driving orphan_demote. Hardcoded at stable=90/endorsed=30/draft=14 in
730
+ // driving orphan_demote. Hardcoded at proven=90/verified=30/draft=14 in
678
731
  // rc.32; chatty workspaces want them tighter, slow ones want them looser.
679
732
  // Each field optional; absent → defaults inside doctor.ts apply. Ranges
680
733
  // chosen so a typo can't accidentally disable the lint (min 1).
734
+ //
735
+ // v2.2 W3-T5 (F-MATURITY-ENDORSED): the canonical maturity enum is
736
+ // draft/verified/proven (KT-DEC-0005), but these threshold keys historically
737
+ // used the legacy stable/endorsed vocabulary — a config authored with the
738
+ // canonical names could never tune the proven/verified tiers. The canonical
739
+ // keys below are the preferred form; the legacy keys are retained for
740
+ // backward-compat (a config written before this fix keeps working). The
741
+ // loader maps stable→proven / endorsed→verified, canonical taking precedence.
742
+ orphan_demote_proven_days: z6.number().int().min(1).max(3650).optional(),
743
+ orphan_demote_verified_days: z6.number().int().min(1).max(3650).optional(),
744
+ orphan_demote_draft_days: z6.number().int().min(1).max(3650).optional(),
745
+ // Legacy aliases (deprecated; map to proven/verified). Kept so existing
746
+ // configs do not silently lose their tuning.
681
747
  orphan_demote_stable_days: z6.number().int().min(1).max(3650).optional(),
682
748
  orphan_demote_endorsed_days: z6.number().int().min(1).max(3650).optional(),
683
- orphan_demote_draft_days: z6.number().int().min(1).max(3650).optional(),
684
749
  // v2.0.0-rc.33 W4-A3 (T4 P2): per-entry summary truncation length used by
685
750
  // knowledge-hint-{broad,narrow}.cjs. Hard-coded at 80 chars in rc.32 — too
686
751
  // short for entries with parameterized summaries (e.g. "Use bcrypt with
@@ -713,7 +778,49 @@ var fabricConfigSchema = z6.object({
713
778
  // so the server-side per-field reader can validate without re-running the
714
779
  // whole fabricConfigSchema on every plan_context call — that lets a corrupt
715
780
  // unrelated field stay isolated from the hot read path.
716
- selection_token_ttl_ms: selectionTokenTtlMsSchema.optional()
781
+ selection_token_ttl_ms: selectionTokenTtlMsSchema.optional(),
782
+ // v2.2 A-INFRA-3 (W1-T3-TOPK): bound on `fab_plan_context` candidate count,
783
+ // applied after BM25 ranking. Absent → library default (24). See
784
+ // planContextTopKSchema for the range/calibration rationale.
785
+ plan_context_top_k: planContextTopKSchema.optional(),
786
+ // v2.2 C5-budget (W2-T3): layered retrieval budget profile. A single coherent
787
+ // strategy across the injection + MCP layers — `balanced` (default) reproduces
788
+ // the historical per-knob defaults exactly, `conservative` / `generous` scale
789
+ // the whole truncation chain (top_k + payload bytes + injection chars) down /
790
+ // up together. Per-field knobs (plan_context_top_k, mcpPayloadLimits.*,
791
+ // hint_broad_budget_chars) still override the profile when set. See
792
+ // retrieval-budget.ts (resolveRetrievalBudget) for the resolution order.
793
+ retrieval_budget_profile: z6.enum(["conservative", "balanced", "generous"]).optional(),
794
+ // v2.2 C2-vector (W2-T7): OPTIONAL dense-embedding semantic retrieval, layered
795
+ // as a recall supplement after BM25. Default OFF (`--no-embed` is the baseline);
796
+ // requires the operator to install the optional `fastembed` package — absent →
797
+ // text-only fallback. Never grows the default install footprint.
798
+ embed_enabled: z6.boolean().optional().default(false),
799
+ // Weight applied to the 0..1 cosine similarity before it joins the additive
800
+ // score. Capped at 49 — strictly BELOW BM25_WEIGHT (50) — so a perfect vector
801
+ // match (weight × 1) can never outscore a single strong BM25 term match. This
802
+ // ENFORCES the "vectors supplement, never override lexical relevance"
803
+ // invariant in the schema rather than leaving it to a comment (W2-REVIEW codex
804
+ // MED-4). Range 0..49; default 30.
805
+ embed_weight: z6.number().int().min(0).max(49).optional().default(30),
806
+ // v2.1 ③ vector-chinese-model (P3): which fastembed model to load. The prior
807
+ // code pinned fastembed's English default (bge-small-en-v1.5) — wrong for the
808
+ // Chinese-heavy zh-CN-hybrid KB. Values are the fastembed@2.x EmbeddingModel
809
+ // enum strings. Default `fast-bge-small-zh-v1.5` (BGESmallZH): light, fast,
810
+ // Chinese-capable (bm25 already covers English/code tokens; the vector term
811
+ // supplements Chinese semantics). `fast-multilingual-e5-large` (MLE5Large) is
812
+ // available for full multilingual recall at a ~1GB download + slower CPU cost.
813
+ // (V1 research: fastembed@2.1.0 has NO multilingual-e5-SMALL — the originally
814
+ // planned pin — so bge-small-zh is the light Chinese choice.)
815
+ embed_model: z6.enum([
816
+ "fast-bge-small-zh-v1.5",
817
+ "fast-multilingual-e5-large",
818
+ "fast-bge-small-en-v1.5",
819
+ "fast-bge-small-en",
820
+ "fast-bge-base-en-v1.5",
821
+ "fast-bge-base-en",
822
+ "fast-all-MiniLM-L6-v2"
823
+ ]).optional().default("fast-bge-small-zh-v1.5")
717
824
  });
718
825
 
719
826
  // src/schemas/fabric-config-introspect.ts
@@ -1226,9 +1333,23 @@ function hasScriptExtension(name) {
1226
1333
  const dot = name.lastIndexOf(".");
1227
1334
  return dot !== -1 && SCRIPT_EXTENSIONS.has(name.slice(dot).toLowerCase());
1228
1335
  }
1229
- function findStoreExecutableViolations(absDir) {
1336
+ var STORE_SCAN_MAX_DEPTH = 32;
1337
+ var STORE_SCAN_MAX_ENTRIES = 1e5;
1338
+ function findStoreExecutableViolations(absDir, options = {}) {
1339
+ const maxDepth = options.maxDepth ?? STORE_SCAN_MAX_DEPTH;
1340
+ const maxEntries = options.maxEntries ?? STORE_SCAN_MAX_ENTRIES;
1230
1341
  const violations = [];
1231
- const walk = (dir, rel) => {
1342
+ let entriesScanned = 0;
1343
+ let bounded = false;
1344
+ const walk = (dir, rel, depth) => {
1345
+ if (bounded) {
1346
+ return;
1347
+ }
1348
+ if (depth > maxDepth) {
1349
+ violations.push(`<scan-bounded: depth > ${maxDepth} at ${rel === "" ? "." : rel}>`);
1350
+ bounded = true;
1351
+ return;
1352
+ }
1232
1353
  let entries;
1233
1354
  try {
1234
1355
  entries = readdirSync(dir);
@@ -1236,9 +1357,18 @@ function findStoreExecutableViolations(absDir) {
1236
1357
  return;
1237
1358
  }
1238
1359
  for (const entry of entries) {
1360
+ if (bounded) {
1361
+ return;
1362
+ }
1239
1363
  if (rel === "" && entry === ".git") {
1240
1364
  continue;
1241
1365
  }
1366
+ entriesScanned += 1;
1367
+ if (entriesScanned > maxEntries) {
1368
+ violations.push(`<scan-bounded: entries > ${maxEntries}>`);
1369
+ bounded = true;
1370
+ return;
1371
+ }
1242
1372
  const abs = join(dir, entry);
1243
1373
  const relPath = rel === "" ? entry : `${rel}/${entry}`;
1244
1374
  let stat;
@@ -1248,7 +1378,7 @@ function findStoreExecutableViolations(absDir) {
1248
1378
  continue;
1249
1379
  }
1250
1380
  if (stat.isDirectory()) {
1251
- walk(abs, relPath);
1381
+ walk(abs, relPath, depth + 1);
1252
1382
  continue;
1253
1383
  }
1254
1384
  if ((stat.mode & 73) !== 0 || hasScriptExtension(entry)) {
@@ -1256,7 +1386,7 @@ function findStoreExecutableViolations(absDir) {
1256
1386
  }
1257
1387
  }
1258
1388
  };
1259
- walk(absDir, "");
1389
+ walk(absDir, "", 0);
1260
1390
  return violations;
1261
1391
  }
1262
1392
 
@@ -1427,6 +1557,76 @@ function aggregatePendingAcrossStores(stores) {
1427
1557
  );
1428
1558
  }
1429
1559
 
1560
+ // src/store/global-config-io.ts
1561
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1562
+ import { homedir } from "os";
1563
+ import { join as join3 } from "path";
1564
+ function resolveGlobalRoot() {
1565
+ return join3(process.env.FABRIC_HOME ?? homedir(), ".fabric");
1566
+ }
1567
+ function globalConfigPath(globalRoot = resolveGlobalRoot()) {
1568
+ return join3(globalRoot, "fabric-global.json");
1569
+ }
1570
+ function loadGlobalConfig(globalRoot = resolveGlobalRoot()) {
1571
+ const path = globalConfigPath(globalRoot);
1572
+ if (!existsSync3(path)) {
1573
+ return null;
1574
+ }
1575
+ return globalConfigSchema.parse(JSON.parse(readFileSync2(path, "utf8")));
1576
+ }
1577
+ function saveGlobalConfig(config, globalRoot = resolveGlobalRoot()) {
1578
+ const validated = globalConfigSchema.parse(config);
1579
+ mkdirSync2(globalRoot, { recursive: true });
1580
+ writeFileSync2(globalConfigPath(globalRoot), `${JSON.stringify(validated, null, 2)}
1581
+ `, "utf8");
1582
+ }
1583
+
1584
+ // src/store/project-config-io.ts
1585
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1586
+ import { join as join4 } from "path";
1587
+ function projectConfigPath(projectRoot) {
1588
+ return join4(projectRoot, ".fabric", "fabric-config.json");
1589
+ }
1590
+ function loadProjectConfig(projectRoot) {
1591
+ const path = projectConfigPath(projectRoot);
1592
+ if (!existsSync4(path)) {
1593
+ return null;
1594
+ }
1595
+ return fabricConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
1596
+ }
1597
+ function saveProjectConfig(config, projectRoot) {
1598
+ const validated = fabricConfigSchema.parse(config);
1599
+ mkdirSync3(join4(projectRoot, ".fabric"), { recursive: true });
1600
+ writeFileSync3(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
1601
+ `, "utf8");
1602
+ }
1603
+
1604
+ // src/store/resolve-input.ts
1605
+ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1606
+ const global = loadGlobalConfig(globalRoot);
1607
+ if (global === null) {
1608
+ return null;
1609
+ }
1610
+ const project = loadProjectConfig(projectRoot);
1611
+ return {
1612
+ uid: global.uid,
1613
+ mountedStores: global.stores.map((s) => ({
1614
+ store_uuid: s.store_uuid,
1615
+ alias: s.alias,
1616
+ ...s.remote === void 0 ? {} : { remote: s.remote },
1617
+ writable: s.writable ?? true,
1618
+ personal: s.personal ?? false
1619
+ })),
1620
+ requiredStores: (project?.required_stores ?? []).map(
1621
+ (r) => ({
1622
+ id: r.id,
1623
+ ...r.suggested_remote === void 0 ? {} : { suggested_remote: r.suggested_remote }
1624
+ })
1625
+ ),
1626
+ ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store }
1627
+ };
1628
+ }
1629
+
1430
1630
  // src/store/secret-scan.ts
1431
1631
  var SECRET_RULES = [
1432
1632
  { rule: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ },
@@ -1463,6 +1663,51 @@ function redactSecrets(content) {
1463
1663
  }
1464
1664
  return out;
1465
1665
  }
1666
+ function scrubRemoteUrl(remote) {
1667
+ const httpStripped = remote.replace(/^(https?:\/\/)[^/@]+@/i, "$1");
1668
+ if (httpStripped !== remote) {
1669
+ return httpStripped;
1670
+ }
1671
+ return remote.replace(
1672
+ /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/@]*:[^/@]*@/,
1673
+ "$1"
1674
+ );
1675
+ }
1676
+
1677
+ // src/scanner/scan-recommendations.ts
1678
+ function buildScanRecommendations(input, t) {
1679
+ const recs = [];
1680
+ if (input.hasExistingFabric === false) {
1681
+ recs.push(t("scan.rec.install"));
1682
+ }
1683
+ if (input.readmeOk === false) {
1684
+ recs.push(t("scan.rec.readme"));
1685
+ }
1686
+ if (input.hasContributing === false) {
1687
+ recs.push(t("scan.rec.contributing"));
1688
+ }
1689
+ switch (input.frameworkKind) {
1690
+ case "cocos-creator":
1691
+ recs.push(t("scan.rec.cocos.lifecycle"));
1692
+ recs.push(t("scan.rec.cocos.human-protect"));
1693
+ if (input.hasMeta === true) {
1694
+ recs.push(t("scan.rec.cocos.meta-lock"));
1695
+ }
1696
+ break;
1697
+ case "next":
1698
+ recs.push(t("scan.rec.next"));
1699
+ break;
1700
+ case "vite":
1701
+ recs.push(t("scan.rec.vite"));
1702
+ break;
1703
+ case "unknown":
1704
+ recs.push(t("scan.rec.unknown"));
1705
+ break;
1706
+ default:
1707
+ recs.push(t("scan.rec.generic", { kind: input.frameworkKind }));
1708
+ }
1709
+ return recs;
1710
+ }
1466
1711
 
1467
1712
  // src/store/cross-store-lint.ts
1468
1713
  function lintCrossStoreReferences(input) {
@@ -1602,10 +1847,24 @@ var resolvedBindingsSnapshotSchema = z14.object({
1602
1847
  }).strict();
1603
1848
 
1604
1849
  // src/store/bindings.ts
1605
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1606
- import { join as join3 } from "path";
1850
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1851
+ import { join as join5, resolve, sep } from "path";
1852
+ var SAFE_PROJECT_ID = /^[A-Za-z0-9._-]+$/;
1853
+ function assertSafeProjectId(projectId) {
1854
+ if (!SAFE_PROJECT_ID.test(projectId) || projectId.includes("..")) {
1855
+ throw new Error(
1856
+ `bindingsSnapshotPath: refusing unsafe project_id ${JSON.stringify(projectId)} (must match ${SAFE_PROJECT_ID} and contain no "..")`
1857
+ );
1858
+ }
1859
+ }
1607
1860
  function bindingsSnapshotPath(globalRoot, projectId) {
1608
- return join3(globalRoot, GLOBAL_STATE_DIR, GLOBAL_BINDINGS_DIR, `${projectId}_resolved.json`);
1861
+ assertSafeProjectId(projectId);
1862
+ const bindingsDir = resolve(join5(globalRoot, GLOBAL_STATE_DIR, GLOBAL_BINDINGS_DIR));
1863
+ const path = resolve(join5(bindingsDir, `${projectId}_resolved.json`));
1864
+ if (path !== bindingsDir && !path.startsWith(bindingsDir + sep)) {
1865
+ throw new Error(`bindingsSnapshotPath: resolved path escapes bindings dir for ${JSON.stringify(projectId)}`);
1866
+ }
1867
+ return path;
1609
1868
  }
1610
1869
  function writeBindingsSnapshot(options) {
1611
1870
  const resolver = createStoreResolver();
@@ -1619,18 +1878,18 @@ function writeBindingsSnapshot(options) {
1619
1878
  write_target: target
1620
1879
  });
1621
1880
  const path = bindingsSnapshotPath(options.globalRoot, options.projectId);
1622
- mkdirSync2(join3(path, ".."), { recursive: true });
1623
- writeFileSync2(path, `${JSON.stringify(snapshot, null, 2)}
1881
+ mkdirSync4(join5(path, ".."), { recursive: true });
1882
+ writeFileSync4(path, `${JSON.stringify(snapshot, null, 2)}
1624
1883
  `, "utf8");
1625
1884
  return snapshot;
1626
1885
  }
1627
1886
  function readBindingsSnapshot(globalRoot, projectId) {
1628
1887
  const path = bindingsSnapshotPath(globalRoot, projectId);
1629
- if (!existsSync3(path)) {
1888
+ if (!existsSync5(path)) {
1630
1889
  return null;
1631
1890
  }
1632
1891
  try {
1633
- const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync2(path, "utf8")));
1892
+ const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync4(path, "utf8")));
1634
1893
  return parsed.success ? parsed.data : null;
1635
1894
  } catch {
1636
1895
  return null;
@@ -1650,6 +1909,8 @@ function addMountedStore(config, store) {
1650
1909
  `alias '${store.alias}' already mounts store ${aliasClash.store_uuid}; choose another alias`
1651
1910
  );
1652
1911
  }
1912
+ const sanitized = store.remote === void 0 ? store : { ...store, remote: scrubRemoteUrl(store.remote) };
1913
+ store = sanitized;
1653
1914
  const existing = config.stores.find((s) => s.store_uuid === store.store_uuid);
1654
1915
  const stores = existing === void 0 ? [...config.stores, store] : config.stores.map((s) => s.store_uuid === store.store_uuid ? store : s);
1655
1916
  return { ...config, stores };
@@ -1665,7 +1926,8 @@ function detachMountedStore(config, alias) {
1665
1926
  };
1666
1927
  }
1667
1928
  function bindRequiredStore(required, entry) {
1668
- return required.some((r) => r.id === entry.id) ? required.map((r) => r.id === entry.id ? entry : r) : [...required, entry];
1929
+ const safeEntry = entry.suggested_remote === void 0 ? entry : { ...entry, suggested_remote: scrubRemoteUrl(entry.suggested_remote) };
1930
+ return required.some((r) => r.id === safeEntry.id) ? required.map((r) => r.id === safeEntry.id ? safeEntry : r) : [...required, safeEntry];
1669
1931
  }
1670
1932
  function explainStore(config, alias) {
1671
1933
  const store = findMountedStore(config, alias);
@@ -2318,6 +2580,15 @@ var assistantTurnObservedEventSchema = z18.object({
2318
2580
  skip_reason: z18.string().nullable()
2319
2581
  })
2320
2582
  ).default([]),
2583
+ // lifecycle-refactor W3-T4 (§2 store 轴 / store-qualified 观测): per-cite store
2584
+ // qualifier, index-aligned with cite_ids. Mirrors the cite-line-parser's
2585
+ // `cite_stores` output (`<alias-or-uuid>:<id>` → the qualifier; a bare id →
2586
+ // null). Persists the store provenance the parser already extracts so
2587
+ // doctor --cite-coverage can break compliance down per store WITHOUT joining
2588
+ // against the store registry. Additive `.optional()` (NOT `.default([])`) so
2589
+ // existing inline event constructors stay valid without supplying it — pre-W3-T4
2590
+ // events parse with the field absent and bucket under the project-local default.
2591
+ cite_stores: z18.array(z18.string().nullable()).optional(),
2321
2592
  client: z18.enum(["cc", "codex", "cursor"]).optional(),
2322
2593
  turn_id: z18.string(),
2323
2594
  envelope_index: z18.number().int().nonnegative().optional(),
@@ -2372,6 +2643,117 @@ var sessionArchiveAttemptedEventSchema = z18.object({
2372
2643
  candidates_proposed: z18.number().int().nonnegative().default(0),
2373
2644
  knowledge_proposed_ids: z18.array(z18.string()).default([])
2374
2645
  });
2646
+ var hookSurfaceEmittedEventSchema = z18.object({
2647
+ ...eventLedgerEnvelopeSchema,
2648
+ event_type: z18.literal("hook_surface_emitted"),
2649
+ hook_name: z18.string(),
2650
+ client: z18.enum(["cc", "codex", "cursor"]),
2651
+ target_channel: z18.string(),
2652
+ rendered_ids: z18.array(z18.string()),
2653
+ delivery_status: z18.enum(["delivered", "suppressed", "error"]),
2654
+ suppression_reason: z18.string().optional()
2655
+ });
2656
+ var hookSignalEmittedEventSchema = z18.object({
2657
+ ...eventLedgerEnvelopeSchema,
2658
+ event_type: z18.literal("hook_signal_emitted"),
2659
+ signal_type: z18.enum(["archive", "review", "maintenance", "other"]),
2660
+ threshold: z18.number(),
2661
+ actual_value: z18.number(),
2662
+ fired: z18.boolean()
2663
+ });
2664
+ var mcpStdioTraceEventSchema = z18.object({
2665
+ ...eventLedgerEnvelopeSchema,
2666
+ event_type: z18.literal("mcp_stdio_trace"),
2667
+ tool_name: z18.string(),
2668
+ request_id: z18.string(),
2669
+ duration_ms: z18.number().nonnegative(),
2670
+ status: z18.enum(["ok", "error"]),
2671
+ payload_bytes_in: z18.number().int().nonnegative(),
2672
+ payload_bytes_out: z18.number().int().nonnegative(),
2673
+ error_code: z18.string().optional()
2674
+ });
2675
+ var payloadGuardObservedEventSchema = z18.object({
2676
+ ...eventLedgerEnvelopeSchema,
2677
+ event_type: z18.literal("payload_guard_observed"),
2678
+ tool_name: z18.string(),
2679
+ path_count: z18.number().int().nonnegative(),
2680
+ tokens_estimated: z18.number().int().nonnegative(),
2681
+ truncated: z18.boolean(),
2682
+ cap: z18.number().int().positive()
2683
+ });
2684
+ var skillInvocationStartedEventSchema = z18.object({
2685
+ ...eventLedgerEnvelopeSchema,
2686
+ event_type: z18.literal("skill_invocation_started"),
2687
+ skill_name: z18.string(),
2688
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2689
+ entry_point: z18.string()
2690
+ });
2691
+ var skillInvocationCompletedEventSchema = z18.object({
2692
+ ...eventLedgerEnvelopeSchema,
2693
+ event_type: z18.literal("skill_invocation_completed"),
2694
+ skill_name: z18.string(),
2695
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2696
+ entry_point: z18.string(),
2697
+ outcome: z18.enum(["completed", "aborted", "error", "no_op"]),
2698
+ elapsed_ms: z18.number().nonnegative().optional()
2699
+ });
2700
+ var skillPhaseTransitionEventSchema = z18.object({
2701
+ ...eventLedgerEnvelopeSchema,
2702
+ event_type: z18.literal("skill_phase_transition"),
2703
+ skill_name: z18.string(),
2704
+ phase: z18.string(),
2705
+ status: z18.enum(["entered", "completed", "skipped", "failed"]),
2706
+ checkpoint: z18.string().optional(),
2707
+ elapsed_ms: z18.number().nonnegative().optional()
2708
+ });
2709
+ var skillTriggerCandidateEventSchema = z18.object({
2710
+ ...eventLedgerEnvelopeSchema,
2711
+ event_type: z18.literal("skill_trigger_candidate"),
2712
+ skill_name: z18.string(),
2713
+ trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2714
+ signal: z18.string(),
2715
+ invoked: z18.boolean()
2716
+ });
2717
+ var llmJudgeRunEventSchema = z18.object({
2718
+ ...eventLedgerEnvelopeSchema,
2719
+ event_type: z18.literal("llm_judge_run"),
2720
+ prompt: z18.string(),
2721
+ version: z18.string(),
2722
+ model: z18.string(),
2723
+ input_trace_id: z18.string(),
2724
+ score: z18.number(),
2725
+ rationale: z18.string()
2726
+ });
2727
+ var clientCapabilitySnapshotEventSchema = z18.object({
2728
+ ...eventLedgerEnvelopeSchema,
2729
+ event_type: z18.literal("client_capability_snapshot"),
2730
+ client: z18.enum(["cc", "codex", "cursor"]),
2731
+ capabilities: z18.array(z18.string()),
2732
+ version: z18.string()
2733
+ });
2734
+ var sessionEndedEventSchema = z18.object({
2735
+ ...eventLedgerEnvelopeSchema,
2736
+ event_type: z18.literal("session_ended")
2737
+ });
2738
+ var fileMutatedEventSchema = z18.object({
2739
+ ...eventLedgerEnvelopeSchema,
2740
+ event_type: z18.literal("file_mutated"),
2741
+ path: z18.string(),
2742
+ tool_call_id: z18.string(),
2743
+ tool_name: z18.string().optional(),
2744
+ source_event_id: z18.string().optional(),
2745
+ store_id: z18.string().optional()
2746
+ });
2747
+ var precompactObservedEventSchema = z18.object({
2748
+ ...eventLedgerEnvelopeSchema,
2749
+ event_type: z18.literal("precompact_observed")
2750
+ });
2751
+ var graphEdgeCandidateRequestedEventSchema = z18.object({
2752
+ ...eventLedgerEnvelopeSchema,
2753
+ event_type: z18.literal("graph_edge_candidate_requested"),
2754
+ stable_id: z18.string(),
2755
+ store: z18.string().optional()
2756
+ });
2375
2757
  var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2376
2758
  knowledgeContextPlannedEventSchema,
2377
2759
  knowledgeSelectionEventSchema,
@@ -2453,8 +2835,88 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2453
2835
  // fabric-archive skill at the end of every invocation. Drives Phase 0.0
2454
2836
  // cross-session digest, outcome-based rescan filter (skips user_dismissed),
2455
2837
  // covered_through_ts watermark, and `fabric doctor --archive-history`.
2456
- sessionArchiveAttemptedEventSchema
2838
+ sessionArchiveAttemptedEventSchema,
2839
+ // v2.1 GATE-INSTR (NEW-N-3): 9 interaction-axis instrumentation events.
2840
+ hookSurfaceEmittedEventSchema,
2841
+ hookSignalEmittedEventSchema,
2842
+ mcpStdioTraceEventSchema,
2843
+ payloadGuardObservedEventSchema,
2844
+ skillInvocationStartedEventSchema,
2845
+ skillInvocationCompletedEventSchema,
2846
+ skillPhaseTransitionEventSchema,
2847
+ skillTriggerCandidateEventSchema,
2848
+ llmJudgeRunEventSchema,
2849
+ clientCapabilitySnapshotEventSchema,
2850
+ // lifecycle-refactor Wave 2 — dormant-hook activation markers.
2851
+ sessionEndedEventSchema,
2852
+ fileMutatedEventSchema,
2853
+ precompactObservedEventSchema,
2854
+ graphEdgeCandidateRequestedEventSchema
2457
2855
  ]);
2856
+
2857
+ // src/text-tokenize.ts
2858
+ var CJK_CLASS = "\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af";
2859
+ var RUN_RE = new RegExp(`[a-z0-9]+|[${CJK_CLASS}]+`, "gu");
2860
+ var CJK_FIRST_RE = new RegExp(`[${CJK_CLASS}]`, "u");
2861
+ function tokenize(text) {
2862
+ if (text.length === 0) {
2863
+ return [];
2864
+ }
2865
+ const tokens = [];
2866
+ const lowered = text.toLowerCase();
2867
+ RUN_RE.lastIndex = 0;
2868
+ let match;
2869
+ while ((match = RUN_RE.exec(lowered)) !== null) {
2870
+ const run = match[0];
2871
+ if (CJK_FIRST_RE.test(run[0])) {
2872
+ if (run.length === 1) {
2873
+ tokens.push(run);
2874
+ } else {
2875
+ for (let i = 0; i < run.length - 1; i += 1) {
2876
+ tokens.push(run.slice(i, i + 2));
2877
+ }
2878
+ }
2879
+ } else {
2880
+ tokens.push(run);
2881
+ }
2882
+ }
2883
+ return tokens;
2884
+ }
2885
+
2886
+ // src/retrieval-budget.ts
2887
+ var PROFILES = {
2888
+ conservative: {
2889
+ topK: 12,
2890
+ payloadWarnBytes: 8192,
2891
+ payloadHardBytes: 32768,
2892
+ injectionChars: 1e3
2893
+ },
2894
+ balanced: {
2895
+ topK: 24,
2896
+ payloadWarnBytes: 16384,
2897
+ payloadHardBytes: 65536,
2898
+ injectionChars: 2e3
2899
+ },
2900
+ generous: {
2901
+ topK: 48,
2902
+ payloadWarnBytes: 32768,
2903
+ payloadHardBytes: 131072,
2904
+ injectionChars: 4e3
2905
+ }
2906
+ };
2907
+ var DEFAULT_RETRIEVAL_BUDGET_PROFILE = "balanced";
2908
+ function resolveRetrievalBudget(overrides) {
2909
+ const base = PROFILES[overrides?.profile ?? DEFAULT_RETRIEVAL_BUDGET_PROFILE];
2910
+ return {
2911
+ topK: overrides?.topK ?? base.topK,
2912
+ payloadWarnBytes: overrides?.payloadWarnBytes ?? base.payloadWarnBytes,
2913
+ payloadHardBytes: overrides?.payloadHardBytes ?? base.payloadHardBytes,
2914
+ injectionChars: overrides?.injectionChars ?? base.injectionChars
2915
+ };
2916
+ }
2917
+ function retrievalBudgetProfile(profile) {
2918
+ return PROFILES[profile];
2919
+ }
2458
2920
  export {
2459
2921
  AGENTS_META_IDENTITY_SOURCES,
2460
2922
  AGENTS_META_LAYERS,
@@ -2464,6 +2926,7 @@ export {
2464
2926
  BOOTSTRAP_MARKER_BEGIN,
2465
2927
  BOOTSTRAP_MARKER_END,
2466
2928
  BOOTSTRAP_REGEX,
2929
+ DEFAULT_RETRIEVAL_BUDGET_PROFILE,
2467
2930
  FabExtractKnowledgeInputSchema,
2468
2931
  FabExtractKnowledgeInputShape,
2469
2932
  FabExtractKnowledgeOutputSchema,
@@ -2528,6 +2991,8 @@ export {
2528
2991
  bootstrapMarkerMigratedEventSchema,
2529
2992
  buildDebugBundle,
2530
2993
  buildFailureTrace,
2994
+ buildScanRecommendations,
2995
+ buildStoreResolveInput,
2531
2996
  candidateFileEntrySchema,
2532
2997
  citeContractMetricsSchema,
2533
2998
  citeContractPolicyActivatedEventSchema,
@@ -2536,6 +3001,7 @@ export {
2536
3001
  citePolicyActivatedEventSchema,
2537
3002
  claudeHookPathMigratedEventSchema,
2538
3003
  claudeSkillPathMigratedEventSchema,
3004
+ clientCapabilitySnapshotEventSchema,
2539
3005
  clientPathsSchema,
2540
3006
  codexSkillPathMigratedEventSchema,
2541
3007
  createProjectRootResolver,
@@ -2564,6 +3030,7 @@ export {
2564
3030
  fabricConfigSchema,
2565
3031
  fabricEventSchema,
2566
3032
  fabricLanguageSchema,
3033
+ fileMutatedEventSchema,
2567
3034
  findMountedStore,
2568
3035
  findStoreExecutableViolations,
2569
3036
  forensicAssertionCoverageSchema,
@@ -2580,10 +3047,14 @@ export {
2580
3047
  formatKnowledgeId,
2581
3048
  getPanelFieldByKey,
2582
3049
  getPanelFields,
3050
+ globalConfigPath,
2583
3051
  globalConfigSchema,
2584
3052
  globalRefSchema,
3053
+ graphEdgeCandidateRequestedEventSchema,
2585
3054
  hasSecrets,
2586
3055
  historyStateQuerySchema,
3056
+ hookSignalEmittedEventSchema,
3057
+ hookSurfaceEmittedEventSchema,
2587
3058
  humanLedgerEntrySchema,
2588
3059
  humanLockApproveRequestSchema,
2589
3060
  humanLockEntrySchema,
@@ -2637,12 +3108,16 @@ export {
2637
3108
  ledgerSourceSchema,
2638
3109
  lintCrossStoreReferences,
2639
3110
  listStoreKnowledge,
3111
+ llmJudgeRunEventSchema,
3112
+ loadGlobalConfig,
3113
+ loadProjectConfig,
2640
3114
  localKnowledgeIdSchema,
2641
3115
  lockApprovedEventSchema,
2642
3116
  lockDriftEventSchema,
2643
3117
  mcpConfigMigratedEventSchema,
2644
3118
  mcpEventLedgerEventSchema,
2645
3119
  mcpPayloadLimitsSchema,
3120
+ mcpStdioTraceEventSchema,
2646
3121
  metaReconciledEventSchema,
2647
3122
  metaReconciledOnStartupEventSchema,
2648
3123
  metaUpdatedEventSchema,
@@ -2658,12 +3133,16 @@ export {
2658
3133
  parseCiteLine,
2659
3134
  parseGlobalRef,
2660
3135
  parseKnowledgeId,
3136
+ payloadGuardObservedEventSchema,
2661
3137
  pendingAutoArchivedEventSchema,
2662
3138
  planContextAnnotations,
2663
3139
  planContextHintNarrowEntrySchema,
2664
3140
  planContextHintOutputSchema,
2665
3141
  planContextInputSchema,
2666
3142
  planContextOutputSchema,
3143
+ planContextTopKSchema,
3144
+ precompactObservedEventSchema,
3145
+ projectConfigPath,
2667
3146
  projectRootGoldenCaseSchema,
2668
3147
  projectRootGoldenFileSchema,
2669
3148
  projectRootResolutionSchema,
@@ -2685,16 +3164,27 @@ export {
2685
3164
  requiredStoreEntrySchema,
2686
3165
  resolveCandidates,
2687
3166
  resolveFabricLocale,
3167
+ resolveGlobalRoot,
3168
+ resolveRetrievalBudget,
2688
3169
  resolveStoreQualifiedId,
2689
3170
  resolvedBindingsSnapshotSchema,
3171
+ retrievalBudgetProfile,
2690
3172
  ruleDescriptionIndexItemSchema,
2691
3173
  ruleDescriptionSchema,
3174
+ saveGlobalConfig,
3175
+ saveProjectConfig,
2692
3176
  scanForSecrets,
2693
3177
  scopeCoordinateSchema,
2694
3178
  scopeRoot,
3179
+ scrubRemoteUrl,
2695
3180
  selectionTokenTtlMsSchema,
2696
3181
  serveLockClearedEventSchema,
2697
3182
  sessionArchiveAttemptedEventSchema,
3183
+ sessionEndedEventSchema,
3184
+ skillInvocationCompletedEventSchema,
3185
+ skillInvocationStartedEventSchema,
3186
+ skillPhaseTransitionEventSchema,
3187
+ skillTriggerCandidateEventSchema,
2698
3188
  storeAwareEntrySchema,
2699
3189
  storeCountersSchema,
2700
3190
  storeIdentitySchema,
@@ -2706,6 +3196,7 @@ export {
2706
3196
  storeResolverWarningSchema,
2707
3197
  storeUuidSchema,
2708
3198
  structuredWarningSchema,
3199
+ tokenize,
2709
3200
  uidSchema,
2710
3201
  withDerivedAgentsMetaNodeDefaults,
2711
3202
  writeBindingsSnapshot,