@fenglimg/fabric-shared 2.3.0-rc.1 → 2.3.0-rc.10

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
@@ -7,7 +7,7 @@ import {
7
7
  BOOTSTRAP_REGEX,
8
8
  matchBootstrapCanonicalLocale,
9
9
  resolveBootstrapCanonical
10
- } from "./chunk-SQKWD7X6.js";
10
+ } from "./chunk-KFFBQRL5.js";
11
11
  import "./chunk-LXNCAKJZ.js";
12
12
  import {
13
13
  PROTECTED_TOKENS,
@@ -16,7 +16,7 @@ import {
16
16
  enMessages,
17
17
  resolveFabricLocale,
18
18
  zhCNMessages
19
- } from "./chunk-IFMFEX3V.js";
19
+ } from "./chunk-XRX6RVZY.js";
20
20
  import {
21
21
  GLOBAL_BINDINGS_DIR,
22
22
  GLOBAL_STATE_DIR,
@@ -51,7 +51,7 @@ import {
51
51
  storeRelativePath,
52
52
  storeRelativePathForMount,
53
53
  storeUuidSchema
54
- } from "./chunk-2GLIAZ5M.js";
54
+ } from "./chunk-ASS2KBB7.js";
55
55
  import {
56
56
  atomicWriteJson,
57
57
  withFileLock
@@ -116,7 +116,7 @@ import {
116
116
  scopeCoordinateSchema,
117
117
  scopeRoot,
118
118
  structuredWarningSchema
119
- } from "./chunk-D7HUHWBI.js";
119
+ } from "./chunk-TV264D7E.js";
120
120
 
121
121
  // src/schemas/agents-meta.ts
122
122
  import { z } from "zod";
