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

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,16 @@
1
- import {
2
- BOOTSTRAP_CANONICAL,
3
- BOOTSTRAP_MARKER_BEGIN,
4
- BOOTSTRAP_MARKER_END,
5
- BOOTSTRAP_REGEX,
6
- LEGACY_KB_MARKER_BEGIN,
7
- LEGACY_KB_MARKER_END,
8
- LEGACY_KB_REGEX
9
- } from "./chunk-AFT7DB4P.js";
1
+ import "./chunk-LXNCAKJZ.js";
10
2
  import {
11
3
  PROTECTED_TOKENS,
12
4
  createTranslator,
13
5
  defaultMessages,
14
- detectNodeLocale,
15
6
  enMessages,
16
- normalizeLocale,
17
7
  resolveFabricLocale,
18
8
  zhCNMessages
19
- } from "./chunk-KUYCTRFI.js";
9
+ } from "./chunk-B2N3I7UX.js";
20
10
  import {
21
11
  atomicWriteJson,
22
12
  withFileLock
23
- } from "./chunk-4N6DMOOW.js";
13
+ } from "./chunk-C7WZPYZE.js";
24
14
  import {
25
15
  FabExtractKnowledgeInputSchema,
26
16
  FabExtractKnowledgeInputShape,
@@ -30,14 +20,17 @@ import {
30
20
  FabReviewOutputSchema,
31
21
  FabReviewOutputShape,
32
22
  KNOWLEDGE_TYPE_CODES,
23
+ KNOWN_SCOPE_PREFIXES,
33
24
  KnowledgeEntryFrontmatterSchema,
34
25
  KnowledgeTypeSchema,
35
26
  LayerSchema,
36
27
  MaturitySchema,
37
28
  ONBOARD_SLOT_NAMES,
38
29
  ONBOARD_SLOT_TOTAL,
39
- PROPOSED_REASON_DESCRIPTIONS,
30
+ PERSONAL_SCOPE,
31
+ PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE,
40
32
  ProposedReasonSchema,
33
+ SCOPE_COORDINATE_PATTERN,
41
34
  StableIdSchema,
42
35
  annotateIntentRequestSchema,
43
36
  archiveScanAnnotations,
@@ -46,12 +39,14 @@ import {
46
39
  citeContractMetricsSchema,
47
40
  citeCoverageReportSchema,
48
41
  citeLayerTypeBreakdownSchema,
42
+ entryScopeMetadataSchema,
49
43
  fabExtractKnowledgeAnnotations,
50
44
  fabReviewAnnotations,
51
45
  formatKnowledgeId,
52
46
  historyStateQuerySchema,
53
47
  humanLockApproveRequestSchema,
54
48
  humanLockFileParamsSchema,
49
+ isPersonalScope,
55
50
  knowledgeSectionsAnnotations,
56
51
  knowledgeSectionsInputSchema,
57
52
  knowledgeSectionsOutputSchema,
@@ -67,9 +62,55 @@ import {
67
62
  recallAnnotations,
68
63
  recallInputSchema,
69
64
  recallOutputSchema,
65
+ scopeCoordinateSchema,
66
+ scopeRoot,
70
67
  structuredWarningSchema
71
- } from "./chunk-355LUDLW.js";
72
- import "./chunk-LXNCAKJZ.js";
68
+ } from "./chunk-5AKCRBKJ.js";
69
+ import {
70
+ BOOTSTRAP_CANONICAL_BY_LOCALE,
71
+ BOOTSTRAP_CANONICAL_EN,
72
+ BOOTSTRAP_CANONICAL_ZH,
73
+ BOOTSTRAP_MARKER_BEGIN,
74
+ BOOTSTRAP_MARKER_END,
75
+ BOOTSTRAP_REGEX,
76
+ matchBootstrapCanonicalLocale,
77
+ resolveBootstrapCanonical
78
+ } from "./chunk-BDJQIOQO.js";
79
+ import {
80
+ GLOBAL_BINDINGS_DIR,
81
+ GLOBAL_STATE_DIR,
82
+ PERSONAL_STORE_SENTINEL,
83
+ STORES_ROOT_DIR,
84
+ STORE_ALIAS_PATTERN,
85
+ STORE_KNOWLEDGE_TYPE_DIRS,
86
+ STORE_LAYOUT,
87
+ STORE_MOUNT_GROUPS,
88
+ STORE_MOUNT_NAME_PATTERN,
89
+ STORE_PROJECT_ID_PATTERN,
90
+ STORE_UUID_PATTERN,
91
+ deriveMountLabel,
92
+ detectNodeLocale,
93
+ globalConfigPath,
94
+ globalConfigSchema,
95
+ loadGlobalConfig,
96
+ mountedStoreSchema,
97
+ normalizeLocale,
98
+ requiredStoreEntrySchema,
99
+ resolveGlobalLocale,
100
+ resolveGlobalRoot,
101
+ saveGlobalConfig,
102
+ storeAliasSchema,
103
+ storeIdentitySchema,
104
+ storeKnowledgeTypeDir,
105
+ storeMountGroup,
106
+ storeMountNameSchema,
107
+ storeMountSubPath,
108
+ storeProjectSchema,
109
+ storeProjectsFileSchema,
110
+ storeRelativePath,
111
+ storeRelativePathForMount,
112
+ storeUuidSchema
113
+ } from "./chunk-2GLIAZ5M.js";
73
114
 
74
115
  // src/schemas/agents-meta.ts
75
116
  import { z } from "zod";
@@ -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()
364
- }).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()
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()
380
357
  }).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,29 @@ 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),
639
+ hint_broad_top_k: z5.number().int().min(1).max(50).optional().default(8),
705
640
  // v2.2 HK2-degrade (W2-T2): char budget for the rendered SessionStart broad-menu
706
641
  // body — the final rung of the degradation ladder after the hint_broad_top_k
707
642
  // count slice. Once the rendered entry/group lines exceed this, the tail
708
643
  // collapses to a single "N more omitted" marker so a large corpus cannot blow
709
644
  // the agent's working memory. Default 2000 (~one screenful); 0 disables the
710
645
  // 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),
646
+ hint_broad_budget_chars: z5.number().int().min(0).max(2e4).optional().default(2e3),
647
+ // W4-1 (KT-DEC-0028 / KT-MOD-0001): scale backstop for the FULL broad index.
648
+ // After W2-1 retired the hint_broad_top_k hard cap, the broad banner shows
649
+ // every broad entry (completeness); this is the only guard — once a store's
650
+ // rendered broad index exceeds this many lines the overflow tail folds into a
651
+ // single drift marker. The doctor `broad-index-drift` lint (W4-2) warns at 80%
652
+ // of this value per store so the corpus can be pruned (fabric-audit) BEFORE the
653
+ // banner silently truncates. Default 50; range 20..500 (read inline by
654
+ // knowledge-hint-broad.cjs#readBroadIndexBackstop with the same bounds).
655
+ broad_index_backstop: z5.number().int().min(20).max(500).optional().default(50),
712
656
  // v2.0.0-rc.37 NEW-16: durable per-signal dismiss for the fabric-hint Stop
713
657
  // hook nudges. Any signal type listed here is suppressed at emit time across
714
658
  // all sessions (the session-scoped sibling lives in a .fabric/.cache sidecar
715
659
  // written on request). Mirrors the cite_evict_interval=0 opt-out convention —
716
660
  // 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(),
661
+ hint_dismiss_signals: z5.array(z5.enum(["archive", "review", "import", "maintenance"])).optional(),
718
662
  // v2.1 ADJ-NEWN-4: user-override escape hatches for the two strong behavioral
719
663
  // policies (cite-before-edit + self-archive). The strong policies can make an
720
664
  // agent feel like a "stubborn parrot" (D2 user-in-control red line); these
