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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,26 +1,60 @@
1
1
  import {
2
- BOOTSTRAP_CANONICAL,
2
+ BOOTSTRAP_CANONICAL_BY_LOCALE,
3
+ BOOTSTRAP_CANONICAL_EN,
4
+ BOOTSTRAP_CANONICAL_ZH,
3
5
  BOOTSTRAP_MARKER_BEGIN,
4
6
  BOOTSTRAP_MARKER_END,
5
7
  BOOTSTRAP_REGEX,
6
- LEGACY_KB_MARKER_BEGIN,
7
- LEGACY_KB_MARKER_END,
8
- LEGACY_KB_REGEX
9
- } from "./chunk-AFT7DB4P.js";
8
+ matchBootstrapCanonicalLocale,
9
+ resolveBootstrapCanonical
10
+ } from "./chunk-BDJQIOQO.js";
10
11
  import {
11
12
  PROTECTED_TOKENS,
12
13
  createTranslator,
13
14
  defaultMessages,
14
- detectNodeLocale,
15
15
  enMessages,
16
- normalizeLocale,
17
16
  resolveFabricLocale,
18
17
  zhCNMessages
19
- } from "./chunk-KUYCTRFI.js";
18
+ } from "./chunk-AQMDXC6J.js";
19
+ import {
20
+ GLOBAL_BINDINGS_DIR,
21
+ GLOBAL_STATE_DIR,
22
+ PERSONAL_STORE_SENTINEL,
23
+ STORES_ROOT_DIR,
24
+ STORE_ALIAS_PATTERN,
25
+ STORE_KNOWLEDGE_TYPE_DIRS,
26
+ STORE_LAYOUT,
27
+ STORE_MOUNT_GROUPS,
28
+ STORE_MOUNT_NAME_PATTERN,
29
+ STORE_PROJECT_ID_PATTERN,
30
+ STORE_UUID_PATTERN,
31
+ deriveMountLabel,
32
+ detectNodeLocale,
33
+ globalConfigPath,
34
+ globalConfigSchema,
35
+ loadGlobalConfig,
36
+ mountedStoreSchema,
37
+ normalizeLocale,
38
+ requiredStoreEntrySchema,
39
+ resolveGlobalLocale,
40
+ resolveGlobalRoot,
41
+ saveGlobalConfig,
42
+ storeAliasSchema,
43
+ storeIdentitySchema,
44
+ storeKnowledgeTypeDir,
45
+ storeMountGroup,
46
+ storeMountNameSchema,
47
+ storeMountSubPath,
48
+ storeProjectSchema,
49
+ storeProjectsFileSchema,
50
+ storeRelativePath,
51
+ storeRelativePathForMount,
52
+ storeUuidSchema
53
+ } from "./chunk-2GLIAZ5M.js";
20
54
  import {
21
55
  atomicWriteJson,
22
56
  withFileLock
23
- } from "./chunk-4N6DMOOW.js";
57
+ } from "./chunk-C7WZPYZE.js";
24
58
  import {
25
59
  FabExtractKnowledgeInputSchema,
26
60
  FabExtractKnowledgeInputShape,
@@ -30,14 +64,17 @@ import {
30
64
  FabReviewOutputSchema,
31
65
  FabReviewOutputShape,
32
66
  KNOWLEDGE_TYPE_CODES,
67
+ KNOWN_SCOPE_PREFIXES,
33
68
  KnowledgeEntryFrontmatterSchema,
34
69
  KnowledgeTypeSchema,
35
70
  LayerSchema,
36
71
  MaturitySchema,
37
72
  ONBOARD_SLOT_NAMES,
38
73
  ONBOARD_SLOT_TOTAL,
39
- PROPOSED_REASON_DESCRIPTIONS,
74
+ PERSONAL_SCOPE,
75
+ PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE,
40
76
  ProposedReasonSchema,
77
+ SCOPE_COORDINATE_PATTERN,
41
78
  StableIdSchema,
42
79
  annotateIntentRequestSchema,
43
80
  archiveScanAnnotations,
@@ -46,12 +83,14 @@ import {
46
83
  citeContractMetricsSchema,
47
84
  citeCoverageReportSchema,
48
85
  citeLayerTypeBreakdownSchema,
86
+ entryScopeMetadataSchema,
49
87
  fabExtractKnowledgeAnnotations,
50
88
  fabReviewAnnotations,
51
89
  formatKnowledgeId,
52
90
  historyStateQuerySchema,
53
91
  humanLockApproveRequestSchema,
54
92
  humanLockFileParamsSchema,
93
+ isPersonalScope,
55
94
  knowledgeSectionsAnnotations,
56
95
  knowledgeSectionsInputSchema,
57
96
  knowledgeSectionsOutputSchema,
@@ -67,8 +106,10 @@ import {
67
106
  recallAnnotations,
68
107
  recallInputSchema,
69
108
  recallOutputSchema,
109
+ scopeCoordinateSchema,
110
+ scopeRoot,
70
111
  structuredWarningSchema
71
- } from "./chunk-355LUDLW.js";
112
+ } from "./chunk-5AKCRBKJ.js";
72
113
  import "./chunk-LXNCAKJZ.js";
73
114
 
74
115
  // src/schemas/agents-meta.ts
@@ -81,10 +122,8 @@ var KNOWLEDGE_TYPE_SINGULAR_TO_PLURAL = {
81
122
  pitfall: "pitfalls",
82
123
  process: "processes"
83
124
  };
84
- var AGENTS_META_LAYERS = ["L0", "L1", "L2"];
85
125
  var AGENTS_META_TOPOLOGY_TYPES = ["mirror", "cross-cutting", "domain", "local", "global"];
86
126
  var AGENTS_META_IDENTITY_SOURCES = ["declared", "derived"];
87
- var agentsLayerSchema = z.enum(AGENTS_META_LAYERS);
88
127
  var agentsTopologyTypeSchema = z.enum(AGENTS_META_TOPOLOGY_TYPES);
89
128
  var agentsIdentitySourceSchema = z.enum(AGENTS_META_IDENTITY_SOURCES);
90
129
  var ruleDescriptionSchema = z.object({
@@ -128,9 +167,6 @@ var ruleDescriptionSchema = z.object({
128
167
  }).strict();
129
168
  var ruleDescriptionIndexItemSchema = z.object({
130
169
  stable_id: z.string(),
131
- level: agentsLayerSchema,
132
- required: z.boolean(),
133
- selectable: z.boolean(),
134
170
  description: ruleDescriptionSchema
135
171
  }).strict();
136
172
  var agentsMetaNodeBaseSchema = z.object({
@@ -140,10 +176,6 @@ var agentsMetaNodeBaseSchema = z.object({
140
176
  hash: z.string(),
141
177
  stable_id: z.string().optional(),
142
178
  identity_source: agentsIdentitySourceSchema.optional(),
143
- activation: z.object({
144
- tier: z.enum(["always", "path", "description"]),
145
- description: z.string().optional()
146
- }).optional(),
147
179
  description: ruleDescriptionSchema.optional(),
148
180
  sections: z.array(z.string()).optional()
149
181
  }).passthrough();
@@ -182,7 +214,6 @@ function withDerivedAgentsMetaNodeDefaults(node) {
182
214
  const identitySource = isKnowledgeEntry ? "declared" : deriveAgentsMetaIdentitySource(node);
183
215
  return {
184
216
  ...node,
185
- level: node.level ?? deriveAgentsMetaLayer(node.file),
186
217
  topology_type: node.topology_type ?? deriveAgentsMetaTopologyType(node.file),
187
218
  stable_id: stableId,
188
219
  identity_source: identitySource
@@ -226,34 +257,12 @@ function deriveAgentsMetaIdentitySource(node) {
226
257
  const derivedStableId = deriveAgentsMetaStableId(node.file);
227
258
  return node.stable_id !== void 0 && node.stable_id !== derivedStableId ? "declared" : "derived";
228
259
  }
229
- function deriveAgentsMetaLayer(file) {
230
- const normalized = normalizePath(file);
231
- if (normalized === "AGENTS.md") {
232
- return "L0";
233
- }
234
- if (hasCrossCuttingSegment(normalized)) {
235
- return "L1";
236
- }
237
- const depthSource = getDepthSource(normalized);
238
- const directoryDepth = getDirectoryDepth(depthSource);
239
- if (directoryDepth === 0) {
240
- return "L0";
241
- }
242
- if (directoryDepth <= 2) {
243
- return "L1";
244
- }
245
- return "L2";
246
- }
247
260
  function deriveAgentsMetaTopologyType(file) {
248
261
  return hasCrossCuttingSegment(normalizePath(file)) ? "cross-cutting" : "mirror";
249
262
  }
250
263
  function getDepthSource(file) {
251
264
  return file.startsWith(FABRIC_AGENTS_PREFIX) ? file.slice(FABRIC_AGENTS_PREFIX.length) : file;
252
265
  }
253
- function getDirectoryDepth(file) {
254
- const segments = file.split("/").filter(Boolean);
255
- return Math.max(segments.length - 1, 0);
256
- }
257
266
  function hasCrossCuttingSegment(file) {
258
267
  return file.split("/").includes("_cross");
259
268
  }
@@ -339,127 +348,35 @@ var humanLockFileSchema = z4.object({
339
348
  });
340
349
 
341
350
  // src/schemas/fabric-config.ts
342
- import { z as z6 } from "zod";
343
-
344
- // src/schemas/store.ts
345
351
  import { z as z5 } from "zod";
346
- var STORE_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
347
- var storeUuidSchema = z5.string().regex(STORE_UUID_PATTERN, "store_uuid must be a canonical lowercase UUID");
348
- var PERSONAL_STORE_SENTINEL = "$personal";
349
- var storeIdentitySchema = z5.object({
350
- // Intrinsic, immutable once minted. Read from store.json, never recomputed.
351
- store_uuid: storeUuidSchema,
352
- // ISO-8601. When the store was first initialized.
353
- created_at: z5.string(),
354
- // Optional human-facing canonical alias baked into the store (e.g. the
355
- // team picks "platform-kb"). Local per-machine aliases are resolved by the
356
- // StoreResolver from config and may differ; this is the suggested default.
357
- canonical_alias: z5.string().optional(),
358
- // Optional one-line description surfaced in `store list` / onboarding.
359
- description: z5.string().optional(),
360
- // The semantic scopes this store is *allowed* to hold. A shared (team)
361
- // store MUST NOT list "personal" (R5#3 privacy boundary, enforced at write
362
- // time in P2). Open coordinate strings — see schemas/scope.ts.
363
- allowed_scopes: z5.array(z5.string()).optional()
352
+ var auditModeSchema = z5.enum(["strict", "warn", "off"]);
353
+ var clientPathsSchema = z5.object({
354
+ claudeCodeCLI: z5.string().optional(),
355
+ claudeCodeDesktop: z5.string().optional(),
356
+ codexCLI: z5.string().optional()
364
357
  }).strict();
365
- var STORE_PROJECT_ID_PATTERN = /^[a-z0-9_-]+$/u;
366
- var storeProjectSchema = z5.object({
367
- // Single scope segment forming the `project:<id>` coordinate. Immutable.
368
- id: z5.string().regex(STORE_PROJECT_ID_PATTERN, "project id must be a single lowercase [a-z0-9_-] segment"),
369
- // Optional human-facing label surfaced in `store project list`.
370
- name: z5.string().optional(),
371
- // ISO-8601. When the project was first registered in this store.
372
- created_at: z5.string()
373
- }).strict();
374
- var storeProjectsFileSchema = z5.object({
375
- projects: z5.array(storeProjectSchema).default([])
376
- }).strict();
377
- var requiredStoreEntrySchema = z5.object({
378
- id: z5.string().min(1),
379
- suggested_remote: z5.union([z5.string().min(1), z5.literal(PERSONAL_STORE_SENTINEL)]).optional()
380
- }).strict();
381
- var STORE_KNOWLEDGE_TYPE_DIRS = [
382
- "models",
383
- "decisions",
384
- "guidelines",
385
- "pitfalls",
386
- "processes"
387
- ];
388
- var STORE_LAYOUT = {
389
- identityFile: "store.json",
390
- // Store-internal project registry (W1/A2). Committed parallel to store.json.
391
- projectsFile: "projects.json",
392
- // v2.2 W4 (agents.meta decolo) — per-store monotonic stable_id counters.
393
- // COMMITTED parallel to store.json/projects.json (NOT gitignored like the
394
- // derived agents.meta) because the counter ledger is non-derivable state that
395
- // must travel with the store on clone: a fresh clone rebuilding from disk-max
396
- // would re-mint a deleted entry's id and corrupt cite history (KT-DEC-0004
397
- // monotonic invariant). Replaces the retired co-location
398
- // <projectRoot>/.fabric/agents.meta.json#counters.
399
- countersFile: "counters.json",
400
- knowledgeDir: "knowledge",
401
- bindingsDir: "bindings",
402
- stateDir: "state"
403
- };
404
- var STORES_ROOT_DIR = "stores";
405
- var GLOBAL_STATE_DIR = "state";
406
- var GLOBAL_BINDINGS_DIR = "bindings";
407
- function storeKnowledgeTypeDir(type) {
408
- return `${STORE_LAYOUT.knowledgeDir}/${type}`;
409
- }
410
- function storeRelativePath(storeUuid) {
411
- return `${STORES_ROOT_DIR}/${storeUuid}`;
412
- }
413
- var mountedStoreSchema = z5.object({
414
- // Intrinsic identity of the mounted store (matches its store.json).
415
- store_uuid: storeUuidSchema,
416
- // Local per-machine alias the user references this store by (resolver maps
417
- // alias → uuid). May differ from the store's canonical_alias.
418
- alias: z5.string().min(1),
419
- // Git remote locator for this clone, if any. Absent = local-only store
420
- // (valid; doctor nudges to add a remote for backup — R5#5, P6).
421
- remote: z5.string().min(1).optional(),
422
- // v2.1.0-rc.1 P3: marks the implicit personal store (the one minted by
423
- // `install --global`). Exactly one mounted store carries personal=true; it
424
- // is the write target for personal-scope entries (R5#3) and always in the
425
- // read-set (S11). Optional (no default) so the output type stays a plain
426
- // optional — consumers coalesce `?? false` when building resolver input.
427
- personal: z5.boolean().optional(),
428
- // Whether writes are accepted into this store from this machine. Optional;
429
- // consumers coalesce `?? true`. Shared stores cloned read-only set false.
430
- writable: z5.boolean().optional()
358
+ var mcpPayloadLimitsSchema = z5.object({
359
+ warnBytes: z5.number().int().positive().optional(),
360
+ hardBytes: z5.number().int().positive().optional()
361
+ }).optional();
362
+ var selectionTokenTtlMsSchema = z5.number().int().min(3e4).max(36e5);
363
+ var planContextTopKSchema = z5.number().int().min(1).max(200);
364
+ var fabricLanguageSchema = z5.enum(["zh-CN", "en"]);
365
+ var defaultLayerFilterSchema = z5.enum(["team", "personal", "both"]);
366
+ var nudgeModeSchema = z5.enum(["silent", "minimal", "normal", "verbose"]);
367
+ var observeConfigSchema = z5.object({
368
+ session_start: z5.boolean().optional(),
369
+ pre_tool_use: z5.boolean().optional(),
370
+ stop: z5.boolean().optional()
431
371
  }).strict();
432
- var globalConfigSchema = z5.object({
433
- // Machine/account identity. Personal-knowledge id namespace (S33/S27).
434
- uid: z5.string().min(1),
435
- // All stores mounted on this machine. The implicit personal store is
436
- // included here once initialized. Default empty so a fresh global config
437
- // (before `install --global`) parses cleanly.
438
- stores: z5.array(mountedStoreSchema).optional().default([])
439
- }).passthrough();
440
-
441
- // src/schemas/fabric-config.ts
442
- var auditModeSchema = z6.enum(["strict", "warn", "off"]);
443
- var clientPathsSchema = z6.object({
444
- claudeCodeCLI: z6.string().optional(),
445
- claudeCodeDesktop: z6.string().optional(),
446
- cursor: z6.string().optional(),
447
- codexCLI: z6.string().optional()
372
+ var writeRouteSchema = z5.object({
373
+ scope: z5.string().regex(
374
+ SCOPE_COORDINATE_PATTERN,
375
+ "write route scope must be ':'-joined lowercase [a-z0-9_-] segments"
376
+ ),
377
+ store: z5.string().min(1)
448
378
  }).strict();
449
- var mcpPayloadLimitsSchema = z6.object({
450
- warnBytes: z6.number().int().positive().optional(),
451
- hardBytes: z6.number().int().positive().optional()
452
- }).optional();
453
- var selectionTokenTtlMsSchema = z6.number().int().min(3e4).max(36e5);
454
- var planContextTopKSchema = z6.number().int().min(1).max(200);
455
- var fabricLanguageSchema = z6.enum([
456
- "match-existing",
457
- "zh-CN",
458
- "en",
459
- "zh-CN-hybrid"
460
- ]);
461
- var defaultLayerFilterSchema = z6.enum(["team", "personal", "both"]);
462
- var fabricConfigSchema = z6.object({
379
+ var fabricConfigSchema = z5.object({
463
380
  clientPaths: clientPathsSchema.optional(),
464
381
  // v2.1.0-rc.1 P0 (S13-projectid): the project's stable identity. A UUID
465
382
  // bound at `fabric install` time; a remote-derived hash is only a SUGGESTED
@@ -468,19 +385,23 @@ var fabricConfigSchema = z6.object({
468
385
  // fabric-config.json files simply lack it and the ProjectRootResolver mints
469
386
  // one on next install. `.fabric/fabric-config.json` carrying this field is
470
387
  // also the upward marker the ProjectRootResolver searches for (S15/S32).
471
- project_id: z6.string().optional(),
388
+ project_id: z5.string().optional(),
389
+ // Store-only runtime binding identity. Defaults to project_id when omitted,
390
+ // but worktrees / sandboxes can set this to isolate hook/runtime state while
391
+ // keeping the same committed project identity.
392
+ workspace_binding_id: z5.string().optional(),
472
393
  // v2.1.0-rc.1 P0 (S59/B3): the stores this repo expects mounted. Each entry
473
394
  // names a store by alias/UUID with an optional suggested_remote (or the
474
395
  // `$personal` sentinel). Drives the read-set (required_stores ∪ implicit
475
396
  // personal, S11/S54) and `clone`'s missing-store onboarding (S51). Optional
476
397
  // + absent → read-set is just the implicit personal store.
477
- required_stores: z6.array(requiredStoreEntrySchema).optional(),
398
+ required_stores: z5.array(requiredStoreEntrySchema).optional(),
478
399
  // v2.1.0-rc.1 P3 (S60 / `store switch-write`): alias of the store that
479
400
  // non-personal-scope writes land in for this project. Set by
480
401
  // `fabric store switch-write <alias>`; consumed as the resolver's
481
402
  // activeWriteAlias. Absent → no active write store yet. Personal-scope
482
403
  // writes always target the implicit personal store regardless (R5#3).
483
- active_write_store: z6.string().optional(),
404
+ active_write_store: z5.string().optional(),
484
405
  // v2.1 global-refactor (W1/A2 — store project registry): the project this repo
485
406
  // currently participates in, as the SINGLE scope segment forming the
486
407
  // `project:<id>` coordinate (schemas/scope.ts). Set by `store bind --project
@@ -489,7 +410,12 @@ var fabricConfigSchema = z6.object({
489
410
  // - recall: keep `project:<active_project>` + non-project coords, drop other
490
411
  // `project:*` entries (G-FILTER).
491
412
  // Absent → the repo has no project binding; recall does not project-filter.
492
- active_project: z6.string().optional(),
413
+ active_project: z5.string().optional(),
414
+ // Global Store Topology: scope-aware write routing for multi shared/org stores.
415
+ // Personal scope ignores these routes and always resolves to the implicit
416
+ // personal store. `active_write_store` remains a backward-compatible fallback.
417
+ write_routes: z5.array(writeRouteSchema).optional(),
418
+ default_write_store: z5.string().optional(),
493
419
  // rc.17 (R-cut): the dev/test fixture-path config field was removed
494
420
  // end-to-end. The `EXTERNAL_FIXTURE_PATH` env var is now the sole source
495
421
  // consumed by `resolveDevMode()`. No z.preprocess alias — pre-rc.17
@@ -497,14 +423,23 @@ var fabricConfigSchema = z6.object({
497
423
  // the lenient root parser (no .strict() at root). Pre-user clean-slate per
498
424
  // memory/feedback_clean_slate.md; mirrors the rc.12 hard-rename precedent
499
425
  // documented above.
500
- scanIgnores: z6.array(z6.string()).optional(),
426
+ scanIgnores: z5.array(z5.string()).optional(),
501
427
  audit_mode: auditModeSchema.optional(),
502
428
  mcpPayloadLimits: mcpPayloadLimitsSchema,
503
- // Backward-compat: both fields are optional with defaults so existing
504
- // fabric-config.json files (pre-grill-followup) parse unchanged. The default
505
- // values themselves are load-bearing see docs/data-schema.md.
506
- fabric_language: fabricLanguageSchema.optional().default("match-existing"),
429
+ // grill-6fixes (D1): `fabric_language` is no longer a per-project field
430
+ // language is a single machine-wide tone in `~/.fabric/fabric-global.json`.
431
+ // The root parser is lenient (no .strict()), so any stale `fabric_language`
432
+ // key left in an existing project config is silently dropped.
507
433
  default_layer_filter: defaultLayerFilterSchema.optional().default("both"),
434
+ // v2.2 dual-sink (Goal A / D4): human-output preset. See nudgeModeSchema for
435
+ // the level semantics + the flow ⊥ observation invariant (nudge_mode never
436
+ // touches the AI additionalContext sink). Default "normal" preserves the
437
+ // pre-dual-sink human visibility so existing dogfood repos see no regression.
438
+ nudge_mode: nudgeModeSchema.optional().default("normal"),
439
+ // v2.2 dual-sink (Goal A / D4): per-event human-output overrides. A set value
440
+ // wins over the nudge_mode preset for that event; absent events fall back to
441
+ // the preset. AI sink unaffected (same invariant as nudge_mode).
442
+ observe: observeConfigSchema.optional(),
508
443
  // Cooldown for the fabric-hint Stop hook (formerly archive-hint, renamed in
509
444
  // rc.5 TASK-010). After ANY of the three signals (archive / review / import)
510
445
  // fires, that signal stays silent for this many hours regardless of state
@@ -512,7 +447,7 @@ var fabricConfigSchema = z6.object({
512
447
  // day if the user keeps ignoring it." Set to 24 to align with the archive
513
448
  // trigger threshold. The legacy `archive_hint_` key is retained for backward
514
449
  // compat with existing user fabric-config.json files.
515
- archive_hint_cooldown_hours: z6.number().int().positive().optional().default(12),
450
+ archive_hint_cooldown_hours: z5.number().int().positive().optional().default(12),
516
451
  // Underseed-node threshold for the fabric-hint Stop hook's import signal
517
452
  // (rc.5 TASK-010). When the canonical knowledge node count is strictly less
518
453
  // than this value AND a successful `init_scan_completed` event happened at
@@ -521,7 +456,7 @@ var fabricConfigSchema = z6.object({
521
456
  // the rule-of-thumb that a workspace with fewer than ten knowledge entries
522
457
  // is below the floor for plan_context retrieval to be meaningful. Also
523
458
  // consumed by `doctor` lint #22 (knowledge_underseeded).
524
- underseed_node_threshold: z6.number().int().positive().optional().default(10),
459
+ underseed_node_threshold: z5.number().int().positive().optional().default(10),
525
460
  // Edit-count threshold for the fabric-hint Stop hook's Signal A
526
461
  // (rc.6 TASK-022 / E5). Signal A fires when EITHER (a) >=24h have elapsed
527
462
  // since the last `knowledge_proposed` event, OR (b) >=archive_edit_threshold
@@ -532,102 +467,102 @@ var fabricConfigSchema = z6.object({
532
467
  // there is probably something worth archiving"; lowered values nag more
533
468
  // aggressively, higher values rely on the 24h fallback. Missing or absent
534
469
  // edit-counter file degrades safely to the 24h-only path.
535
- archive_edit_threshold: z6.number().int().positive().optional().default(20),
470
+ archive_edit_threshold: z5.number().int().positive().optional().default(20),
536
471
  // rc.7 T7: hours-since-last-knowledge_proposed cutoff for Signal A's
537
472
  // time branch. Was hardcoded as 24 in fabric-hint.cjs's THRESHOLD_HOURS;
538
473
  // externalized so chatty workspaces can lower the bar and quiet ones can
539
474
  // raise it. Default 24 preserves rc.6 behavior. See docs/configuration.md.
540
- archive_hint_hours: z6.number().int().positive().optional().default(24),
475
+ archive_hint_hours: z5.number().int().positive().optional().default(24),
541
476
  // rc.7 T7: pending-count cutoff for Signal B (review skill). Was
542
477
  // hardcoded as 10 in fabric-hint.cjs's THRESHOLD_PENDING_COUNT.
543
478
  // Default 10 preserves rc.6 behavior. See docs/configuration.md for
544
479
  // small/medium/large repo recommendations.
545
- review_hint_pending_count: z6.number().int().positive().optional().default(10),
480
+ review_hint_pending_count: z5.number().int().positive().optional().default(10),
546
481
  // rc.7 T7: pending-age cutoff (in days) for Signal B (review skill).
547
482
  // Was hardcoded as 7 in fabric-hint.cjs's THRESHOLD_PENDING_AGE_DAYS.
548
483
  // Default 7 preserves rc.6 behavior. See docs/configuration.md.
549
- review_hint_pending_age_days: z6.number().int().positive().optional().default(7),
484
+ review_hint_pending_age_days: z5.number().int().positive().optional().default(7),
550
485
  // rc.7 T7 + T10 pre-wiring: days-since-last-doctor cutoff for the future
551
486
  // Signal D (maintenance hint). T10 will consume this to decide when the
552
487
  // fabric-hint Stop hook surfaces a "run `fabric doctor`" reminder.
553
488
  // Default 14 reflects a fortnightly cadence — long enough to avoid nag,
554
489
  // short enough to catch index drift before it compounds.
555
- maintenance_hint_days: z6.number().int().positive().optional().default(14),
490
+ maintenance_hint_days: z5.number().int().positive().optional().default(14),
556
491
  // rc.7 T7 + T10 pre-wiring: cooldown between Signal D reminders, in
557
492
  // days. Once Signal D fires, it stays silent for this many days even if
558
493
  // the user doesn't run doctor. Default 7 keeps the reminder weekly at
559
494
  // worst — pairing 14d trigger + 7d cooldown means at most ~2 reminders
560
495
  // per month for a workspace that ignores them.
561
- maintenance_hint_cooldown_days: z6.number().int().positive().optional().default(7),
496
+ maintenance_hint_cooldown_days: z5.number().int().positive().optional().default(7),
562
497
  // rc.9+ (skill-contract-fix B1): first-run import window in months. The
563
498
  // `fabric-import` skill scans this many months of git history on the very
564
499
  // first invocation (when no prior `import_run_completed` event exists).
565
500
  // Default 60 (~5 years) captures the bulk of a mature repo's signal in
566
501
  // one pass; small / fresh repos can lower to 12-24 with no loss.
567
- import_window_first_run_months: z6.number().int().min(1).optional().default(60),
502
+ import_window_first_run_months: z5.number().int().min(1).optional().default(60),
568
503
  // rc.9+ (skill-contract-fix B1): rerun import window in months. After
569
504
  // the first successful import, subsequent runs only scan this many
570
505
  // recent months — assumed everything older has already been crystallized
571
506
  // into pending or canonical knowledge. Default 2 keeps incremental cost
572
507
  // low; raise to 6 if the workspace pauses fabric-import for long stretches.
573
- import_window_rerun_months: z6.number().int().min(1).optional().default(2),
508
+ import_window_rerun_months: z5.number().int().min(1).optional().default(2),
574
509
  // rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
575
510
  // per fabric-import invocation. Prevents one run from dumping hundreds
576
511
  // of proposals when a backfill window is wide open. Default 10 matches
577
512
  // the rule-of-thumb "human can triage ~10 pending entries in one
578
513
  // review pass." Range 1-50.
579
- import_max_pending_per_run: z6.number().int().min(1).max(50).optional().default(10),
514
+ import_max_pending_per_run: z5.number().int().min(1).max(50).optional().default(10),
580
515
  // rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
581
516
  // fabric-import invocation. Bounds runtime on monorepos with high
582
517
  // commit velocity. Default 500 covers ~2 months of typical churn;
583
518
  // range 50-2000. Hitting the cap mid-window is logged but non-fatal.
584
- import_max_commits_scan: z6.number().int().min(50).max(2e3).optional().default(500),
519
+ import_max_commits_scan: z5.number().int().min(50).max(2e3).optional().default(500),
585
520
  // rc.9+ (skill-contract-fix B1): canonical-node count above which
586
521
  // fabric-import's pre-flight should warn / suggest review instead of
587
522
  // proceeding. A workspace with 50+ canonical entries usually benefits
588
523
  // more from `fabric-review` to consolidate than from importing more.
589
524
  // Default 50; raise to 100+ for large polyglot repos.
590
- import_skip_canonical_threshold: z6.number().int().positive().optional().default(50),
525
+ import_skip_canonical_threshold: z5.number().int().positive().optional().default(50),
591
526
  // rc.9+ (skill-contract-fix B1): max candidate entries surfaced per
592
527
  // fabric-archive batch (one invocation of the skill). Pagination knob
593
528
  // for the archive UI flow. Default 8 keeps each batch reviewable in
594
529
  // one sitting; raise for large repos with high archive throughput.
595
- archive_max_candidates_per_batch: z6.number().int().positive().optional().default(8),
530
+ archive_max_candidates_per_batch: z5.number().int().positive().optional().default(8),
596
531
  // rc.9+ (skill-contract-fix B1): max recently-touched paths included
597
532
  // in fabric-archive's "relevant context" lookup. Limits the size of
598
533
  // the path-relevance digest the skill emits when ranking candidates.
599
534
  // Default 20; large repos with deep directory fan-out can raise to
600
535
  // 50+ if archive candidates feel under-contextualized.
601
- archive_max_recent_paths: z6.number().int().positive().optional().default(20),
536
+ archive_max_recent_paths: z5.number().int().positive().optional().default(20),
602
537
  // rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
603
538
  // summarised in the digest the skill loads on start. Prevents the
604
539
  // digest from ballooning past the model context budget on workspaces
605
540
  // that have archived repeatedly. Default 10; lower if context pressure
606
541
  // bites, raise if you want longer-range archive trend visibility.
607
- archive_digest_max_sessions: z6.number().int().positive().optional().default(10),
542
+ archive_digest_max_sessions: z5.number().int().positive().optional().default(10),
608
543
  // rc.9+ (skill-contract-fix B1): max review results returned per
609
544
  // topic when `fabric-review` clusters pending entries. Pagination
610
545
  // knob analogous to archive_max_candidates_per_batch but scoped to
611
546
  // each topic cluster. Default 8; raise to 15-20 for large repos
612
547
  // where each topic legitimately groups many pending entries.
613
- review_topic_result_cap: z6.number().int().positive().optional().default(8),
548
+ review_topic_result_cap: z5.number().int().positive().optional().default(8),
614
549
  // rc.9+ (skill-contract-fix B1): age threshold (in days) above which
615
550
  // a pending entry is considered "stale" by fabric-review and surfaced
616
551
  // for explicit resolve-or-drop decision. Default 14; tighter than the
617
552
  // 7d Signal-B trigger because review specifically targets the long
618
553
  // tail. Large repos with slower cadence can raise to 30.
619
- review_stale_pending_days: z6.number().int().positive().optional().default(14),
554
+ review_stale_pending_days: z5.number().int().positive().optional().default(14),
620
555
  // v2.0.0-rc.34 TASK-05: reverse-unarchive opt-in. When true, callers of the
621
556
  // `unarchiveKnowledge` primitive (and any future doctor auto-detect lint built
622
557
  // on top) will execute the file move + ledger emit. When false (default),
623
558
  // the same callers MUST short-circuit before any mutation — the primitive is
624
559
  // shipped but inert until explicitly enabled. Opt-in posture mirrors the
625
560
  // archive-flow precedent: destructive-ish file moves stay behind a flag.
626
- reverse_unarchive_enabled: z6.boolean().optional().default(false),
561
+ reverse_unarchive_enabled: z5.boolean().optional().default(false),
627
562
  // v2.0.0-rc.34 TASK-05: forces `unarchiveKnowledge` into dry-run mode even
628
563
  // when called with `options.dryRun=false`. Lets operators preview a
629
564
  // restoration pass before flipping `reverse_unarchive_enabled` to true.
630
- reverse_unarchive_dry_run: z6.boolean().optional().default(false),
565
+ reverse_unarchive_dry_run: z5.boolean().optional().default(false),
631
566
  // v2.0.0-rc.34 TASK-06: long-session cite-policy evict window in user-prompt
632
567
  // turns. UserPromptSubmit hook (Claude Code only) maintains a per-session
633
568
  // counter and re-injects the cite contract reminder via
@@ -635,7 +570,7 @@ var fabricConfigSchema = z6.object({
635
570
  // Default 0 = OFF (opt-in). Recommend 10-20 for active sessions; 5 for
636
571
  // high-contract-criticality projects. Other strategies (time-based,
637
572
  // token-budget) deferred to rc.35 per plan locked-decisions 2026-05-26.
638
- cite_evict_interval: z6.number().int().min(0).optional().default(0),
573
+ cite_evict_interval: z5.number().int().min(0).optional().default(0),
639
574
  // v2.1 ⑤ cite-redesign (P5): recall-based cite-accounting hook config. The
640
575
  // rc.34 cite_evict_interval turn-counter above is superseded by the
641
576
  // PreToolUse(Edit/Write) recall-aware nudge in cite-policy-evict.cjs; the old
@@ -645,21 +580,21 @@ var fabricConfigSchema = z6.object({
645
580
  // the cite_evict_interval=0 opt-out convention). `cite_recall_window_minutes`
646
581
  // bounds how far back an in-session fab_recall counts as "informing" the edit
647
582
  // (default 30; 0 = unbounded).
648
- cite_recall_nudge: z6.boolean().optional().default(true),
649
- cite_recall_window_minutes: z6.number().int().min(0).optional().default(30),
583
+ cite_recall_nudge: z5.boolean().optional().default(true),
584
+ cite_recall_window_minutes: z5.number().int().min(0).optional().default(30),
650
585
  // F2: glob exemptions for the cite nudge (cite-policy-evict.cjs). Edit paths
651
586
  // matching any glob skip the "改前先 fab_recall" nudge — meta/orchestration
652
587
  // files (e.g. `.workflow/` scratchpads) are not source the cite policy
653
588
  // governs. MERGED with the hook's built-in [".workflow/**"] default; an
654
589
  // omitted/empty value keeps just that default. `*` = within a path segment,
655
590
  // `**` = across segments.
656
- cite_nudge_ignore_globs: z6.array(z6.string()).optional(),
591
+ cite_nudge_ignore_globs: z5.array(z5.string()).optional(),
657
592
  // v2.1 ④ conflict-detection (P4): bm25 content-similarity threshold (0..1)
658
593
  // for the knowledge-conflict lint (`fabric doctor --lint-conflicts`). A
659
594
  // same-(type,layer) pair whose normalized bm25 similarity reaches this floor
660
595
  // is surfaced as a candidate (possible duplicate OR conflict). Conservative
661
596
  // default 0.5 — raise to reduce noise, lower to catch looser pairs.
662
- conflict_lint_similarity_threshold: z6.number().min(0).max(1).optional().default(0.5),
597
+ conflict_lint_similarity_threshold: z5.number().min(0).max(1).optional().default(0.5),
663
598
  // v2.0.0-rc.22 Scope A T3: sliding-window retention (in days) for the
664
599
  // event ledger rotation primitive (`rotateEventLedgerIfNeeded`). Lines
665
600
  // whose `ts` is older than `now - fabric_event_retention_days * 86_400_000`
@@ -672,7 +607,7 @@ var fabricConfigSchema = z6.object({
672
607
  // Mirrors cite-policy precedent of locking enum-style numeric tunables
673
608
  // to a small literal set (vs free `.positive()`) to prevent fat-finger
674
609
  // misconfig.
675
- fabric_event_retention_days: z6.union([z6.literal(7), z6.literal(30), z6.literal(90)]).optional(),
610
+ fabric_event_retention_days: z5.union([z5.literal(7), z5.literal(30), z5.literal(90)]).optional(),
676
611
  // v2.0.0-rc.23 TASK-014 (F8c): onboard slot opt-out list. Tracks slot
677
612
  // names the user explicitly dismissed during fabric-archive's first-run
678
613
  // onboard phase (or via `fabric config dismiss-slot <slot>`). Dismissed
@@ -689,7 +624,7 @@ var fabricConfigSchema = z6.object({
689
624
  //
690
625
  // Default `[]` keeps the field optional on existing configs — fresh
691
626
  // installs land with no opt-outs.
692
- onboard_slots_opted_out: z6.array(z6.string()).optional().default([]),
627
+ onboard_slots_opted_out: z5.array(z5.string()).optional().default([]),
693
628
  // v2.0.0-rc.33 W2-1 (P0-9): TopK upper bound for the broad SessionStart hint
694
629
  // banner emitted by knowledge-hint-broad.cjs. After plan-context-hint returns
695
630
  // its full broad-scoped index, the hook slices the entries to this many
@@ -701,20 +636,25 @@ var fabricConfigSchema = z6.object({
701
636
  // Range 1..50; values above 20 effectively disable the cap because the
702
637
  // TRUNCATION_THRESHOLD=12 grouped-render kicks in. Mirrors the rc.7 T7 +
703
638
  // archive_max_* pattern of externalizing previously-hardcoded thresholds.
704
- hint_broad_top_k: z6.number().int().min(1).max(50).optional().default(8),
705
- // v2.2 HK2-degrade (W2-T2): char budget for the rendered SessionStart broad-menu
706
- // body the final rung of the degradation ladder after the hint_broad_top_k
707
- // count slice. Once the rendered entry/group lines exceed this, the tail
708
- // collapses to a single "N more omitted" marker so a large corpus cannot blow
709
- // the agent's working memory. Default 2000 (~one screenful); 0 disables the
710
- // budget. Read by knowledge-hint-broad.cjs via readConfigNumber. Range 0..20000.
711
- hint_broad_budget_chars: z6.number().int().min(0).max(2e4).optional().default(2e3),
639
+ hint_broad_top_k: z5.number().int().min(1).max(50).optional().default(8),
640
+ // KT-DEC-0036: the SessionStart broad-menu is now index-only (title + summary
641
+ // per always-active entry, no eager body), so the former `hint_broad_budget_chars`
642
+ // body char-budget knob was retired there is no rendered body left to bound.
643
+ // W4-1 (KT-DEC-0028 / KT-MOD-0001): scale backstop for the FULL broad index.
644
+ // After W2-1 retired the hint_broad_top_k hard cap, the broad banner shows
645
+ // every broad entry (completeness); this is the only guard — once a store's
646
+ // rendered broad index exceeds this many lines the overflow tail folds into a
647
+ // single drift marker. The doctor `broad-index-drift` lint (W4-2) warns at 80%
648
+ // of this value per store so the corpus can be pruned (fabric-audit) BEFORE the
649
+ // banner silently truncates. Default 50; range 20..500 (read inline by
650
+ // knowledge-hint-broad.cjs#readBroadIndexBackstop with the same bounds).
651
+ broad_index_backstop: z5.number().int().min(20).max(500).optional().default(50),
712
652
  // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint Stop
713
653
  // hook nudges. Any signal type listed here is suppressed at emit time across
714
654
  // all sessions (the session-scoped sibling lives in a .fabric/.cache sidecar
715
655
  // written on request). Mirrors the cite_evict_interval=0 opt-out convention —
716
656
  // a knob for an existing surface, not a new feature. Unknown types ignored.
717
- hint_dismiss_signals: z6.array(z6.enum(["archive", "review", "import", "maintenance"])).optional(),
657
+ hint_dismiss_signals: z5.array(z5.enum(["archive", "review", "import", "maintenance"])).optional(),
718
658
  // v2.1 ADJ-NEWN-4: user-override escape hatches for the two strong behavioral
719
659
  // policies (cite-before-edit + self-archive). The strong policies can make an
720
660
  // agent feel like a "stubborn parrot" (D2 user-in-control red line); these
@@ -726,15 +666,15 @@ var fabricConfigSchema = z6.object({
726
666
  // surface, mirroring the cite_evict_interval=0 / hint_dismiss_signals opt-out
727
667
  // convention, NOT a new feature. Wave3 J32 will quantify the friction these
728
668
  // relieve; until then they ship as inert-safe opt-outs.
729
- cite_policy_enabled: z6.boolean().optional().default(true),
730
- self_archive_policy_enabled: z6.boolean().optional().default(true),
669
+ cite_policy_enabled: z5.boolean().optional().default(true),
670
+ self_archive_policy_enabled: z5.boolean().optional().default(true),
731
671
  // v2.0.0-rc.33 W2-1 (P0-9): TopK upper bound for the narrow PreToolUse hint
732
672
  // emitted by knowledge-hint-narrow.cjs. After filtering to entries whose
733
673
  // `relevance_scope === "narrow"` (rc.27 TASK-005 audit §2.5 fix), the hook
734
674
  // slices to this many before the E3 emit-gate / renderSummary pipeline.
735
675
  // Default 5 keeps each per-Edit hint terse — five lines max so the agent's
736
676
  // working memory is not displaced by an unwieldy banner. Range 1..20.
737
- hint_narrow_top_k: z6.number().int().min(1).max(20).optional().default(5),
677
+ hint_narrow_top_k: z5.number().int().min(1).max(20).optional().default(5),
738
678
  // v2.0.0-rc.33 W2-1 (P0-9): per-file dedup window (in PreToolUse turns) for
739
679
  // the narrow hint. Same (file_path, stable_id) tuple stays silent for this
740
680
  // many turns even when the E3 cross-session cache would otherwise re-emit.
@@ -744,7 +684,7 @@ var fabricConfigSchema = z6.object({
744
684
  // Storage: .fabric/.cache/narrow-dedup-window.json — distinct from session-
745
685
  // hints cache so a window-only suppression does not poison cross-session
746
686
  // dedupe semantics.
747
- hint_narrow_dedup_window_turns: z6.number().int().min(1).max(50).optional().default(5),
687
+ hint_narrow_dedup_window_turns: z5.number().int().min(1).max(50).optional().default(5),
748
688
  // v2.0.0-rc.33 W2-5 (P1-8): cooldown between broad SessionStart hint emits,
749
689
  // in hours. Distinct from the archive_hint_cooldown_hours that gates the
750
690
  // fabric-hint Stop hook — knowledge-hint-broad re-fires on every
@@ -753,14 +693,14 @@ var fabricConfigSchema = z6.object({
753
693
  // menu at most once per hour"; 0 means "no cooldown, current behavior."
754
694
  // Range 0..168 (one week). Stored alongside fabric-hint's cooldown cache
755
695
  // under a distinct knowledge-hint-broad key.
756
- hint_broad_cooldown_hours: z6.number().int().min(0).max(168).optional().default(0),
696
+ hint_broad_cooldown_hours: z5.number().int().min(0).max(168).optional().default(0),
757
697
  // v2.0.0-rc.33 W2-5 (P1-8): cooldown for the narrow PreToolUse hint.
758
698
  // Same shape as hint_broad_cooldown_hours but applies to per-Edit hint
759
699
  // re-emission across the cooldown window — independent of E3 session-
760
700
  // hints dedupe. Default 0 preserves rc.32 behavior; set to e.g. 1 to
761
701
  // throttle hint frequency during rapid-fire editing sprints. Range
762
702
  // 0..168 (one week).
763
- hint_narrow_cooldown_hours: z6.number().int().min(0).max(168).optional().default(0),
703
+ hint_narrow_cooldown_hours: z5.number().int().min(0).max(168).optional().default(0),
764
704
  // v2.0.0-rc.33 W4-B3 (T5 P2): per-maturity inactivity thresholds (days)
765
705
  // driving orphan_demote. Hardcoded at proven=90/verified=30/draft=14 in
766
706
  // rc.32; chatty workspaces want them tighter, slow ones want them looser.
@@ -768,26 +708,19 @@ var fabricConfigSchema = z6.object({
768
708
  // chosen so a typo can't accidentally disable the lint (min 1).
769
709
  //
770
710
  // v2.2 W3-T5 (F-MATURITY-ENDORSED): the canonical maturity enum is
771
- // draft/verified/proven (KT-DEC-0005), but these threshold keys historically
772
- // used the legacy stable/endorsed vocabulary — a config authored with the
773
- // canonical names could never tune the proven/verified tiers. The canonical
774
- // keys below are the preferred form; the legacy keys are retained for
775
- // backward-compat (a config written before this fix keeps working). The
776
- // loader maps stable→proven / endorsed→verified, canonical taking precedence.
777
- orphan_demote_proven_days: z6.number().int().min(1).max(3650).optional(),
778
- orphan_demote_verified_days: z6.number().int().min(1).max(3650).optional(),
779
- orphan_demote_draft_days: z6.number().int().min(1).max(3650).optional(),
780
- // Legacy aliases (deprecated; map to proven/verified). Kept so existing
781
- // configs do not silently lose their tuning.
782
- orphan_demote_stable_days: z6.number().int().min(1).max(3650).optional(),
783
- orphan_demote_endorsed_days: z6.number().int().min(1).max(3650).optional(),
711
+ // draft/verified/proven (KT-DEC-0005). These threshold keys use the canonical
712
+ // vocabulary; the loader maps proven→stable / verified→endorsed onto the
713
+ // doctor's internal orphan_demote ladder.
714
+ orphan_demote_proven_days: z5.number().int().min(1).max(3650).optional(),
715
+ orphan_demote_verified_days: z5.number().int().min(1).max(3650).optional(),
716
+ orphan_demote_draft_days: z5.number().int().min(1).max(3650).optional(),
784
717
  // v2.0.0-rc.33 W4-A3 (T4 P2): per-entry summary truncation length used by
785
718
  // knowledge-hint-{broad,narrow}.cjs. Hard-coded at 80 chars in rc.32 — too
786
719
  // short for entries with parameterized summaries (e.g. "Use bcrypt with
787
720
  // cost=12 for password hashing"), too long for terse pitfalls. Range 40..240;
788
721
  // default 80 preserves rc.32 behavior. Both hooks read the same key so the
789
722
  // banner styling stays consistent across SessionStart + PreToolUse.
790
- hint_summary_max_len: z6.number().int().min(40).max(240).optional().default(80),
723
+ hint_summary_max_len: z5.number().int().min(40).max(240).optional().default(80),
791
724
  // v2.0.0-rc.33 W2-6 (P0-7 + P0-8): when true, knowledge-hint hooks emit
792
725
  // their banners as `hookSpecificOutput.additionalContext` JSON on stdout
793
726
  // (per Claude Code PreToolUse hook contract — see
@@ -797,7 +730,7 @@ var fabricConfigSchema = z6.object({
797
730
  // coverage focus (rc.32 baseline 3.1% → primary cause: reminders never
798
731
  // entered model context). Set false to revert to legacy stderr-only mode
799
732
  // for hosts that don't honor the JSON contract.
800
- hint_reminder_to_context: z6.boolean().optional().default(true),
733
+ hint_reminder_to_context: z5.boolean().optional().default(true),
801
734
  // v2.0.0-rc.29 TASK-008 (BUG-F3): selection-token TTL override. The
802
735
  // `fab_plan_context` MCP tool hands clients a `selection_token` whose default
803
736
  // 5-minute lifetime (`SELECTION_TOKEN_TTL_MS` at
@@ -818,26 +751,28 @@ var fabricConfigSchema = z6.object({
818
751
  // applied after BM25 ranking. Absent → library default (24). See
819
752
  // planContextTopKSchema for the range/calibration rationale.
820
753
  plan_context_top_k: planContextTopKSchema.optional(),
821
- // v2.2 C5-budget (W2-T3): layered retrieval budget profile. A single coherent
822
- // strategy across the injection + MCP layers `balanced` (default) reproduces
823
- // the historical per-knob defaults exactly, `conservative` / `generous` scale
824
- // the whole truncation chain (top_k + payload bytes + injection chars) down /
825
- // up together. Per-field knobs (plan_context_top_k, mcpPayloadLimits.*,
826
- // hint_broad_budget_chars) still override the profile when set. See
827
- // retrieval-budget.ts (resolveRetrievalBudget) for the resolution order.
828
- retrieval_budget_profile: z6.enum(["conservative", "balanced", "generous"]).optional(),
754
+ // KT-DEC-0038: ratio-to-top relevance floor (α) for recall / plan_context.
755
+ // After ranking, keep only candidates whose fused score >= α × the top
756
+ // candidate's score — self-normalizing against the current query's max, so it
757
+ // is immune to BM25's uncalibrated cross-query scale. top_k is a pure safety
758
+ // cap above this. Range 0..1; absent → library default (0.25). 0 disables the
759
+ // floor (keep all up to top_k).
760
+ recall_relevance_ratio: z5.number().min(0).max(1).optional(),
761
+ // KT-DEC-0037: the `retrieval_budget_profile` enum was deleted. top_k is the
762
+ // sole retrieval knob (plan_context_top_k above); payload limits pass through
763
+ // explicit `mcpPayloadLimits`, else the fixed PAYLOAD_LIMIT_DEFAULT_* guardrail.
829
764
  // v2.2 C2-vector (W2-T7): OPTIONAL dense-embedding semantic retrieval, layered
830
765
  // as a recall supplement after BM25. Default OFF (`--no-embed` is the baseline);
831
766
  // requires the operator to install the optional `fastembed` package — absent →
832
767
  // text-only fallback. Never grows the default install footprint.
833
- embed_enabled: z6.boolean().optional().default(false),
768
+ embed_enabled: z5.boolean().optional().default(false),
834
769
  // Weight applied to the 0..1 cosine similarity before it joins the additive
835
770
  // score. Capped at 49 — strictly BELOW BM25_WEIGHT (50) — so a perfect vector
836
771
  // match (weight × 1) can never outscore a single strong BM25 term match. This
837
772
  // ENFORCES the "vectors supplement, never override lexical relevance"
838
773
  // invariant in the schema rather than leaving it to a comment (W2-REVIEW codex
839
774
  // MED-4). Range 0..49; default 30.
840
- embed_weight: z6.number().int().min(0).max(49).optional().default(30),
775
+ embed_weight: z5.number().int().min(0).max(49).optional().default(30),
841
776
  // v2.1 ③ vector-chinese-model (P3): which fastembed model to load. The prior
842
777
  // code pinned fastembed's English default (bge-small-en-v1.5) — wrong for the
843
778
  // Chinese-heavy zh-CN-hybrid KB. Values are the fastembed@2.x EmbeddingModel
@@ -847,7 +782,7 @@ var fabricConfigSchema = z6.object({
847
782
  // available for full multilingual recall at a ~1GB download + slower CPU cost.
848
783
  // (V1 research: fastembed@2.1.0 has NO multilingual-e5-SMALL — the originally
849
784
  // planned pin — so bge-small-zh is the light Chinese choice.)
850
- embed_model: z6.enum([
785
+ embed_model: z5.enum([
851
786
  "fast-bge-small-zh-v1.5",
852
787
  "fast-multilingual-e5-large",
853
788
  "fast-bge-small-en-v1.5",
@@ -859,8 +794,8 @@ var fabricConfigSchema = z6.object({
859
794
  });
860
795
 
861
796
  // src/schemas/fabric-config-introspect.ts
862
- import { z as z7 } from "zod";
863
- var positiveIntSchema = z7.coerce.number().int().positive();
797
+ import { z as z6 } from "zod";
798
+ var positiveIntSchema = z6.coerce.number().int().positive();
864
799
  function makePositiveIntField(key, defaultValue) {
865
800
  return {
866
801
  key,
@@ -916,6 +851,28 @@ function makeEnumField(key, group, enumValues, defaultValue) {
916
851
  }
917
852
  };
918
853
  }
854
+ function makeBooleanField(key, defaultValue) {
855
+ return {
856
+ key,
857
+ group: "D_behavior",
858
+ widget: "select",
859
+ label_i18n_key: `cli.config.fields.${key}.label`,
860
+ description_i18n_key: `cli.config.fields.${key}.description`,
861
+ default: String(defaultValue),
862
+ enum_values: ["true", "false"],
863
+ validate(raw) {
864
+ const trimmed = raw.trim();
865
+ if (trimmed === "true") return { ok: true, value: true };
866
+ if (trimmed === "false") return { ok: true, value: false };
867
+ return { ok: false, error: "Must be one of: true, false." };
868
+ },
869
+ format_for_display(value) {
870
+ if (typeof value === "boolean") return String(value);
871
+ if (value === void 0 || value === null) return String(defaultValue);
872
+ return String(value);
873
+ }
874
+ };
875
+ }
919
876
  var SCHEMA_DEFAULTS = fabricConfigSchema.parse({});
920
877
  function pickNumberDefault(key) {
921
878
  const v = SCHEMA_DEFAULTS[key];
@@ -944,12 +901,13 @@ function getPanelFieldByKey(key) {
944
901
  }
945
902
  var PANEL_FIELDS = [
946
903
  // --- Group A: Locale (2) ---
947
- makeEnumField(
948
- "fabric_language",
949
- "A_locale",
950
- fabricLanguageSchema.options,
951
- pickStringDefault("fabric_language")
952
- ),
904
+ // grill-6fixes (D1): `fabric_language` is no longer a project-config field —
905
+ // it is the single machine-wide tone in `~/.fabric/fabric-global.json`. The
906
+ // panel still surfaces it (the `fabric config` language entry), but config.ts
907
+ // special-cases this key to read/write the GLOBAL config instead of the
908
+ // project file. Default is a literal "en" since there is no project-schema
909
+ // default to derive from.
910
+ makeEnumField("fabric_language", "A_locale", fabricLanguageSchema.options, "en"),
953
911
  makeEnumField(
954
912
  "default_layer_filter",
955
913
  "A_locale",
@@ -993,39 +951,23 @@ var PANEL_FIELDS = [
993
951
  "C_audit",
994
952
  auditModeSchema.options,
995
953
  AUDIT_MODE_PANEL_DEFAULT
996
- )
954
+ ),
955
+ // --- Group D: Behavior / features (2) ---
956
+ // nudge_mode — the master switch for the human-visible nudge experience
957
+ // (the most user-facing runtime knob, previously JSON-only). embed_enabled —
958
+ // vector semantic recall, panel-editable now that config lives in `.fabric`
959
+ // (A1); enabling also needs the host-side `fabric install --enable-embed`.
960
+ makeEnumField("nudge_mode", "D_behavior", nudgeModeSchema.options, "normal"),
961
+ makeBooleanField("embed_enabled", false)
997
962
  ];
998
963
 
999
- // src/schemas/scope.ts
1000
- import { z as z8 } from "zod";
1001
- var PERSONAL_SCOPE = "personal";
1002
- var KNOWN_SCOPE_PREFIXES = ["personal", "team", "project", "org"];
1003
- var SCOPE_COORDINATE_PATTERN = /^[a-z0-9_-]+(:[a-z0-9_-]+)*$/u;
1004
- var scopeCoordinateSchema = z8.string().min(1).regex(
1005
- SCOPE_COORDINATE_PATTERN,
1006
- "scope coordinate must be ':'-joined lowercase [a-z0-9_-] segments"
1007
- );
1008
- function scopeRoot(coordinate) {
1009
- const colon = coordinate.indexOf(":");
1010
- return colon === -1 ? coordinate : coordinate.slice(0, colon);
1011
- }
1012
- function isPersonalScope(coordinate) {
1013
- return scopeRoot(coordinate) === PERSONAL_SCOPE;
1014
- }
1015
- var entryScopeMetadataSchema = z8.object({
1016
- semantic_scope: scopeCoordinateSchema,
1017
- // Store alias or UUID. Validated as a non-empty string here; the resolver
1018
- // (P0.6) maps alias→UUID and verifies the store is in the read-set.
1019
- visibility_store: z8.string().min(1)
1020
- }).strict();
1021
-
1022
964
  // src/schemas/store-stable-id.ts
1023
- import { z as z9 } from "zod";
965
+ import { z as z7 } from "zod";
1024
966
  var localKnowledgeIdSchema = StableIdSchema;
1025
967
  var UID_SEGMENT_PATTERN = /^[a-z0-9-]+$/u;
1026
- var uidSchema = z9.string().min(1).regex(UID_SEGMENT_PATTERN, "uid must be lowercase [a-z0-9-] segments");
968
+ var uidSchema = z7.string().min(1).regex(UID_SEGMENT_PATTERN, "uid must be lowercase [a-z0-9-] segments");
1027
969
  var GLOBAL_REF_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(:[a-z0-9-]+)?:K[PT]-(MOD|DEC|GLD|PIT|PRO)-\d{4,}$/u;
1028
- var globalRefSchema = z9.string().regex(GLOBAL_REF_PATTERN, "global_ref must be <store_uuid>[:<uid>]:<local_id>");
970
+ var globalRefSchema = z7.string().regex(GLOBAL_REF_PATTERN, "global_ref must be <store_uuid>[:<uid>]:<local_id>");
1029
971
  function formatGlobalRef(parts) {
1030
972
  const { store_uuid, uid, local_id } = parts;
1031
973
  return uid === void 0 ? `${store_uuid}:${local_id}` : `${store_uuid}:${uid}:${local_id}`;
@@ -1050,14 +992,14 @@ function parseGlobalRef(ref) {
1050
992
  local_id
1051
993
  };
1052
994
  }
1053
- var storeKnowledgeTypeCountersSchema = z9.object({
1054
- MOD: z9.number().int().nonnegative().default(0),
1055
- DEC: z9.number().int().nonnegative().default(0),
1056
- GLD: z9.number().int().nonnegative().default(0),
1057
- PIT: z9.number().int().nonnegative().default(0),
1058
- PRO: z9.number().int().nonnegative().default(0)
995
+ var storeKnowledgeTypeCountersSchema = z7.object({
996
+ MOD: z7.number().int().nonnegative().default(0),
997
+ DEC: z7.number().int().nonnegative().default(0),
998
+ GLD: z7.number().int().nonnegative().default(0),
999
+ PIT: z7.number().int().nonnegative().default(0),
1000
+ PRO: z7.number().int().nonnegative().default(0)
1059
1001
  }).default({ MOD: 0, DEC: 0, GLD: 0, PIT: 0, PRO: 0 });
1060
- var storeCountersSchema = z9.object({
1002
+ var storeCountersSchema = z7.object({
1061
1003
  KP: storeKnowledgeTypeCountersSchema,
1062
1004
  KT: storeKnowledgeTypeCountersSchema
1063
1005
  }).default({
@@ -1066,64 +1008,63 @@ var storeCountersSchema = z9.object({
1066
1008
  });
1067
1009
 
1068
1010
  // src/schemas/parity-matrix.ts
1069
- import { z as z10 } from "zod";
1070
- var PARITY_CLIENTS = ["claudeCode", "codexCLI", "cursor"];
1071
- var parityClientSchema = z10.enum(PARITY_CLIENTS);
1011
+ import { z as z8 } from "zod";
1012
+ var PARITY_CLIENTS = ["claudeCode", "codexCLI"];
1013
+ var parityClientSchema = z8.enum(PARITY_CLIENTS);
1072
1014
  var PARITY_SURFACES = ["skill", "hook", "mcp", "render"];
1073
- var paritySurfaceSchema = z10.enum(PARITY_SURFACES);
1074
- var parityClientExpectationSchema = z10.object({
1075
- supported: z10.boolean(),
1076
- mechanism: z10.string().optional(),
1077
- notes: z10.string().optional()
1015
+ var paritySurfaceSchema = z8.enum(PARITY_SURFACES);
1016
+ var parityClientExpectationSchema = z8.object({
1017
+ supported: z8.boolean(),
1018
+ mechanism: z8.string().optional(),
1019
+ notes: z8.string().optional()
1078
1020
  }).strict();
1079
- var parityCapabilitySchema = z10.object({
1080
- id: z10.string().min(1),
1021
+ var parityCapabilitySchema = z8.object({
1022
+ id: z8.string().min(1),
1081
1023
  surface: paritySurfaceSchema,
1082
- description: z10.string().min(1),
1083
- clients: z10.object({
1024
+ description: z8.string().min(1),
1025
+ clients: z8.object({
1084
1026
  claudeCode: parityClientExpectationSchema,
1085
- codexCLI: parityClientExpectationSchema,
1086
- cursor: parityClientExpectationSchema
1027
+ codexCLI: parityClientExpectationSchema
1087
1028
  }).strict()
1088
1029
  }).strict();
1089
- var parityMatrixSchema = z10.object({
1030
+ var parityMatrixSchema = z8.object({
1090
1031
  // Schema/version tag of the matrix document itself.
1091
- version: z10.string().min(1),
1032
+ version: z8.string().min(1),
1092
1033
  // Free-form note on what release/milestone this matrix targets.
1093
- generated_for: z10.string().min(1),
1094
- capabilities: z10.array(parityCapabilitySchema).min(1)
1034
+ generated_for: z8.string().min(1),
1035
+ capabilities: z8.array(parityCapabilitySchema).min(1)
1095
1036
  }).strict();
1096
1037
 
1097
1038
  // src/resolver/contracts.ts
1098
- import { z as z11 } from "zod";
1039
+ import { z as z9 } from "zod";
1099
1040
  var PROJECT_ROOT_SIGNALS = ["env", "marker", "cwd", "repo"];
1100
- var projectRootSignalSchema = z11.enum(PROJECT_ROOT_SIGNALS);
1101
- var projectRootSignalsSchema = z11.object({
1041
+ var projectRootSignalSchema = z9.enum(PROJECT_ROOT_SIGNALS);
1042
+ var projectRootSignalsSchema = z9.object({
1102
1043
  // FABRIC_PROJECT_ROOT, if set.
1103
- env: z11.string().optional(),
1044
+ env: z9.string().optional(),
1104
1045
  // Nearest directory AT-OR-ABOVE cwd holding `.fabric/fabric-config.json`,
1105
1046
  // if any (the upward marker search result; may equal cwd).
1106
- markerDir: z11.string().optional(),
1047
+ markerDir: z9.string().optional(),
1107
1048
  // Always present — the process cwd.
1108
- cwd: z11.string().min(1),
1049
+ cwd: z9.string().min(1),
1109
1050
  // git repo root, if inside a repo.
1110
- repoRoot: z11.string().optional(),
1051
+ repoRoot: z9.string().optional(),
1111
1052
  // The `project_id` read from the winning root's fabric-config.json during
1112
1053
  // (fs) signal collection. The pure resolver echoes it — it cannot invent a
1113
1054
  // UUID. Worktrees of one repo share the committed config, hence the same
1114
1055
  // project_id (S45 merge). Absent when no .fabric config exists at the root
1115
1056
  // yet (fresh repo-fallback) → resolution still yields the root with a null
1116
1057
  // projectId so the caller can mint+persist one at install time.
1117
- discoveredProjectId: z11.string().optional()
1058
+ discoveredProjectId: z9.string().optional()
1118
1059
  }).strict();
1119
- var projectRootResolutionSchema = z11.object({
1060
+ var projectRootResolutionSchema = z9.object({
1120
1061
  // Absolute project root directory.
1121
- projectRoot: z11.string().min(1),
1062
+ projectRoot: z9.string().min(1),
1122
1063
  // Stable project identity. One repo = one .fabric = one project_id (S32);
1123
1064
  // git worktrees of the same repo resolve to the SAME project_id (S45).
1124
1065
  // Null when the resolved root has no fabric-config.json yet (fresh
1125
1066
  // repo-fallback) — the caller mints + persists a UUID at install time.
1126
- projectId: z11.string().min(1).nullable(),
1067
+ projectId: z9.string().min(1).nullable(),
1127
1068
  // Which signal won.
1128
1069
  signalUsed: projectRootSignalSchema
1129
1070
  }).strict();
@@ -1132,91 +1073,96 @@ var STORE_RESOLVER_WARNING_CODES = [
1132
1073
  // required_stores entry has no matching mounted store (S51)
1133
1074
  "local_only_no_remote",
1134
1075
  // mounted but local-only (R5#5 nudge, non-fatal)
1135
- "alias_unresolved"
1076
+ "alias_unresolved",
1136
1077
  // referenced alias maps to no mounted store
1078
+ "missing_write_route"
1079
+ // multi/shared write requires an explicit route
1137
1080
  ];
1138
- var storeResolverWarningCodeSchema = z11.enum(STORE_RESOLVER_WARNING_CODES);
1139
- var storeResolverWarningSchema = z11.object({
1081
+ var storeResolverWarningCodeSchema = z9.enum(STORE_RESOLVER_WARNING_CODES);
1082
+ var storeResolverWarningSchema = z9.object({
1140
1083
  code: storeResolverWarningCodeSchema,
1141
1084
  // The alias/UUID/id the warning concerns.
1142
- ref: z11.string().min(1),
1143
- message: z11.string().min(1)
1085
+ ref: z9.string().min(1),
1086
+ message: z9.string().min(1)
1144
1087
  }).strict();
1145
- var readSetEntrySchema = z11.object({
1146
- store_uuid: z11.string().min(1),
1147
- alias: z11.string().min(1),
1148
- remote: z11.string().min(1).optional(),
1088
+ var readSetEntrySchema = z9.object({
1089
+ store_uuid: z9.string().min(1),
1090
+ alias: z9.string().min(1),
1091
+ remote: z9.string().min(1).optional(),
1149
1092
  // Whether this store accepts writes from the current context. Personal
1150
1093
  // store is writable; shared stores writable iff mounted with write intent.
1151
- writable: z11.boolean()
1094
+ writable: z9.boolean()
1152
1095
  }).strict();
1153
- var storeReadSetSchema = z11.object({
1154
- stores: z11.array(readSetEntrySchema),
1155
- warnings: z11.array(storeResolverWarningSchema)
1096
+ var storeReadSetSchema = z9.object({
1097
+ stores: z9.array(readSetEntrySchema),
1098
+ warnings: z9.array(storeResolverWarningSchema)
1156
1099
  }).strict();
1157
- var writeTargetSchema = z11.object({
1158
- store_uuid: z11.string().min(1),
1159
- alias: z11.string().min(1)
1100
+ var writeTargetSchema = z9.object({
1101
+ store_uuid: z9.string().min(1),
1102
+ alias: z9.string().min(1)
1160
1103
  }).strict();
1161
- var storeResolveInputSchema = z11.object({
1104
+ var storeResolveInputSchema = z9.object({
1162
1105
  // Machine identity (S33) — namespaces personal ids; identifies personal store.
1163
- uid: z11.string().min(1),
1106
+ uid: z9.string().min(1),
1164
1107
  // Stores mounted on this machine (from global config).
1165
- mountedStores: z11.array(
1166
- z11.object({
1167
- store_uuid: z11.string().min(1),
1168
- alias: z11.string().min(1),
1169
- remote: z11.string().min(1).optional(),
1170
- writable: z11.boolean().default(true),
1108
+ mountedStores: z9.array(
1109
+ z9.object({
1110
+ store_uuid: z9.string().min(1),
1111
+ alias: z9.string().min(1),
1112
+ mount_name: z9.string().min(1).optional(),
1113
+ remote: z9.string().min(1).optional(),
1114
+ writable: z9.boolean().default(true),
1171
1115
  // Marks the implicit personal store.
1172
- personal: z11.boolean().default(false)
1116
+ personal: z9.boolean().default(false)
1173
1117
  }).strict()
1174
1118
  ),
1175
1119
  // The project's declared required_stores (ids/aliases + optional remote).
1176
- requiredStores: z11.array(
1177
- z11.object({
1178
- id: z11.string().min(1),
1179
- suggested_remote: z11.string().min(1).optional()
1120
+ requiredStores: z9.array(
1121
+ z9.object({
1122
+ id: z9.string().min(1),
1123
+ suggested_remote: z9.string().min(1).optional()
1180
1124
  }).strict()
1181
1125
  ),
1182
1126
  // Alias selected as the active write store for non-personal scopes, if any.
1183
- activeWriteAlias: z11.string().min(1).optional()
1127
+ activeWriteAlias: z9.string().min(1).optional(),
1128
+ // Scope-aware write routes. Exact scope wins first, then longest prefix route.
1129
+ writeRoutes: z9.array(
1130
+ z9.object({
1131
+ scope: z9.string().min(1),
1132
+ store: z9.string().min(1)
1133
+ }).strict()
1134
+ ).optional().default([]),
1135
+ defaultWriteAlias: z9.string().min(1).optional()
1184
1136
  }).strict();
1185
- var projectRootGoldenCaseSchema = z11.object({
1186
- name: z11.string().min(1),
1187
- note: z11.string().optional(),
1137
+ var projectRootGoldenCaseSchema = z9.object({
1138
+ name: z9.string().min(1),
1139
+ note: z9.string().optional(),
1188
1140
  signals: projectRootSignalsSchema,
1189
1141
  // null expected = resolver should return no root for these signals.
1190
1142
  expected: projectRootResolutionSchema.nullable()
1191
1143
  }).strict();
1192
- var projectRootGoldenFileSchema = z11.object({
1193
- contract: z11.literal("project-root.golden"),
1194
- cases: z11.array(projectRootGoldenCaseSchema).min(1)
1144
+ var projectRootGoldenFileSchema = z9.object({
1145
+ contract: z9.literal("project-root.golden"),
1146
+ cases: z9.array(projectRootGoldenCaseSchema).min(1)
1195
1147
  }).strict();
1196
- var readSetGoldenCaseSchema = z11.object({
1197
- name: z11.string().min(1),
1198
- note: z11.string().optional(),
1148
+ var readSetGoldenCaseSchema = z9.object({
1149
+ name: z9.string().min(1),
1150
+ note: z9.string().optional(),
1199
1151
  input: storeResolveInputSchema,
1200
1152
  // Scope under test for the write-target expectation.
1201
- writeScope: z11.string().min(1),
1202
- expected: z11.object({
1153
+ writeScope: z9.string().min(1),
1154
+ expected: z9.object({
1203
1155
  readSet: storeReadSetSchema,
1204
1156
  writeTarget: writeTargetSchema.nullable(),
1205
- writeWarnings: z11.array(storeResolverWarningSchema)
1157
+ writeWarnings: z9.array(storeResolverWarningSchema)
1206
1158
  }).strict()
1207
1159
  }).strict();
1208
- var readSetGoldenFileSchema = z11.object({
1209
- contract: z11.literal("read-set.golden"),
1210
- cases: z11.array(readSetGoldenCaseSchema).min(1)
1160
+ var readSetGoldenFileSchema = z9.object({
1161
+ contract: z9.literal("read-set.golden"),
1162
+ cases: z9.array(readSetGoldenCaseSchema).min(1)
1211
1163
  }).strict();
1212
1164
 
1213
1165
  // src/resolver/project-root-resolver.ts
1214
- var ResolverNotImplementedError = class extends Error {
1215
- constructor(what) {
1216
- super(`${what} is not implemented yet (TDD target)`);
1217
- this.name = "ResolverNotImplementedError";
1218
- }
1219
- };
1220
1166
  function createProjectRootResolver() {
1221
1167
  return {
1222
1168
  resolve(signals) {
@@ -1254,12 +1200,65 @@ function personalEntry(input) {
1254
1200
  }
1255
1201
  return entry;
1256
1202
  }
1203
+ function readSetEntryFromMounted(store) {
1204
+ const entry = {
1205
+ store_uuid: store.store_uuid,
1206
+ alias: store.alias,
1207
+ writable: store.writable
1208
+ };
1209
+ if (store.remote !== void 0) {
1210
+ entry.remote = store.remote;
1211
+ return { entry };
1212
+ }
1213
+ return {
1214
+ entry,
1215
+ warning: {
1216
+ code: "local_only_no_remote",
1217
+ ref: store.alias,
1218
+ message: `store '${store.alias}' is local-only; add a git remote to back it up (\`fabric store ... \` / doctor nudge)`
1219
+ }
1220
+ };
1221
+ }
1222
+ function isMountedPersonal(input, storeUuid) {
1223
+ return input.mountedStores.some((store) => store.store_uuid === storeUuid && store.personal);
1224
+ }
1225
+ function routeMatches(routeScope, scope) {
1226
+ return scope === routeScope || scope.startsWith(`${routeScope}:`);
1227
+ }
1228
+ function resolveRouteAlias(input, scope) {
1229
+ const routes = input.writeRoutes ?? [];
1230
+ const exact = routes.find((route) => route.scope === scope);
1231
+ if (exact !== void 0) {
1232
+ return exact.store;
1233
+ }
1234
+ const prefix = routes.filter((route) => routeMatches(route.scope, scope)).sort((a, b) => b.scope.length - a.scope.length)[0];
1235
+ return prefix?.store ?? input.defaultWriteAlias ?? input.activeWriteAlias;
1236
+ }
1237
+ function hasMultipleSharedStores(input, readSet) {
1238
+ return readSet.stores.filter((store) => !isMountedPersonal(input, store.store_uuid)).length > 1;
1239
+ }
1240
+ function hasExplicitRouteOrDefault(input, scope) {
1241
+ const routes = input.writeRoutes ?? [];
1242
+ return routes.some((route) => routeMatches(route.scope, scope)) || input.defaultWriteAlias !== void 0;
1243
+ }
1257
1244
  function createStoreResolver() {
1258
1245
  return {
1259
1246
  resolveReadSet(input) {
1260
1247
  const stores = [];
1261
1248
  const warnings = [];
1249
+ const seenStoreUuids = /* @__PURE__ */ new Set();
1262
1250
  for (const req of input.requiredStores) {
1251
+ if (req.suggested_remote === "$personal") {
1252
+ const personal2 = findPersonal(input);
1253
+ if (personal2 === void 0) {
1254
+ warnings.push({
1255
+ code: "missing_store",
1256
+ ref: req.id,
1257
+ message: `required store '${req.id}' is not mounted; run \`fabric store add\` (suggested remote: $personal)`
1258
+ });
1259
+ }
1260
+ continue;
1261
+ }
1263
1262
  const matched = input.mountedStores.find(
1264
1263
  (m) => !m.personal && (m.alias === req.id || m.store_uuid === req.id)
1265
1264
  );
@@ -1272,25 +1271,20 @@ function createStoreResolver() {
1272
1271
  });
1273
1272
  continue;
1274
1273
  }
1275
- const entry = {
1276
- store_uuid: matched.store_uuid,
1277
- alias: matched.alias,
1278
- writable: matched.writable
1279
- };
1280
- if (matched.remote !== void 0) {
1281
- entry.remote = matched.remote;
1282
- } else {
1283
- warnings.push({
1284
- code: "local_only_no_remote",
1285
- ref: matched.alias,
1286
- message: `store '${matched.alias}' is local-only; add a git remote to back it up (\`fabric store ... \` / doctor nudge)`
1287
- });
1274
+ if (seenStoreUuids.has(matched.store_uuid)) {
1275
+ continue;
1276
+ }
1277
+ const { entry, warning } = readSetEntryFromMounted(matched);
1278
+ if (warning !== void 0) {
1279
+ warnings.push(warning);
1288
1280
  }
1289
1281
  stores.push(entry);
1282
+ seenStoreUuids.add(matched.store_uuid);
1290
1283
  }
1291
1284
  const personal = personalEntry(input);
1292
- if (personal !== void 0) {
1285
+ if (personal !== void 0 && !seenStoreUuids.has(personal.store_uuid)) {
1293
1286
  stores.push(personal);
1287
+ seenStoreUuids.add(personal.store_uuid);
1294
1288
  }
1295
1289
  return { stores, warnings };
1296
1290
  },
@@ -1311,15 +1305,31 @@ function createStoreResolver() {
1311
1305
  }
1312
1306
  return { target: { store_uuid: p.store_uuid, alias: p.alias }, warnings: [] };
1313
1307
  }
1314
- const active = input.activeWriteAlias === void 0 ? void 0 : input.mountedStores.find((m) => m.writable && m.alias === input.activeWriteAlias);
1308
+ const readSet = this.resolveReadSet(input);
1309
+ if (hasMultipleSharedStores(input, readSet) && !hasExplicitRouteOrDefault(input, scope)) {
1310
+ return {
1311
+ target: null,
1312
+ warnings: [
1313
+ {
1314
+ code: "missing_write_route",
1315
+ ref: scope,
1316
+ message: `scope '${scope}' has no explicit write route; set \`fabric store route-write ${scope} <alias>\``
1317
+ }
1318
+ ]
1319
+ };
1320
+ }
1321
+ const routeAlias = resolveRouteAlias(input, scope);
1322
+ const active = routeAlias === void 0 ? void 0 : readSet.stores.find(
1323
+ (store) => store.writable && !isMountedPersonal(input, store.store_uuid) && (store.alias === routeAlias || store.store_uuid === routeAlias)
1324
+ );
1315
1325
  if (active === void 0) {
1316
1326
  return {
1317
1327
  target: null,
1318
1328
  warnings: [
1319
1329
  {
1320
1330
  code: "alias_unresolved",
1321
- ref: input.activeWriteAlias ?? scope,
1322
- message: `no writable store for scope '${scope}'; set an active write store with \`fabric store switch-write\``
1331
+ ref: routeAlias ?? scope,
1332
+ message: `no writable store for scope '${scope}'; set a write route or default write store`
1323
1333
  }
1324
1334
  ]
1325
1335
  };
@@ -1333,7 +1343,8 @@ function createStoreResolver() {
1333
1343
  }
1334
1344
 
1335
1345
  // src/resolver/store-disk-reader.ts
1336
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
1346
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "fs";
1347
+ import { readFile } from "fs/promises";
1337
1348
  import { join } from "path";
1338
1349
  function readStoreIdentity(absDir) {
1339
1350
  const identityFile = join(absDir, STORE_LAYOUT.identityFile);
@@ -1349,6 +1360,17 @@ function readStoreIdentity(absDir) {
1349
1360
  const parsed = storeIdentitySchema.safeParse(raw);
1350
1361
  return parsed.success ? parsed.data : null;
1351
1362
  }
1363
+ async function readStoreIdentityAsync(absDir) {
1364
+ const identityFile = join(absDir, STORE_LAYOUT.identityFile);
1365
+ let raw;
1366
+ try {
1367
+ raw = JSON.parse(await readFile(identityFile, "utf8"));
1368
+ } catch {
1369
+ return null;
1370
+ }
1371
+ const parsed = storeIdentitySchema.safeParse(raw);
1372
+ return parsed.success ? parsed.data : null;
1373
+ }
1352
1374
  function recognizeStoreDir(absDir) {
1353
1375
  return readStoreIdentity(absDir) !== null;
1354
1376
  }
@@ -1408,10 +1430,14 @@ function findStoreExecutableViolations(absDir, options = {}) {
1408
1430
  const relPath = rel === "" ? entry : `${rel}/${entry}`;
1409
1431
  let stat;
1410
1432
  try {
1411
- stat = statSync(abs);
1433
+ stat = lstatSync(abs);
1412
1434
  } catch {
1413
1435
  continue;
1414
1436
  }
1437
+ if (stat.isSymbolicLink()) {
1438
+ violations.push(relPath);
1439
+ continue;
1440
+ }
1415
1441
  if (stat.isDirectory()) {
1416
1442
  walk(abs, relPath, depth + 1);
1417
1443
  continue;
@@ -1525,9 +1551,10 @@ function resolveCandidates(candidates, options = {}) {
1525
1551
  }
1526
1552
 
1527
1553
  // src/store/core.ts
1528
- import { execFileSync } from "child_process";
1529
- import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1554
+ import { execFile } from "child_process";
1555
+ import { access, mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
1530
1556
  import { join as join2 } from "path";
1557
+ import { promisify } from "util";
1531
1558
  var STORE_PENDING_DIR = "pending";
1532
1559
  var STORE_GITIGNORE = [
1533
1560
  "# v2.1 store \u2014 volatile / derived data is never committed",
@@ -1536,96 +1563,106 @@ var STORE_GITIGNORE = [
1536
1563
  ".cache/",
1537
1564
  ""
1538
1565
  ].join("\n");
1539
- function git(cwd, args) {
1540
- execFileSync("git", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
1566
+ var execFileAsync = promisify(execFile);
1567
+ async function git(cwd, args) {
1568
+ await execFileAsync("git", args, { cwd });
1541
1569
  }
1542
- function initStore(absDir, identity, options = {}) {
1570
+ async function initStore(absDir, identity, options = {}) {
1543
1571
  const parsed = storeIdentitySchema.parse(identity);
1544
1572
  const identityFile = join2(absDir, STORE_LAYOUT.identityFile);
1545
- if (existsSync2(identityFile)) {
1573
+ try {
1574
+ await access(identityFile);
1546
1575
  throw new Error(`store already initialized at ${absDir} (store.json exists)`);
1576
+ } catch (err) {
1577
+ if (err instanceof Error && err.message.includes("already initialized")) {
1578
+ throw err;
1579
+ }
1547
1580
  }
1548
1581
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1549
- mkdirSync(join2(absDir, STORE_LAYOUT.knowledgeDir, type), { recursive: true });
1550
- }
1551
- mkdirSync(join2(absDir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR), { recursive: true });
1552
- mkdirSync(join2(absDir, STORE_LAYOUT.bindingsDir), { recursive: true });
1553
- mkdirSync(join2(absDir, STORE_LAYOUT.stateDir), { recursive: true });
1554
- writeFileSync(identityFile, `${JSON.stringify(parsed, null, 2)}
1582
+ const typeDir = join2(absDir, STORE_LAYOUT.knowledgeDir, type);
1583
+ await mkdir(typeDir, { recursive: true });
1584
+ await writeFile(join2(typeDir, ".gitkeep"), "", "utf8");
1585
+ }
1586
+ await mkdir(join2(absDir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR), { recursive: true });
1587
+ await mkdir(join2(absDir, STORE_LAYOUT.bindingsDir), { recursive: true });
1588
+ await mkdir(join2(absDir, STORE_LAYOUT.stateDir), { recursive: true });
1589
+ await writeFile(identityFile, `${JSON.stringify(parsed, null, 2)}
1555
1590
  `, "utf8");
1556
- writeFileSync(join2(absDir, ".gitignore"), STORE_GITIGNORE, "utf8");
1591
+ await writeFile(join2(absDir, ".gitignore"), STORE_GITIGNORE, "utf8");
1557
1592
  if (options.git !== false) {
1558
- git(absDir, ["init", "-b", "main"]);
1593
+ await git(absDir, ["init", "-b", "main"]);
1559
1594
  }
1560
- const readBack = readStoreIdentity(absDir);
1595
+ const readBack = await readStoreIdentityAsync(absDir);
1561
1596
  if (readBack === null) {
1562
1597
  throw new Error(`store init wrote an unrecognizable store.json at ${absDir}`);
1563
1598
  }
1564
1599
  return readBack;
1565
1600
  }
1566
- function listMarkdown(dir) {
1567
- if (!existsSync2(dir)) {
1601
+ async function listMarkdown(dir) {
1602
+ let entries;
1603
+ try {
1604
+ entries = await readdir(dir);
1605
+ } catch {
1568
1606
  return [];
1569
1607
  }
1570
- return readdirSync2(dir).filter((name) => name.endsWith(".md")).sort().map((name) => join2(dir, name));
1608
+ return entries.filter((name) => name.endsWith(".md")).sort().map((name) => join2(dir, name));
1571
1609
  }
1572
- function listStoreKnowledge(store) {
1610
+ async function listStoreKnowledge(store) {
1573
1611
  const refs = [];
1574
1612
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1575
- for (const file of listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, type))) {
1613
+ for (const file of await listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, type))) {
1576
1614
  refs.push({ store_uuid: store.store_uuid, alias: store.alias, type, file });
1577
1615
  }
1578
1616
  }
1579
1617
  return refs;
1580
1618
  }
1581
- function readKnowledgeAcrossStores(stores) {
1582
- return stores.flatMap((store) => listStoreKnowledge(store));
1619
+ async function readKnowledgeAcrossStores(stores) {
1620
+ const lists = await Promise.all(stores.map((store) => listStoreKnowledge(store)));
1621
+ return lists.flat();
1583
1622
  }
1584
1623
  function storeProjectsPath(storeDir) {
1585
1624
  return join2(storeDir, STORE_LAYOUT.projectsFile);
1586
1625
  }
1587
- function readStoreProjects(storeDir) {
1626
+ async function readStoreProjects(storeDir) {
1588
1627
  const path = storeProjectsPath(storeDir);
1589
- if (!existsSync2(path)) {
1590
- return [];
1591
- }
1592
1628
  let raw;
1593
1629
  try {
1594
- raw = JSON.parse(readFileSync2(path, "utf8"));
1630
+ raw = JSON.parse(await readFile2(path, "utf8"));
1595
1631
  } catch {
1596
1632
  return [];
1597
1633
  }
1598
1634
  const parsed = storeProjectsFileSchema.safeParse(raw);
1599
1635
  return parsed.success ? parsed.data.projects : [];
1600
1636
  }
1601
- function storeHasProject(storeDir, id) {
1602
- return readStoreProjects(storeDir).some((p) => p.id === id);
1637
+ async function storeHasProject(storeDir, id) {
1638
+ return (await readStoreProjects(storeDir)).some((p) => p.id === id);
1603
1639
  }
1604
- function addStoreProject(storeDir, project) {
1640
+ async function addStoreProject(storeDir, project) {
1605
1641
  const parsed = storeProjectSchema.parse(project);
1606
- const existing = readStoreProjects(storeDir);
1642
+ const existing = await readStoreProjects(storeDir);
1607
1643
  if (existing.some((p) => p.id === parsed.id)) {
1608
1644
  throw new Error(`project '${parsed.id}' already exists in store at ${storeDir}`);
1609
1645
  }
1610
1646
  const next = [...existing, parsed];
1611
1647
  const validated = storeProjectsFileSchema.parse({ projects: next });
1612
- writeFileSync(storeProjectsPath(storeDir), `${JSON.stringify(validated, null, 2)}
1648
+ await writeFile(storeProjectsPath(storeDir), `${JSON.stringify(validated, null, 2)}
1613
1649
  `, "utf8");
1614
1650
  return validated.projects;
1615
1651
  }
1616
- function aggregatePendingAcrossStores(stores) {
1617
- return stores.flatMap(
1618
- (store) => listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR)).map((file) => ({
1652
+ async function aggregatePendingAcrossStores(stores) {
1653
+ const lists = await Promise.all(stores.map(
1654
+ async (store) => (await listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR))).map((file) => ({
1619
1655
  store_uuid: store.store_uuid,
1620
1656
  alias: store.alias,
1621
1657
  type: STORE_PENDING_DIR,
1622
1658
  file
1623
1659
  }))
1624
- );
1660
+ ));
1661
+ return lists.flat();
1625
1662
  }
1626
1663
 
1627
1664
  // src/store/store-counters.ts
1628
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync2 } from "fs";
1665
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync } from "fs";
1629
1666
  import { join as join3 } from "path";
1630
1667
  function storeCountersPath(storeDir) {
1631
1668
  return join3(storeDir, STORE_LAYOUT.countersFile);
@@ -1633,7 +1670,7 @@ function storeCountersPath(storeDir) {
1633
1670
  function readStoreCounters(storeDir) {
1634
1671
  let raw;
1635
1672
  try {
1636
- raw = readFileSync3(storeCountersPath(storeDir), "utf8");
1673
+ raw = readFileSync2(storeCountersPath(storeDir), "utf8");
1637
1674
  } catch {
1638
1675
  return defaultAgentsMetaCounters();
1639
1676
  }
@@ -1646,10 +1683,39 @@ function readStoreCounters(storeDir) {
1646
1683
  const result = AgentsMetaCountersSchema.safeParse(parsed);
1647
1684
  return result.success ? result.data : defaultAgentsMetaCounters();
1648
1685
  }
1686
+ function preserveCorruptCounters(path, raw) {
1687
+ const corruptedPath = `${path}.corrupted.${Date.now()}`;
1688
+ writeFileSync(corruptedPath, raw, "utf8");
1689
+ return corruptedPath;
1690
+ }
1691
+ function readStoreCountersForAllocation(storeDir) {
1692
+ const path = storeCountersPath(storeDir);
1693
+ if (!existsSync2(path)) {
1694
+ return defaultAgentsMetaCounters();
1695
+ }
1696
+ const raw = readFileSync2(path, "utf8");
1697
+ let parsed;
1698
+ try {
1699
+ parsed = JSON.parse(raw);
1700
+ } catch (error) {
1701
+ const corruptedPath = preserveCorruptCounters(path, raw);
1702
+ throw new Error(
1703
+ `store counters.json is corrupt; forensic copy saved to ${corruptedPath}. Run doctor --fix or reconcileStoreCounters before allocating a new stable_id. Parse error: ${error instanceof Error ? error.message : String(error)}`
1704
+ );
1705
+ }
1706
+ const result = AgentsMetaCountersSchema.safeParse(parsed);
1707
+ if (!result.success) {
1708
+ const corruptedPath = preserveCorruptCounters(path, raw);
1709
+ throw new Error(
1710
+ `store counters.json is schema-invalid; forensic copy saved to ${corruptedPath}. Run doctor --fix or reconcileStoreCounters before allocating a new stable_id.`
1711
+ );
1712
+ }
1713
+ return result.data;
1714
+ }
1649
1715
  async function allocateStoreKnowledgeId(layer, type, storeDir) {
1650
1716
  const countersPath = storeCountersPath(storeDir);
1651
1717
  return withFileLock(`${countersPath}.lock`, async () => {
1652
- const counters = readStoreCounters(storeDir);
1718
+ const counters = readStoreCountersForAllocation(storeDir);
1653
1719
  const { id, nextCounters } = allocateKnowledgeId(layer, type, counters);
1654
1720
  await atomicWriteJson(countersPath, nextCounters, { indent: 2 });
1655
1721
  return id;
@@ -1658,7 +1724,7 @@ async function allocateStoreKnowledgeId(layer, type, storeDir) {
1658
1724
  function readEntryId(file) {
1659
1725
  let content;
1660
1726
  try {
1661
- content = readFileSync3(file, "utf8");
1727
+ content = readFileSync2(file, "utf8");
1662
1728
  } catch {
1663
1729
  return null;
1664
1730
  }
@@ -1678,10 +1744,10 @@ function reconcileStoreCounters(storeDir) {
1678
1744
  };
1679
1745
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1680
1746
  const dir = join3(storeDir, STORE_LAYOUT.knowledgeDir, type);
1681
- if (!existsSync3(dir)) {
1747
+ if (!existsSync2(dir)) {
1682
1748
  continue;
1683
1749
  }
1684
- for (const name of readdirSync3(dir)) {
1750
+ for (const name of readdirSync2(dir)) {
1685
1751
  if (!name.endsWith(".md")) {
1686
1752
  continue;
1687
1753
  }
@@ -1694,52 +1760,28 @@ function reconcileStoreCounters(storeDir) {
1694
1760
  next[layerKey][typeCode] = Math.max(next[layerKey][typeCode], parsed.counter);
1695
1761
  }
1696
1762
  }
1697
- writeFileSync2(storeCountersPath(storeDir), `${JSON.stringify(next, null, 2)}
1763
+ writeFileSync(storeCountersPath(storeDir), `${JSON.stringify(next, null, 2)}
1698
1764
  `, "utf8");
1699
1765
  return next;
1700
1766
  }
1701
1767
 
1702
- // src/store/global-config-io.ts
1703
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1704
- import { homedir } from "os";
1705
- import { join as join4 } from "path";
1706
- function resolveGlobalRoot() {
1707
- return join4(process.env.FABRIC_HOME ?? homedir(), ".fabric");
1708
- }
1709
- function globalConfigPath(globalRoot = resolveGlobalRoot()) {
1710
- return join4(globalRoot, "fabric-global.json");
1711
- }
1712
- function loadGlobalConfig(globalRoot = resolveGlobalRoot()) {
1713
- const path = globalConfigPath(globalRoot);
1714
- if (!existsSync4(path)) {
1715
- return null;
1716
- }
1717
- return globalConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
1718
- }
1719
- function saveGlobalConfig(config, globalRoot = resolveGlobalRoot()) {
1720
- const validated = globalConfigSchema.parse(config);
1721
- mkdirSync2(globalRoot, { recursive: true });
1722
- writeFileSync3(globalConfigPath(globalRoot), `${JSON.stringify(validated, null, 2)}
1723
- `, "utf8");
1724
- }
1725
-
1726
1768
  // src/store/project-config-io.ts
1727
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1728
- import { join as join5 } from "path";
1769
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1770
+ import { join as join4 } from "path";
1729
1771
  function projectConfigPath(projectRoot) {
1730
- return join5(projectRoot, ".fabric", "fabric-config.json");
1772
+ return join4(projectRoot, ".fabric", "fabric-config.json");
1731
1773
  }
1732
1774
  function loadProjectConfig(projectRoot) {
1733
1775
  const path = projectConfigPath(projectRoot);
1734
- if (!existsSync5(path)) {
1776
+ if (!existsSync3(path)) {
1735
1777
  return null;
1736
1778
  }
1737
- return fabricConfigSchema.parse(JSON.parse(readFileSync5(path, "utf8")));
1779
+ return fabricConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
1738
1780
  }
1739
1781
  function saveProjectConfig(config, projectRoot) {
1740
1782
  const validated = fabricConfigSchema.parse(config);
1741
- mkdirSync3(join5(projectRoot, ".fabric"), { recursive: true });
1742
- writeFileSync4(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
1783
+ mkdirSync(join4(projectRoot, ".fabric"), { recursive: true });
1784
+ writeFileSync2(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
1743
1785
  `, "utf8");
1744
1786
  }
1745
1787
 
@@ -1755,6 +1797,7 @@ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1755
1797
  mountedStores: global.stores.map((s) => ({
1756
1798
  store_uuid: s.store_uuid,
1757
1799
  alias: s.alias,
1800
+ ...s.mount_name === void 0 ? {} : { mount_name: s.mount_name },
1758
1801
  ...s.remote === void 0 ? {} : { remote: s.remote },
1759
1802
  writable: s.writable ?? true,
1760
1803
  personal: s.personal ?? false
@@ -1765,22 +1808,43 @@ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1765
1808
  ...r.suggested_remote === void 0 ? {} : { suggested_remote: r.suggested_remote }
1766
1809
  })
1767
1810
  ),
1768
- ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store }
1811
+ ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store },
1812
+ writeRoutes: project?.write_routes ?? [],
1813
+ ...project?.default_write_store === void 0 ? {} : { defaultWriteAlias: project.default_write_store }
1769
1814
  };
1770
1815
  }
1771
1816
 
1772
1817
  // src/store/secret-scan.ts
1773
- var SECRET_RULES = [
1774
- { rule: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ },
1775
- { rule: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ },
1776
- { rule: "openai-api-key", re: /\bsk-[A-Za-z0-9]{20,}\b/ },
1777
- { rule: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/ },
1778
- { rule: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
1818
+ var CREDENTIAL_RULES = [
1819
+ { rule: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/, category: "credential" },
1820
+ { rule: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, category: "credential" },
1821
+ { rule: "openai-api-key", re: /\bsk-[A-Za-z0-9]{20,}\b/, category: "credential" },
1822
+ { rule: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/, category: "credential" },
1823
+ { rule: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, category: "credential" },
1779
1824
  {
1780
1825
  rule: "credential-assignment",
1781
- re: /(?:password|passwd|secret|api[_-]?key|access[_-]?token|token)\s*[:=]\s*['"][^'"\s]{8,}['"]/i
1826
+ re: /(?:password|passwd|secret|api[_-]?key|access[_-]?token|token)\s*[:=]\s*(?:"[^'"\s]{8,}"|'[^'"\s]{8,}'|[A-Za-z0-9_./+=:@-]{8,})/i,
1827
+ category: "credential"
1782
1828
  }
1783
1829
  ];
1830
+ var PII_RULES = [
1831
+ {
1832
+ rule: "email-address",
1833
+ re: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,
1834
+ category: "pii"
1835
+ },
1836
+ {
1837
+ rule: "ipv4-address",
1838
+ re: /\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b/,
1839
+ category: "pii"
1840
+ },
1841
+ {
1842
+ rule: "phone-number",
1843
+ re: /(?<!\d)(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}(?!\d)/,
1844
+ category: "pii"
1845
+ }
1846
+ ];
1847
+ var SECRET_RULES = [...CREDENTIAL_RULES, ...PII_RULES];
1784
1848
  function scanForSecrets(content) {
1785
1849
  const findings = [];
1786
1850
  const lines = content.split(/\r?\n/u);
@@ -1794,12 +1858,26 @@ function scanForSecrets(content) {
1794
1858
  return findings;
1795
1859
  }
1796
1860
  function hasSecrets(content) {
1797
- return scanForSecrets(content).length > 0;
1861
+ const lines = content.split(/\r?\n/u);
1862
+ for (const line of lines) {
1863
+ for (const { re } of CREDENTIAL_RULES) {
1864
+ if (re.test(line)) {
1865
+ return true;
1866
+ }
1867
+ }
1868
+ }
1869
+ return false;
1798
1870
  }
1799
1871
  var REDACTION_PLACEHOLDER_PREFIX = "[REDACTED:";
1800
1872
  function redactSecrets(content) {
1873
+ return redactByRules(content, SECRET_RULES);
1874
+ }
1875
+ function redactPii(content) {
1876
+ return redactByRules(content, PII_RULES);
1877
+ }
1878
+ function redactByRules(content, rules) {
1801
1879
  let out = content;
1802
- for (const { rule, re } of SECRET_RULES) {
1880
+ for (const { rule, re } of rules) {
1803
1881
  const flags = re.flags.includes("i") ? "gi" : "g";
1804
1882
  out = out.replace(new RegExp(re.source, flags), `${REDACTION_PLACEHOLDER_PREFIX}${rule}]`);
1805
1883
  }
@@ -1922,10 +2000,10 @@ function buildDebugBundle(input) {
1922
2000
  }
1923
2001
 
1924
2002
  // src/schemas/provenance.ts
1925
- import { z as z12 } from "zod";
1926
- var knowledgeProvenanceSchema = z12.object({
2003
+ import { z as z10 } from "zod";
2004
+ var knowledgeProvenanceSchema = z10.object({
1927
2005
  store_uuid: storeUuidSchema,
1928
- alias: z12.string().min(1),
2006
+ alias: z10.string().min(1),
1929
2007
  local_id: localKnowledgeIdSchema,
1930
2008
  global_ref: globalRefSchema,
1931
2009
  // Optional scope coordinate of the entry (resolution axis); present when the
@@ -1934,7 +2012,7 @@ var knowledgeProvenanceSchema = z12.object({
1934
2012
  }).strict();
1935
2013
 
1936
2014
  // src/schemas/mcp-store-contracts.ts
1937
- import { z as z13 } from "zod";
2015
+ import { z as z11 } from "zod";
1938
2016
  var MCP_STORE_AWARE_TOOLS = [
1939
2017
  "fab_recall",
1940
2018
  "fab_plan_context",
@@ -1943,14 +2021,14 @@ var MCP_STORE_AWARE_TOOLS = [
1943
2021
  "fab_extract_knowledge",
1944
2022
  "fab_review"
1945
2023
  ];
1946
- var storeAwareEntrySchema = z13.object({
1947
- stable_id: z13.string(),
2024
+ var storeAwareEntrySchema = z11.object({
2025
+ stable_id: z11.string(),
1948
2026
  global_ref: globalRefSchema,
1949
2027
  provenance: knowledgeProvenanceSchema
1950
2028
  }).strict();
1951
- var writtenToStoreSchema = z13.object({
2029
+ var writtenToStoreSchema = z11.object({
1952
2030
  store_uuid: storeUuidSchema,
1953
- alias: z13.string().min(1)
2031
+ alias: z11.string().min(1)
1954
2032
  }).strict();
1955
2033
  var MCP_STORE_AWARE_CONTRACTS = {
1956
2034
  fab_recall: { tool: "fab_recall", surfacesEntries: true, echoesWrittenStore: false },
@@ -1963,7 +2041,7 @@ var MCP_STORE_AWARE_CONTRACTS = {
1963
2041
  fab_archive_scan: {
1964
2042
  tool: "fab_archive_scan",
1965
2043
  surfacesEntries: false,
1966
- echoesWrittenStore: true
2044
+ echoesWrittenStore: false
1967
2045
  },
1968
2046
  fab_extract_knowledge: {
1969
2047
  tool: "fab_extract_knowledge",
@@ -1974,64 +2052,184 @@ var MCP_STORE_AWARE_CONTRACTS = {
1974
2052
  };
1975
2053
 
1976
2054
  // src/schemas/bindings-snapshot.ts
1977
- import { z as z14 } from "zod";
1978
- var resolvedBindingsSnapshotSchema = z14.object({
2055
+ import { z as z12 } from "zod";
2056
+ var resolvedBindingsSnapshotSchema = z12.object({
1979
2057
  // Schema version of the snapshot document.
1980
- version: z14.literal(1),
2058
+ version: z12.literal(1),
1981
2059
  // The project this snapshot is bound to (S13).
1982
- project_id: z14.string().min(1),
2060
+ project_id: z12.string().min(1),
2061
+ // The local runtime binding key. Defaults to project_id for standard repos;
2062
+ // worktrees may isolate state by setting fabric-config.workspace_binding_id.
2063
+ workspace_binding_id: z12.string().min(1),
1983
2064
  // ISO-8601 generation timestamp (provenance / staleness signal for doctor).
1984
- generated_at: z14.string().min(1),
2065
+ generated_at: z12.string().min(1),
1985
2066
  // Pre-resolved read-set (required_stores ∪ implicit personal + warnings).
1986
2067
  read_set: storeReadSetSchema,
1987
2068
  // Pre-resolved active write target for non-personal scopes (null if none).
1988
- write_target: writeTargetSchema.nullable()
2069
+ write_target: writeTargetSchema.nullable(),
2070
+ // Pre-computed store-backed knowledge counts, snapshotted at write time.
2071
+ // PROVENANCE ONLY: these are store-global counts cached in a per-workspace
2072
+ // file, so they go stale whenever store content changes out-of-band (a `git
2073
+ // pull` in the store repo, a sync run from a *different* bound workspace,
2074
+ // etc.) — the snapshot is only regenerated by install/sync/store-ops in the
2075
+ // workspace that runs them (KT-PIT-0017). Hooks MUST NOT trust these numbers
2076
+ // for nudges; they recount live from `knowledge_store_dirs`. Retained for
2077
+ // doctor provenance + backward-compatible fallback when dirs are absent.
2078
+ knowledge_stats: z12.object({
2079
+ pending_count: z12.number().int().nonnegative(),
2080
+ canonical_count: z12.number().int().nonnegative(),
2081
+ oldest_pending_mtime_ms: z12.number().nonnegative().nullable()
2082
+ }).strict().optional(),
2083
+ // Resolved absolute store ROOT dirs the knowledge_stats were derived from.
2084
+ // STABLE across content sync — they only change when mounts/bindings change,
2085
+ // which DOES regenerate the snapshot. Hooks walk `<dir>/knowledge/<type>` +
2086
+ // `<dir>/knowledge/pending` LIVE off these roots so nudge counts are always
2087
+ // fresh regardless of how store content changed (the underseed / review-
2088
+ // backlog false-positive root cure; pairs with knowledge_stats above).
2089
+ knowledge_store_dirs: z12.array(z12.string().min(1)).optional()
1989
2090
  }).strict();
1990
2091
 
1991
2092
  // src/store/bindings.ts
1992
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1993
- import { join as join6, resolve, sep } from "path";
1994
- var SAFE_PROJECT_ID = /^[A-Za-z0-9._-]+$/;
1995
- function assertSafeProjectId(projectId) {
1996
- if (!SAFE_PROJECT_ID.test(projectId) || projectId.includes("..")) {
2093
+ import {
2094
+ existsSync as existsSync4,
2095
+ mkdirSync as mkdirSync2,
2096
+ readdirSync as readdirSync3,
2097
+ readFileSync as readFileSync4,
2098
+ renameSync,
2099
+ statSync,
2100
+ unlinkSync,
2101
+ writeFileSync as writeFileSync3
2102
+ } from "fs";
2103
+ import { join as join5, resolve, sep } from "path";
2104
+ var SAFE_BINDING_ID = /^[A-Za-z0-9._-]+$/;
2105
+ function assertSafeBindingId(bindingId) {
2106
+ if (!SAFE_BINDING_ID.test(bindingId) || bindingId.includes("..")) {
1997
2107
  throw new Error(
1998
- `bindingsSnapshotPath: refusing unsafe project_id ${JSON.stringify(projectId)} (must match ${SAFE_PROJECT_ID} and contain no "..")`
2108
+ `bindingsSnapshotPath: refusing unsafe workspace_binding_id ${JSON.stringify(bindingId)} (must match ${SAFE_BINDING_ID} and contain no "..")`
1999
2109
  );
2000
2110
  }
2001
2111
  }
2002
- function bindingsSnapshotPath(globalRoot, projectId) {
2003
- assertSafeProjectId(projectId);
2004
- const bindingsDir = resolve(join6(globalRoot, GLOBAL_STATE_DIR, GLOBAL_BINDINGS_DIR));
2005
- const path = resolve(join6(bindingsDir, `${projectId}_resolved.json`));
2112
+ function bindingsSnapshotPath(globalRoot, bindingId) {
2113
+ assertSafeBindingId(bindingId);
2114
+ const bindingsDir = resolve(join5(globalRoot, GLOBAL_STATE_DIR, GLOBAL_BINDINGS_DIR));
2115
+ const path = resolve(join5(bindingsDir, `${bindingId}_resolved.json`));
2006
2116
  if (path !== bindingsDir && !path.startsWith(bindingsDir + sep)) {
2007
- throw new Error(`bindingsSnapshotPath: resolved path escapes bindings dir for ${JSON.stringify(projectId)}`);
2117
+ throw new Error(`bindingsSnapshotPath: resolved path escapes bindings dir for ${JSON.stringify(bindingId)}`);
2008
2118
  }
2009
2119
  return path;
2010
2120
  }
2121
+ function resolveWorkspaceBindingId(config) {
2122
+ return config.workspace_binding_id ?? config.project_id;
2123
+ }
2124
+ function countMarkdownFiles(dir) {
2125
+ let count = 0;
2126
+ let oldestMtimeMs = null;
2127
+ if (!existsSync4(dir)) {
2128
+ return { count, oldestMtimeMs };
2129
+ }
2130
+ let entries;
2131
+ try {
2132
+ entries = readdirSync3(dir, { withFileTypes: true });
2133
+ } catch {
2134
+ return { count, oldestMtimeMs };
2135
+ }
2136
+ for (const entry of entries) {
2137
+ const fullPath = join5(dir, entry.name);
2138
+ if (entry.isDirectory()) {
2139
+ const nested = countMarkdownFiles(fullPath);
2140
+ count += nested.count;
2141
+ if (nested.oldestMtimeMs !== null && (oldestMtimeMs === null || nested.oldestMtimeMs < oldestMtimeMs)) {
2142
+ oldestMtimeMs = nested.oldestMtimeMs;
2143
+ }
2144
+ continue;
2145
+ }
2146
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
2147
+ continue;
2148
+ }
2149
+ let mtimeMs;
2150
+ try {
2151
+ mtimeMs = statSync(fullPath).mtimeMs;
2152
+ } catch {
2153
+ continue;
2154
+ }
2155
+ count += 1;
2156
+ if (oldestMtimeMs === null || mtimeMs < oldestMtimeMs) {
2157
+ oldestMtimeMs = mtimeMs;
2158
+ }
2159
+ }
2160
+ return { count, oldestMtimeMs };
2161
+ }
2162
+ function collectKnowledgeStats(globalRoot, resolveInput, readSet) {
2163
+ let pendingCount = 0;
2164
+ let oldestPendingMtimeMs = null;
2165
+ let canonicalCount = 0;
2166
+ const storeDirs = [];
2167
+ for (const store of readSet.stores) {
2168
+ const mounted = resolveInput.mountedStores.find((entry) => entry.store_uuid === store.store_uuid) ?? {
2169
+ store_uuid: store.store_uuid
2170
+ };
2171
+ const storeDir = join5(globalRoot, storeRelativePathForMount(mounted));
2172
+ storeDirs.push(storeDir);
2173
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
2174
+ const canonical = countMarkdownFiles(join5(storeDir, STORE_LAYOUT.knowledgeDir, type));
2175
+ canonicalCount += canonical.count;
2176
+ }
2177
+ const pending = countMarkdownFiles(join5(storeDir, STORE_LAYOUT.knowledgeDir, "pending"));
2178
+ pendingCount += pending.count;
2179
+ if (pending.oldestMtimeMs !== null && (oldestPendingMtimeMs === null || pending.oldestMtimeMs < oldestPendingMtimeMs)) {
2180
+ oldestPendingMtimeMs = pending.oldestMtimeMs;
2181
+ }
2182
+ }
2183
+ return {
2184
+ stats: {
2185
+ pending_count: pendingCount,
2186
+ canonical_count: canonicalCount,
2187
+ oldest_pending_mtime_ms: oldestPendingMtimeMs
2188
+ },
2189
+ storeDirs
2190
+ };
2191
+ }
2192
+ function atomicWriteJsonSync(path, value) {
2193
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
2194
+ try {
2195
+ writeFileSync3(tmpPath, `${JSON.stringify(value, null, 2)}
2196
+ `, "utf8");
2197
+ renameSync(tmpPath, path);
2198
+ } catch (error) {
2199
+ try {
2200
+ unlinkSync(tmpPath);
2201
+ } catch {
2202
+ }
2203
+ throw error;
2204
+ }
2205
+ }
2011
2206
  function writeBindingsSnapshot(options) {
2012
2207
  const resolver = createStoreResolver();
2013
2208
  const read_set = resolver.resolveReadSet(options.resolveInput);
2014
2209
  const { target } = resolver.resolveWriteTarget(options.resolveInput, options.writeScope);
2210
+ const { stats, storeDirs } = collectKnowledgeStats(options.globalRoot, options.resolveInput, read_set);
2015
2211
  const snapshot = resolvedBindingsSnapshotSchema.parse({
2016
2212
  version: 1,
2017
2213
  project_id: options.projectId,
2214
+ workspace_binding_id: options.workspaceBindingId ?? options.projectId,
2018
2215
  generated_at: options.now,
2019
2216
  read_set,
2020
- write_target: target
2217
+ write_target: target,
2218
+ knowledge_stats: stats,
2219
+ knowledge_store_dirs: storeDirs
2021
2220
  });
2022
- const path = bindingsSnapshotPath(options.globalRoot, options.projectId);
2023
- mkdirSync4(join6(path, ".."), { recursive: true });
2024
- writeFileSync5(path, `${JSON.stringify(snapshot, null, 2)}
2025
- `, "utf8");
2221
+ const path = bindingsSnapshotPath(options.globalRoot, snapshot.workspace_binding_id);
2222
+ mkdirSync2(join5(path, ".."), { recursive: true });
2223
+ atomicWriteJsonSync(path, snapshot);
2026
2224
  return snapshot;
2027
2225
  }
2028
2226
  function readBindingsSnapshot(globalRoot, projectId) {
2029
2227
  const path = bindingsSnapshotPath(globalRoot, projectId);
2030
- if (!existsSync6(path)) {
2228
+ if (!existsSync4(path)) {
2031
2229
  return null;
2032
2230
  }
2033
2231
  try {
2034
- const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync6(path, "utf8")));
2232
+ const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync4(path, "utf8")));
2035
2233
  return parsed.success ? parsed.data : null;
2036
2234
  } catch {
2037
2235
  return null;
@@ -2040,7 +2238,9 @@ function readBindingsSnapshot(globalRoot, projectId) {
2040
2238
 
2041
2239
  // src/store/store-lifecycle.ts
2042
2240
  function findMountedStore(config, aliasOrUuid) {
2043
- return config.stores.find((s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid);
2241
+ return config.stores.find(
2242
+ (s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid || s.mount_name === aliasOrUuid
2243
+ );
2044
2244
  }
2045
2245
  function addMountedStore(config, store) {
2046
2246
  const aliasClash = config.stores.find(
@@ -2051,12 +2251,32 @@ function addMountedStore(config, store) {
2051
2251
  `alias '${store.alias}' already mounts store ${aliasClash.store_uuid}; choose another alias`
2052
2252
  );
2053
2253
  }
2254
+ const mountNameClash = store.mount_name === void 0 ? void 0 : config.stores.find(
2255
+ (s) => s.mount_name === store.mount_name && s.store_uuid !== store.store_uuid
2256
+ );
2257
+ if (mountNameClash !== void 0) {
2258
+ throw new Error(
2259
+ `mount_name '${store.mount_name}' already maps to store ${mountNameClash.store_uuid}; choose another mount_name`
2260
+ );
2261
+ }
2054
2262
  const sanitized = store.remote === void 0 ? store : { ...store, remote: scrubRemoteUrl(store.remote) };
2055
2263
  store = sanitized;
2056
2264
  const existing = config.stores.find((s) => s.store_uuid === store.store_uuid);
2057
2265
  const stores = existing === void 0 ? [...config.stores, store] : config.stores.map((s) => s.store_uuid === store.store_uuid ? store : s);
2058
2266
  return { ...config, stores };
2059
2267
  }
2268
+ function disambiguateAlias(existingAliases, desired) {
2269
+ const taken = new Set(existingAliases);
2270
+ if (!taken.has(desired)) {
2271
+ return desired;
2272
+ }
2273
+ for (let n = 2; ; n += 1) {
2274
+ const candidate = `${desired}-${n}`;
2275
+ if (!taken.has(candidate)) {
2276
+ return candidate;
2277
+ }
2278
+ }
2279
+ }
2060
2280
  function detachMountedStore(config, alias) {
2061
2281
  const detached = config.stores.find((s) => s.alias === alias) ?? null;
2062
2282
  if (detached === null) {
@@ -2085,155 +2305,155 @@ function explainStore(config, alias) {
2085
2305
  }
2086
2306
 
2087
2307
  // src/schemas/forensic-report.ts
2088
- import { z as z15 } from "zod";
2089
- var forensicCodeSampleSchema = z15.object({
2090
- path: z15.string(),
2091
- lines: z15.string(),
2092
- snippet: z15.string(),
2093
- pattern_hint: z15.string()
2094
- });
2095
- var forensicEvidenceAnchorSchema = z15.object({
2096
- file: z15.string(),
2097
- line: z15.string(),
2098
- snippet: z15.string()
2099
- });
2100
- var forensicAssertionCoverageSchema = z15.object({
2101
- ratio: z15.number().min(0).max(1),
2102
- total: z15.number().int().nonnegative(),
2103
- matched: z15.number().int().nonnegative(),
2104
- co_occurring_patterns: z15.array(z15.string())
2105
- });
2106
- var forensicAssertionSchema = z15.object({
2107
- type: z15.enum(["framework", "pattern", "invariant", "domain"]),
2108
- statement: z15.string(),
2109
- confidence: z15.enum(["HIGH", "MEDIUM", "LOW"]),
2110
- evidence: z15.array(forensicEvidenceAnchorSchema),
2308
+ import { z as z13 } from "zod";
2309
+ var forensicCodeSampleSchema = z13.object({
2310
+ path: z13.string(),
2311
+ lines: z13.string(),
2312
+ snippet: z13.string(),
2313
+ pattern_hint: z13.string()
2314
+ });
2315
+ var forensicEvidenceAnchorSchema = z13.object({
2316
+ file: z13.string(),
2317
+ line: z13.string(),
2318
+ snippet: z13.string()
2319
+ });
2320
+ var forensicAssertionCoverageSchema = z13.object({
2321
+ ratio: z13.number().min(0).max(1),
2322
+ total: z13.number().int().nonnegative(),
2323
+ matched: z13.number().int().nonnegative(),
2324
+ co_occurring_patterns: z13.array(z13.string())
2325
+ });
2326
+ var forensicAssertionSchema = z13.object({
2327
+ type: z13.enum(["framework", "pattern", "invariant", "domain"]),
2328
+ statement: z13.string(),
2329
+ confidence: z13.enum(["HIGH", "MEDIUM", "LOW"]),
2330
+ evidence: z13.array(forensicEvidenceAnchorSchema),
2111
2331
  coverage: forensicAssertionCoverageSchema,
2112
- proposed_rule: z15.string().optional(),
2113
- alternatives: z15.array(z15.string()).optional()
2114
- });
2115
- var forensicTopologySchema = z15.object({
2116
- total_files: z15.number().int().nonnegative(),
2117
- by_ext: z15.record(z15.number().int().nonnegative()),
2118
- key_dirs: z15.array(z15.string()),
2119
- max_depth: z15.number().int().nonnegative()
2120
- });
2121
- var forensicEntryPointSchema = z15.object({
2122
- path: z15.string(),
2123
- reason: z15.string(),
2124
- size_bytes: z15.number().int().nonnegative().optional()
2125
- });
2126
- var forensicFrameworkSchema = z15.object({
2127
- kind: z15.string(),
2128
- version: z15.string(),
2129
- subkind: z15.string(),
2130
- evidence: z15.array(z15.string())
2131
- });
2132
- var forensicReadmeSchema = z15.object({
2133
- quality: z15.enum(["missing", "stub", "ok"]),
2134
- line_count: z15.number().int().nonnegative(),
2135
- has_contributing: z15.boolean()
2136
- });
2137
- var candidateFileEntrySchema = z15.object({
2138
- path: z15.string(),
2139
- family: z15.enum(["entry", "component", "config", "test", "domain"]),
2140
- rationale: z15.string()
2141
- });
2142
- var forensicSamplingBudgetSchema = z15.object({
2143
- max_files: z15.literal(15),
2144
- max_lines_per_file: z15.literal(100)
2145
- });
2146
- var forensicReportSchema = z15.object({
2147
- version: z15.string(),
2148
- generated_at: z15.string(),
2149
- generated_by: z15.string(),
2150
- target: z15.string(),
2151
- project_name: z15.string(),
2332
+ proposed_rule: z13.string().optional(),
2333
+ alternatives: z13.array(z13.string()).optional()
2334
+ });
2335
+ var forensicTopologySchema = z13.object({
2336
+ total_files: z13.number().int().nonnegative(),
2337
+ by_ext: z13.record(z13.number().int().nonnegative()),
2338
+ key_dirs: z13.array(z13.string()),
2339
+ max_depth: z13.number().int().nonnegative()
2340
+ });
2341
+ var forensicEntryPointSchema = z13.object({
2342
+ path: z13.string(),
2343
+ reason: z13.string(),
2344
+ size_bytes: z13.number().int().nonnegative().optional()
2345
+ });
2346
+ var forensicFrameworkSchema = z13.object({
2347
+ kind: z13.string(),
2348
+ version: z13.string(),
2349
+ subkind: z13.string(),
2350
+ evidence: z13.array(z13.string())
2351
+ });
2352
+ var forensicReadmeSchema = z13.object({
2353
+ quality: z13.enum(["missing", "stub", "ok"]),
2354
+ line_count: z13.number().int().nonnegative(),
2355
+ has_contributing: z13.boolean()
2356
+ });
2357
+ var candidateFileEntrySchema = z13.object({
2358
+ path: z13.string(),
2359
+ family: z13.enum(["entry", "component", "config", "test", "domain"]),
2360
+ rationale: z13.string()
2361
+ });
2362
+ var forensicSamplingBudgetSchema = z13.object({
2363
+ max_files: z13.literal(15),
2364
+ max_lines_per_file: z13.literal(100)
2365
+ });
2366
+ var forensicReportSchema = z13.object({
2367
+ version: z13.string(),
2368
+ generated_at: z13.string(),
2369
+ generated_by: z13.string(),
2370
+ target: z13.string(),
2371
+ project_name: z13.string(),
2152
2372
  framework: forensicFrameworkSchema,
2153
2373
  topology: forensicTopologySchema,
2154
- entry_points: z15.array(forensicEntryPointSchema),
2155
- code_samples: z15.array(forensicCodeSampleSchema),
2156
- assertions: z15.array(forensicAssertionSchema),
2157
- candidate_files: z15.array(candidateFileEntrySchema),
2374
+ entry_points: z13.array(forensicEntryPointSchema),
2375
+ code_samples: z13.array(forensicCodeSampleSchema),
2376
+ assertions: z13.array(forensicAssertionSchema),
2377
+ candidate_files: z13.array(candidateFileEntrySchema),
2158
2378
  sampling_budget: forensicSamplingBudgetSchema,
2159
2379
  readme: forensicReadmeSchema,
2160
- recommendations_for_skill: z15.array(z15.string()).optional()
2380
+ recommendations_for_skill: z13.array(z13.string()).optional()
2161
2381
  });
2162
2382
 
2163
2383
  // src/schemas/init-context.ts
2164
- import { z as z16 } from "zod";
2165
- var initContextFrameworkSchema = z16.object({
2166
- kind: z16.string(),
2167
- version: z16.string(),
2168
- subkind: z16.string()
2169
- });
2170
- var initContextInvariantConfidenceSnapshotSchema = z16.object({
2171
- confidence: z16.enum(["HIGH", "MEDIUM", "LOW"]),
2172
- evidence_refs: z16.array(z16.string())
2173
- });
2174
- var initContextSourceEvidenceSchema = z16.object({
2175
- file: z16.string(),
2176
- lines: z16.string()
2177
- });
2178
- var initContextInvariantSchema = z16.object({
2179
- type: z16.enum(["ban", "require", "protect"]),
2180
- rule: z16.string(),
2181
- rationale: z16.string().optional(),
2384
+ import { z as z14 } from "zod";
2385
+ var initContextFrameworkSchema = z14.object({
2386
+ kind: z14.string(),
2387
+ version: z14.string(),
2388
+ subkind: z14.string()
2389
+ });
2390
+ var initContextInvariantConfidenceSnapshotSchema = z14.object({
2391
+ confidence: z14.enum(["HIGH", "MEDIUM", "LOW"]),
2392
+ evidence_refs: z14.array(z14.string())
2393
+ });
2394
+ var initContextSourceEvidenceSchema = z14.object({
2395
+ file: z14.string(),
2396
+ lines: z14.string()
2397
+ });
2398
+ var initContextInvariantSchema = z14.object({
2399
+ type: z14.enum(["ban", "require", "protect"]),
2400
+ rule: z14.string(),
2401
+ rationale: z14.string().optional(),
2182
2402
  confidence_snapshot: initContextInvariantConfidenceSnapshotSchema.optional(),
2183
- source_evidence: z16.array(initContextSourceEvidenceSchema).optional()
2184
- });
2185
- var initContextDomainGroupSchema = z16.object({
2186
- name: z16.string(),
2187
- paths: z16.array(z16.string()),
2188
- summary: z16.string().optional(),
2189
- topology_type: z16.enum(["mirror", "cross-cutting"]).optional(),
2190
- target_path: z16.string().optional()
2191
- });
2192
- var initContextInterviewTrailEntrySchema = z16.object({
2193
- phase: z16.string(),
2194
- question: z16.string(),
2195
- answer: z16.string(),
2196
- presentation: z16.string().optional(),
2197
- user_corrections: z16.array(z16.string()).optional()
2198
- });
2199
- var initContextSchema = z16.object({
2403
+ source_evidence: z14.array(initContextSourceEvidenceSchema).optional()
2404
+ });
2405
+ var initContextDomainGroupSchema = z14.object({
2406
+ name: z14.string(),
2407
+ paths: z14.array(z14.string()),
2408
+ summary: z14.string().optional(),
2409
+ topology_type: z14.enum(["mirror", "cross-cutting"]).optional(),
2410
+ target_path: z14.string().optional()
2411
+ });
2412
+ var initContextInterviewTrailEntrySchema = z14.object({
2413
+ phase: z14.string(),
2414
+ question: z14.string(),
2415
+ answer: z14.string(),
2416
+ presentation: z14.string().optional(),
2417
+ user_corrections: z14.array(z14.string()).optional()
2418
+ });
2419
+ var initContextSchema = z14.object({
2200
2420
  framework: initContextFrameworkSchema,
2201
- architecture_patterns: z16.array(z16.string()),
2202
- invariants: z16.array(initContextInvariantSchema),
2203
- domain_groups: z16.array(initContextDomainGroupSchema),
2204
- interview_trail: z16.array(initContextInterviewTrailEntrySchema),
2205
- forensic_ref: z16.string()
2421
+ architecture_patterns: z14.array(z14.string()),
2422
+ invariants: z14.array(initContextInvariantSchema),
2423
+ domain_groups: z14.array(initContextDomainGroupSchema),
2424
+ interview_trail: z14.array(initContextInterviewTrailEntrySchema),
2425
+ forensic_ref: z14.string()
2206
2426
  });
2207
2427
 
2208
2428
  // src/schemas/events.ts
2209
- import { z as z17 } from "zod";
2210
- var metaUpdatedEventSchema = z17.object({
2211
- type: z17.literal("meta:updated"),
2429
+ import { z as z15 } from "zod";
2430
+ var metaUpdatedEventSchema = z15.object({
2431
+ type: z15.literal("meta:updated"),
2212
2432
  payload: agentsMetaSchema
2213
2433
  });
2214
- var lockDriftEventSchema = z17.object({
2215
- type: z17.literal("lock:drift"),
2216
- payload: z17.object({
2217
- locked: z17.array(humanLockEntrySchema),
2218
- drifted: z17.array(humanLockEntrySchema)
2434
+ var lockDriftEventSchema = z15.object({
2435
+ type: z15.literal("lock:drift"),
2436
+ payload: z15.object({
2437
+ locked: z15.array(humanLockEntrySchema),
2438
+ drifted: z15.array(humanLockEntrySchema)
2219
2439
  })
2220
2440
  });
2221
- var lockApprovedEventSchema = z17.object({
2222
- type: z17.literal("lock:approved"),
2223
- payload: z17.object({
2224
- locked: z17.array(humanLockEntrySchema),
2225
- approved: z17.array(humanLockEntrySchema)
2441
+ var lockApprovedEventSchema = z15.object({
2442
+ type: z15.literal("lock:approved"),
2443
+ payload: z15.object({
2444
+ locked: z15.array(humanLockEntrySchema),
2445
+ approved: z15.array(humanLockEntrySchema)
2226
2446
  })
2227
2447
  });
2228
- var ledgerAppendedEventSchema = z17.object({
2229
- type: z17.literal("ledger:appended"),
2448
+ var ledgerAppendedEventSchema = z15.object({
2449
+ type: z15.literal("ledger:appended"),
2230
2450
  payload: ledgerEntrySchema
2231
2451
  });
2232
- var driftDetectedEventSchema = z17.object({
2233
- type: z17.literal("drift:detected"),
2452
+ var driftDetectedEventSchema = z15.object({
2453
+ type: z15.literal("drift:detected"),
2234
2454
  payload: forensicReportSchema
2235
2455
  });
2236
- var fabricEventSchema = z17.discriminatedUnion("type", [
2456
+ var fabricEventSchema = z15.discriminatedUnion("type", [
2237
2457
  metaUpdatedEventSchema,
2238
2458
  lockDriftEventSchema,
2239
2459
  lockApprovedEventSchema,
@@ -2242,7 +2462,7 @@ var fabricEventSchema = z17.discriminatedUnion("type", [
2242
2462
  ]);
2243
2463
 
2244
2464
  // src/schemas/event-ledger.ts
2245
- import { z as z18 } from "zod";
2465
+ import { z as z16 } from "zod";
2246
2466
 
2247
2467
  // src/cite-line-parser.ts
2248
2468
  var ID_RE = /^K[TP]-[A-Z]+-\d+$/;
@@ -2256,17 +2476,12 @@ function splitStorePrefix(token) {
2256
2476
  return colon === -1 ? { store: null, id: token } : { store: token.slice(0, colon), id: token.slice(colon + 1) };
2257
2477
  }
2258
2478
  var CHAINED_FROM_ID_RE = /chained-from\s+(K[TP]-[A-Z]+-\d+)/i;
2259
- var LEGACY_CITE_TAG_REMAP = {
2260
- planned: "applied",
2261
- recalled: "applied",
2262
- "chained-from": "applied"
2263
- };
2264
2479
  function normalizeCiteTag(rawTag) {
2265
2480
  const head = rawTag.trim().split(/[\s:]+/)[0].toLowerCase();
2266
2481
  if (head === "applied" || head === "dismissed" || head === "none") {
2267
2482
  return head;
2268
2483
  }
2269
- return LEGACY_CITE_TAG_REMAP[head] ?? "none";
2484
+ return "none";
2270
2485
  }
2271
2486
  function parseTag(rawTag) {
2272
2487
  if (!rawTag) return "none";
@@ -2354,146 +2569,131 @@ function parseCiteLine(raw) {
2354
2569
  }
2355
2570
 
2356
2571
  // src/schemas/event-ledger.ts
2357
- var citeTagSchema = z18.preprocess(
2572
+ var citeTagSchema = z16.preprocess(
2358
2573
  (value) => typeof value === "string" ? normalizeCiteTag(value) : value,
2359
- z18.enum(["applied", "dismissed", "none"])
2574
+ z16.enum(["applied", "dismissed", "none"])
2360
2575
  );
2361
2576
  var eventLedgerEnvelopeSchema = {
2362
- kind: z18.literal("fabric-event"),
2363
- id: z18.string(),
2364
- ts: z18.number().int().nonnegative(),
2365
- schema_version: z18.literal(1),
2366
- correlation_id: z18.string().optional(),
2367
- session_id: z18.string().optional()
2577
+ kind: z16.literal("fabric-event"),
2578
+ id: z16.string(),
2579
+ ts: z16.number().int().nonnegative(),
2580
+ schema_version: z16.literal(1),
2581
+ correlation_id: z16.string().optional(),
2582
+ session_id: z16.string().optional()
2368
2583
  };
2369
- var stringRecordSchema = z18.record(z18.string());
2370
- var knowledgeContextPlannedEventSchema = z18.object({
2584
+ var stringRecordSchema = z16.record(z16.string());
2585
+ var knowledgeContextPlannedEventSchema = z16.object({
2371
2586
  ...eventLedgerEnvelopeSchema,
2372
- event_type: z18.literal("knowledge_context_planned"),
2373
- target_paths: z18.array(z18.string()),
2374
- required_stable_ids: z18.array(z18.string()),
2375
- ai_selectable_stable_ids: z18.array(z18.string()),
2376
- final_stable_ids: z18.array(z18.string()),
2377
- selection_token: z18.string().optional(),
2378
- client_hash: z18.string().optional(),
2379
- intent: z18.string().optional(),
2380
- known_tech: z18.array(z18.string()).optional(),
2381
- diagnostics: z18.array(z18.unknown()).optional()
2382
- });
2383
- var knowledgeSelectionEventSchema = z18.object({
2587
+ event_type: z16.literal("knowledge_context_planned"),
2588
+ target_paths: z16.array(z16.string()),
2589
+ required_stable_ids: z16.array(z16.string()),
2590
+ ai_selectable_stable_ids: z16.array(z16.string()),
2591
+ final_stable_ids: z16.array(z16.string()),
2592
+ selection_token: z16.string().optional(),
2593
+ client_hash: z16.string().optional(),
2594
+ intent: z16.string().optional(),
2595
+ known_tech: z16.array(z16.string()).optional(),
2596
+ diagnostics: z16.array(z16.unknown()).optional()
2597
+ });
2598
+ var knowledgeSelectionEventSchema = z16.object({
2384
2599
  ...eventLedgerEnvelopeSchema,
2385
- event_type: z18.literal("knowledge_selection"),
2386
- selection_token: z18.string(),
2387
- target_paths: z18.array(z18.string()),
2388
- required_stable_ids: z18.array(z18.string()),
2389
- ai_selectable_stable_ids: z18.array(z18.string()),
2390
- ai_selected_stable_ids: z18.array(z18.string()),
2391
- final_stable_ids: z18.array(z18.string()),
2600
+ event_type: z16.literal("knowledge_selection"),
2601
+ selection_token: z16.string(),
2602
+ target_paths: z16.array(z16.string()),
2603
+ required_stable_ids: z16.array(z16.string()),
2604
+ ai_selectable_stable_ids: z16.array(z16.string()),
2605
+ ai_selected_stable_ids: z16.array(z16.string()),
2606
+ final_stable_ids: z16.array(z16.string()),
2392
2607
  ai_selection_reasons: stringRecordSchema,
2393
- rejected_stable_ids: z18.array(z18.string()),
2394
- ignored_stable_ids: z18.array(z18.string())
2608
+ rejected_stable_ids: z16.array(z16.string()),
2609
+ ignored_stable_ids: z16.array(z16.string())
2395
2610
  });
2396
- var knowledgeSectionsFetchedEventSchema = z18.object({
2611
+ var knowledgeSectionsFetchedEventSchema = z16.object({
2397
2612
  ...eventLedgerEnvelopeSchema,
2398
- event_type: z18.literal("knowledge_sections_fetched"),
2399
- selection_token: z18.string(),
2400
- target_paths: z18.array(z18.string()).optional(),
2401
- requested_sections: z18.array(z18.string()),
2402
- final_stable_ids: z18.array(z18.string()),
2403
- ai_selected_stable_ids: z18.array(z18.string()),
2404
- diagnostics: z18.array(z18.unknown()).optional()
2405
- });
2406
- var editIntentCheckedEventSchema = z18.object({
2613
+ event_type: z16.literal("knowledge_sections_fetched"),
2614
+ selection_token: z16.string(),
2615
+ target_paths: z16.array(z16.string()).optional(),
2616
+ requested_sections: z16.array(z16.string()),
2617
+ final_stable_ids: z16.array(z16.string()),
2618
+ ai_selected_stable_ids: z16.array(z16.string()),
2619
+ diagnostics: z16.array(z16.unknown()).optional()
2620
+ });
2621
+ var editIntentCheckedEventSchema = z16.object({
2407
2622
  ...eventLedgerEnvelopeSchema,
2408
- event_type: z18.literal("edit_intent_checked"),
2409
- path: z18.string(),
2410
- compliant: z18.boolean(),
2411
- intent: z18.string(),
2412
- ledger_entry_id: z18.string(),
2623
+ event_type: z16.literal("edit_intent_checked"),
2624
+ path: z16.string(),
2625
+ compliant: z16.boolean(),
2626
+ intent: z16.string(),
2627
+ ledger_entry_id: z16.string(),
2413
2628
  // rc.35 TASK-07 (P0-2): add "hook" — emitted by the PreToolUse narrow hook
2414
2629
  // for every Edit/Write/MultiEdit fire so cite-coverage doctor metrics see
2415
2630
  // actual edit signals (previously editsTouched was permanently 0 because
2416
2631
  // no production caller of appendLedgerEntry existed).
2417
- ledger_source: z18.enum(["ai", "human", "hook"]).optional(),
2418
- commit_sha: z18.string().optional(),
2419
- parent_sha: z18.string().optional(),
2420
- parent_ledger_entry_id: z18.string().optional(),
2421
- diff_stat: z18.string().optional(),
2422
- annotation: z18.string().optional(),
2423
- matched_rule_context_ts: z18.number().int().nonnegative().nullable(),
2424
- window_ms: z18.number().int().nonnegative()
2425
- });
2426
- var knowledgeDriftDetectedEventSchema = z18.object({
2632
+ ledger_source: z16.enum(["ai", "human", "hook"]).optional(),
2633
+ commit_sha: z16.string().optional(),
2634
+ parent_sha: z16.string().optional(),
2635
+ parent_ledger_entry_id: z16.string().optional(),
2636
+ diff_stat: z16.string().optional(),
2637
+ annotation: z16.string().optional(),
2638
+ matched_rule_context_ts: z16.number().int().nonnegative().nullable(),
2639
+ window_ms: z16.number().int().nonnegative()
2640
+ });
2641
+ var knowledgeDriftDetectedEventSchema = z16.object({
2427
2642
  ...eventLedgerEnvelopeSchema,
2428
- event_type: z18.literal("knowledge_drift_detected"),
2429
- revision: z18.string().optional(),
2430
- drifted_stable_ids: z18.array(z18.string()),
2431
- missing_files: z18.array(z18.string()),
2432
- stale_files: z18.array(z18.string()),
2433
- details: z18.array(
2434
- z18.object({
2435
- file: z18.string(),
2436
- stable_id: z18.string(),
2437
- expected_hash: z18.string(),
2438
- actual_hash: z18.string().nullable()
2643
+ event_type: z16.literal("knowledge_drift_detected"),
2644
+ revision: z16.string().optional(),
2645
+ drifted_stable_ids: z16.array(z16.string()),
2646
+ missing_files: z16.array(z16.string()),
2647
+ stale_files: z16.array(z16.string()),
2648
+ details: z16.array(
2649
+ z16.object({
2650
+ file: z16.string(),
2651
+ stable_id: z16.string(),
2652
+ expected_hash: z16.string(),
2653
+ actual_hash: z16.string().nullable()
2439
2654
  })
2440
2655
  ).optional()
2441
2656
  });
2442
- var mcpEventLedgerEventSchema = z18.object({
2443
- ...eventLedgerEnvelopeSchema,
2444
- event_type: z18.literal("mcp_event"),
2445
- mcp_event_id: z18.string(),
2446
- stream_id: z18.string(),
2447
- message: z18.unknown()
2448
- });
2449
- var reapplyCompletedEventSchema = z18.object({
2657
+ var mcpEventLedgerEventSchema = z16.object({
2450
2658
  ...eventLedgerEnvelopeSchema,
2451
- event_type: z18.literal("reapply_completed"),
2452
- preserved_ledger: z18.boolean(),
2453
- preserved_meta: z18.boolean(),
2454
- rules_count: z18.number().int().nonnegative()
2659
+ event_type: z16.literal("mcp_event"),
2660
+ mcp_event_id: z16.string(),
2661
+ stream_id: z16.string(),
2662
+ message: z16.unknown()
2455
2663
  });
2456
- var installDiffAppliedEventSchema = z18.object({
2664
+ var reapplyCompletedEventSchema = z16.object({
2457
2665
  ...eventLedgerEnvelopeSchema,
2458
- event_type: z18.literal("install_diff_applied"),
2459
- applied: z18.array(z18.string()),
2460
- canonical: z18.array(z18.string()),
2461
- drifted: z18.array(z18.string())
2666
+ event_type: z16.literal("reapply_completed"),
2667
+ preserved_ledger: z16.boolean(),
2668
+ preserved_meta: z16.boolean(),
2669
+ rules_count: z16.number().int().nonnegative()
2462
2670
  });
2463
- var eventLedgerTruncatedEventSchema = z18.object({
2671
+ var installDiffAppliedEventSchema = z16.object({
2464
2672
  ...eventLedgerEnvelopeSchema,
2465
- event_type: z18.literal("event_ledger_truncated"),
2466
- byte_offset: z18.number().int().nonnegative(),
2467
- byte_length: z18.number().int().nonnegative(),
2468
- corrupted_path: z18.string()
2673
+ event_type: z16.literal("install_diff_applied"),
2674
+ applied: z16.array(z16.string()),
2675
+ canonical: z16.array(z16.string()),
2676
+ drifted: z16.array(z16.string())
2469
2677
  });
2470
- var mcpConfigMigratedEventSchema = z18.object({
2678
+ var eventLedgerTruncatedEventSchema = z16.object({
2471
2679
  ...eventLedgerEnvelopeSchema,
2472
- event_type: z18.literal("mcp_config_migrated"),
2473
- source: z18.literal("doctor_fix"),
2474
- removed_from: z18.string()
2680
+ event_type: z16.literal("event_ledger_truncated"),
2681
+ byte_offset: z16.number().int().nonnegative(),
2682
+ byte_length: z16.number().int().nonnegative(),
2683
+ corrupted_path: z16.string()
2475
2684
  });
2476
- var bootstrapMarkerMigratedEventSchema = z18.object({
2477
- ...eventLedgerEnvelopeSchema,
2478
- event_type: z18.literal("bootstrap_marker_migrated"),
2479
- path: z18.string(),
2480
- migrated_count: z18.number().int().nonnegative(),
2481
- legacy_marker: z18.literal("fabric:knowledge-base"),
2482
- new_marker: z18.literal("fabric:bootstrap"),
2483
- timestamp: z18.string()
2484
- });
2485
- var metaReconciledOnStartupEventSchema = z18.object({
2685
+ var metaReconciledOnStartupEventSchema = z16.object({
2486
2686
  ...eventLedgerEnvelopeSchema,
2487
- event_type: z18.literal("meta_reconciled_on_startup"),
2488
- reconciled_files: z18.array(z18.string()),
2489
- duration_ms: z18.number().int().nonnegative(),
2490
- source: z18.literal("reconcileKnowledge")
2687
+ event_type: z16.literal("meta_reconciled_on_startup"),
2688
+ reconciled_files: z16.array(z16.string()),
2689
+ duration_ms: z16.number().int().nonnegative(),
2690
+ source: z16.literal("reconcileKnowledge")
2491
2691
  });
2492
- var metaReconciledEventSchema = z18.object({
2692
+ var metaReconciledEventSchema = z16.object({
2493
2693
  ...eventLedgerEnvelopeSchema,
2494
- event_type: z18.literal("meta_reconciled"),
2495
- reconciled_files: z18.array(z18.string()),
2496
- duration_ms: z18.number().int().nonnegative(),
2694
+ event_type: z16.literal("meta_reconciled"),
2695
+ reconciled_files: z16.array(z16.string()),
2696
+ duration_ms: z16.number().int().nonnegative(),
2497
2697
  // v2.0.0-rc.23 TASK-005 (a-B): added `auto-heal-description` trigger so the
2498
2698
  // read-path plan_context handler can drive a full reconcile when it detects
2499
2699
  // any node carrying `description === undefined` (legacy meta drift that the
@@ -2507,7 +2707,7 @@ var metaReconciledEventSchema = z18.object({
2507
2707
  // v2.0.0-rc.29 TASK-005 (BUG-G1): `auto-heal-after-drift` added so
2508
2708
  // `ensureKnowledgeFresh` hot-path can chain a paired reconcile (closing the
2509
2709
  // drift→heal gap) when the caller opts in via `autoHealOnDrift: true`.
2510
- trigger: z18.enum([
2710
+ trigger: z16.enum([
2511
2711
  "doctor",
2512
2712
  "manual",
2513
2713
  "auto-heal-description",
@@ -2515,195 +2715,207 @@ var metaReconciledEventSchema = z18.object({
2515
2715
  "post-approve",
2516
2716
  "post-modify"
2517
2717
  ]),
2518
- source: z18.literal("reconcileKnowledge"),
2718
+ source: z16.literal("reconcileKnowledge"),
2519
2719
  // v2.0.0-rc.22 TASK-014 (Scope E): set when reconcileKnowledge forced a
2520
2720
  // writeKnowledgeMeta on revision drift alone (no per-file content drift).
2521
2721
  // Distinguishes top-level schema/revision repair from the standard per-file
2522
2722
  // drift path. Optional so existing emitters stay unchanged.
2523
- force_write_reason: z18.enum(["revision_drift"]).optional()
2723
+ force_write_reason: z16.enum(["revision_drift"]).optional()
2524
2724
  });
2525
- var claudeSkillPathMigratedEventSchema = z18.object({
2725
+ var claudeSkillPathMigratedEventSchema = z16.object({
2526
2726
  ...eventLedgerEnvelopeSchema,
2527
- event_type: z18.literal("claude_skill_path_migrated"),
2528
- from: z18.string(),
2529
- to: z18.string()
2727
+ event_type: z16.literal("claude_skill_path_migrated"),
2728
+ from: z16.string(),
2729
+ to: z16.string()
2530
2730
  });
2531
- var claudeHookPathMigratedEventSchema = z18.object({
2731
+ var claudeHookPathMigratedEventSchema = z16.object({
2532
2732
  ...eventLedgerEnvelopeSchema,
2533
- event_type: z18.literal("claude_hook_path_migrated"),
2534
- from: z18.string(),
2535
- to: z18.string()
2733
+ event_type: z16.literal("claude_hook_path_migrated"),
2734
+ from: z16.string(),
2735
+ to: z16.string()
2536
2736
  });
2537
- var codexSkillPathMigratedEventSchema = z18.object({
2737
+ var codexSkillPathMigratedEventSchema = z16.object({
2538
2738
  ...eventLedgerEnvelopeSchema,
2539
- event_type: z18.literal("codex_skill_path_migrated"),
2540
- from: z18.string(),
2541
- to: z18.string()
2739
+ event_type: z16.literal("codex_skill_path_migrated"),
2740
+ from: z16.string(),
2741
+ to: z16.string()
2542
2742
  });
2543
- var initScanCompletedEventSchema = z18.object({
2743
+ var initScanCompletedEventSchema = z16.object({
2544
2744
  ...eventLedgerEnvelopeSchema,
2545
- event_type: z18.literal("init_scan_completed"),
2546
- written_stable_ids: z18.array(z18.string()),
2547
- duration_ms: z18.number().int().nonnegative(),
2548
- source: z18.enum(["init", "scan", "doctor_fix", "doctor-rescan"]).optional()
2745
+ event_type: z16.literal("init_scan_completed"),
2746
+ written_stable_ids: z16.array(z16.string()),
2747
+ duration_ms: z16.number().int().nonnegative(),
2748
+ source: z16.enum(["init", "scan", "doctor_fix", "doctor-rescan"]).optional()
2549
2749
  });
2550
- var knowledgeProposedEventSchema = z18.object({
2750
+ var knowledgeProposedEventSchema = z16.object({
2551
2751
  ...eventLedgerEnvelopeSchema,
2552
- event_type: z18.literal("knowledge_proposed"),
2553
- stable_id: z18.string().optional(),
2554
- timestamp: z18.string().datetime(),
2555
- reason: z18.string().optional()
2752
+ event_type: z16.literal("knowledge_proposed"),
2753
+ stable_id: z16.string().optional(),
2754
+ timestamp: z16.string().datetime(),
2755
+ reason: z16.string().optional()
2556
2756
  });
2557
- var knowledgePromoteStartedEventSchema = z18.object({
2757
+ var knowledgePromoteStartedEventSchema = z16.object({
2558
2758
  ...eventLedgerEnvelopeSchema,
2559
- event_type: z18.literal("knowledge_promote_started"),
2560
- stable_id: z18.string().optional(),
2561
- timestamp: z18.string().datetime(),
2562
- reason: z18.string().optional()
2759
+ event_type: z16.literal("knowledge_promote_started"),
2760
+ stable_id: z16.string().optional(),
2761
+ timestamp: z16.string().datetime(),
2762
+ reason: z16.string().optional()
2563
2763
  });
2564
- var knowledgePromotedEventSchema = z18.object({
2764
+ var knowledgePromotedEventSchema = z16.object({
2565
2765
  ...eventLedgerEnvelopeSchema,
2566
- event_type: z18.literal("knowledge_promoted"),
2567
- stable_id: z18.string().optional(),
2568
- timestamp: z18.string().datetime(),
2569
- reason: z18.string().optional()
2766
+ event_type: z16.literal("knowledge_promoted"),
2767
+ stable_id: z16.string().optional(),
2768
+ timestamp: z16.string().datetime(),
2769
+ reason: z16.string().optional()
2570
2770
  });
2571
- var knowledgePromoteFailedEventSchema = z18.object({
2771
+ var knowledgePromoteFailedEventSchema = z16.object({
2572
2772
  ...eventLedgerEnvelopeSchema,
2573
- event_type: z18.literal("knowledge_promote_failed"),
2574
- stable_id: z18.string().optional(),
2575
- timestamp: z18.string().datetime(),
2576
- reason: z18.string()
2773
+ event_type: z16.literal("knowledge_promote_failed"),
2774
+ stable_id: z16.string().optional(),
2775
+ timestamp: z16.string().datetime(),
2776
+ reason: z16.string()
2577
2777
  });
2578
- var knowledgeLayerChangedEventSchema = z18.object({
2778
+ var knowledgeModifiedEventSchema = z16.object({
2579
2779
  ...eventLedgerEnvelopeSchema,
2580
- event_type: z18.literal("knowledge_layer_changed"),
2581
- stable_id: z18.string().optional(),
2582
- timestamp: z18.string().datetime(),
2583
- reason: z18.string().optional(),
2584
- from_layer: z18.enum(["team", "personal"]),
2585
- to_layer: z18.enum(["team", "personal"]),
2780
+ event_type: z16.literal("knowledge_modified"),
2781
+ stable_id: z16.string().optional(),
2782
+ timestamp: z16.string().datetime(),
2783
+ path: z16.string(),
2784
+ changed_fields: z16.array(z16.string()),
2785
+ before: z16.record(z16.unknown()),
2786
+ after: z16.record(z16.unknown()),
2787
+ reason: z16.string().optional()
2788
+ });
2789
+ var knowledgeLayerChangedEventSchema = z16.object({
2790
+ ...eventLedgerEnvelopeSchema,
2791
+ event_type: z16.literal("knowledge_layer_changed"),
2792
+ stable_id: z16.string().optional(),
2793
+ timestamp: z16.string().datetime(),
2794
+ reason: z16.string().optional(),
2795
+ from_layer: z16.enum(["team", "personal"]),
2796
+ to_layer: z16.enum(["team", "personal"]),
2586
2797
  // v2.0.0-rc.37 NEW-24: record the pre-flip stable_id so downstream consumers
2587
2798
  // (fab_plan_context redirect surface, fab_get_knowledge_sections.redirect_to)
2588
2799
  // can map a stale caller-held id back to the post-flip canonical id without
2589
2800
  // requiring the caller to re-issue plan-context. Optional for forward-
2590
2801
  // compatibility with rc ≤36 events that never carried this field.
2591
- previous_stable_id: z18.string().optional()
2802
+ previous_stable_id: z16.string().optional()
2592
2803
  });
2593
- var knowledgeIdRedirectEventSchema = z18.object({
2804
+ var knowledgeIdRedirectEventSchema = z16.object({
2594
2805
  ...eventLedgerEnvelopeSchema,
2595
- event_type: z18.literal("knowledge_id_redirect"),
2596
- timestamp: z18.string().datetime(),
2597
- previous_stable_id: z18.string(),
2598
- new_stable_id: z18.string(),
2599
- reason: z18.string().optional()
2806
+ event_type: z16.literal("knowledge_id_redirect"),
2807
+ timestamp: z16.string().datetime(),
2808
+ previous_stable_id: z16.string(),
2809
+ new_stable_id: z16.string(),
2810
+ reason: z16.string().optional()
2600
2811
  });
2601
- var knowledgeSlugRenamedEventSchema = z18.object({
2812
+ var knowledgeSlugRenamedEventSchema = z16.object({
2602
2813
  ...eventLedgerEnvelopeSchema,
2603
- event_type: z18.literal("knowledge_slug_renamed"),
2604
- stable_id: z18.string().optional(),
2605
- timestamp: z18.string().datetime(),
2606
- reason: z18.string().optional(),
2607
- from_slug: z18.string(),
2608
- to_slug: z18.string()
2609
- });
2610
- var knowledgeDemotedEventSchema = z18.object({
2814
+ event_type: z16.literal("knowledge_slug_renamed"),
2815
+ stable_id: z16.string().optional(),
2816
+ timestamp: z16.string().datetime(),
2817
+ reason: z16.string().optional(),
2818
+ from_slug: z16.string(),
2819
+ to_slug: z16.string()
2820
+ });
2821
+ var knowledgeDemotedEventSchema = z16.object({
2611
2822
  ...eventLedgerEnvelopeSchema,
2612
- event_type: z18.literal("knowledge_demoted"),
2613
- stable_id: z18.string().optional(),
2614
- timestamp: z18.string().datetime(),
2615
- reason: z18.string().optional()
2823
+ event_type: z16.literal("knowledge_demoted"),
2824
+ stable_id: z16.string().optional(),
2825
+ timestamp: z16.string().datetime(),
2826
+ reason: z16.string().optional()
2616
2827
  });
2617
- var knowledgeArchivedEventSchema = z18.object({
2828
+ var knowledgeArchivedEventSchema = z16.object({
2618
2829
  ...eventLedgerEnvelopeSchema,
2619
- event_type: z18.literal("knowledge_archived"),
2620
- stable_id: z18.string().optional(),
2621
- timestamp: z18.string().datetime(),
2622
- reason: z18.string().optional()
2830
+ event_type: z16.literal("knowledge_archived"),
2831
+ stable_id: z16.string().optional(),
2832
+ timestamp: z16.string().datetime(),
2833
+ reason: z16.string().optional()
2623
2834
  });
2624
- var knowledgeArchiveAttemptedEventSchema = z18.object({
2835
+ var knowledgeArchiveAttemptedEventSchema = z16.object({
2625
2836
  ...eventLedgerEnvelopeSchema,
2626
- event_type: z18.literal("knowledge_archive_attempted"),
2627
- stable_id: z18.string().optional(),
2628
- timestamp: z18.string().datetime(),
2629
- reason: z18.string().optional()
2837
+ event_type: z16.literal("knowledge_archive_attempted"),
2838
+ stable_id: z16.string().optional(),
2839
+ timestamp: z16.string().datetime(),
2840
+ reason: z16.string().optional()
2630
2841
  });
2631
- var knowledgeUnarchivedEventSchema = z18.object({
2842
+ var knowledgeUnarchivedEventSchema = z16.object({
2632
2843
  ...eventLedgerEnvelopeSchema,
2633
- event_type: z18.literal("knowledge_unarchived"),
2634
- stable_id: z18.string().optional(),
2635
- timestamp: z18.string().datetime(),
2636
- reason: z18.string().optional(),
2844
+ event_type: z16.literal("knowledge_unarchived"),
2845
+ stable_id: z16.string().optional(),
2846
+ timestamp: z16.string().datetime(),
2847
+ reason: z16.string().optional(),
2637
2848
  // Pre-move archive path (e.g. ".fabric/.archive/decisions/KT-D-0007--single-cjs-hook.md").
2638
- archive_path: z18.string().optional(),
2639
- // Post-move canonical path (e.g. ".fabric/knowledge/team/decisions/KT-D-0007--single-cjs-hook.md").
2640
- restored_to: z18.string().optional()
2849
+ archive_path: z16.string().optional(),
2850
+ // Post-move canonical path (e.g. "knowledge/decisions/KT-DEC-0007--single-cjs-hook.md" inside the resolved store).
2851
+ restored_to: z16.string().optional()
2641
2852
  });
2642
- var knowledgeDeferredEventSchema = z18.object({
2853
+ var knowledgeDeferredEventSchema = z16.object({
2643
2854
  ...eventLedgerEnvelopeSchema,
2644
- event_type: z18.literal("knowledge_deferred"),
2645
- stable_id: z18.string().optional(),
2646
- timestamp: z18.string().datetime(),
2647
- reason: z18.string().optional(),
2648
- until: z18.string().datetime().optional()
2649
- });
2650
- var knowledgeRejectedEventSchema = z18.object({
2855
+ event_type: z16.literal("knowledge_deferred"),
2856
+ stable_id: z16.string().optional(),
2857
+ pending_path: z16.string().optional(),
2858
+ timestamp: z16.string().datetime(),
2859
+ reason: z16.string().optional(),
2860
+ until: z16.string().datetime().optional()
2861
+ });
2862
+ var knowledgeRejectedEventSchema = z16.object({
2651
2863
  ...eventLedgerEnvelopeSchema,
2652
- event_type: z18.literal("knowledge_rejected"),
2653
- stable_id: z18.string().optional(),
2654
- timestamp: z18.string().datetime(),
2655
- reason: z18.string()
2864
+ event_type: z16.literal("knowledge_rejected"),
2865
+ stable_id: z16.string().optional(),
2866
+ timestamp: z16.string().datetime(),
2867
+ reason: z16.string()
2656
2868
  });
2657
- var knowledgeConsumedEventSchema = z18.object({
2869
+ var knowledgeConsumedEventSchema = z16.object({
2658
2870
  ...eventLedgerEnvelopeSchema,
2659
- event_type: z18.literal("knowledge_consumed"),
2660
- stable_id: z18.string(),
2661
- consumed_at: z18.string().datetime(),
2662
- client_hash: z18.string()
2871
+ event_type: z16.literal("knowledge_consumed"),
2872
+ stable_id: z16.string(),
2873
+ consumed_at: z16.string().datetime(),
2874
+ client_hash: z16.string()
2663
2875
  });
2664
- var knowledgeScopeDegradedEventSchema = z18.object({
2876
+ var knowledgeScopeDegradedEventSchema = z16.object({
2665
2877
  ...eventLedgerEnvelopeSchema,
2666
- event_type: z18.literal("knowledge_scope_degraded"),
2667
- stable_id: z18.string(),
2668
- timestamp: z18.string().datetime(),
2669
- from_scope: z18.enum(["narrow", "broad"]),
2670
- to_scope: z18.enum(["narrow", "broad"]),
2671
- reason: z18.string()
2672
- });
2673
- var doctorRunEventSchema = z18.object({
2878
+ event_type: z16.literal("knowledge_scope_degraded"),
2879
+ stable_id: z16.string(),
2880
+ timestamp: z16.string().datetime(),
2881
+ from_scope: z16.enum(["narrow", "broad"]),
2882
+ to_scope: z16.enum(["narrow", "broad"]),
2883
+ reason: z16.string()
2884
+ });
2885
+ var doctorRunEventSchema = z16.object({
2674
2886
  ...eventLedgerEnvelopeSchema,
2675
- event_type: z18.literal("doctor_run"),
2676
- mode: z18.enum(["lint", "fix-knowledge"]),
2677
- issues: z18.number().int().nonnegative(),
2678
- mutations: z18.number().int().nonnegative().optional(),
2679
- timestamp: z18.string().datetime()
2887
+ event_type: z16.literal("doctor_run"),
2888
+ mode: z16.enum(["lint", "fix-knowledge"]),
2889
+ issues: z16.number().int().nonnegative(),
2890
+ mutations: z16.number().int().nonnegative().optional(),
2891
+ timestamp: z16.string().datetime()
2680
2892
  });
2681
- var knowledgePathDangledEventSchema = z18.object({
2893
+ var knowledgePathDangledEventSchema = z16.object({
2682
2894
  ...eventLedgerEnvelopeSchema,
2683
- event_type: z18.literal("knowledge_path_dangled"),
2684
- stable_id: z18.string(),
2685
- removed_glob: z18.string()
2895
+ event_type: z16.literal("knowledge_path_dangled"),
2896
+ stable_id: z16.string(),
2897
+ removed_glob: z16.string()
2686
2898
  });
2687
- var relevanceMigrationRunEventSchema = z18.object({
2899
+ var relevanceMigrationRunEventSchema = z16.object({
2688
2900
  ...eventLedgerEnvelopeSchema,
2689
- event_type: z18.literal("relevance_migration_run"),
2690
- timestamp: z18.string().datetime(),
2691
- scanned_count: z18.number().int().nonnegative(),
2692
- touched_count: z18.number().int().nonnegative()
2901
+ event_type: z16.literal("relevance_migration_run"),
2902
+ timestamp: z16.string().datetime(),
2903
+ scanned_count: z16.number().int().nonnegative(),
2904
+ touched_count: z16.number().int().nonnegative()
2693
2905
  });
2694
- var pendingAutoArchivedEventSchema = z18.object({
2906
+ var pendingAutoArchivedEventSchema = z16.object({
2695
2907
  ...eventLedgerEnvelopeSchema,
2696
- event_type: z18.literal("pending_auto_archived"),
2697
- pending_path: z18.string(),
2698
- archived_to: z18.string(),
2699
- reason: z18.string()
2908
+ event_type: z16.literal("pending_auto_archived"),
2909
+ pending_path: z16.string(),
2910
+ archived_to: z16.string(),
2911
+ reason: z16.string()
2700
2912
  });
2701
- var assistantTurnObservedEventSchema = z18.object({
2913
+ var assistantTurnObservedEventSchema = z16.object({
2702
2914
  ...eventLedgerEnvelopeSchema,
2703
- event_type: z18.literal("assistant_turn_observed"),
2704
- kb_line_raw: z18.string().nullable(),
2705
- cite_ids: z18.array(z18.string()).default([]),
2706
- cite_tags: z18.array(citeTagSchema).default([]),
2915
+ event_type: z16.literal("assistant_turn_observed"),
2916
+ kb_line_raw: z16.string().nullable(),
2917
+ cite_ids: z16.array(z16.string()).default([]),
2918
+ cite_tags: z16.array(citeTagSchema).default([]),
2707
2919
  // v2.0.0-rc.24 TASK-01: per-cite contract commitments. Index-aligned with
2708
2920
  // cite_ids/cite_tags (commitments[i] belongs to cite_ids[i]). Each slot
2709
2921
  // carries `operators[]` (kind + glob target) or `skip_reason` when the cite
@@ -2711,15 +2923,15 @@ var assistantTurnObservedEventSchema = z18.object({
2711
2923
  // empty array via `.default([])` and are excluded from contract-policy
2712
2924
  // audits by the marker-gate (see cite_contract_policy_activated below).
2713
2925
  // Mirrors the rc.20 cite_tags parallel-array evolution exactly.
2714
- cite_commitments: z18.array(
2715
- z18.object({
2716
- operators: z18.array(
2717
- z18.object({
2718
- kind: z18.enum(["edit", "not_edit", "require", "forbid"]),
2719
- target: z18.string()
2926
+ cite_commitments: z16.array(
2927
+ z16.object({
2928
+ operators: z16.array(
2929
+ z16.object({
2930
+ kind: z16.enum(["edit", "not_edit", "require", "forbid"]),
2931
+ target: z16.string()
2720
2932
  })
2721
2933
  ),
2722
- skip_reason: z18.string().nullable()
2934
+ skip_reason: z16.string().nullable()
2723
2935
  })
2724
2936
  ).default([]),
2725
2937
  // lifecycle-refactor W3-T4 (§2 store 轴 / store-qualified 观测): per-cite store
@@ -2729,174 +2941,183 @@ var assistantTurnObservedEventSchema = z18.object({
2729
2941
  // doctor --cite-coverage can break compliance down per store WITHOUT joining
2730
2942
  // against the store registry. Additive `.optional()` (NOT `.default([])`) so
2731
2943
  // existing inline event constructors stay valid without supplying it — pre-W3-T4
2732
- // events parse with the field absent and bucket under the project-local default.
2733
- cite_stores: z18.array(z18.string().nullable()).optional(),
2734
- client: z18.enum(["cc", "codex", "cursor"]).optional(),
2735
- turn_id: z18.string(),
2736
- envelope_index: z18.number().int().nonnegative().optional(),
2737
- timestamp: z18.string().datetime()
2738
- });
2739
- var citePolicyActivatedEventSchema = z18.object({
2944
+ // events parse with the field absent and bucket under the unqualified default.
2945
+ cite_stores: z16.array(z16.string().nullable()).optional(),
2946
+ client: z16.enum(["cc", "codex"]).optional(),
2947
+ turn_id: z16.string(),
2948
+ envelope_index: z16.number().int().nonnegative().optional(),
2949
+ timestamp: z16.string().datetime()
2950
+ });
2951
+ var citePolicyActivatedEventSchema = z16.object({
2740
2952
  ...eventLedgerEnvelopeSchema,
2741
- event_type: z18.literal("cite_policy_activated"),
2742
- policy_version: z18.string(),
2743
- timestamp: z18.string().datetime()
2953
+ event_type: z16.literal("cite_policy_activated"),
2954
+ policy_version: z16.string(),
2955
+ timestamp: z16.string().datetime()
2744
2956
  });
2745
- var citeContractPolicyActivatedEventSchema = z18.object({
2957
+ var citeContractPolicyActivatedEventSchema = z16.object({
2746
2958
  ...eventLedgerEnvelopeSchema,
2747
- event_type: z18.literal("cite_contract_policy_activated")
2959
+ event_type: z16.literal("cite_contract_policy_activated")
2748
2960
  });
2749
- var eventsRotatedEventSchema = z18.object({
2961
+ var eventsRotatedEventSchema = z16.object({
2750
2962
  ...eventLedgerEnvelopeSchema,
2751
- event_type: z18.literal("events_rotated"),
2752
- cutoff_ts: z18.string().datetime(),
2753
- archived_count: z18.number().int().nonnegative(),
2754
- kept_count: z18.number().int().nonnegative(),
2755
- archive_path: z18.string()
2963
+ event_type: z16.literal("events_rotated"),
2964
+ cutoff_ts: z16.string().datetime(),
2965
+ archived_count: z16.number().int().nonnegative(),
2966
+ kept_count: z16.number().int().nonnegative(),
2967
+ archive_path: z16.string()
2756
2968
  });
2757
- var knowledgeMetaAutoHealedEventSchema = z18.object({
2969
+ var knowledgeMetaAutoHealedEventSchema = z16.object({
2758
2970
  ...eventLedgerEnvelopeSchema,
2759
- event_type: z18.literal("knowledge_meta_auto_healed"),
2760
- previous_revision_hash: z18.string(),
2761
- revision_hash: z18.string(),
2762
- trigger: z18.literal("read"),
2763
- caller: z18.enum(["planContext", "getKnowledgeSections", "getKnowledge", "extractKnowledge"]).optional()
2971
+ event_type: z16.literal("knowledge_meta_auto_healed"),
2972
+ previous_revision_hash: z16.string(),
2973
+ revision_hash: z16.string(),
2974
+ trigger: z16.literal("read"),
2975
+ caller: z16.enum(["planContext", "getKnowledgeSections", "getKnowledge", "extractKnowledge"]).optional()
2764
2976
  });
2765
- var serveLockClearedEventSchema = z18.object({
2977
+ var serveLockClearedEventSchema = z16.object({
2766
2978
  ...eventLedgerEnvelopeSchema,
2767
- event_type: z18.literal("serve_lock_cleared"),
2768
- pid: z18.number().int().nonnegative(),
2769
- age_ms: z18.number().int().nonnegative(),
2770
- timestamp: z18.string().datetime()
2979
+ event_type: z16.literal("serve_lock_cleared"),
2980
+ pid: z16.number().int().nonnegative(),
2981
+ age_ms: z16.number().int().nonnegative(),
2982
+ timestamp: z16.string().datetime()
2771
2983
  });
2772
- var knowledgeEnrichedEventSchema = z18.object({
2984
+ var knowledgeEnrichedEventSchema = z16.object({
2773
2985
  ...eventLedgerEnvelopeSchema,
2774
- event_type: z18.literal("knowledge_enriched"),
2775
- path: z18.string(),
2776
- added_fields: z18.array(z18.enum(["intent_clues", "tech_stack", "impact", "must_read_if"])),
2777
- mode: z18.enum(["auto", "preview", "readonly", "interactive"]),
2778
- timestamp: z18.string().datetime()
2986
+ event_type: z16.literal("knowledge_enriched"),
2987
+ path: z16.string(),
2988
+ added_fields: z16.array(z16.enum(["intent_clues", "tech_stack", "impact", "must_read_if"])),
2989
+ mode: z16.enum(["auto", "preview", "readonly", "interactive"]),
2990
+ timestamp: z16.string().datetime()
2779
2991
  });
2780
- var sessionArchiveAttemptedEventSchema = z18.object({
2992
+ var sessionArchiveAttemptedEventSchema = z16.object({
2781
2993
  ...eventLedgerEnvelopeSchema,
2782
- event_type: z18.literal("session_archive_attempted"),
2783
- outcome: z18.enum(["proposed", "viability_failed", "user_dismissed", "skipped_no_signal"]),
2784
- covered_through_ts: z18.number().int().nonnegative(),
2785
- candidates_proposed: z18.number().int().nonnegative().default(0),
2786
- knowledge_proposed_ids: z18.array(z18.string()).default([])
2994
+ event_type: z16.literal("session_archive_attempted"),
2995
+ outcome: z16.enum(["proposed", "viability_failed", "user_dismissed", "skipped_no_signal"]),
2996
+ covered_through_ts: z16.number().int().nonnegative(),
2997
+ candidates_proposed: z16.number().int().nonnegative().default(0),
2998
+ knowledge_proposed_ids: z16.array(z16.string()).default([])
2787
2999
  });
2788
- var hookSurfaceEmittedEventSchema = z18.object({
3000
+ var hookSurfaceEmittedEventSchema = z16.object({
2789
3001
  ...eventLedgerEnvelopeSchema,
2790
- event_type: z18.literal("hook_surface_emitted"),
2791
- hook_name: z18.string(),
2792
- client: z18.enum(["cc", "codex", "cursor"]),
2793
- target_channel: z18.string(),
2794
- rendered_ids: z18.array(z18.string()),
2795
- delivery_status: z18.enum(["delivered", "suppressed", "error"]),
2796
- suppression_reason: z18.string().optional()
2797
- });
2798
- var hookSignalEmittedEventSchema = z18.object({
3002
+ event_type: z16.literal("hook_surface_emitted"),
3003
+ hook_name: z16.string(),
3004
+ client: z16.enum(["cc", "codex"]),
3005
+ target_channel: z16.string(),
3006
+ rendered_ids: z16.array(z16.string()),
3007
+ delivery_status: z16.enum(["delivered", "suppressed", "error"]),
3008
+ suppression_reason: z16.string().optional()
3009
+ });
3010
+ var hookSignalEmittedEventSchema = z16.object({
2799
3011
  ...eventLedgerEnvelopeSchema,
2800
- event_type: z18.literal("hook_signal_emitted"),
2801
- signal_type: z18.enum(["archive", "review", "maintenance", "other"]),
2802
- threshold: z18.number(),
2803
- actual_value: z18.number(),
2804
- fired: z18.boolean()
3012
+ event_type: z16.literal("hook_signal_emitted"),
3013
+ signal_type: z16.enum(["archive", "review", "maintenance", "other"]),
3014
+ threshold: z16.number(),
3015
+ actual_value: z16.number(),
3016
+ fired: z16.boolean()
2805
3017
  });
2806
- var mcpStdioTraceEventSchema = z18.object({
3018
+ var mcpStdioTraceEventSchema = z16.object({
2807
3019
  ...eventLedgerEnvelopeSchema,
2808
- event_type: z18.literal("mcp_stdio_trace"),
2809
- tool_name: z18.string(),
2810
- request_id: z18.string(),
2811
- duration_ms: z18.number().nonnegative(),
2812
- status: z18.enum(["ok", "error"]),
2813
- payload_bytes_in: z18.number().int().nonnegative(),
2814
- payload_bytes_out: z18.number().int().nonnegative(),
2815
- error_code: z18.string().optional()
2816
- });
2817
- var payloadGuardObservedEventSchema = z18.object({
3020
+ event_type: z16.literal("mcp_stdio_trace"),
3021
+ tool_name: z16.string(),
3022
+ request_id: z16.string(),
3023
+ duration_ms: z16.number().nonnegative(),
3024
+ status: z16.enum(["ok", "error"]),
3025
+ payload_bytes_in: z16.number().int().nonnegative(),
3026
+ payload_bytes_out: z16.number().int().nonnegative(),
3027
+ error_code: z16.string().optional()
3028
+ });
3029
+ var payloadGuardObservedEventSchema = z16.object({
2818
3030
  ...eventLedgerEnvelopeSchema,
2819
- event_type: z18.literal("payload_guard_observed"),
2820
- tool_name: z18.string(),
2821
- path_count: z18.number().int().nonnegative(),
2822
- tokens_estimated: z18.number().int().nonnegative(),
2823
- truncated: z18.boolean(),
2824
- cap: z18.number().int().positive()
2825
- });
2826
- var skillInvocationStartedEventSchema = z18.object({
3031
+ event_type: z16.literal("payload_guard_observed"),
3032
+ tool_name: z16.string(),
3033
+ path_count: z16.number().int().nonnegative(),
3034
+ tokens_estimated: z16.number().int().nonnegative(),
3035
+ truncated: z16.boolean(),
3036
+ cap: z16.number().int().positive()
3037
+ });
3038
+ var skillInvocationStartedEventSchema = z16.object({
2827
3039
  ...eventLedgerEnvelopeSchema,
2828
- event_type: z18.literal("skill_invocation_started"),
2829
- skill_name: z18.string(),
2830
- trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2831
- entry_point: z18.string()
3040
+ event_type: z16.literal("skill_invocation_started"),
3041
+ skill_name: z16.string(),
3042
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3043
+ entry_point: z16.string()
2832
3044
  });
2833
- var skillInvocationCompletedEventSchema = z18.object({
3045
+ var skillInvocationCompletedEventSchema = z16.object({
2834
3046
  ...eventLedgerEnvelopeSchema,
2835
- event_type: z18.literal("skill_invocation_completed"),
2836
- skill_name: z18.string(),
2837
- trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2838
- entry_point: z18.string(),
2839
- outcome: z18.enum(["completed", "aborted", "error", "no_op"]),
2840
- elapsed_ms: z18.number().nonnegative().optional()
2841
- });
2842
- var skillPhaseTransitionEventSchema = z18.object({
3047
+ event_type: z16.literal("skill_invocation_completed"),
3048
+ skill_name: z16.string(),
3049
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3050
+ entry_point: z16.string(),
3051
+ outcome: z16.enum(["completed", "aborted", "error", "no_op"]),
3052
+ elapsed_ms: z16.number().nonnegative().optional()
3053
+ });
3054
+ var skillPhaseTransitionEventSchema = z16.object({
2843
3055
  ...eventLedgerEnvelopeSchema,
2844
- event_type: z18.literal("skill_phase_transition"),
2845
- skill_name: z18.string(),
2846
- phase: z18.string(),
2847
- status: z18.enum(["entered", "completed", "skipped", "failed"]),
2848
- checkpoint: z18.string().optional(),
2849
- elapsed_ms: z18.number().nonnegative().optional()
2850
- });
2851
- var skillTriggerCandidateEventSchema = z18.object({
3056
+ event_type: z16.literal("skill_phase_transition"),
3057
+ skill_name: z16.string(),
3058
+ phase: z16.string(),
3059
+ status: z16.enum(["entered", "completed", "skipped", "failed"]),
3060
+ checkpoint: z16.string().optional(),
3061
+ elapsed_ms: z16.number().nonnegative().optional()
3062
+ });
3063
+ var skillTriggerCandidateEventSchema = z16.object({
2852
3064
  ...eventLedgerEnvelopeSchema,
2853
- event_type: z18.literal("skill_trigger_candidate"),
2854
- skill_name: z18.string(),
2855
- trigger_source: z18.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
2856
- signal: z18.string(),
2857
- invoked: z18.boolean()
3065
+ event_type: z16.literal("skill_trigger_candidate"),
3066
+ skill_name: z16.string(),
3067
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3068
+ signal: z16.string(),
3069
+ invoked: z16.boolean()
2858
3070
  });
2859
- var llmJudgeRunEventSchema = z18.object({
3071
+ var llmJudgeRunEventSchema = z16.object({
2860
3072
  ...eventLedgerEnvelopeSchema,
2861
- event_type: z18.literal("llm_judge_run"),
2862
- prompt: z18.string(),
2863
- version: z18.string(),
2864
- model: z18.string(),
2865
- input_trace_id: z18.string(),
2866
- score: z18.number(),
2867
- rationale: z18.string()
2868
- });
2869
- var clientCapabilitySnapshotEventSchema = z18.object({
3073
+ event_type: z16.literal("llm_judge_run"),
3074
+ prompt: z16.string(),
3075
+ version: z16.string(),
3076
+ model: z16.string(),
3077
+ input_trace_id: z16.string(),
3078
+ score: z16.number(),
3079
+ rationale: z16.string()
3080
+ });
3081
+ var clientCapabilitySnapshotEventSchema = z16.object({
2870
3082
  ...eventLedgerEnvelopeSchema,
2871
- event_type: z18.literal("client_capability_snapshot"),
2872
- client: z18.enum(["cc", "codex", "cursor"]),
2873
- capabilities: z18.array(z18.string()),
2874
- version: z18.string()
3083
+ event_type: z16.literal("client_capability_snapshot"),
3084
+ client: z16.enum(["cc", "codex"]),
3085
+ capabilities: z16.array(z16.string()),
3086
+ version: z16.string()
2875
3087
  });
2876
- var sessionEndedEventSchema = z18.object({
3088
+ var sessionEndedEventSchema = z16.object({
2877
3089
  ...eventLedgerEnvelopeSchema,
2878
- event_type: z18.literal("session_ended")
3090
+ event_type: z16.literal("session_ended")
2879
3091
  });
2880
- var fileMutatedEventSchema = z18.object({
3092
+ var fileMutatedEventSchema = z16.object({
2881
3093
  ...eventLedgerEnvelopeSchema,
2882
- event_type: z18.literal("file_mutated"),
2883
- path: z18.string(),
2884
- tool_call_id: z18.string(),
2885
- tool_name: z18.string().optional(),
2886
- source_event_id: z18.string().optional(),
2887
- store_id: z18.string().optional()
2888
- });
2889
- var precompactObservedEventSchema = z18.object({
3094
+ event_type: z16.literal("file_mutated"),
3095
+ path: z16.string(),
3096
+ tool_call_id: z16.string(),
3097
+ tool_name: z16.string().optional(),
3098
+ source_event_id: z16.string().optional(),
3099
+ store_id: z16.string().optional()
3100
+ });
3101
+ var knowledgeBodyReadEventSchema = z16.object({
2890
3102
  ...eventLedgerEnvelopeSchema,
2891
- event_type: z18.literal("precompact_observed")
3103
+ event_type: z16.literal("knowledge_body_read"),
3104
+ stable_id: z16.string(),
3105
+ store: z16.string().optional(),
3106
+ path: z16.string(),
3107
+ tool_call_id: z16.string().optional(),
3108
+ tool_name: z16.string().optional()
3109
+ });
3110
+ var precompactObservedEventSchema = z16.object({
3111
+ ...eventLedgerEnvelopeSchema,
3112
+ event_type: z16.literal("precompact_observed")
2892
3113
  });
2893
- var graphEdgeCandidateRequestedEventSchema = z18.object({
3114
+ var graphEdgeCandidateRequestedEventSchema = z16.object({
2894
3115
  ...eventLedgerEnvelopeSchema,
2895
- event_type: z18.literal("graph_edge_candidate_requested"),
2896
- stable_id: z18.string(),
2897
- store: z18.string().optional()
3116
+ event_type: z16.literal("graph_edge_candidate_requested"),
3117
+ stable_id: z16.string(),
3118
+ store: z16.string().optional()
2898
3119
  });
2899
- var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
3120
+ var eventLedgerEventSchema = z16.discriminatedUnion("event_type", [
2900
3121
  knowledgeContextPlannedEventSchema,
2901
3122
  knowledgeSelectionEventSchema,
2902
3123
  knowledgeSectionsFetchedEventSchema,
@@ -2906,10 +3127,6 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2906
3127
  reapplyCompletedEventSchema,
2907
3128
  installDiffAppliedEventSchema,
2908
3129
  eventLedgerTruncatedEventSchema,
2909
- mcpConfigMigratedEventSchema,
2910
- // v2.0.0-rc.19 TASK-004: bootstrap_marker_migrated — one-time fabric:knowledge-base
2911
- // → fabric:bootstrap marker rewrite emitted per file by `fabric doctor --fix`.
2912
- bootstrapMarkerMigratedEventSchema,
2913
3130
  metaReconciledOnStartupEventSchema,
2914
3131
  metaReconciledEventSchema,
2915
3132
  claudeSkillPathMigratedEventSchema,
@@ -2921,6 +3138,7 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2921
3138
  knowledgePromoteStartedEventSchema,
2922
3139
  knowledgePromotedEventSchema,
2923
3140
  knowledgePromoteFailedEventSchema,
3141
+ knowledgeModifiedEventSchema,
2924
3142
  knowledgeLayerChangedEventSchema,
2925
3143
  // v2.0.0-rc.37 NEW-24: dedicated old→new stable_id mapping event
2926
3144
  knowledgeIdRedirectEventSchema,
@@ -2992,6 +3210,9 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2992
3210
  // lifecycle-refactor Wave 2 — dormant-hook activation markers.
2993
3211
  sessionEndedEventSchema,
2994
3212
  fileMutatedEventSchema,
3213
+ // KT-DEC-0030: knowledge_body_read — PostToolUse native-Read consumption marker
3214
+ // (replaces knowledge_consumed as the funnel's "body opened" signal).
3215
+ knowledgeBodyReadEventSchema,
2995
3216
  precompactObservedEventSchema,
2996
3217
  graphEdgeCandidateRequestedEventSchema
2997
3218
  ]);
@@ -3024,51 +3245,16 @@ function tokenize(text) {
3024
3245
  }
3025
3246
  return tokens;
3026
3247
  }
3027
-
3028
- // src/retrieval-budget.ts
3029
- var PROFILES = {
3030
- conservative: {
3031
- topK: 12,
3032
- payloadWarnBytes: 8192,
3033
- payloadHardBytes: 32768,
3034
- injectionChars: 1e3
3035
- },
3036
- balanced: {
3037
- topK: 24,
3038
- payloadWarnBytes: 16384,
3039
- payloadHardBytes: 65536,
3040
- injectionChars: 2e3
3041
- },
3042
- generous: {
3043
- topK: 48,
3044
- payloadWarnBytes: 32768,
3045
- payloadHardBytes: 131072,
3046
- injectionChars: 4e3
3047
- }
3048
- };
3049
- var DEFAULT_RETRIEVAL_BUDGET_PROFILE = "balanced";
3050
- function resolveRetrievalBudget(overrides) {
3051
- const base = PROFILES[overrides?.profile ?? DEFAULT_RETRIEVAL_BUDGET_PROFILE];
3052
- return {
3053
- topK: overrides?.topK ?? base.topK,
3054
- payloadWarnBytes: overrides?.payloadWarnBytes ?? base.payloadWarnBytes,
3055
- payloadHardBytes: overrides?.payloadHardBytes ?? base.payloadHardBytes,
3056
- injectionChars: overrides?.injectionChars ?? base.injectionChars
3057
- };
3058
- }
3059
- function retrievalBudgetProfile(profile) {
3060
- return PROFILES[profile];
3061
- }
3062
3248
  export {
3063
3249
  AGENTS_META_IDENTITY_SOURCES,
3064
- AGENTS_META_LAYERS,
3065
3250
  AGENTS_META_TOPOLOGY_TYPES,
3066
3251
  AgentsMetaCountersSchema,
3067
- BOOTSTRAP_CANONICAL,
3252
+ BOOTSTRAP_CANONICAL_BY_LOCALE,
3253
+ BOOTSTRAP_CANONICAL_EN,
3254
+ BOOTSTRAP_CANONICAL_ZH,
3068
3255
  BOOTSTRAP_MARKER_BEGIN,
3069
3256
  BOOTSTRAP_MARKER_END,
3070
3257
  BOOTSTRAP_REGEX,
3071
- DEFAULT_RETRIEVAL_BUDGET_PROFILE,
3072
3258
  FabExtractKnowledgeInputSchema,
3073
3259
  FabExtractKnowledgeInputShape,
3074
3260
  FabExtractKnowledgeOutputSchema,
@@ -3084,9 +3270,6 @@ export {
3084
3270
  KNOWN_SCOPE_PREFIXES,
3085
3271
  KnowledgeEntryFrontmatterSchema,
3086
3272
  KnowledgeTypeSchema,
3087
- LEGACY_KB_MARKER_BEGIN,
3088
- LEGACY_KB_MARKER_END,
3089
- LEGACY_KB_REGEX,
3090
3273
  LayerSchema,
3091
3274
  MCP_STORE_AWARE_CONTRACTS,
3092
3275
  MCP_STORE_AWARE_TOOLS,
@@ -3098,16 +3281,18 @@ export {
3098
3281
  PERSONAL_SCOPE,
3099
3282
  PERSONAL_STORE_SENTINEL,
3100
3283
  PROJECT_ROOT_SIGNALS,
3101
- PROPOSED_REASON_DESCRIPTIONS,
3284
+ PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE,
3102
3285
  PROTECTED_TOKENS,
3103
3286
  ProposedReasonSchema,
3104
3287
  REDACTION_PLACEHOLDER_PREFIX,
3105
- ResolverNotImplementedError,
3106
3288
  SCOPE_COORDINATE_PATTERN,
3107
3289
  STORES_ROOT_DIR,
3290
+ STORE_ALIAS_PATTERN,
3108
3291
  STORE_GITIGNORE,
3109
3292
  STORE_KNOWLEDGE_TYPE_DIRS,
3110
3293
  STORE_LAYOUT,
3294
+ STORE_MOUNT_GROUPS,
3295
+ STORE_MOUNT_NAME_PATTERN,
3111
3296
  STORE_PENDING_DIR,
3112
3297
  STORE_PROJECT_ID_PATTERN,
3113
3298
  STORE_RESOLVER_WARNING_CODES,
@@ -3117,7 +3302,6 @@ export {
3117
3302
  addMountedStore,
3118
3303
  addStoreProject,
3119
3304
  agentsIdentitySourceSchema,
3120
- agentsLayerSchema,
3121
3305
  agentsMetaNodeSchema,
3122
3306
  agentsMetaSchema,
3123
3307
  agentsTopologyTypeSchema,
@@ -3133,7 +3317,6 @@ export {
3133
3317
  auditModeSchema,
3134
3318
  bindRequiredStore,
3135
3319
  bindingsSnapshotPath,
3136
- bootstrapMarkerMigratedEventSchema,
3137
3320
  buildDebugBundle,
3138
3321
  buildFailureTrace,
3139
3322
  buildScanRecommendations,
@@ -3156,11 +3339,12 @@ export {
3156
3339
  defaultLayerFilterSchema,
3157
3340
  defaultMessages,
3158
3341
  deriveAgentsMetaIdentitySource,
3159
- deriveAgentsMetaLayer,
3160
3342
  deriveAgentsMetaStableId,
3161
3343
  deriveAgentsMetaTopologyType,
3344
+ deriveMountLabel,
3162
3345
  detachMountedStore,
3163
3346
  detectNodeLocale,
3347
+ disambiguateAlias,
3164
3348
  doctorRunEventSchema,
3165
3349
  driftDetectedEventSchema,
3166
3350
  editIntentCheckedEventSchema,
@@ -3220,6 +3404,7 @@ export {
3220
3404
  isPersonalScope,
3221
3405
  knowledgeArchiveAttemptedEventSchema,
3222
3406
  knowledgeArchivedEventSchema,
3407
+ knowledgeBodyReadEventSchema,
3223
3408
  knowledgeConsumedEventSchema,
3224
3409
  knowledgeContextPlannedEventSchema,
3225
3410
  knowledgeDeferredEventSchema,
@@ -3229,6 +3414,7 @@ export {
3229
3414
  knowledgeIdRedirectEventSchema,
3230
3415
  knowledgeLayerChangedEventSchema,
3231
3416
  knowledgeMetaAutoHealedEventSchema,
3417
+ knowledgeModifiedEventSchema,
3232
3418
  knowledgePathDangledEventSchema,
3233
3419
  knowledgePromoteFailedEventSchema,
3234
3420
  knowledgePromoteStartedEventSchema,
@@ -3259,7 +3445,7 @@ export {
3259
3445
  localKnowledgeIdSchema,
3260
3446
  lockApprovedEventSchema,
3261
3447
  lockDriftEventSchema,
3262
- mcpConfigMigratedEventSchema,
3448
+ matchBootstrapCanonicalLocale,
3263
3449
  mcpEventLedgerEventSchema,
3264
3450
  mcpPayloadLimitsSchema,
3265
3451
  mcpStdioTraceEventSchema,
@@ -3269,6 +3455,8 @@ export {
3269
3455
  mountedStoreSchema,
3270
3456
  normalizeCiteTag,
3271
3457
  normalizeLocale,
3458
+ nudgeModeSchema,
3459
+ observeConfigSchema,
3272
3460
  onboardSlotSchema,
3273
3461
  parityCapabilitySchema,
3274
3462
  parityClientExpectationSchema,
@@ -3300,6 +3488,7 @@ export {
3300
3488
  readSetGoldenFileSchema,
3301
3489
  readStoreCounters,
3302
3490
  readStoreIdentity,
3491
+ readStoreIdentityAsync,
3303
3492
  readStoreProjects,
3304
3493
  reapplyCompletedEventSchema,
3305
3494
  recallAnnotations,
@@ -3307,16 +3496,18 @@ export {
3307
3496
  recallOutputSchema,
3308
3497
  recognizeStoreDir,
3309
3498
  reconcileStoreCounters,
3499
+ redactPii,
3310
3500
  redactSecrets,
3311
3501
  relevanceMigrationRunEventSchema,
3312
3502
  requiredStoreEntrySchema,
3503
+ resolveBootstrapCanonical,
3313
3504
  resolveCandidates,
3314
3505
  resolveFabricLocale,
3506
+ resolveGlobalLocale,
3315
3507
  resolveGlobalRoot,
3316
- resolveRetrievalBudget,
3317
3508
  resolveStoreQualifiedId,
3509
+ resolveWorkspaceBindingId,
3318
3510
  resolvedBindingsSnapshotSchema,
3319
- retrievalBudgetProfile,
3320
3511
  ruleDescriptionIndexItemSchema,
3321
3512
  ruleDescriptionSchema,
3322
3513
  saveGlobalConfig,
@@ -3333,16 +3524,21 @@ export {
3333
3524
  skillInvocationStartedEventSchema,
3334
3525
  skillPhaseTransitionEventSchema,
3335
3526
  skillTriggerCandidateEventSchema,
3527
+ storeAliasSchema,
3336
3528
  storeAwareEntrySchema,
3337
3529
  storeCountersPath,
3338
3530
  storeCountersSchema,
3339
3531
  storeHasProject,
3340
3532
  storeIdentitySchema,
3341
3533
  storeKnowledgeTypeDir,
3534
+ storeMountGroup,
3535
+ storeMountNameSchema,
3536
+ storeMountSubPath,
3342
3537
  storeProjectSchema,
3343
3538
  storeProjectsFileSchema,
3344
3539
  storeReadSetSchema,
3345
3540
  storeRelativePath,
3541
+ storeRelativePathForMount,
3346
3542
  storeResolveInputSchema,
3347
3543
  storeResolverWarningCodeSchema,
3348
3544
  storeResolverWarningSchema,
@@ -3352,6 +3548,7 @@ export {
3352
3548
  uidSchema,
3353
3549
  withDerivedAgentsMetaNodeDefaults,
3354
3550
  writeBindingsSnapshot,
3551
+ writeRouteSchema,
3355
3552
  writeTargetSchema,
3356
3553
  writtenToStoreSchema,
3357
3554
  zhCNMessages