@@ -138,7 +138,6 @@ var ruleDescriptionSchema = z.object({
138
138
  tech_stack: z.array(z.string()),
139
139
  impact: z.array(z.string()),
140
140
  must_read_if: z.string(),
141
- entities: z.array(z.string()).optional(),
142
141
  // v2.0 knowledge entry fields (TASK-002 schemas). All optional for backward compat.
143
142
  id: z.string().optional(),
144
143
  // rc.31 NEW-1: forward-compat for legacy on-disk agents.meta.json carrying
@@ -151,8 +150,8 @@ var ruleDescriptionSchema = z.object({
151
150
  z.enum(["models", "decisions", "guidelines", "pitfalls", "processes"])
152
151
  ).optional(),
153
152
  maturity: z.enum(["draft", "verified", "proven"]).optional(),
154
- knowledge_layer: z.enum(["personal", "team"]).optional(),
155
- layer_reason: z.string().optional(),
153
+ // W4/Track1: `knowledge_layer` removed — an entry's layer is derived from its
154
+ // stable_id prefix (KP-→personal, else team; KT-DEC-0004), never a field.
156
155
  created_at: z.string().optional(),
157
156
  // v2/rc.2: flat flow-style YAML array; populated by init-scan from forensic tech stack and editable by user. Used by rc.3 review skill for tag-filter search.
158
157
  tags: z.array(z.string()).default([]).optional(),
@@ -355,6 +354,32 @@ var humanLockFileSchema = z4.object({
355
354
 
356
355
  // src/schemas/fabric-config.ts
357
356
  import { z as z5 } from "zod";
357
+ function isNonPersonalRequiredStore(entry) {
358
+ return entry.id !== PERSONAL_STORE_SENTINEL;
359
+ }
360
+ function refineMaxOneTeamStore(entries, ctx) {
361
+ const teamCount = entries.filter(isNonPersonalRequiredStore).length;
362
+ if (teamCount > 1) {
363
+ ctx.addIssue({
364
+ code: z5.ZodIssueCode.custom,
365
+ message: `a project may bind at most one team store (found ${teamCount}); run \`fabric install\` to migrate to the single team slot`
366
+ });
367
+ }
368
+ }
369
+ function migrateRequiredStores(config) {
370
+ const declared = config.required_stores;
371
+ if (declared === void 0) {
372
+ return config;
373
+ }
374
+ const team = declared.filter(isNonPersonalRequiredStore);
375
+ if (team.length <= 1) {
376
+ return config;
377
+ }
378
+ const personal = declared.filter((entry) => !isNonPersonalRequiredStore(entry));
379
+ const active = config.active_write_store;
380
+ const kept = (active !== void 0 ? team.find((entry) => entry.id === active) : void 0) ?? team[0];
381
+ return { ...config, required_stores: [...personal, kept] };
382
+ }
358
383
  var auditModeSchema = z5.enum(["strict", "warn", "off"]);
359
384
  var clientPathsSchema = z5.object({
360
385
  claudeCodeCLI: z5.string().optional(),
@@ -401,7 +426,9 @@ var fabricConfigSchema = z5.object({
401
426
  // `$personal` sentinel). Drives the read-set (required_stores ∪ implicit
402
427
  // personal, S11/S54) and `clone`'s missing-store onboarding (S51). Optional
403
428
  // + absent → read-set is just the implicit personal store.
404
- required_stores: z5.array(requiredStoreEntrySchema).optional(),
429
+ // W2 dual-slot (TASK-002 / R6): at most ONE non-personal (team-type) store —
430
+ // the two-slot model. >1 fails parse and is migrated by `migrateRequiredStores`.
431
+ required_stores: z5.array(requiredStoreEntrySchema).superRefine(refineMaxOneTeamStore).optional(),
405
432
  // v2.1.0-rc.1 P3 (S60 / `store switch-write`): alias of the store that
406
433
  // non-personal-scope writes land in for this project. Set by
407
434
  // `fabric store switch-write <alias>`; consumed as the resolver's
@@ -584,12 +611,37 @@ var fabricConfigSchema = z5.object({
584
611
  // banner silently truncates. Default 50; range 20..500 (read inline by
585
612
  // knowledge-hint-broad.cjs#readBroadIndexBackstop with the same bounds).
586
613
  broad_index_backstop: z5.number().int().min(20).max(500).optional().default(50),
587
- // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint Stop
588
- // hook nudges. Any signal type listed here is suppressed at emit time across
614
+ // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint hook
615
+ // nudges. Any signal type listed here is suppressed at emit time across
589
616
  // all sessions (the session-scoped sibling lives in a .fabric/.cache sidecar
590
617
  // written on request). Mirrors the cite_evict_interval=0 opt-out convention —
591
618
  // a knob for an existing surface, not a new feature. Unknown types ignored.
592
- hint_dismiss_signals: z5.array(z5.enum(["archive", "review", "import", "maintenance"])).optional(),
619
+ //
620
+ // TASK-005 (grill G5 / C-004 "全 nudge MUST 可 dismiss"): the enum now spans
621
+ // ALL nudge surfaces, not just the fabric-hint Stop signals:
622
+ // - Stop (fabric-hint): archive / review / import / maintenance
623
+ // - SessionStart (broad): review / import / maintenance now surface as
624
+ // the SessionStart summary line (see
625
+ // buildSessionStartSinks H4 ladder) — the same
626
+ // dismiss key silences them there too.
627
+ // - PreToolUse (per-edit): "narrow" (knowledge-hint-narrow) and
628
+ // "cite-evict" (cite-before-edit nudge).
629
+ // C-004 semantics are enforced at the trigger sites, NOT here: "narrow"
630
+ // defaults ON (impact-bearing) and "cite-evict" defaults OFF-able; listing
631
+ // either key here is the durable opt-out. Adding enum values is backward
632
+ // compatible — legacy configs that omit the new keys parse unchanged, and
633
+ // unknown on-disk values are dropped by the lenient root parser.
634
+ hint_dismiss_signals: z5.array(
635
+ z5.enum([
636
+ "archive",
637
+ "review",
638
+ "import",
639
+ "maintenance",
640
+ // per-edit (PreToolUse) nudge surfaces — TASK-005
641
+ "narrow",
642
+ "cite-evict"
643
+ ])
644
+ ).optional(),
593
645
  // v2.1 ADJ-NEWN-4: user-override escape hatches for the two strong behavioral
594
646
  // policies (cite-before-edit + self-archive). The strong policies can make an
595
647
  // agent feel like a "stubborn parrot" (D2 user-in-control red line); these
@@ -649,6 +701,27 @@ var fabricConfigSchema = z5.object({
649
701
  orphan_demote_proven_days: z5.number().int().min(1).max(3650).optional(),
650
702
  orphan_demote_verified_days: z5.number().int().min(1).max(3650).optional(),
651
703
  orphan_demote_draft_days: z5.number().int().min(1).max(3650).optional(),
704
+ // PLN-004 F1 (credibility content-age decay): per-knowledge-type half-lives
705
+ // (days) for the recall-scoring credibility MULTIPLIER. Orthogonal to the
706
+ // orphan_demote usage-inactivity ladder above (that is last-activity age; this
707
+ // is content age off created_at) and to recencyBoost (a 7-day additive
708
+ // freshness bump). Team-curated knowledge decays slower than transient work, so
709
+ // defaults run longer than upstream: decisions 180 / guidelines 150 / models
710
+ // 150 / pitfalls 120 / processes 120 (applied in config-loader). Each optional;
711
+ // absent → the loader default. Range 1..3650 mirrors orphan_demote.
712
+ credibility_half_life_decisions_days: z5.number().int().min(1).max(3650).optional(),
713
+ credibility_half_life_guidelines_days: z5.number().int().min(1).max(3650).optional(),
714
+ credibility_half_life_models_days: z5.number().int().min(1).max(3650).optional(),
715
+ credibility_half_life_pitfalls_days: z5.number().int().min(1).max(3650).optional(),
716
+ credibility_half_life_processes_days: z5.number().int().min(1).max(3650).optional(),
717
+ // PLN-004 F1: per-maturity floor the credibility multiplier never decays below
718
+ // (a stale-but-endorsed entry keeps a minimum weight). Higher maturity → higher
719
+ // floor: draft 0.4 / verified 0.55 / proven 0.7 (config-loader defaults). Range
720
+ // [0,1]. This is the softened form of the "content leads structural" invariant —
721
+ // credibility may sink a stale match but never zero it.
722
+ credibility_floor_draft: z5.number().min(0).max(1).optional(),
723
+ credibility_floor_verified: z5.number().min(0).max(1).optional(),
724
+ credibility_floor_proven: z5.number().min(0).max(1).optional(),
652
725
  // v2.2 C1 (processes/maturity-promotion-rubric-v1): days a `broad` entry may
653
726
  // go without a fab-review re-confirmation before doctor surfaces a RECHECK
654
727
  // nudge. `broad` is EXEMPT from usage-age decay (it is SessionStart-pushed,
@@ -706,10 +779,13 @@ var fabricConfigSchema = z5.object({
706
779
  // sole retrieval knob (plan_context_top_k above); payload limits pass through
707
780
  // explicit `mcpPayloadLimits`, else the fixed PAYLOAD_LIMIT_DEFAULT_* guardrail.
708
781
  // v2.2 C2-vector (W2-T7): OPTIONAL dense-embedding semantic retrieval, layered
709
- // as a recall supplement after BM25. Default OFF (`--no-embed` is the baseline);
710
- // requires the operator to install the optional `fastembed` package absent →
711
- // text-only fallback. Never grows the default install footprint.
712
- embed_enabled: z5.boolean().optional().default(false),
782
+ // as a recall supplement after BM25. P1 recall-engine-refactor (TASK-004):
783
+ // Default ON `fastembed` is now an optionalDependency (auto-installed; absent →
784
+ // degrade-safe text-only fallback), so CJK semantic recall is on out of the box.
785
+ // OFF only when set explicitly to false. This default mirrors the runtime read in
786
+ // config-loader.ts (`embed_enabled !== false`); keep the two in sync so config
787
+ // introspection never reports a default that contradicts runtime behavior.
788
+ embed_enabled: z5.boolean().optional().default(true),
713
789
  // Weight applied to the 0..1 cosine similarity before it joins the additive
714
790
  // score. Capped at 49 — strictly BELOW BM25_WEIGHT (50) — so a perfect vector
715
791
  // match (weight × 1) can never outscore a single strong BM25 term match. This
@@ -734,8 +810,25 @@ var fabricConfigSchema = z5.object({
734
810
  "fast-bge-base-en-v1.5",
735
811
  "fast-bge-base-en",
736
812
  "fast-all-MiniLM-L6-v2"
737
- ]).optional().default("fast-bge-small-zh-v1.5")
738
- });
813
+ ]).optional().default("fast-bge-small-zh-v1.5"),
814
+ // P1 recall-engine-refactor (TASK-003 + follow-up): content-channel fusion
815
+ // strategy. 'additive' = the weighted-sum path (BM25_WEIGHT·bm25 + vectorWeight·
816
+ // vector + structural); the vector term is structurally minor (cosine·30 vs an
817
+ // unbounded BM25), so additive is effectively BM25-led. 'rrf' = Reciprocal Rank
818
+ // Fusion over the two CONTENT channels (bm25_rank, vector_rank, equal footing)
819
+ // + a re-scaled structural tiebreaker — lets semantic recall actually matter.
820
+ // 'auto' (DEFAULT) = adaptive: use 'rrf' WHEN the vector channel is actually
821
+ // producing scores (embeddings installed + model warm), else fall back to
822
+ // 'additive'. This is the safe default — real-store shadow showed single-channel
823
+ // rrf (no vectors) is strictly worse than additive, so auto never lets that
824
+ // happen. no-query ranking is byte-identical under every value.
825
+ fusion: z5.enum(["additive", "rrf", "auto"]).optional().default("auto")
826
+ });
827
+ var fabricConfigLoadSchema = fabricConfigSchema.merge(
828
+ z5.object({
829
+ required_stores: z5.array(requiredStoreEntrySchema).optional()
830
+ })
831
+ );
739
832
 
740
833
  // src/schemas/fabric-config-introspect.ts
741
834
  import { z as z6 } from "zod";
@@ -900,9 +993,15 @@ var PANEL_FIELDS = [
900
993
  // nudge_mode — the master switch for the human-visible nudge experience
901
994
  // (the most user-facing runtime knob, previously JSON-only). embed_enabled —
902
995
  // vector semantic recall, panel-editable now that config lives in `.fabric`
903
- // (A1); enabling also needs the host-side `fabric install --enable-embed`.
996
+ // (A1). TASK-004: default ON (fastembed is an optionalDependency, degrade-safe
997
+ // when absent) — this introspection default mirrors the runtime read in
998
+ // config-loader.ts so the panel never shows a default that contradicts behavior.
904
999
  makeEnumField("nudge_mode", "D_behavior", nudgeModeSchema.options, "normal"),
905
- makeBooleanField("embed_enabled", false)
1000
+ makeBooleanField("embed_enabled", true),
1001
+ // P1 recall-engine-refactor (follow-up): the content-channel fusion strategy,
1002
+ // panel-editable so it sits next to embed_enabled (the two go together — rrf
1003
+ // only pays off when embeddings are on). 'auto' is the safe adaptive default.
1004
+ makeEnumField("fusion", "D_behavior", ["additive", "rrf", "auto"], "auto")
906
1005
  ];
907
1006
 
908
1007
  // src/schemas/store-stable-id.ts
@@ -1069,6 +1168,13 @@ var storeResolveInputSchema = z9.object({
1069
1168
  ),
1070
1169
  // Alias selected as the active write store for non-personal scopes, if any.
1071
1170
  activeWriteAlias: z9.string().min(1).optional(),
1171
+ // Alias/UUID of the ACTIVE personal store among possibly-many mounted
1172
+ // `personal:true` stores (语义 A: singleton-at-a-time). Drives the SINGLE
1173
+ // personal choke point (findPersonal) → both read-set inclusion and the
1174
+ // personal-scope write-target. Absent or dangling ⇒ the resolver falls back
1175
+ // to the first mounted personal, so legacy single-personal configs are
1176
+ // unchanged. Sourced from `~/.fabric/fabric-global.json` → active_personal_store.
1177
+ activePersonalAlias: z9.string().min(1).optional(),
1072
1178
  // Scope-aware write routes. Exact scope wins first, then longest prefix route.
1073
1179
  writeRoutes: z9.array(
1074
1180
  z9.object({
@@ -1131,7 +1237,16 @@ function createProjectRootResolver() {
1131
1237
 
1132
1238
  // src/resolver/store-resolver.ts
1133
1239
  function findPersonal(input) {
1134
- return input.mountedStores.find((s) => s.personal);
1240
+ const actives = input.mountedStores.filter((s) => s.personal);
1241
+ if (input.activePersonalAlias !== void 0) {
1242
+ const picked = actives.find(
1243
+ (s) => s.alias === input.activePersonalAlias || s.store_uuid === input.activePersonalAlias
1244
+ );
1245
+ if (picked !== void 0) {
1246
+ return picked;
1247
+ }
1248
+ }
1249
+ return actives[0];
1135
1250
  }
1136
1251
  function personalEntry(input) {
1137
1252
  const p = findPersonal(input);
@@ -1499,6 +1614,7 @@ import { execFile } from "child_process";
1499
1614
  import { access, mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
1500
1615
  import { join as join2 } from "path";
1501
1616
  import { promisify } from "util";
1617
+ var STORE_PROJECTS_DIR = "projects";
1502
1618
  var STORE_PENDING_DIR = "pending";
1503
1619
  var STORE_GITIGNORE = [
1504
1620
  "# v2.1 store \u2014 volatile / derived data is never committed",
@@ -1552,12 +1668,28 @@ async function listMarkdown(dir) {
1552
1668
  return entries.filter((name) => name.endsWith(".md")).sort().map((name) => join2(dir, name));
1553
1669
  }
1554
1670
  async function listStoreKnowledge(store) {
1671
+ const knowledgeDir = join2(store.dir, STORE_LAYOUT.knowledgeDir);
1555
1672
  const refs = [];
1556
1673
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1557
- for (const file of await listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, type))) {
1674
+ for (const file of await listMarkdown(join2(knowledgeDir, type))) {
1558
1675
  refs.push({ store_uuid: store.store_uuid, alias: store.alias, type, file });
1559
1676
  }
1560
1677
  }
1678
+ let projectEntries;
1679
+ try {
1680
+ projectEntries = await readdir(join2(knowledgeDir, STORE_PROJECTS_DIR));
1681
+ } catch {
1682
+ return refs;
1683
+ }
1684
+ const reserved = /* @__PURE__ */ new Set([STORE_PROJECTS_DIR, ...STORE_KNOWLEDGE_TYPE_DIRS]);
1685
+ const projectIds = projectEntries.filter((id) => STORE_PROJECT_ID_PATTERN.test(id) && !reserved.has(id)).sort();
1686
+ for (const project of projectIds) {
1687
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1688
+ for (const file of await listMarkdown(join2(knowledgeDir, STORE_PROJECTS_DIR, project, type))) {
1689
+ refs.push({ store_uuid: store.store_uuid, alias: store.alias, type, file, project });
1690
+ }
1691
+ }
1692
+ }
1561
1693
  return refs;
1562
1694
  }
1563
1695
  async function readKnowledgeAcrossStores(stores) {
@@ -1720,7 +1852,12 @@ function loadProjectConfig(projectRoot) {
1720
1852
  if (!existsSync3(path)) {
1721
1853
  return null;
1722
1854
  }
1723
- return fabricConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
1855
+ const raw = JSON.parse(readFileSync3(path, "utf8"));
1856
+ const parsed = fabricConfigSchema.safeParse(raw);
1857
+ if (parsed.success) {
1858
+ return parsed.data;
1859
+ }
1860
+ return fabricConfigLoadSchema.parse(raw);
1724
1861
  }
1725
1862
  function saveProjectConfig(config, projectRoot) {
1726
1863
  const validated = fabricConfigSchema.parse(config);
@@ -1753,6 +1890,7 @@ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1753
1890
  })
1754
1891
  ),
1755
1892
  ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store },
1893
+ ...global.active_personal_store === void 0 ? {} : { activePersonalAlias: global.active_personal_store },
1756
1894
  writeRoutes: project?.write_routes ?? [],
1757
1895
  ...project?.default_write_store === void 0 ? {} : { defaultWriteAlias: project.default_write_store }
1758
1896
  };
@@ -3161,6 +3299,49 @@ var eventLedgerEventSchema = z16.discriminatedUnion("event_type", [
3161
3299
  graphEdgeCandidateRequestedEventSchema
3162
3300
  ]);
3163
3301
 
3302
+ // src/high-value-predicate.ts
3303
+ var HIGH_VALUE_ARCHIVE_EVENT_TYPES = /* @__PURE__ */ new Set([
3304
+ "knowledge_context_planned",
3305
+ "edit_paths_recorded",
3306
+ "edit_intent_checked"
3307
+ // the real high-freq edit signal (rc.37 NEW-14/B3)
3308
+ ]);
3309
+ var NORMATIVE_KEYWORDS = [
3310
+ "\u4EE5\u540E",
3311
+ "always",
3312
+ "never",
3313
+ "from now on",
3314
+ "\u4E0B\u6B21",
3315
+ "\u8BB0\u4E00\u4E0B",
3316
+ "\u6C38\u8FDC\u4E0D\u8981"
3317
+ ];
3318
+ function isHighValueArchiveCandidate(events, sessionId, watermarkTs) {
3319
+ if (!Array.isArray(events)) return false;
3320
+ if (typeof sessionId !== "string" || sessionId.length === 0) return false;
3321
+ const wm = typeof watermarkTs === "number" ? watermarkTs : 0;
3322
+ let latestTurn = null;
3323
+ for (const raw of events) {
3324
+ const e = raw;
3325
+ if (!e || typeof e.ts !== "number" || e.ts <= wm) continue;
3326
+ if (e.session_id !== sessionId) continue;
3327
+ if (typeof e.event_type === "string" && HIGH_VALUE_ARCHIVE_EVENT_TYPES.has(e.event_type)) {
3328
+ return true;
3329
+ }
3330
+ if (e.event_type === "assistant_turn_observed") {
3331
+ if (latestTurn === null || typeof latestTurn.ts === "number" && e.ts > latestTurn.ts) {
3332
+ latestTurn = e;
3333
+ }
3334
+ }
3335
+ }
3336
+ if (latestTurn !== null) {
3337
+ const haystack = JSON.stringify(latestTurn).toLowerCase();
3338
+ for (const kw of NORMATIVE_KEYWORDS) {
3339
+ if (haystack.includes(kw.toLowerCase())) return true;
3340
+ }
3341
+ }
3342
+ return false;
3343
+ }
3344
+
3164
3345
  // src/text-tokenize.ts
3165
3346
  var CJK_CLASS = "\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af";
3166
3347
  var RUN_RE = new RegExp(`[a-z0-9]+|[${CJK_CLASS}]+`, "gu");
@@ -3339,6 +3520,7 @@ export {
3339
3520
  fabExtractKnowledgeAnnotations,
3340
3521
  fabPendingAnnotations,
3341
3522
  fabReviewAnnotations,
3523
+ fabricConfigLoadSchema,
3342
3524
  fabricConfigSchema,
3343
3525
  fabricEventSchema,
3344
3526
  fabricLanguageSchema,
@@ -3382,7 +3564,9 @@ export {
3382
3564
  initScanCompletedEventSchema,
3383
3565
  initStore,
3384
3566
  installDiffAppliedEventSchema,
3567
+ isHighValueArchiveCandidate,
3385
3568
  isKnowledgeStableId,
3569
+ isNonPersonalRequiredStore,
3386
3570
  isPersonalLeakIntoSharedStore,
3387
3571
  isPersonalScope,
3388
3572
  knowledgeArchiveAttemptedEventSchema,
@@ -3435,6 +3619,7 @@ export {
3435
3619
  metaReconciledEventSchema,
3436
3620
  metaReconciledOnStartupEventSchema,
3437
3621
  metaUpdatedEventSchema,
3622
+ migrateRequiredStores,
3438
3623
  mountedStoreSchema,
3439
3624
  normalizeCiteTag,
3440
3625
  normalizeLocale,