@@ -726,15 +670,15 @@ var fabricConfigSchema = z6.object({
726
670
  // surface, mirroring the cite_evict_interval=0 / hint_dismiss_signals opt-out
727
671
  // convention, NOT a new feature. Wave3 J32 will quantify the friction these
728
672
  // 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),
673
+ cite_policy_enabled: z5.boolean().optional().default(true),
674
+ self_archive_policy_enabled: z5.boolean().optional().default(true),
731
675
  // v2.0.0-rc.33 W2-1 (P0-9): TopK upper bound for the narrow PreToolUse hint
732
676
  // emitted by knowledge-hint-narrow.cjs. After filtering to entries whose
733
677
  // `relevance_scope === "narrow"` (rc.27 TASK-005 audit §2.5 fix), the hook
734
678
  // slices to this many before the E3 emit-gate / renderSummary pipeline.
735
679
  // Default 5 keeps each per-Edit hint terse — five lines max so the agent's
736
680
  // 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),
681
+ hint_narrow_top_k: z5.number().int().min(1).max(20).optional().default(5),
738
682
  // v2.0.0-rc.33 W2-1 (P0-9): per-file dedup window (in PreToolUse turns) for
739
683
  // the narrow hint. Same (file_path, stable_id) tuple stays silent for this
740
684
  // many turns even when the E3 cross-session cache would otherwise re-emit.
@@ -744,7 +688,7 @@ var fabricConfigSchema = z6.object({
744
688
  // Storage: .fabric/.cache/narrow-dedup-window.json — distinct from session-
745
689
  // hints cache so a window-only suppression does not poison cross-session
746
690
  // dedupe semantics.
747
- hint_narrow_dedup_window_turns: z6.number().int().min(1).max(50).optional().default(5),
691
+ hint_narrow_dedup_window_turns: z5.number().int().min(1).max(50).optional().default(5),
748
692
  // v2.0.0-rc.33 W2-5 (P1-8): cooldown between broad SessionStart hint emits,
749
693
  // in hours. Distinct from the archive_hint_cooldown_hours that gates the
750
694
  // fabric-hint Stop hook — knowledge-hint-broad re-fires on every
@@ -753,14 +697,14 @@ var fabricConfigSchema = z6.object({
753
697
  // menu at most once per hour"; 0 means "no cooldown, current behavior."
754
698
  // Range 0..168 (one week). Stored alongside fabric-hint's cooldown cache
755
699
  // under a distinct knowledge-hint-broad key.
756
- hint_broad_cooldown_hours: z6.number().int().min(0).max(168).optional().default(0),
700
+ hint_broad_cooldown_hours: z5.number().int().min(0).max(168).optional().default(0),
757
701
  // v2.0.0-rc.33 W2-5 (P1-8): cooldown for the narrow PreToolUse hint.
758
702
  // Same shape as hint_broad_cooldown_hours but applies to per-Edit hint
759
703
  // re-emission across the cooldown window — independent of E3 session-
760
704
  // hints dedupe. Default 0 preserves rc.32 behavior; set to e.g. 1 to
761
705
  // throttle hint frequency during rapid-fire editing sprints. Range
762
706
  // 0..168 (one week).
763
- hint_narrow_cooldown_hours: z6.number().int().min(0).max(168).optional().default(0),
707
+ hint_narrow_cooldown_hours: z5.number().int().min(0).max(168).optional().default(0),
764
708
  // v2.0.0-rc.33 W4-B3 (T5 P2): per-maturity inactivity thresholds (days)
765
709
  // driving orphan_demote. Hardcoded at proven=90/verified=30/draft=14 in
766
710
  // rc.32; chatty workspaces want them tighter, slow ones want them looser.
@@ -768,26 +712,19 @@ var fabricConfigSchema = z6.object({
768
712
  // chosen so a typo can't accidentally disable the lint (min 1).
769
713
  //
770
714
  // 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(),
715
+ // draft/verified/proven (KT-DEC-0005). These threshold keys use the canonical
716
+ // vocabulary; the loader maps proven→stable / verified→endorsed onto the
717
+ // doctor's internal orphan_demote ladder.
718
+ orphan_demote_proven_days: z5.number().int().min(1).max(3650).optional(),
719
+ orphan_demote_verified_days: z5.number().int().min(1).max(3650).optional(),
720
+ orphan_demote_draft_days: z5.number().int().min(1).max(3650).optional(),
784
721
  // v2.0.0-rc.33 W4-A3 (T4 P2): per-entry summary truncation length used by
785
722
  // knowledge-hint-{broad,narrow}.cjs. Hard-coded at 80 chars in rc.32 — too
786
723
  // short for entries with parameterized summaries (e.g. "Use bcrypt with
787
724
  // cost=12 for password hashing"), too long for terse pitfalls. Range 40..240;
788
725
  // default 80 preserves rc.32 behavior. Both hooks read the same key so the
789
726
  // banner styling stays consistent across SessionStart + PreToolUse.
790
- hint_summary_max_len: z6.number().int().min(40).max(240).optional().default(80),
727
+ hint_summary_max_len: z5.number().int().min(40).max(240).optional().default(80),
791
728
  // v2.0.0-rc.33 W2-6 (P0-7 + P0-8): when true, knowledge-hint hooks emit
792
729
  // their banners as `hookSpecificOutput.additionalContext` JSON on stdout
793
730
  // (per Claude Code PreToolUse hook contract — see
@@ -797,7 +734,7 @@ var fabricConfigSchema = z6.object({
797
734
  // coverage focus (rc.32 baseline 3.1% → primary cause: reminders never
798
735
  // entered model context). Set false to revert to legacy stderr-only mode
799
736
  // for hosts that don't honor the JSON contract.
800
- hint_reminder_to_context: z6.boolean().optional().default(true),
737
+ hint_reminder_to_context: z5.boolean().optional().default(true),
801
738
  // v2.0.0-rc.29 TASK-008 (BUG-F3): selection-token TTL override. The
802
739
  // `fab_plan_context` MCP tool hands clients a `selection_token` whose default
803
740
  // 5-minute lifetime (`SELECTION_TOKEN_TTL_MS` at
@@ -825,19 +762,27 @@ var fabricConfigSchema = z6.object({
825
762
  // up together. Per-field knobs (plan_context_top_k, mcpPayloadLimits.*,
826
763
  // hint_broad_budget_chars) still override the profile when set. See
827
764
  // retrieval-budget.ts (resolveRetrievalBudget) for the resolution order.
828
- retrieval_budget_profile: z6.enum(["conservative", "balanced", "generous"]).optional(),
765
+ retrieval_budget_profile: z5.enum(["conservative", "balanced", "generous"]).optional(),
766
+ // grill-report C-009 / R2-R5 mitigation knob: per-field override for the
767
+ // fab_recall body-tier budget (bytes). Absent → derived from the profile
768
+ // (balanced = 4096). Bump it if the lean default drops too many bodies and the
769
+ // AI is not following up with fab_get_knowledge_sections (R2 observation).
770
+ // Range 256B..1MB — below 256B even one body never fits; above 1MB defeats the
771
+ // whole lean-recall purpose. Overrides the profile when set, like the other
772
+ // per-field knobs.
773
+ recall_body_budget_bytes: z5.number().int().min(256).max(1048576).optional(),
829
774
  // v2.2 C2-vector (W2-T7): OPTIONAL dense-embedding semantic retrieval, layered
830
775
  // as a recall supplement after BM25. Default OFF (`--no-embed` is the baseline);
831
776
  // requires the operator to install the optional `fastembed` package — absent →
832
777
  // text-only fallback. Never grows the default install footprint.
833
- embed_enabled: z6.boolean().optional().default(false),
778
+ embed_enabled: z5.boolean().optional().default(false),
834
779
  // Weight applied to the 0..1 cosine similarity before it joins the additive
835
780
  // score. Capped at 49 — strictly BELOW BM25_WEIGHT (50) — so a perfect vector
836
781
  // match (weight × 1) can never outscore a single strong BM25 term match. This
837
782
  // ENFORCES the "vectors supplement, never override lexical relevance"
838
783
  // invariant in the schema rather than leaving it to a comment (W2-REVIEW codex
839
784
  // MED-4). Range 0..49; default 30.
840
- embed_weight: z6.number().int().min(0).max(49).optional().default(30),
785
+ embed_weight: z5.number().int().min(0).max(49).optional().default(30),
841
786
  // v2.1 ③ vector-chinese-model (P3): which fastembed model to load. The prior
842
787
  // code pinned fastembed's English default (bge-small-en-v1.5) — wrong for the
843
788
  // Chinese-heavy zh-CN-hybrid KB. Values are the fastembed@2.x EmbeddingModel
@@ -847,7 +792,7 @@ var fabricConfigSchema = z6.object({
847
792
  // available for full multilingual recall at a ~1GB download + slower CPU cost.
848
793
  // (V1 research: fastembed@2.1.0 has NO multilingual-e5-SMALL — the originally
849
794
  // planned pin — so bge-small-zh is the light Chinese choice.)
850
- embed_model: z6.enum([
795
+ embed_model: z5.enum([
851
796
  "fast-bge-small-zh-v1.5",
852
797
  "fast-multilingual-e5-large",
853
798
  "fast-bge-small-en-v1.5",
@@ -859,8 +804,8 @@ var fabricConfigSchema = z6.object({
859
804
  });
860
805
 
861
806
  // src/schemas/fabric-config-introspect.ts
862
- import { z as z7 } from "zod";
863
- var positiveIntSchema = z7.coerce.number().int().positive();
807
+ import { z as z6 } from "zod";
808
+ var positiveIntSchema = z6.coerce.number().int().positive();
864
809
  function makePositiveIntField(key, defaultValue) {
865
810
  return {
866
811
  key,
@@ -916,6 +861,28 @@ function makeEnumField(key, group, enumValues, defaultValue) {
916
861
  }
917
862
  };
918
863
  }
864
+ function makeBooleanField(key, defaultValue) {
865
+ return {
866
+ key,
867
+ group: "D_behavior",
868
+ widget: "select",
869
+ label_i18n_key: `cli.config.fields.${key}.label`,
870
+ description_i18n_key: `cli.config.fields.${key}.description`,
871
+ default: String(defaultValue),
872
+ enum_values: ["true", "false"],
873
+ validate(raw) {
874
+ const trimmed = raw.trim();
875
+ if (trimmed === "true") return { ok: true, value: true };
876
+ if (trimmed === "false") return { ok: true, value: false };
877
+ return { ok: false, error: "Must be one of: true, false." };
878
+ },
879
+ format_for_display(value) {
880
+ if (typeof value === "boolean") return String(value);
881
+ if (value === void 0 || value === null) return String(defaultValue);
882
+ return String(value);
883
+ }
884
+ };
885
+ }
919
886
  var SCHEMA_DEFAULTS = fabricConfigSchema.parse({});
920
887
  function pickNumberDefault(key) {
921
888
  const v = SCHEMA_DEFAULTS[key];
@@ -944,12 +911,13 @@ function getPanelFieldByKey(key) {
944
911
  }
945
912
  var PANEL_FIELDS = [
946
913
  // --- Group A: Locale (2) ---
947
- makeEnumField(
948
- "fabric_language",
949
- "A_locale",
950
- fabricLanguageSchema.options,
951
- pickStringDefault("fabric_language")
952
- ),
914
+ // grill-6fixes (D1): `fabric_language` is no longer a project-config field —
915
+ // it is the single machine-wide tone in `~/.fabric/fabric-global.json`. The
916
+ // panel still surfaces it (the `fabric config` language entry), but config.ts
917
+ // special-cases this key to read/write the GLOBAL config instead of the
918
+ // project file. Default is a literal "en" since there is no project-schema
919
+ // default to derive from.
920
+ makeEnumField("fabric_language", "A_locale", fabricLanguageSchema.options, "en"),
953
921
  makeEnumField(
954
922
  "default_layer_filter",
955
923
  "A_locale",
@@ -993,39 +961,23 @@ var PANEL_FIELDS = [
993
961
  "C_audit",
994
962
  auditModeSchema.options,
995
963
  AUDIT_MODE_PANEL_DEFAULT
996
- )
964
+ ),
965
+ // --- Group D: Behavior / features (2) ---
966
+ // nudge_mode — the master switch for the human-visible nudge experience
967
+ // (the most user-facing runtime knob, previously JSON-only). embed_enabled —
968
+ // vector semantic recall, panel-editable now that config lives in `.fabric`
969
+ // (A1); enabling also needs the host-side `fabric install --enable-embed`.
970
+ makeEnumField("nudge_mode", "D_behavior", nudgeModeSchema.options, "normal"),
971
+ makeBooleanField("embed_enabled", false)
997
972
  ];
998
973
 
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
974
  // src/schemas/store-stable-id.ts
1023
- import { z as z9 } from "zod";
975
+ import { z as z7 } from "zod";
1024
976
  var localKnowledgeIdSchema = StableIdSchema;
1025
977
  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");
978
+ var uidSchema = z7.string().min(1).regex(UID_SEGMENT_PATTERN, "uid must be lowercase [a-z0-9-] segments");
1027
979
  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>");
980
+ var globalRefSchema = z7.string().regex(GLOBAL_REF_PATTERN, "global_ref must be <store_uuid>[:<uid>]:<local_id>");
1029
981
  function formatGlobalRef(parts) {
1030
982
  const { store_uuid, uid, local_id } = parts;
1031
983
  return uid === void 0 ? `${store_uuid}:${local_id}` : `${store_uuid}:${uid}:${local_id}`;
@@ -1050,14 +1002,14 @@ function parseGlobalRef(ref) {
1050
1002
  local_id
1051
1003
  };
1052
1004
  }
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)
1005
+ var storeKnowledgeTypeCountersSchema = z7.object({
1006
+ MOD: z7.number().int().nonnegative().default(0),
1007
+ DEC: z7.number().int().nonnegative().default(0),
1008
+ GLD: z7.number().int().nonnegative().default(0),
1009
+ PIT: z7.number().int().nonnegative().default(0),
1010
+ PRO: z7.number().int().nonnegative().default(0)
1059
1011
  }).default({ MOD: 0, DEC: 0, GLD: 0, PIT: 0, PRO: 0 });
1060
- var storeCountersSchema = z9.object({
1012
+ var storeCountersSchema = z7.object({
1061
1013
  KP: storeKnowledgeTypeCountersSchema,
1062
1014
  KT: storeKnowledgeTypeCountersSchema
1063
1015
  }).default({
@@ -1066,64 +1018,63 @@ var storeCountersSchema = z9.object({
1066
1018
  });
1067
1019
 
1068
1020
  // 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);
1021
+ import { z as z8 } from "zod";
1022
+ var PARITY_CLIENTS = ["claudeCode", "codexCLI"];
1023
+ var parityClientSchema = z8.enum(PARITY_CLIENTS);
1072
1024
  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()
1025
+ var paritySurfaceSchema = z8.enum(PARITY_SURFACES);
1026
+ var parityClientExpectationSchema = z8.object({
1027
+ supported: z8.boolean(),
1028
+ mechanism: z8.string().optional(),
1029
+ notes: z8.string().optional()
1078
1030
  }).strict();
1079
- var parityCapabilitySchema = z10.object({
1080
- id: z10.string().min(1),
1031
+ var parityCapabilitySchema = z8.object({
1032
+ id: z8.string().min(1),
1081
1033
  surface: paritySurfaceSchema,
1082
- description: z10.string().min(1),
1083
- clients: z10.object({
1034
+ description: z8.string().min(1),
1035
+ clients: z8.object({
1084
1036
  claudeCode: parityClientExpectationSchema,
1085
- codexCLI: parityClientExpectationSchema,
1086
- cursor: parityClientExpectationSchema
1037
+ codexCLI: parityClientExpectationSchema
1087
1038
  }).strict()
1088
1039
  }).strict();
1089
- var parityMatrixSchema = z10.object({
1040
+ var parityMatrixSchema = z8.object({
1090
1041
  // Schema/version tag of the matrix document itself.
1091
- version: z10.string().min(1),
1042
+ version: z8.string().min(1),
1092
1043
  // Free-form note on what release/milestone this matrix targets.
1093
- generated_for: z10.string().min(1),
1094
- capabilities: z10.array(parityCapabilitySchema).min(1)
1044
+ generated_for: z8.string().min(1),
1045
+ capabilities: z8.array(parityCapabilitySchema).min(1)
1095
1046
  }).strict();
1096
1047
 
1097
1048
  // src/resolver/contracts.ts
1098
- import { z as z11 } from "zod";
1049
+ import { z as z9 } from "zod";
1099
1050
  var PROJECT_ROOT_SIGNALS = ["env", "marker", "cwd", "repo"];
1100
- var projectRootSignalSchema = z11.enum(PROJECT_ROOT_SIGNALS);
1101
- var projectRootSignalsSchema = z11.object({
1051
+ var projectRootSignalSchema = z9.enum(PROJECT_ROOT_SIGNALS);
1052
+ var projectRootSignalsSchema = z9.object({
1102
1053
  // FABRIC_PROJECT_ROOT, if set.
1103
- env: z11.string().optional(),
1054
+ env: z9.string().optional(),
1104
1055
  // Nearest directory AT-OR-ABOVE cwd holding `.fabric/fabric-config.json`,
1105
1056
  // if any (the upward marker search result; may equal cwd).
1106
- markerDir: z11.string().optional(),
1057
+ markerDir: z9.string().optional(),
1107
1058
  // Always present — the process cwd.
1108
- cwd: z11.string().min(1),
1059
+ cwd: z9.string().min(1),
1109
1060
  // git repo root, if inside a repo.
1110
- repoRoot: z11.string().optional(),
1061
+ repoRoot: z9.string().optional(),
1111
1062
  // The `project_id` read from the winning root's fabric-config.json during
1112
1063
  // (fs) signal collection. The pure resolver echoes it — it cannot invent a
1113
1064
  // UUID. Worktrees of one repo share the committed config, hence the same
1114
1065
  // project_id (S45 merge). Absent when no .fabric config exists at the root
1115
1066
  // yet (fresh repo-fallback) → resolution still yields the root with a null
1116
1067
  // projectId so the caller can mint+persist one at install time.
1117
- discoveredProjectId: z11.string().optional()
1068
+ discoveredProjectId: z9.string().optional()
1118
1069
  }).strict();
1119
- var projectRootResolutionSchema = z11.object({
1070
+ var projectRootResolutionSchema = z9.object({
1120
1071
  // Absolute project root directory.
1121
- projectRoot: z11.string().min(1),
1072
+ projectRoot: z9.string().min(1),
1122
1073
  // Stable project identity. One repo = one .fabric = one project_id (S32);
1123
1074
  // git worktrees of the same repo resolve to the SAME project_id (S45).
1124
1075
  // Null when the resolved root has no fabric-config.json yet (fresh
1125
1076
  // repo-fallback) — the caller mints + persists a UUID at install time.
1126
- projectId: z11.string().min(1).nullable(),
1077
+ projectId: z9.string().min(1).nullable(),
1127
1078
  // Which signal won.
1128
1079
  signalUsed: projectRootSignalSchema
1129
1080
  }).strict();
@@ -1132,91 +1083,96 @@ var STORE_RESOLVER_WARNING_CODES = [
1132
1083
  // required_stores entry has no matching mounted store (S51)
1133
1084
  "local_only_no_remote",
1134
1085
  // mounted but local-only (R5#5 nudge, non-fatal)
1135
- "alias_unresolved"
1086
+ "alias_unresolved",
1136
1087
  // referenced alias maps to no mounted store
1088
+ "missing_write_route"
1089
+ // multi/shared write requires an explicit route
1137
1090
  ];
1138
- var storeResolverWarningCodeSchema = z11.enum(STORE_RESOLVER_WARNING_CODES);
1139
- var storeResolverWarningSchema = z11.object({
1091
+ var storeResolverWarningCodeSchema = z9.enum(STORE_RESOLVER_WARNING_CODES);
1092
+ var storeResolverWarningSchema = z9.object({
1140
1093
  code: storeResolverWarningCodeSchema,
1141
1094
  // The alias/UUID/id the warning concerns.
1142
- ref: z11.string().min(1),
1143
- message: z11.string().min(1)
1095
+ ref: z9.string().min(1),
1096
+ message: z9.string().min(1)
1144
1097
  }).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(),
1098
+ var readSetEntrySchema = z9.object({
1099
+ store_uuid: z9.string().min(1),
1100
+ alias: z9.string().min(1),
1101
+ remote: z9.string().min(1).optional(),
1149
1102
  // Whether this store accepts writes from the current context. Personal
1150
1103
  // store is writable; shared stores writable iff mounted with write intent.
1151
- writable: z11.boolean()
1104
+ writable: z9.boolean()
1152
1105
  }).strict();
1153
- var storeReadSetSchema = z11.object({
1154
- stores: z11.array(readSetEntrySchema),
1155
- warnings: z11.array(storeResolverWarningSchema)
1106
+ var storeReadSetSchema = z9.object({
1107
+ stores: z9.array(readSetEntrySchema),
1108
+ warnings: z9.array(storeResolverWarningSchema)
1156
1109
  }).strict();
1157
- var writeTargetSchema = z11.object({
1158
- store_uuid: z11.string().min(1),
1159
- alias: z11.string().min(1)
1110
+ var writeTargetSchema = z9.object({
1111
+ store_uuid: z9.string().min(1),
1112
+ alias: z9.string().min(1)
1160
1113
  }).strict();
1161
- var storeResolveInputSchema = z11.object({
1114
+ var storeResolveInputSchema = z9.object({
1162
1115
  // Machine identity (S33) — namespaces personal ids; identifies personal store.
1163
- uid: z11.string().min(1),
1116
+ uid: z9.string().min(1),
1164
1117
  // 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),
1118
+ mountedStores: z9.array(
1119
+ z9.object({
1120
+ store_uuid: z9.string().min(1),
1121
+ alias: z9.string().min(1),
1122
+ mount_name: z9.string().min(1).optional(),
1123
+ remote: z9.string().min(1).optional(),
1124
+ writable: z9.boolean().default(true),
1171
1125
  // Marks the implicit personal store.
1172
- personal: z11.boolean().default(false)
1126
+ personal: z9.boolean().default(false)
1173
1127
  }).strict()
1174
1128
  ),
1175
1129
  // 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()
1130
+ requiredStores: z9.array(
1131
+ z9.object({
1132
+ id: z9.string().min(1),
1133
+ suggested_remote: z9.string().min(1).optional()
1180
1134
  }).strict()
1181
1135
  ),
1182
1136
  // Alias selected as the active write store for non-personal scopes, if any.
1183
- activeWriteAlias: z11.string().min(1).optional()
1137
+ activeWriteAlias: z9.string().min(1).optional(),
1138
+ // Scope-aware write routes. Exact scope wins first, then longest prefix route.
1139
+ writeRoutes: z9.array(
1140
+ z9.object({
1141
+ scope: z9.string().min(1),
1142
+ store: z9.string().min(1)
1143
+ }).strict()
1144
+ ).optional().default([]),
1145
+ defaultWriteAlias: z9.string().min(1).optional()
1184
1146
  }).strict();
1185
- var projectRootGoldenCaseSchema = z11.object({
1186
- name: z11.string().min(1),
1187
- note: z11.string().optional(),
1147
+ var projectRootGoldenCaseSchema = z9.object({
1148
+ name: z9.string().min(1),
1149
+ note: z9.string().optional(),
1188
1150
  signals: projectRootSignalsSchema,
1189
1151
  // null expected = resolver should return no root for these signals.
1190
1152
  expected: projectRootResolutionSchema.nullable()
1191
1153
  }).strict();
1192
- var projectRootGoldenFileSchema = z11.object({
1193
- contract: z11.literal("project-root.golden"),
1194
- cases: z11.array(projectRootGoldenCaseSchema).min(1)
1154
+ var projectRootGoldenFileSchema = z9.object({
1155
+ contract: z9.literal("project-root.golden"),
1156
+ cases: z9.array(projectRootGoldenCaseSchema).min(1)
1195
1157
  }).strict();
1196
- var readSetGoldenCaseSchema = z11.object({
1197
- name: z11.string().min(1),
1198
- note: z11.string().optional(),
1158
+ var readSetGoldenCaseSchema = z9.object({
1159
+ name: z9.string().min(1),
1160
+ note: z9.string().optional(),
1199
1161
  input: storeResolveInputSchema,
1200
1162
  // Scope under test for the write-target expectation.
1201
- writeScope: z11.string().min(1),
1202
- expected: z11.object({
1163
+ writeScope: z9.string().min(1),
1164
+ expected: z9.object({
1203
1165
  readSet: storeReadSetSchema,
1204
1166
  writeTarget: writeTargetSchema.nullable(),
1205
- writeWarnings: z11.array(storeResolverWarningSchema)
1167
+ writeWarnings: z9.array(storeResolverWarningSchema)
1206
1168
  }).strict()
1207
1169
  }).strict();
1208
- var readSetGoldenFileSchema = z11.object({
1209
- contract: z11.literal("read-set.golden"),
1210
- cases: z11.array(readSetGoldenCaseSchema).min(1)
1170
+ var readSetGoldenFileSchema = z9.object({
1171
+ contract: z9.literal("read-set.golden"),
1172
+ cases: z9.array(readSetGoldenCaseSchema).min(1)
1211
1173
  }).strict();
1212
1174
 
1213
1175
  // 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
1176
  function createProjectRootResolver() {
1221
1177
  return {
1222
1178
  resolve(signals) {
@@ -1254,12 +1210,65 @@ function personalEntry(input) {
1254
1210
  }
1255
1211
  return entry;
1256
1212
  }
1213
+ function readSetEntryFromMounted(store) {
1214
+ const entry = {
1215
+ store_uuid: store.store_uuid,
1216
+ alias: store.alias,
1217
+ writable: store.writable
1218
+ };
1219
+ if (store.remote !== void 0) {
1220
+ entry.remote = store.remote;
1221
+ return { entry };
1222
+ }
1223
+ return {
1224
+ entry,
1225
+ warning: {
1226
+ code: "local_only_no_remote",
1227
+ ref: store.alias,
1228
+ message: `store '${store.alias}' is local-only; add a git remote to back it up (\`fabric store ... \` / doctor nudge)`
1229
+ }
1230
+ };
1231
+ }
1232
+ function isMountedPersonal(input, storeUuid) {
1233
+ return input.mountedStores.some((store) => store.store_uuid === storeUuid && store.personal);
1234
+ }
1235
+ function routeMatches(routeScope, scope) {
1236
+ return scope === routeScope || scope.startsWith(`${routeScope}:`);
1237
+ }
1238
+ function resolveRouteAlias(input, scope) {
1239
+ const routes = input.writeRoutes ?? [];
1240
+ const exact = routes.find((route) => route.scope === scope);
1241
+ if (exact !== void 0) {
1242
+ return exact.store;
1243
+ }
1244
+ const prefix = routes.filter((route) => routeMatches(route.scope, scope)).sort((a, b) => b.scope.length - a.scope.length)[0];
1245
+ return prefix?.store ?? input.defaultWriteAlias ?? input.activeWriteAlias;
1246
+ }
1247
+ function hasMultipleSharedStores(input, readSet) {
1248
+ return readSet.stores.filter((store) => !isMountedPersonal(input, store.store_uuid)).length > 1;
1249
+ }
1250
+ function hasExplicitRouteOrDefault(input, scope) {
1251
+ const routes = input.writeRoutes ?? [];
1252
+ return routes.some((route) => routeMatches(route.scope, scope)) || input.defaultWriteAlias !== void 0;
1253
+ }
1257
1254
  function createStoreResolver() {
1258
1255
  return {
1259
1256
  resolveReadSet(input) {
1260
1257
  const stores = [];
1261
1258
  const warnings = [];
1259
+ const seenStoreUuids = /* @__PURE__ */ new Set();
1262
1260
  for (const req of input.requiredStores) {
1261
+ if (req.suggested_remote === "$personal") {
1262
+ const personal2 = findPersonal(input);
1263
+ if (personal2 === void 0) {
1264
+ warnings.push({
1265
+ code: "missing_store",
1266
+ ref: req.id,
1267
+ message: `required store '${req.id}' is not mounted; run \`fabric store add\` (suggested remote: $personal)`
1268
+ });
1269
+ }
1270
+ continue;
1271
+ }
1263
1272
  const matched = input.mountedStores.find(
1264
1273
  (m) => !m.personal && (m.alias === req.id || m.store_uuid === req.id)
1265
1274
  );
@@ -1272,25 +1281,20 @@ function createStoreResolver() {
1272
1281
  });
1273
1282
  continue;
1274
1283
  }
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
- });
1284
+ if (seenStoreUuids.has(matched.store_uuid)) {
1285
+ continue;
1286
+ }
1287
+ const { entry, warning } = readSetEntryFromMounted(matched);
1288
+ if (warning !== void 0) {
1289
+ warnings.push(warning);
1288
1290
  }
1289
1291
  stores.push(entry);
1292
+ seenStoreUuids.add(matched.store_uuid);
1290
1293
  }
1291
1294
  const personal = personalEntry(input);
1292
- if (personal !== void 0) {
1295
+ if (personal !== void 0 && !seenStoreUuids.has(personal.store_uuid)) {
1293
1296
  stores.push(personal);
1297
+ seenStoreUuids.add(personal.store_uuid);
1294
1298
  }
1295
1299
  return { stores, warnings };
1296
1300
  },
@@ -1311,15 +1315,31 @@ function createStoreResolver() {
1311
1315
  }
1312
1316
  return { target: { store_uuid: p.store_uuid, alias: p.alias }, warnings: [] };
1313
1317
  }
1314
- const active = input.activeWriteAlias === void 0 ? void 0 : input.mountedStores.find((m) => m.writable && m.alias === input.activeWriteAlias);
1318
+ const readSet = this.resolveReadSet(input);
1319
+ if (hasMultipleSharedStores(input, readSet) && !hasExplicitRouteOrDefault(input, scope)) {
1320
+ return {
1321
+ target: null,
1322
+ warnings: [
1323
+ {
1324
+ code: "missing_write_route",
1325
+ ref: scope,
1326
+ message: `scope '${scope}' has no explicit write route; set \`fabric store route-write ${scope} <alias>\``
1327
+ }
1328
+ ]
1329
+ };
1330
+ }
1331
+ const routeAlias = resolveRouteAlias(input, scope);
1332
+ const active = routeAlias === void 0 ? void 0 : readSet.stores.find(
1333
+ (store) => store.writable && !isMountedPersonal(input, store.store_uuid) && (store.alias === routeAlias || store.store_uuid === routeAlias)
1334
+ );
1315
1335
  if (active === void 0) {
1316
1336
  return {
1317
1337
  target: null,
1318
1338
  warnings: [
1319
1339
  {
1320
1340
  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\``
1341
+ ref: routeAlias ?? scope,
1342
+ message: `no writable store for scope '${scope}'; set a write route or default write store`
1323
1343
  }
1324
1344
  ]
1325
1345
  };
@@ -1333,7 +1353,8 @@ function createStoreResolver() {
1333
1353
  }
1334
1354
 
1335
1355
  // src/resolver/store-disk-reader.ts
1336
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
1356
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "fs";
1357
+ import { readFile } from "fs/promises";
1337
1358
  import { join } from "path";
1338
1359
  function readStoreIdentity(absDir) {
1339
1360
  const identityFile = join(absDir, STORE_LAYOUT.identityFile);
@@ -1349,6 +1370,17 @@ function readStoreIdentity(absDir) {
1349
1370
  const parsed = storeIdentitySchema.safeParse(raw);
1350
1371
  return parsed.success ? parsed.data : null;
1351
1372
  }
1373
+ async function readStoreIdentityAsync(absDir) {
1374
+ const identityFile = join(absDir, STORE_LAYOUT.identityFile);
1375
+ let raw;
1376
+ try {
1377
+ raw = JSON.parse(await readFile(identityFile, "utf8"));
1378
+ } catch {
1379
+ return null;
1380
+ }
1381
+ const parsed = storeIdentitySchema.safeParse(raw);
1382
+ return parsed.success ? parsed.data : null;
1383
+ }
1352
1384
  function recognizeStoreDir(absDir) {
1353
1385
  return readStoreIdentity(absDir) !== null;
1354
1386
  }
@@ -1408,10 +1440,14 @@ function findStoreExecutableViolations(absDir, options = {}) {
1408
1440
  const relPath = rel === "" ? entry : `${rel}/${entry}`;
1409
1441
  let stat;
1410
1442
  try {
1411
- stat = statSync(abs);
1443
+ stat = lstatSync(abs);
1412
1444
  } catch {
1413
1445
  continue;
1414
1446
  }
1447
+ if (stat.isSymbolicLink()) {
1448
+ violations.push(relPath);
1449
+ continue;
1450
+ }
1415
1451
  if (stat.isDirectory()) {
1416
1452
  walk(abs, relPath, depth + 1);
1417
1453
  continue;
@@ -1525,9 +1561,10 @@ function resolveCandidates(candidates, options = {}) {
1525
1561
  }
1526
1562
 
1527
1563
  // 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";
1564
+ import { execFile } from "child_process";
1565
+ import { access, mkdir, readdir, readFile as readFile2, writeFile } from "fs/promises";
1530
1566
  import { join as join2 } from "path";
1567
+ import { promisify } from "util";
1531
1568
  var STORE_PENDING_DIR = "pending";
1532
1569
  var STORE_GITIGNORE = [
1533
1570
  "# v2.1 store \u2014 volatile / derived data is never committed",
@@ -1536,96 +1573,106 @@ var STORE_GITIGNORE = [
1536
1573
  ".cache/",
1537
1574
  ""
1538
1575
  ].join("\n");
1539
- function git(cwd, args) {
1540
- execFileSync("git", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
1576
+ var execFileAsync = promisify(execFile);
1577
+ async function git(cwd, args) {
1578
+ await execFileAsync("git", args, { cwd });
1541
1579
  }
1542
- function initStore(absDir, identity, options = {}) {
1580
+ async function initStore(absDir, identity, options = {}) {
1543
1581
  const parsed = storeIdentitySchema.parse(identity);
1544
1582
  const identityFile = join2(absDir, STORE_LAYOUT.identityFile);
1545
- if (existsSync2(identityFile)) {
1583
+ try {
1584
+ await access(identityFile);
1546
1585
  throw new Error(`store already initialized at ${absDir} (store.json exists)`);
1586
+ } catch (err) {
1587
+ if (err instanceof Error && err.message.includes("already initialized")) {
1588
+ throw err;
1589
+ }
1547
1590
  }
1548
1591
  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)}
1592
+ const typeDir = join2(absDir, STORE_LAYOUT.knowledgeDir, type);
1593
+ await mkdir(typeDir, { recursive: true });
1594
+ await writeFile(join2(typeDir, ".gitkeep"), "", "utf8");
1595
+ }
1596
+ await mkdir(join2(absDir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR), { recursive: true });
1597
+ await mkdir(join2(absDir, STORE_LAYOUT.bindingsDir), { recursive: true });
1598
+ await mkdir(join2(absDir, STORE_LAYOUT.stateDir), { recursive: true });
1599
+ await writeFile(identityFile, `${JSON.stringify(parsed, null, 2)}
1555
1600
  `, "utf8");
1556
- writeFileSync(join2(absDir, ".gitignore"), STORE_GITIGNORE, "utf8");
1601
+ await writeFile(join2(absDir, ".gitignore"), STORE_GITIGNORE, "utf8");
1557
1602
  if (options.git !== false) {
1558
- git(absDir, ["init", "-b", "main"]);
1603
+ await git(absDir, ["init", "-b", "main"]);
1559
1604
  }
1560
- const readBack = readStoreIdentity(absDir);
1605
+ const readBack = await readStoreIdentityAsync(absDir);
1561
1606
  if (readBack === null) {
1562
1607
  throw new Error(`store init wrote an unrecognizable store.json at ${absDir}`);
1563
1608
  }
1564
1609
  return readBack;
1565
1610
  }
1566
- function listMarkdown(dir) {
1567
- if (!existsSync2(dir)) {
1611
+ async function listMarkdown(dir) {
1612
+ let entries;
1613
+ try {
1614
+ entries = await readdir(dir);
1615
+ } catch {
1568
1616
  return [];
1569
1617
  }
1570
- return readdirSync2(dir).filter((name) => name.endsWith(".md")).sort().map((name) => join2(dir, name));
1618
+ return entries.filter((name) => name.endsWith(".md")).sort().map((name) => join2(dir, name));
1571
1619
  }
1572
- function listStoreKnowledge(store) {
1620
+ async function listStoreKnowledge(store) {
1573
1621
  const refs = [];
1574
1622
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1575
- for (const file of listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, type))) {
1623
+ for (const file of await listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, type))) {
1576
1624
  refs.push({ store_uuid: store.store_uuid, alias: store.alias, type, file });
1577
1625
  }
1578
1626
  }
1579
1627
  return refs;
1580
1628
  }
1581
- function readKnowledgeAcrossStores(stores) {
1582
- return stores.flatMap((store) => listStoreKnowledge(store));
1629
+ async function readKnowledgeAcrossStores(stores) {
1630
+ const lists = await Promise.all(stores.map((store) => listStoreKnowledge(store)));
1631
+ return lists.flat();
1583
1632
  }
1584
1633
  function storeProjectsPath(storeDir) {
1585
1634
  return join2(storeDir, STORE_LAYOUT.projectsFile);
1586
1635
  }
1587
- function readStoreProjects(storeDir) {
1636
+ async function readStoreProjects(storeDir) {
1588
1637
  const path = storeProjectsPath(storeDir);
1589
- if (!existsSync2(path)) {
1590
- return [];
1591
- }
1592
1638
  let raw;
1593
1639
  try {
1594
- raw = JSON.parse(readFileSync2(path, "utf8"));
1640
+ raw = JSON.parse(await readFile2(path, "utf8"));
1595
1641
  } catch {
1596
1642
  return [];
1597
1643
  }
1598
1644
  const parsed = storeProjectsFileSchema.safeParse(raw);
1599
1645
  return parsed.success ? parsed.data.projects : [];
1600
1646
  }
1601
- function storeHasProject(storeDir, id) {
1602
- return readStoreProjects(storeDir).some((p) => p.id === id);
1647
+ async function storeHasProject(storeDir, id) {
1648
+ return (await readStoreProjects(storeDir)).some((p) => p.id === id);
1603
1649
  }
1604
- function addStoreProject(storeDir, project) {
1650
+ async function addStoreProject(storeDir, project) {
1605
1651
  const parsed = storeProjectSchema.parse(project);
1606
- const existing = readStoreProjects(storeDir);
1652
+ const existing = await readStoreProjects(storeDir);
1607
1653
  if (existing.some((p) => p.id === parsed.id)) {
1608
1654
  throw new Error(`project '${parsed.id}' already exists in store at ${storeDir}`);
1609
1655
  }
1610
1656
  const next = [...existing, parsed];
1611
1657
  const validated = storeProjectsFileSchema.parse({ projects: next });
1612
- writeFileSync(storeProjectsPath(storeDir), `${JSON.stringify(validated, null, 2)}
1658
+ await writeFile(storeProjectsPath(storeDir), `${JSON.stringify(validated, null, 2)}
1613
1659
  `, "utf8");
1614
1660
  return validated.projects;
1615
1661
  }
1616
- function aggregatePendingAcrossStores(stores) {
1617
- return stores.flatMap(
1618
- (store) => listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR)).map((file) => ({
1662
+ async function aggregatePendingAcrossStores(stores) {
1663
+ const lists = await Promise.all(stores.map(
1664
+ async (store) => (await listMarkdown(join2(store.dir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR))).map((file) => ({
1619
1665
  store_uuid: store.store_uuid,
1620
1666
  alias: store.alias,
1621
1667
  type: STORE_PENDING_DIR,
1622
1668
  file
1623
1669
  }))
1624
- );
1670
+ ));
1671
+ return lists.flat();
1625
1672
  }
1626
1673
 
1627
1674
  // src/store/store-counters.ts
1628
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync2 } from "fs";
1675
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync } from "fs";
1629
1676
  import { join as join3 } from "path";
1630
1677
  function storeCountersPath(storeDir) {
1631
1678
  return join3(storeDir, STORE_LAYOUT.countersFile);
@@ -1633,7 +1680,7 @@ function storeCountersPath(storeDir) {
1633
1680
  function readStoreCounters(storeDir) {
1634
1681
  let raw;
1635
1682
  try {
1636
- raw = readFileSync3(storeCountersPath(storeDir), "utf8");
1683
+ raw = readFileSync2(storeCountersPath(storeDir), "utf8");
1637
1684
  } catch {
1638
1685
  return defaultAgentsMetaCounters();
1639
1686
  }
@@ -1646,10 +1693,39 @@ function readStoreCounters(storeDir) {
1646
1693
  const result = AgentsMetaCountersSchema.safeParse(parsed);
1647
1694
  return result.success ? result.data : defaultAgentsMetaCounters();
1648
1695
  }
1696
+ function preserveCorruptCounters(path, raw) {
1697
+ const corruptedPath = `${path}.corrupted.${Date.now()}`;
1698
+ writeFileSync(corruptedPath, raw, "utf8");
1699
+ return corruptedPath;
1700
+ }
1701
+ function readStoreCountersForAllocation(storeDir) {
1702
+ const path = storeCountersPath(storeDir);
1703
+ if (!existsSync2(path)) {
1704
+ return defaultAgentsMetaCounters();
1705
+ }
1706
+ const raw = readFileSync2(path, "utf8");
1707
+ let parsed;
1708
+ try {
1709
+ parsed = JSON.parse(raw);
1710
+ } catch (error) {
1711
+ const corruptedPath = preserveCorruptCounters(path, raw);
1712
+ throw new Error(
1713
+ `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)}`
1714
+ );
1715
+ }
1716
+ const result = AgentsMetaCountersSchema.safeParse(parsed);
1717
+ if (!result.success) {
1718
+ const corruptedPath = preserveCorruptCounters(path, raw);
1719
+ throw new Error(
1720
+ `store counters.json is schema-invalid; forensic copy saved to ${corruptedPath}. Run doctor --fix or reconcileStoreCounters before allocating a new stable_id.`
1721
+ );
1722
+ }
1723
+ return result.data;
1724
+ }
1649
1725
  async function allocateStoreKnowledgeId(layer, type, storeDir) {
1650
1726
  const countersPath = storeCountersPath(storeDir);
1651
1727
  return withFileLock(`${countersPath}.lock`, async () => {
1652
- const counters = readStoreCounters(storeDir);
1728
+ const counters = readStoreCountersForAllocation(storeDir);
1653
1729
  const { id, nextCounters } = allocateKnowledgeId(layer, type, counters);
1654
1730
  await atomicWriteJson(countersPath, nextCounters, { indent: 2 });
1655
1731
  return id;
@@ -1658,7 +1734,7 @@ async function allocateStoreKnowledgeId(layer, type, storeDir) {
1658
1734
  function readEntryId(file) {
1659
1735
  let content;
1660
1736
  try {
1661
- content = readFileSync3(file, "utf8");
1737
+ content = readFileSync2(file, "utf8");
1662
1738
  } catch {
1663
1739
  return null;
1664
1740
  }
@@ -1678,10 +1754,10 @@ function reconcileStoreCounters(storeDir) {
1678
1754
  };
1679
1755
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
1680
1756
  const dir = join3(storeDir, STORE_LAYOUT.knowledgeDir, type);
1681
- if (!existsSync3(dir)) {
1757
+ if (!existsSync2(dir)) {
1682
1758
  continue;
1683
1759
  }
1684
- for (const name of readdirSync3(dir)) {
1760
+ for (const name of readdirSync2(dir)) {
1685
1761
  if (!name.endsWith(".md")) {
1686
1762
  continue;
1687
1763
  }
@@ -1694,52 +1770,28 @@ function reconcileStoreCounters(storeDir) {
1694
1770
  next[layerKey][typeCode] = Math.max(next[layerKey][typeCode], parsed.counter);
1695
1771
  }
1696
1772
  }
1697
- writeFileSync2(storeCountersPath(storeDir), `${JSON.stringify(next, null, 2)}
1773
+ writeFileSync(storeCountersPath(storeDir), `${JSON.stringify(next, null, 2)}
1698
1774
  `, "utf8");
1699
1775
  return next;
1700
1776
  }
1701
1777
 
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
1778
  // 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";
1779
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1780
+ import { join as join4 } from "path";
1729
1781
  function projectConfigPath(projectRoot) {
1730
- return join5(projectRoot, ".fabric", "fabric-config.json");
1782
+ return join4(projectRoot, ".fabric", "fabric-config.json");
1731
1783
  }
1732
1784
  function loadProjectConfig(projectRoot) {
1733
1785
  const path = projectConfigPath(projectRoot);
1734
- if (!existsSync5(path)) {
1786
+ if (!existsSync3(path)) {
1735
1787
  return null;
1736
1788
  }
1737
- return fabricConfigSchema.parse(JSON.parse(readFileSync5(path, "utf8")));
1789
+ return fabricConfigSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
1738
1790
  }
1739
1791
  function saveProjectConfig(config, projectRoot) {
1740
1792
  const validated = fabricConfigSchema.parse(config);
1741
- mkdirSync3(join5(projectRoot, ".fabric"), { recursive: true });
1742
- writeFileSync4(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
1793
+ mkdirSync(join4(projectRoot, ".fabric"), { recursive: true });
1794
+ writeFileSync2(projectConfigPath(projectRoot), `${JSON.stringify(validated, null, 2)}
1743
1795
  `, "utf8");
1744
1796
  }
1745
1797
 
@@ -1755,6 +1807,7 @@ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1755
1807
  mountedStores: global.stores.map((s) => ({
1756
1808
  store_uuid: s.store_uuid,
1757
1809
  alias: s.alias,
1810
+ ...s.mount_name === void 0 ? {} : { mount_name: s.mount_name },
1758
1811
  ...s.remote === void 0 ? {} : { remote: s.remote },
1759
1812
  writable: s.writable ?? true,
1760
1813
  personal: s.personal ?? false
@@ -1765,22 +1818,43 @@ function buildStoreResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
1765
1818
  ...r.suggested_remote === void 0 ? {} : { suggested_remote: r.suggested_remote }
1766
1819
  })
1767
1820
  ),
1768
- ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store }
1821
+ ...project?.active_write_store === void 0 ? {} : { activeWriteAlias: project.active_write_store },
1822
+ writeRoutes: project?.write_routes ?? [],
1823
+ ...project?.default_write_store === void 0 ? {} : { defaultWriteAlias: project.default_write_store }
1769
1824
  };
1770
1825
  }
1771
1826
 
1772
1827
  // 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/ },
1828
+ var CREDENTIAL_RULES = [
1829
+ { rule: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/, category: "credential" },
1830
+ { rule: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, category: "credential" },
1831
+ { rule: "openai-api-key", re: /\bsk-[A-Za-z0-9]{20,}\b/, category: "credential" },
1832
+ { rule: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/, category: "credential" },
1833
+ { rule: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, category: "credential" },
1779
1834
  {
1780
1835
  rule: "credential-assignment",
1781
- re: /(?:password|passwd|secret|api[_-]?key|access[_-]?token|token)\s*[:=]\s*['"][^'"\s]{8,}['"]/i
1836
+ re: /(?:password|passwd|secret|api[_-]?key|access[_-]?token|token)\s*[:=]\s*(?:"[^'"\s]{8,}"|'[^'"\s]{8,}'|[A-Za-z0-9_./+=:@-]{8,})/i,
1837
+ category: "credential"
1838
+ }
1839
+ ];
1840
+ var PII_RULES = [
1841
+ {
1842
+ rule: "email-address",
1843
+ re: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,
1844
+ category: "pii"
1845
+ },
1846
+ {
1847
+ rule: "ipv4-address",
1848
+ 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/,
1849
+ category: "pii"
1850
+ },
1851
+ {
1852
+ rule: "phone-number",
1853
+ re: /(?<!\d)(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}(?!\d)/,
1854
+ category: "pii"
1782
1855
  }
1783
1856
  ];
1857
+ var SECRET_RULES = [...CREDENTIAL_RULES, ...PII_RULES];
1784
1858
  function scanForSecrets(content) {
1785
1859
  const findings = [];
1786
1860
  const lines = content.split(/\r?\n/u);
@@ -1794,12 +1868,26 @@ function scanForSecrets(content) {
1794
1868
  return findings;
1795
1869
  }
1796
1870
  function hasSecrets(content) {
1797
- return scanForSecrets(content).length > 0;
1871
+ const lines = content.split(/\r?\n/u);
1872
+ for (const line of lines) {
1873
+ for (const { re } of CREDENTIAL_RULES) {
1874
+ if (re.test(line)) {
1875
+ return true;
1876
+ }
1877
+ }
1878
+ }
1879
+ return false;
1798
1880
  }
1799
1881
  var REDACTION_PLACEHOLDER_PREFIX = "[REDACTED:";
1800
1882
  function redactSecrets(content) {
1883
+ return redactByRules(content, SECRET_RULES);
1884
+ }
1885
+ function redactPii(content) {
1886
+ return redactByRules(content, PII_RULES);
1887
+ }
1888
+ function redactByRules(content, rules) {
1801
1889
  let out = content;
1802
- for (const { rule, re } of SECRET_RULES) {
1890
+ for (const { rule, re } of rules) {
1803
1891
  const flags = re.flags.includes("i") ? "gi" : "g";
1804
1892
  out = out.replace(new RegExp(re.source, flags), `${REDACTION_PLACEHOLDER_PREFIX}${rule}]`);
1805
1893
  }
@@ -1922,10 +2010,10 @@ function buildDebugBundle(input) {
1922
2010
  }
1923
2011
 
1924
2012
  // src/schemas/provenance.ts
1925
- import { z as z12 } from "zod";
1926
- var knowledgeProvenanceSchema = z12.object({
2013
+ import { z as z10 } from "zod";
2014
+ var knowledgeProvenanceSchema = z10.object({
1927
2015
  store_uuid: storeUuidSchema,
1928
- alias: z12.string().min(1),
2016
+ alias: z10.string().min(1),
1929
2017
  local_id: localKnowledgeIdSchema,
1930
2018
  global_ref: globalRefSchema,
1931
2019
  // Optional scope coordinate of the entry (resolution axis); present when the
@@ -1934,7 +2022,7 @@ var knowledgeProvenanceSchema = z12.object({
1934
2022
  }).strict();
1935
2023
 
1936
2024
  // src/schemas/mcp-store-contracts.ts
1937
- import { z as z13 } from "zod";
2025
+ import { z as z11 } from "zod";
1938
2026
  var MCP_STORE_AWARE_TOOLS = [
1939
2027
  "fab_recall",
1940
2028
  "fab_plan_context",
@@ -1943,14 +2031,14 @@ var MCP_STORE_AWARE_TOOLS = [
1943
2031
  "fab_extract_knowledge",
1944
2032
  "fab_review"
1945
2033
  ];
1946
- var storeAwareEntrySchema = z13.object({
1947
- stable_id: z13.string(),
2034
+ var storeAwareEntrySchema = z11.object({
2035
+ stable_id: z11.string(),
1948
2036
  global_ref: globalRefSchema,
1949
2037
  provenance: knowledgeProvenanceSchema
1950
2038
  }).strict();
1951
- var writtenToStoreSchema = z13.object({
2039
+ var writtenToStoreSchema = z11.object({
1952
2040
  store_uuid: storeUuidSchema,
1953
- alias: z13.string().min(1)
2041
+ alias: z11.string().min(1)
1954
2042
  }).strict();
1955
2043
  var MCP_STORE_AWARE_CONTRACTS = {
1956
2044
  fab_recall: { tool: "fab_recall", surfacesEntries: true, echoesWrittenStore: false },
@@ -1963,7 +2051,7 @@ var MCP_STORE_AWARE_CONTRACTS = {
1963
2051
  fab_archive_scan: {
1964
2052
  tool: "fab_archive_scan",
1965
2053
  surfacesEntries: false,
1966
- echoesWrittenStore: true
2054
+ echoesWrittenStore: false
1967
2055
  },
1968
2056
  fab_extract_knowledge: {
1969
2057
  tool: "fab_extract_knowledge",
@@ -1974,64 +2062,184 @@ var MCP_STORE_AWARE_CONTRACTS = {
1974
2062
  };
1975
2063
 
1976
2064
  // src/schemas/bindings-snapshot.ts
1977
- import { z as z14 } from "zod";
1978
- var resolvedBindingsSnapshotSchema = z14.object({
2065
+ import { z as z12 } from "zod";
2066
+ var resolvedBindingsSnapshotSchema = z12.object({
1979
2067
  // Schema version of the snapshot document.
1980
- version: z14.literal(1),
2068
+ version: z12.literal(1),
1981
2069
  // The project this snapshot is bound to (S13).
1982
- project_id: z14.string().min(1),
2070
+ project_id: z12.string().min(1),
2071
+ // The local runtime binding key. Defaults to project_id for standard repos;
2072
+ // worktrees may isolate state by setting fabric-config.workspace_binding_id.
2073
+ workspace_binding_id: z12.string().min(1),
1983
2074
  // ISO-8601 generation timestamp (provenance / staleness signal for doctor).
1984
- generated_at: z14.string().min(1),
2075
+ generated_at: z12.string().min(1),
1985
2076
  // Pre-resolved read-set (required_stores ∪ implicit personal + warnings).
1986
2077
  read_set: storeReadSetSchema,
1987
2078
  // Pre-resolved active write target for non-personal scopes (null if none).
1988
- write_target: writeTargetSchema.nullable()
2079
+ write_target: writeTargetSchema.nullable(),
2080
+ // Pre-computed store-backed knowledge counts, snapshotted at write time.
2081
+ // PROVENANCE ONLY: these are store-global counts cached in a per-workspace
2082
+ // file, so they go stale whenever store content changes out-of-band (a `git
2083
+ // pull` in the store repo, a sync run from a *different* bound workspace,
2084
+ // etc.) — the snapshot is only regenerated by install/sync/store-ops in the
2085
+ // workspace that runs them (KT-PIT-0017). Hooks MUST NOT trust these numbers
2086
+ // for nudges; they recount live from `knowledge_store_dirs`. Retained for
2087
+ // doctor provenance + backward-compatible fallback when dirs are absent.
2088
+ knowledge_stats: z12.object({
2089
+ pending_count: z12.number().int().nonnegative(),
2090
+ canonical_count: z12.number().int().nonnegative(),
2091
+ oldest_pending_mtime_ms: z12.number().nonnegative().nullable()
2092
+ }).strict().optional(),
2093
+ // Resolved absolute store ROOT dirs the knowledge_stats were derived from.
2094
+ // STABLE across content sync — they only change when mounts/bindings change,
2095
+ // which DOES regenerate the snapshot. Hooks walk `<dir>/knowledge/<type>` +
2096
+ // `<dir>/knowledge/pending` LIVE off these roots so nudge counts are always
2097
+ // fresh regardless of how store content changed (the underseed / review-
2098
+ // backlog false-positive root cure; pairs with knowledge_stats above).
2099
+ knowledge_store_dirs: z12.array(z12.string().min(1)).optional()
1989
2100
  }).strict();
1990
2101
 
1991
2102
  // 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("..")) {
2103
+ import {
2104
+ existsSync as existsSync4,
2105
+ mkdirSync as mkdirSync2,
2106
+ readdirSync as readdirSync3,
2107
+ readFileSync as readFileSync4,
2108
+ renameSync,
2109
+ statSync,
2110
+ unlinkSync,
2111
+ writeFileSync as writeFileSync3
2112
+ } from "fs";
2113
+ import { join as join5, resolve, sep } from "path";
2114
+ var SAFE_BINDING_ID = /^[A-Za-z0-9._-]+$/;
2115
+ function assertSafeBindingId(bindingId) {
2116
+ if (!SAFE_BINDING_ID.test(bindingId) || bindingId.includes("..")) {
1997
2117
  throw new Error(
1998
- `bindingsSnapshotPath: refusing unsafe project_id ${JSON.stringify(projectId)} (must match ${SAFE_PROJECT_ID} and contain no "..")`
2118
+ `bindingsSnapshotPath: refusing unsafe workspace_binding_id ${JSON.stringify(bindingId)} (must match ${SAFE_BINDING_ID} and contain no "..")`
1999
2119
  );
2000
2120
  }
2001
2121
  }
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`));
2122
+ function bindingsSnapshotPath(globalRoot, bindingId) {
2123
+ assertSafeBindingId(bindingId);
2124
+ const bindingsDir = resolve(join5(globalRoot, GLOBAL_STATE_DIR, GLOBAL_BINDINGS_DIR));
2125
+ const path = resolve(join5(bindingsDir, `${bindingId}_resolved.json`));
2006
2126
  if (path !== bindingsDir && !path.startsWith(bindingsDir + sep)) {
2007
- throw new Error(`bindingsSnapshotPath: resolved path escapes bindings dir for ${JSON.stringify(projectId)}`);
2127
+ throw new Error(`bindingsSnapshotPath: resolved path escapes bindings dir for ${JSON.stringify(bindingId)}`);
2008
2128
  }
2009
2129
  return path;
2010
2130
  }
2131
+ function resolveWorkspaceBindingId(config) {
2132
+ return config.workspace_binding_id ?? config.project_id;
2133
+ }
2134
+ function countMarkdownFiles(dir) {
2135
+ let count = 0;
2136
+ let oldestMtimeMs = null;
2137
+ if (!existsSync4(dir)) {
2138
+ return { count, oldestMtimeMs };
2139
+ }
2140
+ let entries;
2141
+ try {
2142
+ entries = readdirSync3(dir, { withFileTypes: true });
2143
+ } catch {
2144
+ return { count, oldestMtimeMs };
2145
+ }
2146
+ for (const entry of entries) {
2147
+ const fullPath = join5(dir, entry.name);
2148
+ if (entry.isDirectory()) {
2149
+ const nested = countMarkdownFiles(fullPath);
2150
+ count += nested.count;
2151
+ if (nested.oldestMtimeMs !== null && (oldestMtimeMs === null || nested.oldestMtimeMs < oldestMtimeMs)) {
2152
+ oldestMtimeMs = nested.oldestMtimeMs;
2153
+ }
2154
+ continue;
2155
+ }
2156
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
2157
+ continue;
2158
+ }
2159
+ let mtimeMs;
2160
+ try {
2161
+ mtimeMs = statSync(fullPath).mtimeMs;
2162
+ } catch {
2163
+ continue;
2164
+ }
2165
+ count += 1;
2166
+ if (oldestMtimeMs === null || mtimeMs < oldestMtimeMs) {
2167
+ oldestMtimeMs = mtimeMs;
2168
+ }
2169
+ }
2170
+ return { count, oldestMtimeMs };
2171
+ }
2172
+ function collectKnowledgeStats(globalRoot, resolveInput, readSet) {
2173
+ let pendingCount = 0;
2174
+ let oldestPendingMtimeMs = null;
2175
+ let canonicalCount = 0;
2176
+ const storeDirs = [];
2177
+ for (const store of readSet.stores) {
2178
+ const mounted = resolveInput.mountedStores.find((entry) => entry.store_uuid === store.store_uuid) ?? {
2179
+ store_uuid: store.store_uuid
2180
+ };
2181
+ const storeDir = join5(globalRoot, storeRelativePathForMount(mounted));
2182
+ storeDirs.push(storeDir);
2183
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
2184
+ const canonical = countMarkdownFiles(join5(storeDir, STORE_LAYOUT.knowledgeDir, type));
2185
+ canonicalCount += canonical.count;
2186
+ }
2187
+ const pending = countMarkdownFiles(join5(storeDir, STORE_LAYOUT.knowledgeDir, "pending"));
2188
+ pendingCount += pending.count;
2189
+ if (pending.oldestMtimeMs !== null && (oldestPendingMtimeMs === null || pending.oldestMtimeMs < oldestPendingMtimeMs)) {
2190
+ oldestPendingMtimeMs = pending.oldestMtimeMs;
2191
+ }
2192
+ }
2193
+ return {
2194
+ stats: {
2195
+ pending_count: pendingCount,
2196
+ canonical_count: canonicalCount,
2197
+ oldest_pending_mtime_ms: oldestPendingMtimeMs
2198
+ },
2199
+ storeDirs
2200
+ };
2201
+ }
2202
+ function atomicWriteJsonSync(path, value) {
2203
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
2204
+ try {
2205
+ writeFileSync3(tmpPath, `${JSON.stringify(value, null, 2)}
2206
+ `, "utf8");
2207
+ renameSync(tmpPath, path);
2208
+ } catch (error) {
2209
+ try {
2210
+ unlinkSync(tmpPath);
2211
+ } catch {
2212
+ }
2213
+ throw error;
2214
+ }
2215
+ }
2011
2216
  function writeBindingsSnapshot(options) {
2012
2217
  const resolver = createStoreResolver();
2013
2218
  const read_set = resolver.resolveReadSet(options.resolveInput);
2014
2219
  const { target } = resolver.resolveWriteTarget(options.resolveInput, options.writeScope);
2220
+ const { stats, storeDirs } = collectKnowledgeStats(options.globalRoot, options.resolveInput, read_set);
2015
2221
  const snapshot = resolvedBindingsSnapshotSchema.parse({
2016
2222
  version: 1,
2017
2223
  project_id: options.projectId,
2224
+ workspace_binding_id: options.workspaceBindingId ?? options.projectId,
2018
2225
  generated_at: options.now,
2019
2226
  read_set,
2020
- write_target: target
2227
+ write_target: target,
2228
+ knowledge_stats: stats,
2229
+ knowledge_store_dirs: storeDirs
2021
2230
  });
2022
- const path = bindingsSnapshotPath(options.globalRoot, options.projectId);
2023
- mkdirSync4(join6(path, ".."), { recursive: true });
2024
- writeFileSync5(path, `${JSON.stringify(snapshot, null, 2)}
2025
- `, "utf8");
2231
+ const path = bindingsSnapshotPath(options.globalRoot, snapshot.workspace_binding_id);
2232
+ mkdirSync2(join5(path, ".."), { recursive: true });
2233
+ atomicWriteJsonSync(path, snapshot);
2026
2234
  return snapshot;
2027
2235
  }
2028
2236
  function readBindingsSnapshot(globalRoot, projectId) {
2029
2237
  const path = bindingsSnapshotPath(globalRoot, projectId);
2030
- if (!existsSync6(path)) {
2238
+ if (!existsSync4(path)) {
2031
2239
  return null;
2032
2240
  }
2033
2241
  try {
2034
- const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync6(path, "utf8")));
2242
+ const parsed = resolvedBindingsSnapshotSchema.safeParse(JSON.parse(readFileSync4(path, "utf8")));
2035
2243
  return parsed.success ? parsed.data : null;
2036
2244
  } catch {
2037
2245
  return null;
@@ -2040,7 +2248,9 @@ function readBindingsSnapshot(globalRoot, projectId) {
2040
2248
 
2041
2249
  // src/store/store-lifecycle.ts
2042
2250
  function findMountedStore(config, aliasOrUuid) {
2043
- return config.stores.find((s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid);
2251
+ return config.stores.find(
2252
+ (s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid || s.mount_name === aliasOrUuid
2253
+ );
2044
2254
  }
2045
2255
  function addMountedStore(config, store) {
2046
2256
  const aliasClash = config.stores.find(
@@ -2051,12 +2261,32 @@ function addMountedStore(config, store) {
2051
2261
  `alias '${store.alias}' already mounts store ${aliasClash.store_uuid}; choose another alias`
2052
2262
  );
2053
2263
  }
2264
+ const mountNameClash = store.mount_name === void 0 ? void 0 : config.stores.find(
2265
+ (s) => s.mount_name === store.mount_name && s.store_uuid !== store.store_uuid
2266
+ );
2267
+ if (mountNameClash !== void 0) {
2268
+ throw new Error(
2269
+ `mount_name '${store.mount_name}' already maps to store ${mountNameClash.store_uuid}; choose another mount_name`
2270
+ );
2271
+ }
2054
2272
  const sanitized = store.remote === void 0 ? store : { ...store, remote: scrubRemoteUrl(store.remote) };
2055
2273
  store = sanitized;
2056
2274
  const existing = config.stores.find((s) => s.store_uuid === store.store_uuid);
2057
2275
  const stores = existing === void 0 ? [...config.stores, store] : config.stores.map((s) => s.store_uuid === store.store_uuid ? store : s);
2058
2276
  return { ...config, stores };
2059
2277
  }
2278
+ function disambiguateAlias(existingAliases, desired) {
2279
+ const taken = new Set(existingAliases);
2280
+ if (!taken.has(desired)) {
2281
+ return desired;
2282
+ }
2283
+ for (let n = 2; ; n += 1) {
2284
+ const candidate = `${desired}-${n}`;
2285
+ if (!taken.has(candidate)) {
2286
+ return candidate;
2287
+ }
2288
+ }
2289
+ }
2060
2290
  function detachMountedStore(config, alias) {
2061
2291
  const detached = config.stores.find((s) => s.alias === alias) ?? null;
2062
2292
  if (detached === null) {
@@ -2085,155 +2315,155 @@ function explainStore(config, alias) {
2085
2315
  }
2086
2316
 
2087
2317
  // 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),
2318
+ import { z as z13 } from "zod";
2319
+ var forensicCodeSampleSchema = z13.object({
2320
+ path: z13.string(),
2321
+ lines: z13.string(),
2322
+ snippet: z13.string(),
2323
+ pattern_hint: z13.string()
2324
+ });
2325
+ var forensicEvidenceAnchorSchema = z13.object({
2326
+ file: z13.string(),
2327
+ line: z13.string(),
2328
+ snippet: z13.string()
2329
+ });
2330
+ var forensicAssertionCoverageSchema = z13.object({
2331
+ ratio: z13.number().min(0).max(1),
2332
+ total: z13.number().int().nonnegative(),
2333
+ matched: z13.number().int().nonnegative(),
2334
+ co_occurring_patterns: z13.array(z13.string())
2335
+ });
2336
+ var forensicAssertionSchema = z13.object({
2337
+ type: z13.enum(["framework", "pattern", "invariant", "domain"]),
2338
+ statement: z13.string(),
2339
+ confidence: z13.enum(["HIGH", "MEDIUM", "LOW"]),
2340
+ evidence: z13.array(forensicEvidenceAnchorSchema),
2111
2341
  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(),
2342
+ proposed_rule: z13.string().optional(),
2343
+ alternatives: z13.array(z13.string()).optional()
2344
+ });
2345
+ var forensicTopologySchema = z13.object({
2346
+ total_files: z13.number().int().nonnegative(),
2347
+ by_ext: z13.record(z13.number().int().nonnegative()),
2348
+ key_dirs: z13.array(z13.string()),
2349
+ max_depth: z13.number().int().nonnegative()
2350
+ });
2351
+ var forensicEntryPointSchema = z13.object({
2352
+ path: z13.string(),
2353
+ reason: z13.string(),
2354
+ size_bytes: z13.number().int().nonnegative().optional()
2355
+ });
2356
+ var forensicFrameworkSchema = z13.object({
2357
+ kind: z13.string(),
2358
+ version: z13.string(),
2359
+ subkind: z13.string(),
2360
+ evidence: z13.array(z13.string())
2361
+ });
2362
+ var forensicReadmeSchema = z13.object({
2363
+ quality: z13.enum(["missing", "stub", "ok"]),
2364
+ line_count: z13.number().int().nonnegative(),
2365
+ has_contributing: z13.boolean()
2366
+ });
2367
+ var candidateFileEntrySchema = z13.object({
2368
+ path: z13.string(),
2369
+ family: z13.enum(["entry", "component", "config", "test", "domain"]),
2370
+ rationale: z13.string()
2371
+ });
2372
+ var forensicSamplingBudgetSchema = z13.object({
2373
+ max_files: z13.literal(15),
2374
+ max_lines_per_file: z13.literal(100)
2375
+ });
2376
+ var forensicReportSchema = z13.object({
2377
+ version: z13.string(),
2378
+ generated_at: z13.string(),
2379
+ generated_by: z13.string(),
2380
+ target: z13.string(),
2381
+ project_name: z13.string(),
2152
2382
  framework: forensicFrameworkSchema,
2153
2383
  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),
2384
+ entry_points: z13.array(forensicEntryPointSchema),
2385
+ code_samples: z13.array(forensicCodeSampleSchema),
2386
+ assertions: z13.array(forensicAssertionSchema),
2387
+ candidate_files: z13.array(candidateFileEntrySchema),
2158
2388
  sampling_budget: forensicSamplingBudgetSchema,
2159
2389
  readme: forensicReadmeSchema,
2160
- recommendations_for_skill: z15.array(z15.string()).optional()
2390
+ recommendations_for_skill: z13.array(z13.string()).optional()
2161
2391
  });
2162
2392
 
2163
2393
  // 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(),
2394
+ import { z as z14 } from "zod";
2395
+ var initContextFrameworkSchema = z14.object({
2396
+ kind: z14.string(),
2397
+ version: z14.string(),
2398
+ subkind: z14.string()
2399
+ });
2400
+ var initContextInvariantConfidenceSnapshotSchema = z14.object({
2401
+ confidence: z14.enum(["HIGH", "MEDIUM", "LOW"]),
2402
+ evidence_refs: z14.array(z14.string())
2403
+ });
2404
+ var initContextSourceEvidenceSchema = z14.object({
2405
+ file: z14.string(),
2406
+ lines: z14.string()
2407
+ });
2408
+ var initContextInvariantSchema = z14.object({
2409
+ type: z14.enum(["ban", "require", "protect"]),
2410
+ rule: z14.string(),
2411
+ rationale: z14.string().optional(),
2182
2412
  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({
2413
+ source_evidence: z14.array(initContextSourceEvidenceSchema).optional()
2414
+ });
2415
+ var initContextDomainGroupSchema = z14.object({
2416
+ name: z14.string(),
2417
+ paths: z14.array(z14.string()),
2418
+ summary: z14.string().optional(),
2419
+ topology_type: z14.enum(["mirror", "cross-cutting"]).optional(),
2420
+ target_path: z14.string().optional()
2421
+ });
2422
+ var initContextInterviewTrailEntrySchema = z14.object({
2423
+ phase: z14.string(),
2424
+ question: z14.string(),
2425
+ answer: z14.string(),
2426
+ presentation: z14.string().optional(),
2427
+ user_corrections: z14.array(z14.string()).optional()
2428
+ });
2429
+ var initContextSchema = z14.object({
2200
2430
  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()
2431
+ architecture_patterns: z14.array(z14.string()),
2432
+ invariants: z14.array(initContextInvariantSchema),
2433
+ domain_groups: z14.array(initContextDomainGroupSchema),
2434
+ interview_trail: z14.array(initContextInterviewTrailEntrySchema),
2435
+ forensic_ref: z14.string()
2206
2436
  });
2207
2437
 
2208
2438
  // src/schemas/events.ts
2209
- import { z as z17 } from "zod";
2210
- var metaUpdatedEventSchema = z17.object({
2211
- type: z17.literal("meta:updated"),
2439
+ import { z as z15 } from "zod";
2440
+ var metaUpdatedEventSchema = z15.object({
2441
+ type: z15.literal("meta:updated"),
2212
2442
  payload: agentsMetaSchema
2213
2443
  });
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)
2444
+ var lockDriftEventSchema = z15.object({
2445
+ type: z15.literal("lock:drift"),
2446
+ payload: z15.object({
2447
+ locked: z15.array(humanLockEntrySchema),
2448
+ drifted: z15.array(humanLockEntrySchema)
2219
2449
  })
2220
2450
  });
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)
2451
+ var lockApprovedEventSchema = z15.object({
2452
+ type: z15.literal("lock:approved"),
2453
+ payload: z15.object({
2454
+ locked: z15.array(humanLockEntrySchema),
2455
+ approved: z15.array(humanLockEntrySchema)
2226
2456
  })
2227
2457
  });
2228
- var ledgerAppendedEventSchema = z17.object({
2229
- type: z17.literal("ledger:appended"),
2458
+ var ledgerAppendedEventSchema = z15.object({
2459
+ type: z15.literal("ledger:appended"),
2230
2460
  payload: ledgerEntrySchema
2231
2461
  });
2232
- var driftDetectedEventSchema = z17.object({
2233
- type: z17.literal("drift:detected"),
2462
+ var driftDetectedEventSchema = z15.object({
2463
+ type: z15.literal("drift:detected"),
2234
2464
  payload: forensicReportSchema
2235
2465
  });
2236
- var fabricEventSchema = z17.discriminatedUnion("type", [
2466
+ var fabricEventSchema = z15.discriminatedUnion("type", [
2237
2467
  metaUpdatedEventSchema,
2238
2468
  lockDriftEventSchema,
2239
2469
  lockApprovedEventSchema,
@@ -2242,7 +2472,7 @@ var fabricEventSchema = z17.discriminatedUnion("type", [
2242
2472
  ]);
2243
2473
 
2244
2474
  // src/schemas/event-ledger.ts
2245
- import { z as z18 } from "zod";
2475
+ import { z as z16 } from "zod";
2246
2476
 
2247
2477
  // src/cite-line-parser.ts
2248
2478
  var ID_RE = /^K[TP]-[A-Z]+-\d+$/;
@@ -2256,17 +2486,12 @@ function splitStorePrefix(token) {
2256
2486
  return colon === -1 ? { store: null, id: token } : { store: token.slice(0, colon), id: token.slice(colon + 1) };
2257
2487
  }
2258
2488
  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
2489
  function normalizeCiteTag(rawTag) {
2265
2490
  const head = rawTag.trim().split(/[\s:]+/)[0].toLowerCase();
2266
2491
  if (head === "applied" || head === "dismissed" || head === "none") {
2267
2492
  return head;
2268
2493
  }
2269
- return LEGACY_CITE_TAG_REMAP[head] ?? "none";
2494
+ return "none";
2270
2495
  }
2271
2496
  function parseTag(rawTag) {
2272
2497
  if (!rawTag) return "none";
@@ -2354,146 +2579,131 @@ function parseCiteLine(raw) {
2354
2579
  }
2355
2580
 
2356
2581
  // src/schemas/event-ledger.ts
2357
- var citeTagSchema = z18.preprocess(
2582
+ var citeTagSchema = z16.preprocess(
2358
2583
  (value) => typeof value === "string" ? normalizeCiteTag(value) : value,
2359
- z18.enum(["applied", "dismissed", "none"])
2584
+ z16.enum(["applied", "dismissed", "none"])
2360
2585
  );
2361
2586
  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()
2587
+ kind: z16.literal("fabric-event"),
2588
+ id: z16.string(),
2589
+ ts: z16.number().int().nonnegative(),
2590
+ schema_version: z16.literal(1),
2591
+ correlation_id: z16.string().optional(),
2592
+ session_id: z16.string().optional()
2368
2593
  };
2369
- var stringRecordSchema = z18.record(z18.string());
2370
- var knowledgeContextPlannedEventSchema = z18.object({
2594
+ var stringRecordSchema = z16.record(z16.string());
2595
+ var knowledgeContextPlannedEventSchema = z16.object({
2371
2596
  ...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({
2597
+ event_type: z16.literal("knowledge_context_planned"),
2598
+ target_paths: z16.array(z16.string()),
2599
+ required_stable_ids: z16.array(z16.string()),
2600
+ ai_selectable_stable_ids: z16.array(z16.string()),
2601
+ final_stable_ids: z16.array(z16.string()),
2602
+ selection_token: z16.string().optional(),
2603
+ client_hash: z16.string().optional(),
2604
+ intent: z16.string().optional(),
2605
+ known_tech: z16.array(z16.string()).optional(),
2606
+ diagnostics: z16.array(z16.unknown()).optional()
2607
+ });
2608
+ var knowledgeSelectionEventSchema = z16.object({
2384
2609
  ...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()),
2610
+ event_type: z16.literal("knowledge_selection"),
2611
+ selection_token: z16.string(),
2612
+ target_paths: z16.array(z16.string()),
2613
+ required_stable_ids: z16.array(z16.string()),
2614
+ ai_selectable_stable_ids: z16.array(z16.string()),
2615
+ ai_selected_stable_ids: z16.array(z16.string()),
2616
+ final_stable_ids: z16.array(z16.string()),
2392
2617
  ai_selection_reasons: stringRecordSchema,
2393
- rejected_stable_ids: z18.array(z18.string()),
2394
- ignored_stable_ids: z18.array(z18.string())
2618
+ rejected_stable_ids: z16.array(z16.string()),
2619
+ ignored_stable_ids: z16.array(z16.string())
2395
2620
  });
2396
- var knowledgeSectionsFetchedEventSchema = z18.object({
2621
+ var knowledgeSectionsFetchedEventSchema = z16.object({
2397
2622
  ...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({
2623
+ event_type: z16.literal("knowledge_sections_fetched"),
2624
+ selection_token: z16.string(),
2625
+ target_paths: z16.array(z16.string()).optional(),
2626
+ requested_sections: z16.array(z16.string()),
2627
+ final_stable_ids: z16.array(z16.string()),
2628
+ ai_selected_stable_ids: z16.array(z16.string()),
2629
+ diagnostics: z16.array(z16.unknown()).optional()
2630
+ });
2631
+ var editIntentCheckedEventSchema = z16.object({
2407
2632
  ...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(),
2633
+ event_type: z16.literal("edit_intent_checked"),
2634
+ path: z16.string(),
2635
+ compliant: z16.boolean(),
2636
+ intent: z16.string(),
2637
+ ledger_entry_id: z16.string(),
2413
2638
  // rc.35 TASK-07 (P0-2): add "hook" — emitted by the PreToolUse narrow hook
2414
2639
  // for every Edit/Write/MultiEdit fire so cite-coverage doctor metrics see
2415
2640
  // actual edit signals (previously editsTouched was permanently 0 because
2416
2641
  // 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({
2642
+ ledger_source: z16.enum(["ai", "human", "hook"]).optional(),
2643
+ commit_sha: z16.string().optional(),
2644
+ parent_sha: z16.string().optional(),
2645
+ parent_ledger_entry_id: z16.string().optional(),
2646
+ diff_stat: z16.string().optional(),
2647
+ annotation: z16.string().optional(),
2648
+ matched_rule_context_ts: z16.number().int().nonnegative().nullable(),
2649
+ window_ms: z16.number().int().nonnegative()
2650
+ });
2651
+ var knowledgeDriftDetectedEventSchema = z16.object({
2427
2652
  ...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()
2653
+ event_type: z16.literal("knowledge_drift_detected"),
2654
+ revision: z16.string().optional(),
2655
+ drifted_stable_ids: z16.array(z16.string()),
2656
+ missing_files: z16.array(z16.string()),
2657
+ stale_files: z16.array(z16.string()),
2658
+ details: z16.array(
2659
+ z16.object({
2660
+ file: z16.string(),
2661
+ stable_id: z16.string(),
2662
+ expected_hash: z16.string(),
2663
+ actual_hash: z16.string().nullable()
2439
2664
  })
2440
2665
  ).optional()
2441
2666
  });
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({
2667
+ var mcpEventLedgerEventSchema = z16.object({
2450
2668
  ...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()
2669
+ event_type: z16.literal("mcp_event"),
2670
+ mcp_event_id: z16.string(),
2671
+ stream_id: z16.string(),
2672
+ message: z16.unknown()
2455
2673
  });
2456
- var installDiffAppliedEventSchema = z18.object({
2674
+ var reapplyCompletedEventSchema = z16.object({
2457
2675
  ...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())
2676
+ event_type: z16.literal("reapply_completed"),
2677
+ preserved_ledger: z16.boolean(),
2678
+ preserved_meta: z16.boolean(),
2679
+ rules_count: z16.number().int().nonnegative()
2462
2680
  });
2463
- var eventLedgerTruncatedEventSchema = z18.object({
2681
+ var installDiffAppliedEventSchema = z16.object({
2464
2682
  ...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()
2683
+ event_type: z16.literal("install_diff_applied"),
2684
+ applied: z16.array(z16.string()),
2685
+ canonical: z16.array(z16.string()),
2686
+ drifted: z16.array(z16.string())
2469
2687
  });
2470
- var mcpConfigMigratedEventSchema = z18.object({
2688
+ var eventLedgerTruncatedEventSchema = z16.object({
2471
2689
  ...eventLedgerEnvelopeSchema,
2472
- event_type: z18.literal("mcp_config_migrated"),
2473
- source: z18.literal("doctor_fix"),
2474
- removed_from: z18.string()
2690
+ event_type: z16.literal("event_ledger_truncated"),
2691
+ byte_offset: z16.number().int().nonnegative(),
2692
+ byte_length: z16.number().int().nonnegative(),
2693
+ corrupted_path: z16.string()
2475
2694
  });
2476
- var bootstrapMarkerMigratedEventSchema = z18.object({
2695
+ var metaReconciledOnStartupEventSchema = z16.object({
2477
2696
  ...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({
2486
- ...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")
2697
+ event_type: z16.literal("meta_reconciled_on_startup"),
2698
+ reconciled_files: z16.array(z16.string()),
2699
+ duration_ms: z16.number().int().nonnegative(),
2700
+ source: z16.literal("reconcileKnowledge")
2491
2701
  });
2492
- var metaReconciledEventSchema = z18.object({
2702
+ var metaReconciledEventSchema = z16.object({
2493
2703
  ...eventLedgerEnvelopeSchema,
2494
- event_type: z18.literal("meta_reconciled"),
2495
- reconciled_files: z18.array(z18.string()),
2496
- duration_ms: z18.number().int().nonnegative(),
2704
+ event_type: z16.literal("meta_reconciled"),
2705
+ reconciled_files: z16.array(z16.string()),
2706
+ duration_ms: z16.number().int().nonnegative(),
2497
2707
  // v2.0.0-rc.23 TASK-005 (a-B): added `auto-heal-description` trigger so the
2498
2708
  // read-path plan_context handler can drive a full reconcile when it detects
2499
2709
  // any node carrying `description === undefined` (legacy meta drift that the
@@ -2507,7 +2717,7 @@ var metaReconciledEventSchema = z18.object({
2507
2717
  // v2.0.0-rc.29 TASK-005 (BUG-G1): `auto-heal-after-drift` added so
2508
2718
  // `ensureKnowledgeFresh` hot-path can chain a paired reconcile (closing the
2509
2719
  // drift→heal gap) when the caller opts in via `autoHealOnDrift: true`.
2510
- trigger: z18.enum([
2720
+ trigger: z16.enum([
2511
2721
  "doctor",
2512
2722
  "manual",
2513
2723
  "auto-heal-description",
@@ -2515,195 +2725,207 @@ var metaReconciledEventSchema = z18.object({
2515
2725
  "post-approve",
2516
2726
  "post-modify"
2517
2727
  ]),
2518
- source: z18.literal("reconcileKnowledge"),
2728
+ source: z16.literal("reconcileKnowledge"),
2519
2729
  // v2.0.0-rc.22 TASK-014 (Scope E): set when reconcileKnowledge forced a
2520
2730
  // writeKnowledgeMeta on revision drift alone (no per-file content drift).
2521
2731
  // Distinguishes top-level schema/revision repair from the standard per-file
2522
2732
  // drift path. Optional so existing emitters stay unchanged.
2523
- force_write_reason: z18.enum(["revision_drift"]).optional()
2733
+ force_write_reason: z16.enum(["revision_drift"]).optional()
2524
2734
  });
2525
- var claudeSkillPathMigratedEventSchema = z18.object({
2735
+ var claudeSkillPathMigratedEventSchema = z16.object({
2526
2736
  ...eventLedgerEnvelopeSchema,
2527
- event_type: z18.literal("claude_skill_path_migrated"),
2528
- from: z18.string(),
2529
- to: z18.string()
2737
+ event_type: z16.literal("claude_skill_path_migrated"),
2738
+ from: z16.string(),
2739
+ to: z16.string()
2530
2740
  });
2531
- var claudeHookPathMigratedEventSchema = z18.object({
2741
+ var claudeHookPathMigratedEventSchema = z16.object({
2532
2742
  ...eventLedgerEnvelopeSchema,
2533
- event_type: z18.literal("claude_hook_path_migrated"),
2534
- from: z18.string(),
2535
- to: z18.string()
2743
+ event_type: z16.literal("claude_hook_path_migrated"),
2744
+ from: z16.string(),
2745
+ to: z16.string()
2536
2746
  });
2537
- var codexSkillPathMigratedEventSchema = z18.object({
2747
+ var codexSkillPathMigratedEventSchema = z16.object({
2538
2748
  ...eventLedgerEnvelopeSchema,
2539
- event_type: z18.literal("codex_skill_path_migrated"),
2540
- from: z18.string(),
2541
- to: z18.string()
2749
+ event_type: z16.literal("codex_skill_path_migrated"),
2750
+ from: z16.string(),
2751
+ to: z16.string()
2542
2752
  });
2543
- var initScanCompletedEventSchema = z18.object({
2753
+ var initScanCompletedEventSchema = z16.object({
2544
2754
  ...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()
2755
+ event_type: z16.literal("init_scan_completed"),
2756
+ written_stable_ids: z16.array(z16.string()),
2757
+ duration_ms: z16.number().int().nonnegative(),
2758
+ source: z16.enum(["init", "scan", "doctor_fix", "doctor-rescan"]).optional()
2549
2759
  });
2550
- var knowledgeProposedEventSchema = z18.object({
2760
+ var knowledgeProposedEventSchema = z16.object({
2551
2761
  ...eventLedgerEnvelopeSchema,
2552
- event_type: z18.literal("knowledge_proposed"),
2553
- stable_id: z18.string().optional(),
2554
- timestamp: z18.string().datetime(),
2555
- reason: z18.string().optional()
2762
+ event_type: z16.literal("knowledge_proposed"),
2763
+ stable_id: z16.string().optional(),
2764
+ timestamp: z16.string().datetime(),
2765
+ reason: z16.string().optional()
2556
2766
  });
2557
- var knowledgePromoteStartedEventSchema = z18.object({
2767
+ var knowledgePromoteStartedEventSchema = z16.object({
2558
2768
  ...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()
2769
+ event_type: z16.literal("knowledge_promote_started"),
2770
+ stable_id: z16.string().optional(),
2771
+ timestamp: z16.string().datetime(),
2772
+ reason: z16.string().optional()
2563
2773
  });
2564
- var knowledgePromotedEventSchema = z18.object({
2774
+ var knowledgePromotedEventSchema = z16.object({
2565
2775
  ...eventLedgerEnvelopeSchema,
2566
- event_type: z18.literal("knowledge_promoted"),
2567
- stable_id: z18.string().optional(),
2568
- timestamp: z18.string().datetime(),
2569
- reason: z18.string().optional()
2776
+ event_type: z16.literal("knowledge_promoted"),
2777
+ stable_id: z16.string().optional(),
2778
+ timestamp: z16.string().datetime(),
2779
+ reason: z16.string().optional()
2570
2780
  });
2571
- var knowledgePromoteFailedEventSchema = z18.object({
2781
+ var knowledgePromoteFailedEventSchema = z16.object({
2572
2782
  ...eventLedgerEnvelopeSchema,
2573
- event_type: z18.literal("knowledge_promote_failed"),
2574
- stable_id: z18.string().optional(),
2575
- timestamp: z18.string().datetime(),
2576
- reason: z18.string()
2783
+ event_type: z16.literal("knowledge_promote_failed"),
2784
+ stable_id: z16.string().optional(),
2785
+ timestamp: z16.string().datetime(),
2786
+ reason: z16.string()
2577
2787
  });
2578
- var knowledgeLayerChangedEventSchema = z18.object({
2788
+ var knowledgeModifiedEventSchema = z16.object({
2789
+ ...eventLedgerEnvelopeSchema,
2790
+ event_type: z16.literal("knowledge_modified"),
2791
+ stable_id: z16.string().optional(),
2792
+ timestamp: z16.string().datetime(),
2793
+ path: z16.string(),
2794
+ changed_fields: z16.array(z16.string()),
2795
+ before: z16.record(z16.unknown()),
2796
+ after: z16.record(z16.unknown()),
2797
+ reason: z16.string().optional()
2798
+ });
2799
+ var knowledgeLayerChangedEventSchema = z16.object({
2579
2800
  ...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"]),
2801
+ event_type: z16.literal("knowledge_layer_changed"),
2802
+ stable_id: z16.string().optional(),
2803
+ timestamp: z16.string().datetime(),
2804
+ reason: z16.string().optional(),
2805
+ from_layer: z16.enum(["team", "personal"]),
2806
+ to_layer: z16.enum(["team", "personal"]),
2586
2807
  // v2.0.0-rc.37 NEW-24: record the pre-flip stable_id so downstream consumers
2587
2808
  // (fab_plan_context redirect surface, fab_get_knowledge_sections.redirect_to)
2588
2809
  // can map a stale caller-held id back to the post-flip canonical id without
2589
2810
  // requiring the caller to re-issue plan-context. Optional for forward-
2590
2811
  // compatibility with rc ≤36 events that never carried this field.
2591
- previous_stable_id: z18.string().optional()
2812
+ previous_stable_id: z16.string().optional()
2592
2813
  });
2593
- var knowledgeIdRedirectEventSchema = z18.object({
2814
+ var knowledgeIdRedirectEventSchema = z16.object({
2594
2815
  ...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()
2816
+ event_type: z16.literal("knowledge_id_redirect"),
2817
+ timestamp: z16.string().datetime(),
2818
+ previous_stable_id: z16.string(),
2819
+ new_stable_id: z16.string(),
2820
+ reason: z16.string().optional()
2600
2821
  });
2601
- var knowledgeSlugRenamedEventSchema = z18.object({
2822
+ var knowledgeSlugRenamedEventSchema = z16.object({
2602
2823
  ...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({
2824
+ event_type: z16.literal("knowledge_slug_renamed"),
2825
+ stable_id: z16.string().optional(),
2826
+ timestamp: z16.string().datetime(),
2827
+ reason: z16.string().optional(),
2828
+ from_slug: z16.string(),
2829
+ to_slug: z16.string()
2830
+ });
2831
+ var knowledgeDemotedEventSchema = z16.object({
2611
2832
  ...eventLedgerEnvelopeSchema,
2612
- event_type: z18.literal("knowledge_demoted"),
2613
- stable_id: z18.string().optional(),
2614
- timestamp: z18.string().datetime(),
2615
- reason: z18.string().optional()
2833
+ event_type: z16.literal("knowledge_demoted"),
2834
+ stable_id: z16.string().optional(),
2835
+ timestamp: z16.string().datetime(),
2836
+ reason: z16.string().optional()
2616
2837
  });
2617
- var knowledgeArchivedEventSchema = z18.object({
2838
+ var knowledgeArchivedEventSchema = z16.object({
2618
2839
  ...eventLedgerEnvelopeSchema,
2619
- event_type: z18.literal("knowledge_archived"),
2620
- stable_id: z18.string().optional(),
2621
- timestamp: z18.string().datetime(),
2622
- reason: z18.string().optional()
2840
+ event_type: z16.literal("knowledge_archived"),
2841
+ stable_id: z16.string().optional(),
2842
+ timestamp: z16.string().datetime(),
2843
+ reason: z16.string().optional()
2623
2844
  });
2624
- var knowledgeArchiveAttemptedEventSchema = z18.object({
2845
+ var knowledgeArchiveAttemptedEventSchema = z16.object({
2625
2846
  ...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()
2847
+ event_type: z16.literal("knowledge_archive_attempted"),
2848
+ stable_id: z16.string().optional(),
2849
+ timestamp: z16.string().datetime(),
2850
+ reason: z16.string().optional()
2630
2851
  });
2631
- var knowledgeUnarchivedEventSchema = z18.object({
2852
+ var knowledgeUnarchivedEventSchema = z16.object({
2632
2853
  ...eventLedgerEnvelopeSchema,
2633
- event_type: z18.literal("knowledge_unarchived"),
2634
- stable_id: z18.string().optional(),
2635
- timestamp: z18.string().datetime(),
2636
- reason: z18.string().optional(),
2854
+ event_type: z16.literal("knowledge_unarchived"),
2855
+ stable_id: z16.string().optional(),
2856
+ timestamp: z16.string().datetime(),
2857
+ reason: z16.string().optional(),
2637
2858
  // 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()
2859
+ archive_path: z16.string().optional(),
2860
+ // Post-move canonical path (e.g. "knowledge/decisions/KT-DEC-0007--single-cjs-hook.md" inside the resolved store).
2861
+ restored_to: z16.string().optional()
2641
2862
  });
2642
- var knowledgeDeferredEventSchema = z18.object({
2863
+ var knowledgeDeferredEventSchema = z16.object({
2643
2864
  ...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({
2865
+ event_type: z16.literal("knowledge_deferred"),
2866
+ stable_id: z16.string().optional(),
2867
+ pending_path: z16.string().optional(),
2868
+ timestamp: z16.string().datetime(),
2869
+ reason: z16.string().optional(),
2870
+ until: z16.string().datetime().optional()
2871
+ });
2872
+ var knowledgeRejectedEventSchema = z16.object({
2651
2873
  ...eventLedgerEnvelopeSchema,
2652
- event_type: z18.literal("knowledge_rejected"),
2653
- stable_id: z18.string().optional(),
2654
- timestamp: z18.string().datetime(),
2655
- reason: z18.string()
2874
+ event_type: z16.literal("knowledge_rejected"),
2875
+ stable_id: z16.string().optional(),
2876
+ timestamp: z16.string().datetime(),
2877
+ reason: z16.string()
2656
2878
  });
2657
- var knowledgeConsumedEventSchema = z18.object({
2879
+ var knowledgeConsumedEventSchema = z16.object({
2658
2880
  ...eventLedgerEnvelopeSchema,
2659
- event_type: z18.literal("knowledge_consumed"),
2660
- stable_id: z18.string(),
2661
- consumed_at: z18.string().datetime(),
2662
- client_hash: z18.string()
2881
+ event_type: z16.literal("knowledge_consumed"),
2882
+ stable_id: z16.string(),
2883
+ consumed_at: z16.string().datetime(),
2884
+ client_hash: z16.string()
2663
2885
  });
2664
- var knowledgeScopeDegradedEventSchema = z18.object({
2886
+ var knowledgeScopeDegradedEventSchema = z16.object({
2665
2887
  ...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({
2888
+ event_type: z16.literal("knowledge_scope_degraded"),
2889
+ stable_id: z16.string(),
2890
+ timestamp: z16.string().datetime(),
2891
+ from_scope: z16.enum(["narrow", "broad"]),
2892
+ to_scope: z16.enum(["narrow", "broad"]),
2893
+ reason: z16.string()
2894
+ });
2895
+ var doctorRunEventSchema = z16.object({
2674
2896
  ...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()
2897
+ event_type: z16.literal("doctor_run"),
2898
+ mode: z16.enum(["lint", "fix-knowledge"]),
2899
+ issues: z16.number().int().nonnegative(),
2900
+ mutations: z16.number().int().nonnegative().optional(),
2901
+ timestamp: z16.string().datetime()
2680
2902
  });
2681
- var knowledgePathDangledEventSchema = z18.object({
2903
+ var knowledgePathDangledEventSchema = z16.object({
2682
2904
  ...eventLedgerEnvelopeSchema,
2683
- event_type: z18.literal("knowledge_path_dangled"),
2684
- stable_id: z18.string(),
2685
- removed_glob: z18.string()
2905
+ event_type: z16.literal("knowledge_path_dangled"),
2906
+ stable_id: z16.string(),
2907
+ removed_glob: z16.string()
2686
2908
  });
2687
- var relevanceMigrationRunEventSchema = z18.object({
2909
+ var relevanceMigrationRunEventSchema = z16.object({
2688
2910
  ...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()
2911
+ event_type: z16.literal("relevance_migration_run"),
2912
+ timestamp: z16.string().datetime(),
2913
+ scanned_count: z16.number().int().nonnegative(),
2914
+ touched_count: z16.number().int().nonnegative()
2693
2915
  });
2694
- var pendingAutoArchivedEventSchema = z18.object({
2916
+ var pendingAutoArchivedEventSchema = z16.object({
2695
2917
  ...eventLedgerEnvelopeSchema,
2696
- event_type: z18.literal("pending_auto_archived"),
2697
- pending_path: z18.string(),
2698
- archived_to: z18.string(),
2699
- reason: z18.string()
2918
+ event_type: z16.literal("pending_auto_archived"),
2919
+ pending_path: z16.string(),
2920
+ archived_to: z16.string(),
2921
+ reason: z16.string()
2700
2922
  });
2701
- var assistantTurnObservedEventSchema = z18.object({
2923
+ var assistantTurnObservedEventSchema = z16.object({
2702
2924
  ...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([]),
2925
+ event_type: z16.literal("assistant_turn_observed"),
2926
+ kb_line_raw: z16.string().nullable(),
2927
+ cite_ids: z16.array(z16.string()).default([]),
2928
+ cite_tags: z16.array(citeTagSchema).default([]),
2707
2929
  // v2.0.0-rc.24 TASK-01: per-cite contract commitments. Index-aligned with
2708
2930
  // cite_ids/cite_tags (commitments[i] belongs to cite_ids[i]). Each slot
2709
2931
  // carries `operators[]` (kind + glob target) or `skip_reason` when the cite
@@ -2711,15 +2933,15 @@ var assistantTurnObservedEventSchema = z18.object({
2711
2933
  // empty array via `.default([])` and are excluded from contract-policy
2712
2934
  // audits by the marker-gate (see cite_contract_policy_activated below).
2713
2935
  // 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()
2936
+ cite_commitments: z16.array(
2937
+ z16.object({
2938
+ operators: z16.array(
2939
+ z16.object({
2940
+ kind: z16.enum(["edit", "not_edit", "require", "forbid"]),
2941
+ target: z16.string()
2720
2942
  })
2721
2943
  ),
2722
- skip_reason: z18.string().nullable()
2944
+ skip_reason: z16.string().nullable()
2723
2945
  })
2724
2946
  ).default([]),
2725
2947
  // lifecycle-refactor W3-T4 (§2 store 轴 / store-qualified 观测): per-cite store
@@ -2729,174 +2951,183 @@ var assistantTurnObservedEventSchema = z18.object({
2729
2951
  // doctor --cite-coverage can break compliance down per store WITHOUT joining
2730
2952
  // against the store registry. Additive `.optional()` (NOT `.default([])`) so
2731
2953
  // 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({
2954
+ // events parse with the field absent and bucket under the unqualified default.
2955
+ cite_stores: z16.array(z16.string().nullable()).optional(),
2956
+ client: z16.enum(["cc", "codex"]).optional(),
2957
+ turn_id: z16.string(),
2958
+ envelope_index: z16.number().int().nonnegative().optional(),
2959
+ timestamp: z16.string().datetime()
2960
+ });
2961
+ var citePolicyActivatedEventSchema = z16.object({
2740
2962
  ...eventLedgerEnvelopeSchema,
2741
- event_type: z18.literal("cite_policy_activated"),
2742
- policy_version: z18.string(),
2743
- timestamp: z18.string().datetime()
2963
+ event_type: z16.literal("cite_policy_activated"),
2964
+ policy_version: z16.string(),
2965
+ timestamp: z16.string().datetime()
2744
2966
  });
2745
- var citeContractPolicyActivatedEventSchema = z18.object({
2967
+ var citeContractPolicyActivatedEventSchema = z16.object({
2746
2968
  ...eventLedgerEnvelopeSchema,
2747
- event_type: z18.literal("cite_contract_policy_activated")
2969
+ event_type: z16.literal("cite_contract_policy_activated")
2748
2970
  });
2749
- var eventsRotatedEventSchema = z18.object({
2971
+ var eventsRotatedEventSchema = z16.object({
2750
2972
  ...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()
2973
+ event_type: z16.literal("events_rotated"),
2974
+ cutoff_ts: z16.string().datetime(),
2975
+ archived_count: z16.number().int().nonnegative(),
2976
+ kept_count: z16.number().int().nonnegative(),
2977
+ archive_path: z16.string()
2756
2978
  });
2757
- var knowledgeMetaAutoHealedEventSchema = z18.object({
2979
+ var knowledgeMetaAutoHealedEventSchema = z16.object({
2758
2980
  ...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()
2981
+ event_type: z16.literal("knowledge_meta_auto_healed"),
2982
+ previous_revision_hash: z16.string(),
2983
+ revision_hash: z16.string(),
2984
+ trigger: z16.literal("read"),
2985
+ caller: z16.enum(["planContext", "getKnowledgeSections", "getKnowledge", "extractKnowledge"]).optional()
2764
2986
  });
2765
- var serveLockClearedEventSchema = z18.object({
2987
+ var serveLockClearedEventSchema = z16.object({
2766
2988
  ...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()
2989
+ event_type: z16.literal("serve_lock_cleared"),
2990
+ pid: z16.number().int().nonnegative(),
2991
+ age_ms: z16.number().int().nonnegative(),
2992
+ timestamp: z16.string().datetime()
2771
2993
  });
2772
- var knowledgeEnrichedEventSchema = z18.object({
2994
+ var knowledgeEnrichedEventSchema = z16.object({
2773
2995
  ...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()
2996
+ event_type: z16.literal("knowledge_enriched"),
2997
+ path: z16.string(),
2998
+ added_fields: z16.array(z16.enum(["intent_clues", "tech_stack", "impact", "must_read_if"])),
2999
+ mode: z16.enum(["auto", "preview", "readonly", "interactive"]),
3000
+ timestamp: z16.string().datetime()
2779
3001
  });
2780
- var sessionArchiveAttemptedEventSchema = z18.object({
3002
+ var sessionArchiveAttemptedEventSchema = z16.object({
2781
3003
  ...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([])
3004
+ event_type: z16.literal("session_archive_attempted"),
3005
+ outcome: z16.enum(["proposed", "viability_failed", "user_dismissed", "skipped_no_signal"]),
3006
+ covered_through_ts: z16.number().int().nonnegative(),
3007
+ candidates_proposed: z16.number().int().nonnegative().default(0),
3008
+ knowledge_proposed_ids: z16.array(z16.string()).default([])
2787
3009
  });
2788
- var hookSurfaceEmittedEventSchema = z18.object({
3010
+ var hookSurfaceEmittedEventSchema = z16.object({
2789
3011
  ...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({
3012
+ event_type: z16.literal("hook_surface_emitted"),
3013
+ hook_name: z16.string(),
3014
+ client: z16.enum(["cc", "codex"]),
3015
+ target_channel: z16.string(),
3016
+ rendered_ids: z16.array(z16.string()),
3017
+ delivery_status: z16.enum(["delivered", "suppressed", "error"]),
3018
+ suppression_reason: z16.string().optional()
3019
+ });
3020
+ var hookSignalEmittedEventSchema = z16.object({
2799
3021
  ...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()
3022
+ event_type: z16.literal("hook_signal_emitted"),
3023
+ signal_type: z16.enum(["archive", "review", "maintenance", "other"]),
3024
+ threshold: z16.number(),
3025
+ actual_value: z16.number(),
3026
+ fired: z16.boolean()
2805
3027
  });
2806
- var mcpStdioTraceEventSchema = z18.object({
3028
+ var mcpStdioTraceEventSchema = z16.object({
2807
3029
  ...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({
3030
+ event_type: z16.literal("mcp_stdio_trace"),
3031
+ tool_name: z16.string(),
3032
+ request_id: z16.string(),
3033
+ duration_ms: z16.number().nonnegative(),
3034
+ status: z16.enum(["ok", "error"]),
3035
+ payload_bytes_in: z16.number().int().nonnegative(),
3036
+ payload_bytes_out: z16.number().int().nonnegative(),
3037
+ error_code: z16.string().optional()
3038
+ });
3039
+ var payloadGuardObservedEventSchema = z16.object({
2818
3040
  ...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({
3041
+ event_type: z16.literal("payload_guard_observed"),
3042
+ tool_name: z16.string(),
3043
+ path_count: z16.number().int().nonnegative(),
3044
+ tokens_estimated: z16.number().int().nonnegative(),
3045
+ truncated: z16.boolean(),
3046
+ cap: z16.number().int().positive()
3047
+ });
3048
+ var skillInvocationStartedEventSchema = z16.object({
2827
3049
  ...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()
3050
+ event_type: z16.literal("skill_invocation_started"),
3051
+ skill_name: z16.string(),
3052
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3053
+ entry_point: z16.string()
2832
3054
  });
2833
- var skillInvocationCompletedEventSchema = z18.object({
3055
+ var skillInvocationCompletedEventSchema = z16.object({
2834
3056
  ...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({
3057
+ event_type: z16.literal("skill_invocation_completed"),
3058
+ skill_name: z16.string(),
3059
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3060
+ entry_point: z16.string(),
3061
+ outcome: z16.enum(["completed", "aborted", "error", "no_op"]),
3062
+ elapsed_ms: z16.number().nonnegative().optional()
3063
+ });
3064
+ var skillPhaseTransitionEventSchema = z16.object({
2843
3065
  ...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({
3066
+ event_type: z16.literal("skill_phase_transition"),
3067
+ skill_name: z16.string(),
3068
+ phase: z16.string(),
3069
+ status: z16.enum(["entered", "completed", "skipped", "failed"]),
3070
+ checkpoint: z16.string().optional(),
3071
+ elapsed_ms: z16.number().nonnegative().optional()
3072
+ });
3073
+ var skillTriggerCandidateEventSchema = z16.object({
2852
3074
  ...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()
3075
+ event_type: z16.literal("skill_trigger_candidate"),
3076
+ skill_name: z16.string(),
3077
+ trigger_source: z16.enum(["user", "auto_invoke", "ai_self_trigger", "chained"]),
3078
+ signal: z16.string(),
3079
+ invoked: z16.boolean()
2858
3080
  });
2859
- var llmJudgeRunEventSchema = z18.object({
3081
+ var llmJudgeRunEventSchema = z16.object({
2860
3082
  ...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({
3083
+ event_type: z16.literal("llm_judge_run"),
3084
+ prompt: z16.string(),
3085
+ version: z16.string(),
3086
+ model: z16.string(),
3087
+ input_trace_id: z16.string(),
3088
+ score: z16.number(),
3089
+ rationale: z16.string()
3090
+ });
3091
+ var clientCapabilitySnapshotEventSchema = z16.object({
2870
3092
  ...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()
3093
+ event_type: z16.literal("client_capability_snapshot"),
3094
+ client: z16.enum(["cc", "codex"]),
3095
+ capabilities: z16.array(z16.string()),
3096
+ version: z16.string()
2875
3097
  });
2876
- var sessionEndedEventSchema = z18.object({
3098
+ var sessionEndedEventSchema = z16.object({
2877
3099
  ...eventLedgerEnvelopeSchema,
2878
- event_type: z18.literal("session_ended")
3100
+ event_type: z16.literal("session_ended")
2879
3101
  });
2880
- var fileMutatedEventSchema = z18.object({
3102
+ var fileMutatedEventSchema = z16.object({
3103
+ ...eventLedgerEnvelopeSchema,
3104
+ event_type: z16.literal("file_mutated"),
3105
+ path: z16.string(),
3106
+ tool_call_id: z16.string(),
3107
+ tool_name: z16.string().optional(),
3108
+ source_event_id: z16.string().optional(),
3109
+ store_id: z16.string().optional()
3110
+ });
3111
+ var knowledgeBodyReadEventSchema = z16.object({
2881
3112
  ...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({
3113
+ event_type: z16.literal("knowledge_body_read"),
3114
+ stable_id: z16.string(),
3115
+ store: z16.string().optional(),
3116
+ path: z16.string(),
3117
+ tool_call_id: z16.string().optional(),
3118
+ tool_name: z16.string().optional()
3119
+ });
3120
+ var precompactObservedEventSchema = z16.object({
2890
3121
  ...eventLedgerEnvelopeSchema,
2891
- event_type: z18.literal("precompact_observed")
3122
+ event_type: z16.literal("precompact_observed")
2892
3123
  });
2893
- var graphEdgeCandidateRequestedEventSchema = z18.object({
3124
+ var graphEdgeCandidateRequestedEventSchema = z16.object({
2894
3125
  ...eventLedgerEnvelopeSchema,
2895
- event_type: z18.literal("graph_edge_candidate_requested"),
2896
- stable_id: z18.string(),
2897
- store: z18.string().optional()
3126
+ event_type: z16.literal("graph_edge_candidate_requested"),
3127
+ stable_id: z16.string(),
3128
+ store: z16.string().optional()
2898
3129
  });
2899
- var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
3130
+ var eventLedgerEventSchema = z16.discriminatedUnion("event_type", [
2900
3131
  knowledgeContextPlannedEventSchema,
2901
3132
  knowledgeSelectionEventSchema,
2902
3133
  knowledgeSectionsFetchedEventSchema,
@@ -2906,10 +3137,6 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2906
3137
  reapplyCompletedEventSchema,
2907
3138
  installDiffAppliedEventSchema,
2908
3139
  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
3140
  metaReconciledOnStartupEventSchema,
2914
3141
  metaReconciledEventSchema,
2915
3142
  claudeSkillPathMigratedEventSchema,
@@ -2921,6 +3148,7 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2921
3148
  knowledgePromoteStartedEventSchema,
2922
3149
  knowledgePromotedEventSchema,
2923
3150
  knowledgePromoteFailedEventSchema,
3151
+ knowledgeModifiedEventSchema,
2924
3152
  knowledgeLayerChangedEventSchema,
2925
3153
  // v2.0.0-rc.37 NEW-24: dedicated old→new stable_id mapping event
2926
3154
  knowledgeIdRedirectEventSchema,
@@ -2992,6 +3220,9 @@ var eventLedgerEventSchema = z18.discriminatedUnion("event_type", [
2992
3220
  // lifecycle-refactor Wave 2 — dormant-hook activation markers.
2993
3221
  sessionEndedEventSchema,
2994
3222
  fileMutatedEventSchema,
3223
+ // KT-DEC-0030: knowledge_body_read — PostToolUse native-Read consumption marker
3224
+ // (replaces knowledge_consumed as the funnel's "body opened" signal).
3225
+ knowledgeBodyReadEventSchema,
2995
3226
  precompactObservedEventSchema,
2996
3227
  graphEdgeCandidateRequestedEventSchema
2997
3228
  ]);
@@ -3031,19 +3262,24 @@ var PROFILES = {
3031
3262
  topK: 12,
3032
3263
  payloadWarnBytes: 8192,
3033
3264
  payloadHardBytes: 32768,
3034
- injectionChars: 1e3
3265
+ injectionChars: 1e3,
3266
+ bodyBudgetBytes: 2048
3035
3267
  },
3036
3268
  balanced: {
3037
3269
  topK: 24,
3038
3270
  payloadWarnBytes: 16384,
3039
3271
  payloadHardBytes: 65536,
3040
- injectionChars: 2e3
3272
+ injectionChars: 2e3,
3273
+ // grill-report C-009: ~4KB ≈ 1~3 typical bodies. Independent of the 16KB
3274
+ // payloadWarnBytes (the response cap), small on purpose — see field doc.
3275
+ bodyBudgetBytes: 4096
3041
3276
  },
3042
3277
  generous: {
3043
3278
  topK: 48,
3044
3279
  payloadWarnBytes: 32768,
3045
3280
  payloadHardBytes: 131072,
3046
- injectionChars: 4e3
3281
+ injectionChars: 4e3,
3282
+ bodyBudgetBytes: 8192
3047
3283
  }
3048
3284
  };
3049
3285
  var DEFAULT_RETRIEVAL_BUDGET_PROFILE = "balanced";
@@ -3053,7 +3289,8 @@ function resolveRetrievalBudget(overrides) {
3053
3289
  topK: overrides?.topK ?? base.topK,
3054
3290
  payloadWarnBytes: overrides?.payloadWarnBytes ?? base.payloadWarnBytes,
3055
3291
  payloadHardBytes: overrides?.payloadHardBytes ?? base.payloadHardBytes,
3056
- injectionChars: overrides?.injectionChars ?? base.injectionChars
3292
+ injectionChars: overrides?.injectionChars ?? base.injectionChars,
3293
+ bodyBudgetBytes: overrides?.bodyBudgetBytes ?? base.bodyBudgetBytes
3057
3294
  };
3058
3295
  }
3059
3296
  function retrievalBudgetProfile(profile) {
@@ -3061,10 +3298,11 @@ function retrievalBudgetProfile(profile) {
3061
3298
  }
3062
3299
  export {
3063
3300
  AGENTS_META_IDENTITY_SOURCES,
3064
- AGENTS_META_LAYERS,
3065
3301
  AGENTS_META_TOPOLOGY_TYPES,
3066
3302
  AgentsMetaCountersSchema,
3067
- BOOTSTRAP_CANONICAL,
3303
+ BOOTSTRAP_CANONICAL_BY_LOCALE,
3304
+ BOOTSTRAP_CANONICAL_EN,
3305
+ BOOTSTRAP_CANONICAL_ZH,
3068
3306
  BOOTSTRAP_MARKER_BEGIN,
3069
3307
  BOOTSTRAP_MARKER_END,
3070
3308
  BOOTSTRAP_REGEX,
@@ -3084,9 +3322,6 @@ export {
3084
3322
  KNOWN_SCOPE_PREFIXES,
3085
3323
  KnowledgeEntryFrontmatterSchema,
3086
3324
  KnowledgeTypeSchema,
3087
- LEGACY_KB_MARKER_BEGIN,
3088
- LEGACY_KB_MARKER_END,
3089
- LEGACY_KB_REGEX,
3090
3325
  LayerSchema,
3091
3326
  MCP_STORE_AWARE_CONTRACTS,
3092
3327
  MCP_STORE_AWARE_TOOLS,
@@ -3098,16 +3333,18 @@ export {
3098
3333
  PERSONAL_SCOPE,
3099
3334
  PERSONAL_STORE_SENTINEL,
3100
3335
  PROJECT_ROOT_SIGNALS,
3101
- PROPOSED_REASON_DESCRIPTIONS,
3336
+ PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE,
3102
3337
  PROTECTED_TOKENS,
3103
3338
  ProposedReasonSchema,
3104
3339
  REDACTION_PLACEHOLDER_PREFIX,
3105
- ResolverNotImplementedError,
3106
3340
  SCOPE_COORDINATE_PATTERN,
3107
3341
  STORES_ROOT_DIR,
3342
+ STORE_ALIAS_PATTERN,
3108
3343
  STORE_GITIGNORE,
3109
3344
  STORE_KNOWLEDGE_TYPE_DIRS,
3110
3345
  STORE_LAYOUT,
3346
+ STORE_MOUNT_GROUPS,
3347
+ STORE_MOUNT_NAME_PATTERN,
3111
3348
  STORE_PENDING_DIR,
3112
3349
  STORE_PROJECT_ID_PATTERN,
3113
3350
  STORE_RESOLVER_WARNING_CODES,
@@ -3117,7 +3354,6 @@ export {
3117
3354
  addMountedStore,
3118
3355
  addStoreProject,
3119
3356
  agentsIdentitySourceSchema,
3120
- agentsLayerSchema,
3121
3357
  agentsMetaNodeSchema,
3122
3358
  agentsMetaSchema,
3123
3359
  agentsTopologyTypeSchema,
@@ -3133,7 +3369,6 @@ export {
3133
3369
  auditModeSchema,
3134
3370
  bindRequiredStore,
3135
3371
  bindingsSnapshotPath,
3136
- bootstrapMarkerMigratedEventSchema,
3137
3372
  buildDebugBundle,
3138
3373
  buildFailureTrace,
3139
3374
  buildScanRecommendations,
@@ -3156,11 +3391,12 @@ export {
3156
3391
  defaultLayerFilterSchema,
3157
3392
  defaultMessages,
3158
3393
  deriveAgentsMetaIdentitySource,
3159
- deriveAgentsMetaLayer,
3160
3394
  deriveAgentsMetaStableId,
3161
3395
  deriveAgentsMetaTopologyType,
3396
+ deriveMountLabel,
3162
3397
  detachMountedStore,
3163
3398
  detectNodeLocale,
3399
+ disambiguateAlias,
3164
3400
  doctorRunEventSchema,
3165
3401
  driftDetectedEventSchema,
3166
3402
  editIntentCheckedEventSchema,
@@ -3220,6 +3456,7 @@ export {
3220
3456
  isPersonalScope,
3221
3457
  knowledgeArchiveAttemptedEventSchema,
3222
3458
  knowledgeArchivedEventSchema,
3459
+ knowledgeBodyReadEventSchema,
3223
3460
  knowledgeConsumedEventSchema,
3224
3461
  knowledgeContextPlannedEventSchema,
3225
3462
  knowledgeDeferredEventSchema,
@@ -3229,6 +3466,7 @@ export {
3229
3466
  knowledgeIdRedirectEventSchema,
3230
3467
  knowledgeLayerChangedEventSchema,
3231
3468
  knowledgeMetaAutoHealedEventSchema,
3469
+ knowledgeModifiedEventSchema,
3232
3470
  knowledgePathDangledEventSchema,
3233
3471
  knowledgePromoteFailedEventSchema,
3234
3472
  knowledgePromoteStartedEventSchema,
@@ -3259,7 +3497,7 @@ export {
3259
3497
  localKnowledgeIdSchema,
3260
3498
  lockApprovedEventSchema,
3261
3499
  lockDriftEventSchema,
3262
- mcpConfigMigratedEventSchema,
3500
+ matchBootstrapCanonicalLocale,
3263
3501
  mcpEventLedgerEventSchema,
3264
3502
  mcpPayloadLimitsSchema,
3265
3503
  mcpStdioTraceEventSchema,
@@ -3269,6 +3507,8 @@ export {
3269
3507
  mountedStoreSchema,
3270
3508
  normalizeCiteTag,
3271
3509
  normalizeLocale,
3510
+ nudgeModeSchema,
3511
+ observeConfigSchema,
3272
3512
  onboardSlotSchema,
3273
3513
  parityCapabilitySchema,
3274
3514
  parityClientExpectationSchema,
@@ -3300,6 +3540,7 @@ export {
3300
3540
  readSetGoldenFileSchema,
3301
3541
  readStoreCounters,
3302
3542
  readStoreIdentity,
3543
+ readStoreIdentityAsync,
3303
3544
  readStoreProjects,
3304
3545
  reapplyCompletedEventSchema,
3305
3546
  recallAnnotations,
@@ -3307,14 +3548,18 @@ export {
3307
3548
  recallOutputSchema,
3308
3549
  recognizeStoreDir,
3309
3550
  reconcileStoreCounters,
3551
+ redactPii,
3310
3552
  redactSecrets,
3311
3553
  relevanceMigrationRunEventSchema,
3312
3554
  requiredStoreEntrySchema,
3555
+ resolveBootstrapCanonical,
3313
3556
  resolveCandidates,
3314
3557
  resolveFabricLocale,
3558
+ resolveGlobalLocale,
3315
3559
  resolveGlobalRoot,
3316
3560
  resolveRetrievalBudget,
3317
3561
  resolveStoreQualifiedId,
3562
+ resolveWorkspaceBindingId,
3318
3563
  resolvedBindingsSnapshotSchema,
3319
3564
  retrievalBudgetProfile,
3320
3565
  ruleDescriptionIndexItemSchema,
@@ -3333,16 +3578,21 @@ export {
3333
3578
  skillInvocationStartedEventSchema,
3334
3579
  skillPhaseTransitionEventSchema,
3335
3580
  skillTriggerCandidateEventSchema,
3581
+ storeAliasSchema,
3336
3582
  storeAwareEntrySchema,
3337
3583
  storeCountersPath,
3338
3584
  storeCountersSchema,
3339
3585
  storeHasProject,
3340
3586
  storeIdentitySchema,
3341
3587
  storeKnowledgeTypeDir,
3588
+ storeMountGroup,
3589
+ storeMountNameSchema,
3590
+ storeMountSubPath,
3342
3591
  storeProjectSchema,
3343
3592
  storeProjectsFileSchema,
3344
3593
  storeReadSetSchema,
3345
3594
  storeRelativePath,
3595
+ storeRelativePathForMount,
3346
3596
  storeResolveInputSchema,
3347
3597
  storeResolverWarningCodeSchema,
3348
3598
  storeResolverWarningSchema,
@@ -3352,6 +3602,7 @@ export {
3352
3602
  uidSchema,
3353
3603
  withDerivedAgentsMetaNodeDefaults,
3354
3604
  writeBindingsSnapshot,
3605
+ writeRouteSchema,
3355
3606
  writeTargetSchema,
3356
3607
  writtenToStoreSchema,
3357
3608
  zhCNMessages