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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  // src/schemas/api-contracts.ts
2
- import { z as z2 } from "zod";
2
+ import { z as z3 } from "zod";
3
3
 
4
4
  // src/onboard-slots.ts
5
5
  import { z } from "zod";
@@ -13,63 +13,91 @@ var ONBOARD_SLOT_NAMES = [
13
13
  var onboardSlotSchema = z.enum(ONBOARD_SLOT_NAMES);
14
14
  var ONBOARD_SLOT_TOTAL = ONBOARD_SLOT_NAMES.length;
15
15
 
16
+ // src/schemas/scope.ts
17
+ import { z as z2 } from "zod";
18
+ var PERSONAL_SCOPE = "personal";
19
+ var KNOWN_SCOPE_PREFIXES = ["personal", "team", "project", "org"];
20
+ var SCOPE_COORDINATE_PATTERN = /^[a-z0-9_-]+(:[a-z0-9_-]+)*$/u;
21
+ var scopeCoordinateSchema = z2.string().min(1).regex(
22
+ SCOPE_COORDINATE_PATTERN,
23
+ "scope coordinate must be ':'-joined lowercase [a-z0-9_-] segments"
24
+ );
25
+ function scopeRoot(coordinate) {
26
+ const colon = coordinate.indexOf(":");
27
+ return colon === -1 ? coordinate : coordinate.slice(0, colon);
28
+ }
29
+ function isPersonalScope(coordinate) {
30
+ return scopeRoot(coordinate) === PERSONAL_SCOPE;
31
+ }
32
+ var entryScopeMetadataSchema = z2.object({
33
+ semantic_scope: scopeCoordinateSchema,
34
+ // Store alias or UUID. Validated as a non-empty string here; the resolver
35
+ // (P0.6) maps alias→UUID and verifies the store is in the read-set.
36
+ visibility_store: z2.string().min(1)
37
+ }).strict();
38
+
16
39
  // src/schemas/api-contracts.ts
17
- var structuredWarningSchema = z2.object({
18
- code: z2.string(),
19
- file: z2.string(),
20
- line: z2.number().optional(),
21
- action_hint: z2.string()
40
+ var structuredWarningSchema = z3.object({
41
+ code: z3.string(),
42
+ file: z3.string(),
43
+ line: z3.number().optional(),
44
+ message: z3.string().optional(),
45
+ action_hint: z3.string()
22
46
  });
23
- var _knowledgeTypeEnum = z2.enum(["models", "decisions", "guidelines", "pitfalls", "processes"]);
24
- var _maturityEnum = z2.enum(["draft", "verified", "proven"]);
25
- var _layerEnum = z2.enum(["personal", "team"]);
26
- var _ruleDescriptionSchema = z2.object({
27
- summary: z2.string(),
28
- intent_clues: z2.array(z2.string()),
29
- tech_stack: z2.array(z2.string()),
30
- impact: z2.array(z2.string()),
31
- must_read_if: z2.string(),
32
- entities: z2.array(z2.string()).optional(),
47
+ var _knowledgeTypeEnum = z3.enum(["models", "decisions", "guidelines", "pitfalls", "processes"]);
48
+ var _maturityEnum = z3.enum(["draft", "verified", "proven"]);
49
+ var _layerEnum = z3.enum(["personal", "team"]);
50
+ var _ruleDescriptionSchema = z3.object({
51
+ summary: z3.string(),
52
+ intent_clues: z3.array(z3.string()),
53
+ tech_stack: z3.array(z3.string()),
54
+ impact: z3.array(z3.string()),
55
+ must_read_if: z3.string(),
56
+ entities: z3.array(z3.string()).optional(),
33
57
  // v2.0: optional knowledge-entry fields. Absent for v1.x rules; present for
34
58
  // entries that declare frontmatter `id/type/maturity/layer`.
35
- id: z2.string().optional(),
59
+ id: z3.string().optional(),
36
60
  knowledge_type: _knowledgeTypeEnum.optional(),
37
61
  maturity: _maturityEnum.optional(),
38
62
  knowledge_layer: _layerEnum.optional(),
39
- layer_reason: z2.string().optional(),
40
- created_at: z2.string().optional(),
63
+ layer_reason: z3.string().optional(),
64
+ created_at: z3.string().optional(),
41
65
  // v2.0.0-rc.38 UX-3 (D-MCP fold ③): these three were previously carried ONLY
42
66
  // as top-level mirrors on the index item. With the mirrors removed,
43
67
  // `description` becomes their canonical (and only) home, so the schema must
44
68
  // validate them here. Optional + default-safe (tags/[]/broad) so legacy
45
69
  // entries without frontmatter still parse.
46
- tags: z2.array(z2.string()).optional(),
47
- relevance_scope: z2.enum(["narrow", "broad"]).optional(),
48
- relevance_paths: z2.array(z2.string()).optional(),
70
+ tags: z3.array(z3.string()).optional(),
71
+ relevance_scope: z3.enum(["narrow", "broad"]).optional(),
72
+ relevance_paths: z3.array(z3.string()).optional(),
49
73
  // v2.2 H2-related (W1-T7) — W1-REVIEW codex HIGH-2: the MCP-facing description
50
74
  // schema must also carry `related`, else zod strips the graph edges on output
51
75
  // validation and they never reach the client (MC1 include_related / fabric-
52
76
  // connect would see nothing). Mirrors the agents-meta ruleDescriptionSchema.
53
- related: z2.array(z2.string()).optional()
77
+ related: z3.array(z3.string()).optional()
54
78
  });
55
- var _descriptionIndexItemSchema = z2.object({
56
- stable_id: z2.string(),
57
- description: _ruleDescriptionSchema
79
+ var _descriptionIndexItemSchema = z3.object({
80
+ stable_id: z3.string(),
81
+ description: _ruleDescriptionSchema,
82
+ // recall dedupe marker: true when this candidate is ALSO injected in full at
83
+ // SessionStart ("ALWAYS-ACTIVE RULES" = broad model/guideline). MUST be
84
+ // declared here or zod .strip() drops it at the MCP boundary (KT-PIT-0005),
85
+ // silently breaking the marker even though recall() sets it. Only ever true.
86
+ always_active: z3.boolean().optional()
58
87
  });
59
- var _requirementProfileSchema = z2.object({
60
- target_path: z2.string(),
61
- known_tech: z2.array(z2.string()),
62
- user_intent: z2.string(),
63
- detected_entities: z2.array(z2.string())
88
+ var _requirementProfileSchema = z3.object({
89
+ target_path: z3.string(),
90
+ known_tech: z3.array(z3.string()),
91
+ detected_entities: z3.array(z3.string())
64
92
  });
65
- var planContextInputSchema = z2.object({
66
- paths: z2.array(z2.string()).min(1).describe("Candidate file paths to build neutral rule selection context for"),
67
- intent: z2.string().optional().describe("User-stated requirement or implementation intent; used only to build a neutral requirement profile"),
68
- known_tech: z2.array(z2.string()).optional().describe("Known technologies involved in the requirement profile"),
69
- detected_entities: z2.record(z2.array(z2.string())).optional().describe("Optional path-keyed detected entities for the requirement profile"),
70
- client_hash: z2.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection"),
71
- correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
72
- session_id: z2.string().optional().describe(
93
+ var planContextInputSchema = z3.object({
94
+ paths: z3.array(z3.string()).min(1).describe("Candidate file paths to build neutral rule selection context for"),
95
+ intent: z3.string().optional().describe("User-stated requirement or implementation intent; used only to build a neutral requirement profile"),
96
+ known_tech: z3.array(z3.string()).optional().describe("Known technologies involved in the requirement profile"),
97
+ detected_entities: z3.record(z3.array(z3.string())).optional().describe("Optional path-keyed detected entities for the requirement profile"),
98
+ client_hash: z3.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection"),
99
+ correlation_id: z3.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
100
+ session_id: z3.string().optional().describe(
73
101
  "Recommended: pass the current client session id (Claude Code: $session_id; Codex: corresponding identifier) \u2014 enables cross-session debt tracking in fabric doctor and accurate archive-hint cross-session count. Falls back gracefully if omitted."
74
102
  ),
75
103
  // v2.0-rc.5 A3 (TASK-007): `include_deprecated` removed — it was a no-op
@@ -78,55 +106,58 @@ var planContextInputSchema = z2.object({
78
106
  // v2/rc.2 (Q6): client-supplied layer scope. When omitted, the server
79
107
  // falls back to fabric-config.default_layer_filter (TASK-002) so a single
80
108
  // workspace policy controls the default. Explicit values override.
81
- layer_filter: z2.enum(["team", "personal", "both"]).optional().describe(
109
+ layer_filter: z3.enum(["team", "personal", "both"]).optional().describe(
82
110
  "Restrict description_index to the named layer. Default: fabric-config.default_layer_filter (TASK-002)."
83
111
  ),
84
112
  // v2.0-rc.5 C3 (TASK-012): explicit path context for `narrow` relevance
85
113
  // filtering. When omitted, the server falls back to `paths` so existing
86
114
  // callers see narrowing against the requested paths. When the resolved
87
115
  // list is empty, the narrow filter fails open (every narrow entry passes).
88
- target_paths: z2.array(z2.string()).optional().describe(
116
+ target_paths: z3.array(z3.string()).optional().describe(
89
117
  "Path context for narrow-scope relevance filtering. Defaults to `paths`; empty = no filter."
90
118
  )
91
119
  });
92
- var _preflightDiagnosticSchema = z2.object({
120
+ var _preflightDiagnosticSchema = z3.object({
93
121
  // v2.0.0-rc.38 UX-2: `empty_shell_suppressed` surfaces draft entries whose
94
122
  // description carries no selection signal (summary === stable_id + empty
95
123
  // intent_clues/tech_stack/impact). They are filtered out of `candidates` to
96
124
  // cut noise; this diagnostic names them so `fabric doctor` /
97
125
  // --enrich-descriptions can prompt enrichment.
98
- code: z2.enum(["missing_description", "empty_shell_suppressed"]),
99
- severity: z2.literal("warn"),
100
- message: z2.string(),
101
- stable_ids: z2.array(z2.string()).optional(),
102
- path: z2.string().optional()
126
+ code: z3.enum(["missing_description", "empty_shell_suppressed"]),
127
+ severity: z3.literal("warn"),
128
+ message: z3.string(),
129
+ stable_ids: z3.array(z3.string()).optional(),
130
+ path: z3.string().optional()
103
131
  });
104
- var planContextOutputSchema = z2.object({
105
- revision_hash: z2.string(),
106
- stale: z2.boolean(),
107
- selection_token: z2.string(),
108
- entries: z2.array(
109
- z2.object({
110
- path: z2.string(),
132
+ var planContextOutputSchema = z3.object({
133
+ revision_hash: z3.string(),
134
+ stale: z3.boolean(),
135
+ selection_token: z3.string(),
136
+ entries: z3.array(
137
+ z3.object({
138
+ path: z3.string(),
111
139
  requirement_profile: _requirementProfileSchema
112
140
  })
113
141
  ),
114
- candidates: z2.array(_descriptionIndexItemSchema),
142
+ // v2.2 payload de-dup: single top-level echo of the caller's `intent` (was
143
+ // duplicated into every entry's requirement_profile). Omitted when no intent.
144
+ intent: z3.string().optional(),
145
+ candidates: z3.array(_descriptionIndexItemSchema),
115
146
  // v2.2 A-INFRA-3 (W1-T3-TOPK) / MC4-payload-budget (W1-T4): number of
116
147
  // lower-ranked candidates dropped by the unified truncation chain (top_k cap
117
148
  // + payload-budget trim). Present and > 0 ONLY when truncation fired, so the
118
149
  // steady-state wire shape is unchanged. Lets the LLM know the returned set is
119
150
  // not exhaustive ("N more exist; narrow your intent").
120
- omitted_candidate_count: z2.number().int().nonnegative().optional(),
121
- preflight_diagnostics: z2.array(_preflightDiagnosticSchema),
122
- warnings: z2.array(structuredWarningSchema).optional(),
151
+ omitted_candidate_count: z3.number().int().nonnegative().optional(),
152
+ preflight_diagnostics: z3.array(_preflightDiagnosticSchema),
153
+ warnings: z3.array(structuredWarningSchema).optional(),
123
154
  // v2.0.0-rc.22 Scope D T-D2: optional auto-heal banner fields. Surfaced
124
155
  // ONLY when the loadActiveMetaOrStale call detected drift and rebuilt the
125
156
  // meta in-place. Downstream CLI / hint renderers use this pair to render a
126
157
  // "knowledge meta auto-healed (was <prev>, now <curr>)" notice without
127
158
  // having to query the event ledger.
128
- auto_healed: z2.boolean().optional(),
129
- previous_revision_hash: z2.string().optional(),
159
+ auto_healed: z3.boolean().optional(),
160
+ previous_revision_hash: z3.string().optional(),
130
161
  // v2.0.0-rc.37 NEW-24: stale-id redirect map. Populated when one or more
131
162
  // recent fab_review modify-layer flips reassigned a canonical stable_id
132
163
  // and the NEW id is in this response's description_index. Callers that
@@ -134,13 +165,13 @@ var planContextOutputSchema = z2.object({
134
165
  // the new id before issuing fab_get_knowledge_sections / fab_recall. Empty
135
166
  // (field omitted) when no actionable redirects exist for the surfaced
136
167
  // candidate set. See packages/server/src/services/id-redirect.ts.
137
- redirects: z2.record(z2.string()).optional(),
168
+ redirects: z3.record(z3.string()).optional(),
138
169
  // lifecycle-refactor W3-T2 (§7 图谱消费): related-expansion provenance map
139
170
  // (appended id → surfaced source id). Present only when `include_related` was
140
171
  // requested AND at least one in-corpus one-hop neighbour was appended. Omitted
141
172
  // on the graph-empty / steady-state path. Additive — declare it here or zod
142
173
  // strips it on output validation.
143
- related_appended: z2.record(z2.string()).optional()
174
+ related_appended: z3.record(z3.string()).optional()
144
175
  });
145
176
  var planContextAnnotations = {
146
177
  readOnlyHint: true,
@@ -149,65 +180,69 @@ var planContextAnnotations = {
149
180
  openWorldHint: false,
150
181
  title: "Plan rule context"
151
182
  };
152
- var planContextHintNarrowEntrySchema = z2.object({
153
- id: z2.string(),
154
- type: z2.string(),
155
- maturity: z2.string(),
156
- summary: z2.string()
183
+ var planContextHintNarrowEntrySchema = z3.object({
184
+ id: z3.string(),
185
+ type: z3.string(),
186
+ maturity: z3.string(),
187
+ summary: z3.string(),
188
+ // W2-2 (KT-DEC-0027): the entry's must_read_if trigger hook, forwarded for the
189
+ // SessionStart REFERENCE rendering (decision/pitfall/process → title + hook).
190
+ // Optional — omitted when the frontmatter declares none.
191
+ must_read_if: z3.string().optional()
157
192
  });
158
- var planContextHintOutputSchema = z2.object({
159
- version: z2.literal(1),
160
- revision_hash: z2.string(),
161
- target_paths: z2.array(z2.string()),
162
- narrow: z2.array(planContextHintNarrowEntrySchema),
163
- broad_count: z2.number().int().nonnegative()
193
+ var planContextHintOutputSchema = z3.object({
194
+ version: z3.literal(1),
195
+ revision_hash: z3.string(),
196
+ target_paths: z3.array(z3.string()),
197
+ narrow: z3.array(planContextHintNarrowEntrySchema),
198
+ broad_count: z3.number().int().nonnegative()
164
199
  });
165
- var knowledgeSectionsInputSchema = z2.object({
166
- selection_token: z2.string().min(1).describe("Selection token returned by fab_plan_context"),
167
- ai_selected_stable_ids: z2.array(z2.string()).describe(
200
+ var knowledgeSectionsInputSchema = z3.object({
201
+ selection_token: z3.string().min(1).describe("Selection token returned by fab_plan_context"),
202
+ ai_selected_stable_ids: z3.array(z3.string()).describe(
168
203
  "Stable ids picked from fab_plan_context candidates[].stable_id; choose 1..N to fetch bodies for"
169
204
  ),
170
- ai_selection_reasons: z2.record(z2.string().min(1)).optional().default({}).describe(
205
+ ai_selection_reasons: z3.record(z3.string().min(1)).optional().default({}).describe(
171
206
  "Optional reason for each AI-selected L1 stable_id (audit telemetry). Omit to fetch bodies without annotating \u2014 server defaults to {} rather than rejecting the documented two-step call."
172
207
  ),
173
- correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
174
- session_id: z2.string().optional().describe("Optional caller-provided session id for Event Ledger records"),
208
+ correlation_id: z3.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
209
+ session_id: z3.string().optional().describe("Optional caller-provided session id for Event Ledger records"),
175
210
  // v2.0 rc.5 TASK-014 (C5): optional client identity hash propagated into
176
211
  // knowledge_consumed events. Falls back to empty string when unset — full
177
212
  // client-identity propagation deferred to rc.6.
178
- client_hash: z2.string().optional().describe("Optional caller-provided client hash propagated into knowledge_consumed events")
213
+ client_hash: z3.string().optional().describe("Optional caller-provided client hash propagated into knowledge_consumed events")
179
214
  });
180
- var knowledgeSectionsOutputSchema = z2.object({
181
- revision_hash: z2.string(),
215
+ var knowledgeSectionsOutputSchema = z3.object({
216
+ revision_hash: z3.string(),
182
217
  // v2.0.0-rc.38 UX-13 (D-MCP step-2 audit): the deprecated `precedence`
183
218
  // L2/L1/L0 tuple (flagged "removed in rc.24" but still emitted) is gone — it
184
219
  // was a constant 3-string field on every response read by no production
185
- // consumer. Use rules[].level for ordering.
186
- selected_stable_ids: z2.array(z2.string()),
187
- rules: z2.array(
188
- z2.object({
189
- stable_id: z2.string(),
190
- level: z2.enum(["L0", "L1", "L2"]),
191
- path: z2.string(),
220
+ // consumer. v2.0.0-rc.38 Goal B: the dead L0/L1/L2 `level` axis was retired
221
+ // too (dead-write — no consumer ordered by it).
222
+ selected_stable_ids: z3.array(z3.string()),
223
+ rules: z3.array(
224
+ z3.object({
225
+ stable_id: z3.string(),
226
+ path: z3.string(),
192
227
  // v2.0.0-rc.23 TASK-013 (F8b): replaced the legacy
193
228
  // `sections: Record<string,string>` (keyed by the 4-element A-set enum)
194
229
  // with the full markdown body (frontmatter stripped). Callers scan the
195
230
  // body for whichever B-set heading they need (Summary / Why proposed /
196
231
  // Session context / Evidence) — section-name discipline is now a writer
197
232
  // convention, not an API contract.
198
- body: z2.string()
233
+ body: z3.string()
199
234
  })
200
235
  ),
201
- diagnostics: z2.array(
236
+ diagnostics: z3.array(
202
237
  // v2.0.0-rc.23 TASK-013 (F8b): `missing_section` was removed alongside the
203
238
  // A-set enum. `missing_knowledge_metadata` stays as the warn-level signal
204
239
  // for un-migrated v1.x entries (no knowledge_type AND no knowledge_layer
205
240
  // in frontmatter). Does NOT block selection.
206
- z2.object({
207
- code: z2.enum(["missing_knowledge_metadata", "unresolved_selected_id"]),
208
- severity: z2.literal("warn"),
209
- stable_id: z2.string(),
210
- message: z2.string()
241
+ z3.object({
242
+ code: z3.enum(["missing_knowledge_metadata", "unresolved_selected_id"]),
243
+ severity: z3.literal("warn"),
244
+ stable_id: z3.string(),
245
+ message: z3.string()
211
246
  })
212
247
  ),
213
248
  // v2/rc.3 (Q6) + v2.0.0-rc.37 NEW-24: present iff at least one stable_id in
@@ -217,113 +252,96 @@ var knowledgeSectionsOutputSchema = z2.object({
217
252
  // (old_id → new_id) when multiple rewrites fire in one fetch. Both shapes
218
253
  // are accepted for forward-compat; readers should branch on shape and
219
254
  // refresh their cached ids accordingly.
220
- redirect_to: z2.union([
221
- z2.object({ stable_id: z2.string() }),
222
- z2.record(z2.string())
255
+ redirect_to: z3.union([
256
+ z3.object({ stable_id: z3.string() }),
257
+ z3.record(z3.string())
223
258
  ]).optional().describe(
224
259
  "Post-layer-flip redirect. Pre-rc.37: { stable_id } shape from rc.3 fab_review/modify. rc.37+: also accepts a (old_id \u2192 new_id) map for fab_get_knowledge_sections / fab_recall transparent rewrite."
225
260
  ),
226
- warnings: z2.array(structuredWarningSchema).optional()
261
+ warnings: z3.array(structuredWarningSchema).optional()
227
262
  });
228
263
  var knowledgeSectionsAnnotations = {
229
264
  readOnlyHint: true,
230
265
  idempotentHint: true,
231
266
  destructiveHint: false,
232
267
  openWorldHint: false,
233
- title: "Filter rule sections"
268
+ title: "Fetch knowledge entry bodies"
234
269
  };
235
- var recallInputSchema = z2.object({
236
- paths: z2.array(z2.string()).min(1).describe(
237
- "Candidate file paths to recall Fabric rules for. Same semantics as fab_plan_context.paths."
270
+ var recallInputSchema = z3.object({
271
+ paths: z3.array(z3.string()).min(1).describe(
272
+ "Candidate file paths to recall Fabric knowledge entries for. Same semantics as fab_plan_context.paths."
238
273
  ),
239
- intent: z2.string().optional().describe("User-stated requirement or implementation intent; used to build a neutral requirement profile."),
240
- known_tech: z2.array(z2.string()).optional().describe("Known technologies involved."),
241
- detected_entities: z2.record(z2.array(z2.string())).optional().describe("Optional path-keyed detected entities."),
242
- client_hash: z2.string().optional().describe("Revision hash from a prior call; enables stale detection."),
243
- correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
244
- session_id: z2.string().optional().describe(
274
+ intent: z3.string().optional().describe("User-stated requirement or implementation intent; used to build a neutral requirement profile."),
275
+ known_tech: z3.array(z3.string()).optional().describe("Known technologies involved."),
276
+ detected_entities: z3.record(z3.array(z3.string())).optional().describe("Optional path-keyed detected entities."),
277
+ client_hash: z3.string().optional().describe("Revision hash from a prior call; enables stale detection."),
278
+ correlation_id: z3.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
279
+ session_id: z3.string().optional().describe(
245
280
  "Current client session id (Claude Code: $session_id; Codex: corresponding identifier). Enables cross-session debt tracking. Falls back gracefully if omitted."
246
281
  ),
247
- layer_filter: z2.enum(["team", "personal", "both"]).optional().describe(
282
+ layer_filter: z3.enum(["team", "personal", "both"]).optional().describe(
248
283
  "Restrict recall to the named layer. Default: fabric-config.default_layer_filter."
249
284
  ),
250
- target_paths: z2.array(z2.string()).optional().describe(
285
+ target_paths: z3.array(z3.string()).optional().describe(
251
286
  "Path context for narrow-scope relevance filtering. Defaults to `paths`; empty = no filter."
252
287
  ),
253
- ids: z2.array(z2.string()).optional().describe(
254
- "Optional explicit stable_ids to fetch. When omitted, fab_recall returns ALL entries plan-context surfaces (the common case after rc.37 selectable-filter removal). When provided, filters fetched bodies to this set."
288
+ ids: z3.array(z3.string()).optional().describe(
289
+ "Optional explicit stable_ids to SCOPE the returned read paths. When omitted, `paths` carries one read path per surfaced candidate. The candidate DESCRIPTION index is always returned in full for discovery \u2014 `ids` only narrows which read paths are surfaced (e.g. when you already know which entries to Read). Stale ids are redirect-rewritten before matching."
255
290
  ),
256
- // v2.2 MC1-recall-pack (W2-T4): graph expansion.
257
- include_related: z2.boolean().optional().describe(
258
- "When true, also fetch the one-hop `related` graph neighbours (of the selected entries) that are present in the candidate set. Useful with a scoped `ids` to pull connected knowledge in one call."
291
+ // W1-3 / KT-DEC-0031: graph expansion (surface related read paths, no body).
292
+ include_related: z3.boolean().optional().describe(
293
+ "When true, also surface the one-hop `related` graph neighbours (of the surfaced entries) that are present in the candidate set \u2014 their descriptions and read paths, NOT their bodies."
259
294
  )
260
295
  });
261
- var recallOutputSchema = z2.object({
262
- revision_hash: z2.string(),
263
- stale: z2.boolean(),
264
- // Selection token surfaced for callers who want to continue the conversation
265
- // with fab_get_knowledge_sections (e.g. fetch additional ids later) — every
266
- // recall response is still token-backed internally.
267
- selection_token: z2.string(),
296
+ var recallOutputSchema = z3.object({
297
+ revision_hash: z3.string(),
298
+ stale: z3.boolean(),
268
299
  // v2.0.0-rc.38 UX-1/UX-4: mirrors planContextOutputSchema fold ① — per-path
269
300
  // description_index collapsed into a single top-level `candidates`, and
270
301
  // `preflight_diagnostics` lifted out of the removed `shared` wrapper.
271
- entries: z2.array(
272
- z2.object({
273
- path: z2.string(),
302
+ entries: z3.array(
303
+ z3.object({
304
+ path: z3.string(),
274
305
  requirement_profile: _requirementProfileSchema
275
306
  })
276
307
  ),
277
- candidates: z2.array(_descriptionIndexItemSchema),
278
- // v2.2 W1-REVIEW codex MED-5: fab_recall spreads `...planResult`, so it carries
279
- // the truncation signal too. Declare it here or zod strips it on output
280
- // validation the RECOMMENDED one-step entry would silently lose the "more
281
- // candidates exist" signal that plan_context surfaces.
282
- omitted_candidate_count: z2.number().int().nonnegative().optional(),
283
- preflight_diagnostics: z2.array(_preflightDiagnosticSchema),
284
- // Same shape as knowledgeSectionsOutputSchema.rules full body keyed by stable_id.
285
- rules: z2.array(
286
- z2.object({
287
- stable_id: z2.string(),
288
- level: z2.enum(["L0", "L1", "L2"]),
289
- path: z2.string(),
290
- body: z2.string(),
291
- // lifecycle-refactor W3-T4 (§2 store 轴 / store-qualified 观测 / D7 物理 store
292
- // 边界对 AI 可见): per-rule store provenance so the caller can trace which
293
- // store each recalled entry came from. cross-store-recall already prefixes
294
- // store entries `<alias>:<stable_id>`; this surfaces that as a structured
295
- // field (`{ alias }`) instead of forcing the caller to parse the id. Omitted
296
- // for project-local entries (no alias prefix). Additive — declare it or zod
297
- // strips it on output validation.
298
- store: z2.object({ alias: z2.string() }).optional()
308
+ // v2.2 payload de-dup: single top-level echo of the caller's `intent` (was
309
+ // duplicated into every entry's requirement_profile). Omitted when no intent.
310
+ intent: z3.string().optional(),
311
+ // W1 (KT-DEC-0026): the discovery index every surfaced candidate's
312
+ // DESCRIPTION (summary / intent_clues / must_read_if / related ...). No body.
313
+ candidates: z3.array(_descriptionIndexItemSchema),
314
+ // W1 (KT-DEC-0026): the read-path index — one entry per surfaced candidate
315
+ // (scoped by `ids` when provided), in ranked order. `path` is the on-disk
316
+ // knowledge file the agent Reads to load the body on demand; `store` is the
317
+ // originating store alias (omitted for unqualified entries).
318
+ paths: z3.array(
319
+ z3.object({
320
+ stable_id: z3.string(),
321
+ path: z3.string(),
322
+ store: z3.object({ alias: z3.string() }).optional()
299
323
  })
300
324
  ),
301
- selected_stable_ids: z2.array(z2.string()),
302
- diagnostics: z2.array(
303
- z2.object({
304
- code: z2.enum(["missing_knowledge_metadata", "unresolved_selected_id"]),
305
- severity: z2.literal("warn"),
306
- stable_id: z2.string(),
307
- message: z2.string()
308
- })
309
- ),
310
- warnings: z2.array(structuredWarningSchema).optional(),
311
- auto_healed: z2.boolean().optional(),
312
- previous_revision_hash: z2.string().optional(),
313
- // v2.0.0-rc.37 NEW-24: parallel to planContextOutputSchema.redirects see
314
- // that field for semantics. fab_recall transparently rewrites any old ids
315
- // passed via `ids` before fetching bodies; the surfaced map still exposes
316
- // the substitution to callers that want to refresh their cached state.
317
- redirects: z2.record(z2.string()).optional(),
318
- // v2.2 MC1-recall-pack (W2-T4): packaging increments a standing behavioral
319
- // directive (cite-before-edit), dynamic next-step hints, and a truncation
320
- // summary, so the one-call recall is self-describing.
321
- directive: z2.string(),
322
- next_steps: z2.array(z2.string()).optional(),
323
- truncation: z2.object({
324
- omitted_candidate_count: z2.number().int().nonnegative(),
325
- returned_candidate_count: z2.number().int().nonnegative()
326
- }).optional()
325
+ // Number of lower-ranked candidates dropped by the retrieval budget. Present
326
+ // (and > 0) ONLY when truncation fired — keeps the steady-state wire shape
327
+ // unchanged while signalling "more exist; narrow your intent".
328
+ omitted_candidate_count: z3.number().int().nonnegative().optional(),
329
+ preflight_diagnostics: z3.array(_preflightDiagnosticSchema),
330
+ warnings: z3.array(structuredWarningSchema).optional(),
331
+ auto_healed: z3.boolean().optional(),
332
+ previous_revision_hash: z3.string().optional(),
333
+ // v2.0.0-rc.37 NEW-24: parallel to planContextOutputSchema.redirects — stale
334
+ // (pre layer-flip) ids in `ids` are redirect-rewritten before matching; the
335
+ // surfaced map exposes the substitution so callers refresh cached state.
336
+ redirects: z3.record(z3.string()).optional(),
337
+ // lifecycle-refactor W3-T2 (§7 图谱消费): related-expansion provenance map
338
+ // (appended id surfaced source id). Present only when include_related
339
+ // appended an in-corpus neighbour. Omitted on the steady-state path.
340
+ related_appended: z3.record(z3.string()).optional(),
341
+ // v2.2 MC1-recall-pack: standing behavioral directive (cite-before-edit) +
342
+ // dynamic discovery hints, so the one-call recall is self-describing.
343
+ directive: z3.string(),
344
+ next_steps: z3.array(z3.string()).optional()
327
345
  });
328
346
  var recallAnnotations = {
329
347
  readOnlyHint: true,
@@ -332,34 +350,34 @@ var recallAnnotations = {
332
350
  openWorldHint: false,
333
351
  title: "Recall Fabric knowledge (one-call)"
334
352
  };
335
- var archiveScanInputSchema = z2.object({
336
- range: z2.union([z2.array(z2.string()).min(1), z2.literal("all")]).optional().describe(
353
+ var archiveScanInputSchema = z3.object({
354
+ range: z3.union([z3.array(z3.string()).min(1), z3.literal("all")]).optional().describe(
337
355
  "Phase 0 scope: explicit session_id[] to constrain the scan, or the 'all' sentinel. Omitted = scan everything since the last knowledge_proposed anchor."
338
356
  ),
339
- now_ms: z2.number().int().nonnegative().optional().describe("Override for the anti-loop cooldown clock (testing). Defaults to Date.now()."),
340
- correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
341
- session_id: z2.string().optional().describe("Current client session id; recorded for cross-session debt tracking.")
357
+ now_ms: z3.number().int().nonnegative().optional().describe("Override for the anti-loop cooldown clock (testing). Defaults to Date.now()."),
358
+ correlation_id: z3.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
359
+ session_id: z3.string().optional().describe("Current client session id; recorded for cross-session debt tracking.")
342
360
  });
343
- var archiveScanOutputSchema = z2.object({
361
+ var archiveScanOutputSchema = z3.object({
344
362
  // ts of the most recent knowledge_proposed event (the lower bound), or null
345
363
  // when the workspace has never archived (scan everything).
346
- anchor_ts: z2.number().nullable(),
364
+ anchor_ts: z3.number().nullable(),
347
365
  // Distinct session_ids since the anchor that survived the outcome filter,
348
366
  // in first-seen order — ready for the Skill to load digests + stitch.
349
- session_ids: z2.array(z2.string()),
367
+ session_ids: z3.array(z3.string()),
350
368
  // Sessions dropped by the filter, with the rule that fired (audit/debug).
351
- dropped: z2.array(
352
- z2.object({
353
- session_id: z2.string(),
354
- reason: z2.enum(["user_dismissed", "cooldown", "no_new_signal"])
369
+ dropped: z3.array(
370
+ z3.object({
371
+ session_id: z3.string(),
372
+ reason: z3.enum(["user_dismissed", "cooldown", "no_new_signal"])
355
373
  })
356
374
  ),
357
375
  // max ts examined across the scan — becomes the next covered_through_ts.
358
- covered_through_ts: z2.number().nullable(),
376
+ covered_through_ts: z3.number().nullable(),
359
377
  // Idempotency keys already proposed by prior archive runs but not yet
360
378
  // reviewed (Phase 4.5 cross-session pending dedupe). Drop matching candidates.
361
- already_proposed_keys: z2.array(z2.string()),
362
- warnings: z2.array(structuredWarningSchema).optional()
379
+ already_proposed_keys: z3.array(z3.string()),
380
+ warnings: z3.array(structuredWarningSchema).optional()
363
381
  });
364
382
  var archiveScanAnnotations = {
365
383
  readOnlyHint: true,
@@ -368,7 +386,7 @@ var archiveScanAnnotations = {
368
386
  openWorldHint: false,
369
387
  title: "Scan event ledger for archive candidates (deterministic)"
370
388
  };
371
- var ProposedReasonSchema = z2.enum([
389
+ var ProposedReasonSchema = z3.enum([
372
390
  "explicit-user-mark",
373
391
  "diagnostic-then-fix",
374
392
  "decision-confirmation",
@@ -376,7 +394,7 @@ var ProposedReasonSchema = z2.enum([
376
394
  "new-dependency-or-pattern",
377
395
  "dismissal-with-reason"
378
396
  ]);
379
- var PROPOSED_REASON_DESCRIPTIONS = {
397
+ var PROPOSED_REASON_DESCRIPTIONS_ZH = {
380
398
  "explicit-user-mark": "\u7528\u6237\u663E\u5F0F\u6807\u8BB0\u9700\u5F52\u6863\uFF08always / never / \u4E0B\u6B21\u6CE8\u610F \u7B49\u89C4\u8303\u6027\u8BED\u8A00\uFF09\u3002",
381
399
  "diagnostic-then-fix": "\u8BCA\u65AD\u8FC7\u7A0B\u53D1\u73B0\u65B0\u6A21\u5F0F\u6216\u8E29\u5751\uFF0C\u4FEE\u590D\u540E\u503C\u5F97\u6C89\u6DC0\u3002",
382
400
  "decision-confirmation": "\u22652 \u5019\u9009\u65B9\u6848\u7ECF\u6743\u8861\u540E\u786E\u8BA4\u9009\u578B\uFF0C\u9700\u4FDD\u7559 rationale\u3002",
@@ -384,8 +402,20 @@ var PROPOSED_REASON_DESCRIPTIONS = {
384
402
  "new-dependency-or-pattern": "\u5F15\u5165\u65B0\u4F9D\u8D56 / \u65B0\u6A21\u5F0F / \u65B0\u547D\u540D\u7EA6\u5B9A\u3002",
385
403
  "dismissal-with-reason": "\u7528\u6237\u660E\u786E\u62D2\u7EDD\u67D0\u65B9\u6848\u5E76\u7ED9\u51FA\u539F\u56E0\uFF0C\u539F\u56E0\u5373\u53EF\u5F52\u6863\u77E5\u8BC6\u3002"
386
404
  };
387
- var _sourceSessionsField = z2.array(z2.string().min(1)).min(1);
388
- var _FabExtractKnowledgeInputBaseSchema = z2.object({
405
+ var PROPOSED_REASON_DESCRIPTIONS_EN = {
406
+ "explicit-user-mark": "User explicitly marked this for archival (normative language: always / never / next time, etc.).",
407
+ "diagnostic-then-fix": "A new pattern or pitfall surfaced during diagnosis and is worth retaining after the fix.",
408
+ "decision-confirmation": "A choice was confirmed after weighing \u22652 candidate approaches; the rationale must be preserved.",
409
+ "wrong-turn-revert": "A path was tried then reverted; the wrong turn itself is a pitfall worth recording.",
410
+ "new-dependency-or-pattern": "Introduces a new dependency / pattern / naming convention.",
411
+ "dismissal-with-reason": "The user explicitly rejected an approach and gave a reason; the reason is archivable knowledge."
412
+ };
413
+ var PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE = {
414
+ "zh-CN": PROPOSED_REASON_DESCRIPTIONS_ZH,
415
+ en: PROPOSED_REASON_DESCRIPTIONS_EN
416
+ };
417
+ var _sourceSessionsField = z3.array(z3.string().min(1)).min(1);
418
+ var _FabExtractKnowledgeInputBaseSchema = z3.object({
389
419
  // v2.0.0-rc.7 T5: array form. rc.23 dropped the legacy single-string alias.
390
420
  // v2.2 全砍 F13: REQUIRED in the base schema (was `.optional()`) so the MCP
391
421
  // tool's advertised inputSchema (registerTool reads `.shape`) matches the
@@ -394,15 +424,19 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
394
424
  source_sessions: _sourceSessionsField.describe(
395
425
  "Originating session ids (REQUIRED, non-empty array); correlates with Event Ledger records. Array form (T5+, rc.23 made it the sole accepted shape)."
396
426
  ),
397
- recent_paths: z2.array(z2.string()).describe("Workspace paths recently touched in the source session \u2014 used as scope hints"),
398
- user_messages_summary: z2.string().describe("Skill-side summary of the user's intent/messages, kept compact"),
399
- type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).describe("Knowledge type bucket (plural form, mirrors directory layout)"),
400
- slug: z2.string().describe("URL-safe short identifier proposed by the Skill; server may sanitize"),
401
- // rc.5 B1: dual pending root. When 'personal', the server writes to
402
- // ~/.fabric/knowledge/pending/<type>/; otherwise to .fabric/knowledge/pending/<type>/.
427
+ recent_paths: z3.array(z3.string()).describe("Workspace paths recently touched in the source session \u2014 used as scope hints"),
428
+ user_messages_summary: z3.string().describe("Skill-side summary of the user's intent/messages, kept compact"),
429
+ type: z3.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).describe("Knowledge type bucket (plural form, mirrors directory layout)"),
430
+ slug: z3.string().describe("URL-safe short identifier proposed by the Skill; server may sanitize"),
431
+ // Store-only cutover: layer is a compatibility audience hint. The server
432
+ // resolves the actual write target through semantic_scope/write_routes and
433
+ // writes to the selected mounted store's knowledge/pending/<type>/ tree.
403
434
  // Defaults to 'team' to preserve existing call sites (Skill bumps as needed).
404
- layer: z2.enum(["team", "personal"]).optional().describe(
405
- "Storage layer for the pending entry. 'team' writes under the workspace; 'personal' writes under the user's home. Defaults to 'team'."
435
+ layer: z3.enum(["team", "personal"]).optional().describe(
436
+ "Compatibility storage audience. 'personal' writes to the personal store; non-personal writes resolve by semantic_scope/write_routes. Defaults to 'team'."
437
+ ),
438
+ semantic_scope: z3.string().regex(SCOPE_COORDINATE_PATTERN).optional().describe(
439
+ "Logical audience/write route coordinate for this pending entry, e.g. personal, team, project:fabric-v2, org:acme:team:platform. Server validates and resolves it through write_routes."
406
440
  ),
407
441
  // v2.0.0-rc.7 T6: proposed_reason — required enum that drives `## Why
408
442
  // proposed` rendering. Skills (archive / import / review) infer the
@@ -414,7 +448,7 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
414
448
  // captures the session goal + key turning point. Future-self review reads
415
449
  // this without conversation transcript access. Min length guards against
416
450
  // empty placeholders; cap is soft (no max), Skill caps at ~600 chars.
417
- session_context: z2.string().min(20, { message: "session_context must be \u226520 chars (3-5 lines describing goal + turning point)" }).describe(
451
+ session_context: z3.string().min(20, { message: "session_context must be \u226520 chars (3-5 lines describing goal + turning point)" }).describe(
418
452
  "3-5 line markdown blob \u2014 session goal + key turning point. Reviewed by future-self without transcript access."
419
453
  ),
420
454
  // v2.0.0-rc.8 A1 (skill-contract-fix): relevance scope/paths on the
@@ -428,10 +462,10 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
428
462
  // a `knowledge_scope_degraded` event keyed by `pending:<idempotency_key>`.
429
463
  // NOTE: these fields MUST NOT be part of the idempotency hash inputs at
430
464
  // extract-knowledge.ts:78 — preserves rc.5→rc.7 collision detection.
431
- relevance_scope: z2.enum(["narrow", "broad"]).optional().describe(
465
+ relevance_scope: z3.enum(["narrow", "broad"]).optional().describe(
432
466
  "Optional relevance scope. 'narrow' restricts plan-context-hint surfacing to relevance_paths; 'broad' always surfaces. Omit to let the meta-builder default to 'broad'. Personal + narrow is silently degraded to broad + []."
433
467
  ),
434
- relevance_paths: z2.array(z2.string()).optional().describe(
468
+ relevance_paths: z3.array(z3.string()).optional().describe(
435
469
  "Optional path anchors for narrow scope. Workspace-relative globs or paths. Omit to let the meta-builder default to []. Ignored when scope is broad (server preserves the array for audit)."
436
470
  ),
437
471
  // v2.0.0-rc.23 TASK-006 (a-C1): four optional structured fields that the
@@ -448,16 +482,16 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
448
482
  // relevance_paths follow the same rule). Including them would let an LLM
449
483
  // re-roll of the same observation create a second pending file just because
450
484
  // its inferred metadata wording drifted.
451
- intent_clues: z2.array(z2.string()).optional().describe(
485
+ intent_clues: z3.array(z3.string()).optional().describe(
452
486
  "Short LLM-readable triggers describing when this rule should fire and when it should not. Each item \u226480 chars, imperative phrasing (e.g. 'when editing Cocos UI batch code', 'NOT for non-batch contexts'). Optional \u2014 omit when the skill cannot infer cleanly."
453
487
  ),
454
- tech_stack: z2.array(z2.string()).optional().describe(
488
+ tech_stack: z3.array(z3.string()).optional().describe(
455
489
  "Tech stack / languages / frameworks the rule applies to (e.g. ['typescript', 'cocos-creator', 'nodejs']). Inferred from recent_paths file extensions and manifest files. Optional \u2014 omit when the rule is stack-agnostic."
456
490
  ),
457
- impact: z2.array(z2.string()).optional().describe(
491
+ impact: z3.array(z3.string()).optional().describe(
458
492
  "Consequences of ignoring this rule, used by the LLM to weight relevance vs cost. Each item \u2264120 chars (e.g. 'O(n\xB2) re-render on every frame', 'silent data loss on collision'). Optional \u2014 omit when impact is not observable."
459
493
  ),
460
- must_read_if: z2.string().optional().describe(
494
+ must_read_if: z3.string().optional().describe(
461
495
  "One-line strong trigger; when this condition holds the entry is considered required reading. Single line \u2264160 chars (e.g. 'touching anything under packages/cli/src/commands/hooks.ts'). Optional \u2014 omit when no single strong trigger fits."
462
496
  ),
463
497
  // v2.0.0-rc.37 NEW-37 (werewolf dogfood remediation): optional tags array.
@@ -468,7 +502,7 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
468
502
  // IDEMPOTENCY: tags MUST NOT 参与 idempotency_key hash(同 relevance_*
469
503
  // / intent_clues 等可变字段一致),re-extract 时 tags 调整不应产生重复
470
504
  // pending file。
471
- tags: z2.array(z2.string()).optional().describe(
505
+ tags: z3.array(z3.string()).optional().describe(
472
506
  "Optional topic tags (2-4 kebab-case strings recommended). Drives cross-entry retrieval + topic clustering. Skill-inferred from session content; omit when not confidently inferable. Empty array allowed but discouraged (degrades narrow hint topic signal)."
473
507
  ),
474
508
  // v2.0.0-rc.23 TASK-014 (F8c): optional onboard-slot tag. The S5 slot
@@ -503,7 +537,7 @@ var _FabExtractKnowledgeInputBaseSchema = z2.object({
503
537
  // signal was captured. Like relevance_paths it MUST NOT participate in
504
538
  // the idempotency_key hash (an idempotent re-extract may surface a
505
539
  // slightly different read set without spawning a duplicate pending).
506
- evidence_paths: z2.array(z2.string()).optional().describe(
540
+ evidence_paths: z3.array(z3.string()).optional().describe(
507
541
  "Workspace-relative paths the agent CONSULTED (read but never modified) while building this knowledge. Documents context without affecting activation. Lifted from the legacy body ## Evidence markdown block into structured frontmatter so plan-context retrieval can read it as data."
508
542
  )
509
543
  });
@@ -512,7 +546,7 @@ var FabExtractKnowledgeInputSchema = _FabExtractKnowledgeInputBaseSchema.superRe
512
546
  const hasArray = Array.isArray(value.source_sessions) && value.source_sessions.length > 0;
513
547
  if (!hasArray) {
514
548
  ctx.addIssue({
515
- code: z2.ZodIssueCode.custom,
549
+ code: z3.ZodIssueCode.custom,
516
550
  message: "source_sessions (non-empty string array) is required",
517
551
  path: ["source_sessions"]
518
552
  });
@@ -520,12 +554,12 @@ var FabExtractKnowledgeInputSchema = _FabExtractKnowledgeInputBaseSchema.superRe
520
554
  }
521
555
  );
522
556
  var FabExtractKnowledgeInputShape = _FabExtractKnowledgeInputBaseSchema.shape;
523
- var FabExtractKnowledgeOutputSchema = z2.object({
524
- pending_path: z2.string().describe("Workspace-relative path to the persisted pending entry"),
525
- idempotency_key: z2.string().describe("Stable key derived from inputs; identical inputs yield identical key"),
557
+ var FabExtractKnowledgeOutputSchema = z3.object({
558
+ pending_path: z3.string().describe("Workspace-relative path to the persisted pending entry"),
559
+ idempotency_key: z3.string().describe("Stable key derived from inputs; identical inputs yield identical key"),
526
560
  // v2.0.0-rc.23 TASK-009 (d): optional warnings surface for the first-reconcile
527
561
  // gate (`meta_stale` / `reconcile_failed`). Absent on the steady-state path.
528
- warnings: z2.array(structuredWarningSchema).optional()
562
+ warnings: z3.array(structuredWarningSchema).optional()
529
563
  });
530
564
  var fabExtractKnowledgeAnnotations = {
531
565
  readOnlyHint: false,
@@ -534,21 +568,21 @@ var fabExtractKnowledgeAnnotations = {
534
568
  openWorldHint: false,
535
569
  title: "Extract pending knowledge entry"
536
570
  };
537
- var _fabReviewFiltersSchema = z2.object({
538
- type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).optional(),
539
- layer: z2.enum(["team", "personal", "both"]).optional(),
540
- maturity: z2.enum(["draft", "verified", "proven"]).optional(),
541
- tags: z2.array(z2.string()).optional(),
571
+ var _fabReviewFiltersSchema = z3.object({
572
+ type: z3.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).optional(),
573
+ layer: z3.enum(["team", "personal", "both"]).optional(),
574
+ maturity: z3.enum(["draft", "verified", "proven"]).optional(),
575
+ tags: z3.array(z3.string()).optional(),
542
576
  // rc.4 TASK-006 fix (c): ISO-8601 lower bound on entry created_at; entries
543
577
  // strictly older than this threshold are excluded from list / search
544
578
  // results. Additive optional field — existing callers unaffected.
545
- created_after: z2.string().datetime().optional(),
579
+ created_after: z3.string().datetime().optional(),
546
580
  // v2.0.0-rc.27 TASK-001 (§2.2/§2.3): opt-in surfacing of lifecycle-filtered
547
581
  // entries. Default (omit both) hides rejected entries and deferred entries
548
582
  // whose deferred_until is in the future. Pass true to include them — e.g.
549
583
  // for vacuum tooling, audit dashboards, or "show me what I parked" UX.
550
- include_rejected: z2.boolean().optional(),
551
- include_deferred: z2.boolean().optional(),
584
+ include_rejected: z3.boolean().optional(),
585
+ include_deferred: z3.boolean().optional(),
552
586
  // v2.0.0-rc.27 TASK-006 (audit §2.23): opt-in body inspection. Default
553
587
  // list/search return only frontmatter-derived fields — a malicious
554
588
  // pending entry could hide a prompt-injection payload under `## Evidence`
@@ -558,41 +592,57 @@ var _fabReviewFiltersSchema = z2.object({
558
592
  // default-off design keeps the wire payload small for routine list
559
593
  // calls; reviewer workflows pass `true` before approving so the body
560
594
  // is rendered into the reviewer's UI for visual scan.
561
- include_body: z2.boolean().optional()
595
+ include_body: z3.boolean().optional()
562
596
  }).optional();
563
- var _fabReviewModifyChangesSchema = z2.object({
564
- title: z2.string().optional(),
565
- summary: z2.string().optional(),
597
+ var _fabReviewModifyChangesSchema = z3.object({
598
+ title: z3.string().optional(),
599
+ summary: z3.string().optional(),
566
600
  // Q7: writing `layer` here triggers a layer-flip; downstream callers may
567
601
  // observe a redirect_to in fab_get_knowledge_sections if stable_id changes.
568
- layer: z2.enum(["team", "personal"]).optional(),
569
- maturity: z2.enum(["draft", "verified", "proven"]).optional(),
570
- tags: z2.array(z2.string()).optional(),
602
+ layer: z3.enum(["team", "personal"]).optional(),
603
+ maturity: z3.enum(["draft", "verified", "proven"]).optional(),
604
+ tags: z3.array(z3.string()).optional(),
571
605
  // v2.0-rc.5 C3 (TASK-012): relevance scope/paths patches. Applied to
572
606
  // pending AND canonical entries. When an explicit team→personal layer flip
573
607
  // arrives on a narrow entry, the server auto-degrades to broad + [] and
574
608
  // emits a `knowledge_scope_degraded` event regardless of what the caller
575
609
  // sent in these fields (personal-implies-broad).
576
- relevance_scope: z2.enum(["narrow", "broad"]).optional(),
577
- relevance_paths: z2.array(z2.string()).optional()
610
+ relevance_scope: z3.enum(["narrow", "broad"]).optional(),
611
+ relevance_paths: z3.array(z3.string()).optional(),
612
+ // v2.2 project-scope migration: re-scope an existing entry's resolution
613
+ // coordinate (e.g. team → project:fabric-v2) WITHOUT moving stores
614
+ // (scope ⊥ store, S42/A2). The in-place modify path keeps visibility_store
615
+ // intact, so a team→project flip just relabels who recall surfaces it to
616
+ // (G-FILTER, cross-store-recall.ts) — the entry stays physically in the
617
+ // same shared store. A personal-root coordinate is rejected here: landing
618
+ // an entry in the personal store is a store move, which is the dedicated
619
+ // modify-layer path (R5#3 privacy boundary), never an in-place scalar edit.
620
+ semantic_scope: z3.string().regex(SCOPE_COORDINATE_PATTERN).optional(),
621
+ // v2.2 graph edges (KT-DEC-0031 wiki seam): write the `related` H2 adjacency
622
+ // (bare or store-qualified stable_ids this entry points at). REPLACE semantics
623
+ // mirror tags/relevance_paths — the caller (fabric-connect) reads existing
624
+ // edges via fab_recall and sends the merged set. Absent this field the modify
625
+ // path silently dropped `related` via zod .strip() (KT-PIT-0005 recurrence),
626
+ // leaving the only programmatic related-write path non-functional.
627
+ related: z3.array(z3.string()).optional()
578
628
  });
579
- var FabReviewInputSchema = z2.discriminatedUnion("action", [
580
- z2.object({
581
- action: z2.literal("list"),
629
+ var FabReviewInputSchema = z3.discriminatedUnion("action", [
630
+ z3.object({
631
+ action: z3.literal("list"),
582
632
  filters: _fabReviewFiltersSchema
583
633
  }),
584
- z2.object({
585
- action: z2.literal("approve"),
586
- pending_paths: z2.array(z2.string()).min(1)
634
+ z3.object({
635
+ action: z3.literal("approve"),
636
+ pending_paths: z3.array(z3.string()).min(1)
587
637
  }),
588
- z2.object({
589
- action: z2.literal("reject"),
590
- pending_paths: z2.array(z2.string()).min(1),
591
- reason: z2.string().min(1)
638
+ z3.object({
639
+ action: z3.literal("reject"),
640
+ pending_paths: z3.array(z3.string()).min(1),
641
+ reason: z3.string().min(1)
592
642
  }),
593
- z2.object({
594
- action: z2.literal("modify"),
595
- pending_path: z2.string().min(1),
643
+ z3.object({
644
+ action: z3.literal("modify"),
645
+ pending_path: z3.string().min(1),
596
646
  changes: _fabReviewModifyChangesSchema
597
647
  }),
598
648
  // v2.0.0-rc.37 NEW-12: explicit modify split. `modify-content` edits scalar
@@ -601,177 +651,176 @@ var FabReviewInputSchema = z2.discriminatedUnion("action", [
601
651
  // (changes.layer REQUIRED) which may reallocate the stable_id + emit an
602
652
  // id-redirect (rc.37 NEW-24). Legacy `modify` stays for back-compat and
603
653
  // routes by whether changes.layer is present.
604
- z2.object({
605
- action: z2.literal("modify-content"),
606
- pending_path: z2.string().min(1),
654
+ z3.object({
655
+ action: z3.literal("modify-content"),
656
+ pending_path: z3.string().min(1),
607
657
  changes: _fabReviewModifyChangesSchema
608
658
  }),
609
- z2.object({
610
- action: z2.literal("modify-layer"),
611
- pending_path: z2.string().min(1),
659
+ z3.object({
660
+ action: z3.literal("modify-layer"),
661
+ pending_path: z3.string().min(1),
612
662
  changes: _fabReviewModifyChangesSchema.extend({
613
- layer: z2.enum(["team", "personal"])
663
+ layer: z3.enum(["team", "personal"])
614
664
  })
615
665
  }),
616
- z2.object({
617
- action: z2.literal("search"),
618
- query: z2.string().min(1),
666
+ z3.object({
667
+ action: z3.literal("search"),
668
+ query: z3.string().min(1),
619
669
  filters: _fabReviewFiltersSchema
620
670
  }),
621
- z2.object({
622
- action: z2.literal("defer"),
623
- pending_paths: z2.array(z2.string()).min(1),
624
- until: z2.string().datetime().optional(),
625
- reason: z2.string().optional()
671
+ z3.object({
672
+ action: z3.literal("defer"),
673
+ pending_paths: z3.array(z3.string()).min(1),
674
+ until: z3.string().datetime().optional(),
675
+ reason: z3.string().optional()
626
676
  })
627
677
  ]);
628
678
  var FabReviewInputShape = {
629
- action: z2.enum(["list", "approve", "reject", "modify", "modify-content", "modify-layer", "search", "defer"]).describe(
679
+ action: z3.enum(["list", "approve", "reject", "modify", "modify-content", "modify-layer", "search", "defer"]).describe(
630
680
  "Action selector. Discriminates the per-action fields below; required. modify-content edits scalars (no layer); modify-layer is the layer-flip path (changes.layer required); modify is the legacy combined alias."
631
681
  ),
632
682
  filters: _fabReviewFiltersSchema.describe(
633
683
  "Optional filters (type/layer/maturity/tags/created_after). Used by action=list and action=search."
634
684
  ),
635
- pending_paths: z2.array(z2.string()).min(1).optional().describe(
685
+ pending_paths: z3.array(z3.string()).min(1).optional().describe(
636
686
  "Workspace-relative pending entry paths. Required when action=approve|reject|defer (non-empty array)."
637
687
  ),
638
- pending_path: z2.string().min(1).optional().describe(
688
+ pending_path: z3.string().min(1).optional().describe(
639
689
  "Workspace-relative pending OR canonical entry path. Required when action=modify."
640
690
  ),
641
- reason: z2.string().optional().describe(
691
+ reason: z3.string().optional().describe(
642
692
  "Reason string. Required (non-empty) when action=reject; optional when action=defer."
643
693
  ),
644
694
  changes: _fabReviewModifyChangesSchema.optional().describe(
645
- "Frontmatter scalar patches (title/summary/layer/maturity/tags/relevance_*). Required when action=modify."
695
+ "Frontmatter scalar patches (title/summary/layer/maturity/tags/relevance_*/semantic_scope/related). Required when action=modify. semantic_scope re-scopes the entry's resolution coordinate in place (e.g. team \u2192 project:<id>) without moving stores; personal-root coordinates are rejected (use modify-layer)."
646
696
  ),
647
- query: z2.string().min(1).optional().describe(
697
+ query: z3.string().min(1).optional().describe(
648
698
  "Substring query against title/summary/tags/path. Required (non-empty) when action=search."
649
699
  ),
650
- until: z2.string().datetime().optional().describe(
700
+ until: z3.string().datetime().optional().describe(
651
701
  "ISO-8601 datetime upper bound for the deferral. Optional; used only when action=defer."
652
702
  )
653
703
  };
654
- var _fabReviewListItemSchema = z2.object({
655
- pending_path: z2.string(),
704
+ var _fabReviewListItemSchema = z3.object({
705
+ pending_path: z3.string(),
656
706
  // v2.0.0-rc.27 TASK-001 (§2.12): for personal-layer entries `pending_path`
657
707
  // carries the human-friendly `~/...` form (legacy contract) while
658
708
  // `pending_path_absolute` carries the os-expanded absolute path. Programmatic
659
709
  // consumers (Read tool, fs.readFile, downstream MCP servers) should prefer
660
710
  // the absolute variant — the `~` is a shell-only sigil that breaks every
661
711
  // non-shell consumer. Team entries omit this field because their
662
- // `pending_path` is already project-relative and unambiguous.
663
- pending_path_absolute: z2.string().optional(),
664
- type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
665
- layer: z2.enum(["team", "personal"]),
666
- maturity: z2.enum(["draft", "verified", "proven"]),
667
- tags: z2.array(z2.string()).optional(),
668
- title: z2.string().optional(),
669
- summary: z2.string().optional(),
670
- // rc.5 B1: dual pending root. 'team' = workspace .fabric/knowledge/pending,
671
- // 'personal' = ~/.fabric/knowledge/pending. Distinct from `layer` (frontmatter):
672
- // origin reflects where the pending file actually lives on disk; layer reflects
673
- // the declared classification that will drive the approve destination.
674
- origin: z2.enum(["team", "personal"]).optional(),
712
+ // `pending_path` is already store-resolved and unambiguous.
713
+ pending_path_absolute: z3.string().optional(),
714
+ type: z3.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
715
+ layer: z3.enum(["team", "personal"]),
716
+ maturity: z3.enum(["draft", "verified", "proven"]),
717
+ tags: z3.array(z3.string()).optional(),
718
+ title: z3.string().optional(),
719
+ summary: z3.string().optional(),
720
+ // Store-only cutover: origin reflects the resolved store audience where the
721
+ // pending file lives; layer reflects the declared classification that will
722
+ // drive approval semantics.
723
+ origin: z3.enum(["team", "personal"]).optional(),
675
724
  // v2.0.0-rc.27 TASK-001 (§2.2/§2.3): frontmatter status markers. Default
676
725
  // "active" (or absent). `rejected` entries are excluded from list/search
677
726
  // unless filters.include_rejected=true; `deferred` entries are excluded
678
727
  // when deferred_until is in the future. Authored by reject/defer write
679
728
  // paths — never by extract or approve.
680
- status: z2.enum(["active", "rejected", "deferred"]).optional(),
681
- deferred_until: z2.string().datetime().optional(),
729
+ status: z3.enum(["active", "rejected", "deferred"]).optional(),
730
+ deferred_until: z3.string().datetime().optional(),
682
731
  // v2.0.0-rc.27 TASK-006 (audit §2.23): full body content (everything
683
732
  // after the closing `---` of frontmatter). Surfaced only when caller
684
733
  // passes `filters.include_body: true`. Default-omitted to keep payload
685
734
  // small for routine list calls.
686
- body: z2.string().optional()
735
+ body: z3.string().optional()
687
736
  });
688
- var _fabReviewSearchItemSchema = z2.object({
689
- // Search hits live in one of two trees:
690
- // - "pending" → .fabric/knowledge/pending/ (or ~/.fabric/knowledge/pending/)
691
- // - "canonical" → .fabric/knowledge/{decisions,pitfalls,...} (or personal mirror)
692
- area: z2.enum(["pending", "canonical"]),
693
- path: z2.string(),
694
- path_absolute: z2.string().optional(),
695
- type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
696
- layer: z2.enum(["team", "personal"]),
697
- maturity: z2.enum(["draft", "verified", "proven"]),
698
- tags: z2.array(z2.string()).optional(),
699
- title: z2.string().optional(),
700
- summary: z2.string().optional(),
701
- origin: z2.enum(["team", "personal"]).optional(),
702
- status: z2.enum(["active", "rejected", "deferred"]).optional(),
703
- deferred_until: z2.string().datetime().optional(),
704
- body: z2.string().optional(),
737
+ var _fabReviewSearchItemSchema = z3.object({
738
+ // Search hits live in one of two store trees:
739
+ // - "pending" → mounted store `knowledge/pending/`
740
+ // - "canonical" → mounted store `knowledge/{decisions,pitfalls,...}`
741
+ area: z3.enum(["pending", "canonical"]),
742
+ path: z3.string(),
743
+ path_absolute: z3.string().optional(),
744
+ type: z3.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
745
+ layer: z3.enum(["team", "personal"]),
746
+ maturity: z3.enum(["draft", "verified", "proven"]),
747
+ tags: z3.array(z3.string()).optional(),
748
+ title: z3.string().optional(),
749
+ summary: z3.string().optional(),
750
+ origin: z3.enum(["team", "personal"]).optional(),
751
+ status: z3.enum(["active", "rejected", "deferred"]).optional(),
752
+ deferred_until: z3.string().datetime().optional(),
753
+ body: z3.string().optional(),
705
754
  // For pending hits the upstream stable_id may still be unassigned — keep it
706
755
  // optional so canonical hits (which always have one) parse alongside pending
707
756
  // hits in the same array.
708
- stable_id: z2.string().optional()
757
+ stable_id: z3.string().optional()
709
758
  });
710
- var FabReviewOutputSchema = z2.discriminatedUnion("action", [
711
- z2.object({
712
- action: z2.literal("list"),
713
- items: z2.array(_fabReviewListItemSchema),
714
- warnings: z2.array(structuredWarningSchema).optional()
759
+ var FabReviewOutputSchema = z3.discriminatedUnion("action", [
760
+ z3.object({
761
+ action: z3.literal("list"),
762
+ items: z3.array(_fabReviewListItemSchema),
763
+ warnings: z3.array(structuredWarningSchema).optional()
715
764
  }),
716
- z2.object({
717
- action: z2.literal("approve"),
718
- approved: z2.array(z2.object({ pending_path: z2.string(), stable_id: z2.string() })),
719
- warnings: z2.array(structuredWarningSchema).optional()
765
+ z3.object({
766
+ action: z3.literal("approve"),
767
+ approved: z3.array(z3.object({ pending_path: z3.string(), stable_id: z3.string() })),
768
+ warnings: z3.array(structuredWarningSchema).optional()
720
769
  }),
721
- z2.object({
722
- action: z2.literal("reject"),
723
- rejected: z2.array(z2.string()),
724
- warnings: z2.array(structuredWarningSchema).optional()
770
+ z3.object({
771
+ action: z3.literal("reject"),
772
+ rejected: z3.array(z3.string()),
773
+ warnings: z3.array(structuredWarningSchema).optional()
725
774
  }),
726
- z2.object({
727
- action: z2.literal("modify"),
728
- pending_path: z2.string(),
775
+ z3.object({
776
+ action: z3.literal("modify"),
777
+ pending_path: z3.string(),
729
778
  // When a layer-flip occurred, prior_stable_id and new_stable_id differ.
730
- prior_stable_id: z2.string().optional(),
731
- new_stable_id: z2.string().optional(),
732
- warnings: z2.array(structuredWarningSchema).optional()
779
+ prior_stable_id: z3.string().optional(),
780
+ new_stable_id: z3.string().optional(),
781
+ warnings: z3.array(structuredWarningSchema).optional()
733
782
  }),
734
- z2.object({
735
- action: z2.literal("search"),
783
+ z3.object({
784
+ action: z3.literal("search"),
736
785
  // v2.0.0-rc.29 TASK-007 (BUG-M4): search returns the new search-item
737
786
  // shape with `area` discriminator + neutrally-named `path` field.
738
- items: z2.array(_fabReviewSearchItemSchema),
739
- warnings: z2.array(structuredWarningSchema).optional()
787
+ items: z3.array(_fabReviewSearchItemSchema),
788
+ warnings: z3.array(structuredWarningSchema).optional()
740
789
  }),
741
- z2.object({
742
- action: z2.literal("defer"),
743
- deferred: z2.array(z2.string()),
744
- warnings: z2.array(structuredWarningSchema).optional()
790
+ z3.object({
791
+ action: z3.literal("defer"),
792
+ deferred: z3.array(z3.string()),
793
+ warnings: z3.array(structuredWarningSchema).optional()
745
794
  })
746
795
  ]);
747
796
  var FabReviewOutputShape = {
748
- action: z2.enum(["list", "approve", "reject", "modify", "search", "defer"]).describe(
797
+ action: z3.enum(["list", "approve", "reject", "modify", "search", "defer"]).describe(
749
798
  "Echoes the input action; clients can switch on it for per-variant fields below."
750
799
  ),
751
- items: z2.array(z2.union([_fabReviewListItemSchema, _fabReviewSearchItemSchema])).optional().describe(
800
+ items: z3.array(z3.union([_fabReviewListItemSchema, _fabReviewSearchItemSchema])).optional().describe(
752
801
  "Pending entries (action=list, `pending_path` shape) or pending+canonical entries (action=search, `area`+`path` shape)."
753
802
  ),
754
- approved: z2.array(z2.object({ pending_path: z2.string(), stable_id: z2.string() })).optional().describe(
803
+ approved: z3.array(z3.object({ pending_path: z3.string(), stable_id: z3.string() })).optional().describe(
755
804
  "Allocated stable ids paired with their original pending paths. Present when action=approve."
756
805
  ),
757
- rejected: z2.array(z2.string()).optional().describe(
806
+ rejected: z3.array(z3.string()).optional().describe(
758
807
  "Pending paths that were rejected (files retained on disk; doctor owns vacuum). Present when action=reject."
759
808
  ),
760
- pending_path: z2.string().optional().describe(
809
+ pending_path: z3.string().optional().describe(
761
810
  "Echoed target path for the modification. Present when action=modify."
762
811
  ),
763
- prior_stable_id: z2.string().optional().describe(
812
+ prior_stable_id: z3.string().optional().describe(
764
813
  "Prior stable id. Present when action=modify AND a layer-flip reallocated the id."
765
814
  ),
766
- new_stable_id: z2.string().optional().describe(
815
+ new_stable_id: z3.string().optional().describe(
767
816
  "New stable id after reallocation. Present when action=modify AND a layer-flip reallocated the id."
768
817
  ),
769
- deferred: z2.array(z2.string()).optional().describe(
818
+ deferred: z3.array(z3.string()).optional().describe(
770
819
  "Pending paths that were deferred (files retained on disk). Present when action=defer."
771
820
  ),
772
821
  // v2.0.0-rc.23 TASK-009 (d): optional warnings surface for the first-reconcile
773
822
  // gate (`meta_stale` / `reconcile_failed`). Absent on the steady-state path.
774
- warnings: z2.array(structuredWarningSchema).optional()
823
+ warnings: z3.array(structuredWarningSchema).optional()
775
824
  };
776
825
  var fabReviewAnnotations = {
777
826
  readOnlyHint: false,
@@ -780,35 +829,35 @@ var fabReviewAnnotations = {
780
829
  openWorldHint: false,
781
830
  title: "Review pending knowledge entries"
782
831
  };
783
- var citeContractMetricsSchema = z2.object({
784
- decisions_cited: z2.number().int().nonnegative(),
785
- pitfalls_cited: z2.number().int().nonnegative(),
786
- contract_with: z2.number().int().nonnegative(),
787
- contract_missing: z2.number().int().nonnegative(),
788
- hard_violated: z2.number().int().nonnegative(),
789
- cite_id_unresolved: z2.number().int().nonnegative(),
790
- skip_count: z2.record(z2.string(), z2.number().int().nonnegative())
832
+ var citeContractMetricsSchema = z3.object({
833
+ decisions_cited: z3.number().int().nonnegative(),
834
+ pitfalls_cited: z3.number().int().nonnegative(),
835
+ contract_with: z3.number().int().nonnegative(),
836
+ contract_missing: z3.number().int().nonnegative(),
837
+ hard_violated: z3.number().int().nonnegative(),
838
+ cite_id_unresolved: z3.number().int().nonnegative(),
839
+ skip_count: z3.record(z3.string(), z3.number().int().nonnegative())
791
840
  });
792
- var citeLayerTypeBreakdownSchema = z2.object({
793
- team: z2.record(z2.string(), z2.number().int().nonnegative()),
794
- personal: z2.record(z2.string(), z2.number().int().nonnegative())
841
+ var citeLayerTypeBreakdownSchema = z3.object({
842
+ team: z3.record(z3.string(), z3.number().int().nonnegative()),
843
+ personal: z3.record(z3.string(), z3.number().int().nonnegative())
795
844
  });
796
- var citeCoverageReportSchema = z2.object({
797
- status: z2.enum(["ok", "skipped"]),
798
- marker_ts: z2.number().int().nonnegative(),
799
- marker_emitted_now: z2.boolean(),
800
- since_ts: z2.number().int().nonnegative(),
801
- client_filter: z2.enum(["cc", "codex", "cursor", "all"]),
845
+ var citeCoverageReportSchema = z3.object({
846
+ status: z3.enum(["ok", "skipped"]),
847
+ marker_ts: z3.number().int().nonnegative(),
848
+ marker_emitted_now: z3.boolean(),
849
+ since_ts: z3.number().int().nonnegative(),
850
+ client_filter: z3.enum(["cc", "codex", "all"]),
802
851
  // v2.0.0-rc.24 TASK-08: layer filter discriminator. Optional so pre-TASK-10
803
852
  // CLI callers (which never set the flag) still parse. Defaults to "all" at
804
853
  // the service layer.
805
- layer_filter: z2.enum(["team", "personal", "all"]).optional(),
806
- metrics: z2.object({
807
- edits_touched: z2.number().int().nonnegative(),
808
- qualifying_cites: z2.number().int().nonnegative(),
809
- recalled_unverified: z2.number().int().nonnegative(),
810
- expected_but_missed: z2.number().int().nonnegative(),
811
- total_turns: z2.number().int().nonnegative(),
854
+ layer_filter: z3.enum(["team", "personal", "all"]).optional(),
855
+ metrics: z3.object({
856
+ edits_touched: z3.number().int().nonnegative(),
857
+ qualifying_cites: z3.number().int().nonnegative(),
858
+ recalled_unverified: z3.number().int().nonnegative(),
859
+ expected_but_missed: z3.number().int().nonnegative(),
860
+ total_turns: z3.number().int().nonnegative(),
812
861
  // v2.0.0-rc.38 UX-8 (C, user-authorized): cite-policy COMPLIANCE rate —
813
862
  // the corrected G-CITE semantic. The legacy qualifying_cites/edits ratio
814
863
  // measured "how often an applicable KB id existed" (a function of corpus
@@ -817,14 +866,14 @@ var citeCoverageReportSchema = z2.object({
817
866
  // `KB: none [reason]` (the policy explicitly allows the none sentinel) —
818
867
  // over the turns where a cite was expected. null when no cite-expected
819
868
  // turns observed (avoids a misleading 0/0 → 0). Range [0,1].
820
- cite_compliance_rate: z2.number().min(0).max(1).nullable().optional(),
821
- compliant_cites: z2.number().int().nonnegative().optional(),
822
- noncompliant_cites: z2.number().int().nonnegative().optional(),
869
+ cite_compliance_rate: z3.number().min(0).max(1).nullable().optional(),
870
+ compliant_cites: z3.number().int().nonnegative().optional(),
871
+ noncompliant_cites: z3.number().int().nonnegative().optional(),
823
872
  // Edit signals lacking session_id → uncorrelatable, silently excluded from
824
873
  // expected_but_missed. >0 typically means a stale pre-session_id hook is
825
874
  // installed (run `fabric install`). Surfaced so the denominator gap is
826
875
  // visible rather than a silent 100% confound.
827
- uncorrelatable_edits: z2.number().int().nonnegative().optional(),
876
+ uncorrelatable_edits: z3.number().int().nonnegative().optional(),
828
877
  // v2.1 ⑤ cite-redesign (P5): recall-based coverage口径. The redesign infers
829
878
  // a citation from real behavior — an in-session fab_recall
830
879
  // (knowledge_context_planned) whose target_paths overlap a subsequently
@@ -833,8 +882,8 @@ var citeCoverageReportSchema = z2.object({
833
882
  // window) by such an overlapping recall. recall_coverage_rate =
834
883
  // recall_backed_edits / edits_touched (null when no edits). Additive — the
835
884
  // legacy first-line-`KB:` metrics above are unchanged (back-compat).
836
- recall_backed_edits: z2.number().int().nonnegative().optional(),
837
- recall_coverage_rate: z2.number().min(0).max(1).nullable().optional(),
885
+ recall_backed_edits: z3.number().int().nonnegative().optional(),
886
+ recall_coverage_rate: z3.number().min(0).max(1).nullable().optional(),
838
887
  // v2.2.0-rc.1 W1-T3 (cite 诚实拆分 / lifecycle §3): exposed_and_mutated is a
839
888
  // WEAK auxiliary signal — strictly SEPARATE from cite_compliance_rate (which
840
889
  // is the true explicit-adherence rate, currently ~2.5%). It MUST NOT be
@@ -851,9 +900,9 @@ var citeCoverageReportSchema = z2.object({
851
900
  // distinct (session_id, stable_id) pairs satisfying all three; `ids` =
852
901
  // sorted distinct stable_ids (capped, diagnostics only). Always >= 0; null/
853
902
  // absent on degraded/skipped reports.
854
- exposed_and_mutated: z2.object({
855
- count: z2.number().int().nonnegative(),
856
- ids: z2.array(z2.string()).optional()
903
+ exposed_and_mutated: z3.object({
904
+ count: z3.number().int().nonnegative(),
905
+ ids: z3.array(z3.string()).optional()
857
906
  }).optional(),
858
907
  // lifecycle-refactor W2-T4 (§5 row7 PostToolUse / §0 下沉 doctor): mutation
859
908
  // funnel rebuilt offline from the new `file_mutated` PostToolUse marker —
@@ -864,8 +913,8 @@ var citeCoverageReportSchema = z2.object({
864
913
  // PostToolUse parallel-fire race). Strictly ADDITIVE — never folded into
865
914
  // cite_compliance_rate (honesty 铁律, mirrors exposed_and_mutated). Absent on
866
915
  // degraded/skipped reports.
867
- mutations_observed: z2.object({
868
- count: z2.number().int().nonnegative()
916
+ mutations_observed: z3.object({
917
+ count: z3.number().int().nonnegative()
869
918
  }).optional(),
870
919
  // lifecycle-refactor W2-T4 (§5 row7 mutation_pool + downgrade): low-confidence
871
920
  // mutation attribution pool. A `file_mutated` event is `attributed` ONLY when
@@ -878,41 +927,41 @@ var citeCoverageReportSchema = z2.object({
878
927
  // (升 fallback via session shell event + baseline) is a SPECULATIVE
879
928
  // implementation note — deliberately NOT run here (doctor stays read-only,
880
929
  // no git diff / no disk write). Additive; absent on degraded/skipped reports.
881
- mutation_pool: z2.object({
882
- attributed: z2.number().int().nonnegative(),
883
- unattributed_workspace_dirty: z2.number().int().nonnegative()
930
+ mutation_pool: z3.object({
931
+ attributed: z3.number().int().nonnegative(),
932
+ unattributed_workspace_dirty: z3.number().int().nonnegative()
884
933
  }).optional(),
885
934
  // lifecycle-refactor W2-T4 (§5 row2 SessionEnd funnel 对账下沉 doctor): the
886
935
  // SessionEnd hook only O(1)-appends a `session_ended` marker; this counts the
887
936
  // distinct sessions that emitted one (funnel "closed" boundary). Purely an
888
937
  // observability marker — not joined into any rate. Additive.
889
- sessions_closed: z2.object({
890
- count: z2.number().int().nonnegative()
938
+ sessions_closed: z3.object({
939
+ count: z3.number().int().nonnegative()
891
940
  }).optional()
892
941
  }),
893
- per_client: z2.record(
894
- z2.string(),
895
- z2.object({
896
- edits_touched: z2.number().int().nonnegative().optional(),
897
- qualifying_cites: z2.number().int().nonnegative().optional(),
898
- recalled_unverified: z2.number().int().nonnegative().optional(),
899
- expected_but_missed: z2.number().int().nonnegative().optional(),
900
- total_turns: z2.number().int().nonnegative().optional()
942
+ per_client: z3.record(
943
+ z3.string(),
944
+ z3.object({
945
+ edits_touched: z3.number().int().nonnegative().optional(),
946
+ qualifying_cites: z3.number().int().nonnegative().optional(),
947
+ recalled_unverified: z3.number().int().nonnegative().optional(),
948
+ expected_but_missed: z3.number().int().nonnegative().optional(),
949
+ total_turns: z3.number().int().nonnegative().optional()
901
950
  })
902
951
  ).optional(),
903
- dismissed_reason_histogram: z2.record(z2.string(), z2.number().int().nonnegative()).optional(),
904
- none_reason_histogram: z2.record(z2.string(), z2.number().int().nonnegative()).optional(),
952
+ dismissed_reason_histogram: z3.record(z3.string(), z3.number().int().nonnegative()).optional(),
953
+ none_reason_histogram: z3.record(z3.string(), z3.number().int().nonnegative()).optional(),
905
954
  // v2.0.0-rc.24 TASK-08: contract-policy audit metrics. Status discriminates
906
955
  // populated vs degraded modes. contract_metrics + per_layer_type are emitted
907
956
  // (zeroed) in degraded modes so the renderer iterates one stable shape.
908
- contract_metrics_status: z2.enum(["ok", "skipped:bootstrap_drift", "awaiting_marker"]).optional(),
957
+ contract_metrics_status: z3.enum(["ok", "skipped:bootstrap_drift", "awaiting_marker"]).optional(),
909
958
  contract_metrics: citeContractMetricsSchema.optional(),
910
959
  per_layer_type: citeLayerTypeBreakdownSchema.optional(),
911
- contract_marker_ts: z2.number().int().nonnegative().optional(),
912
- generated_at: z2.string()
960
+ contract_marker_ts: z3.number().int().nonnegative().optional(),
961
+ generated_at: z3.string()
913
962
  });
914
- var ledgerSourceSchema = z2.enum(["ai", "human"]);
915
- var timestampFilterSchema = z2.preprocess((value) => {
963
+ var ledgerSourceSchema = z3.enum(["ai", "human"]);
964
+ var timestampFilterSchema = z3.preprocess((value) => {
916
965
  if (value === void 0 || value === null || value === "") {
917
966
  return void 0;
918
967
  }
@@ -931,38 +980,38 @@ var timestampFilterSchema = z2.preprocess((value) => {
931
980
  return Number.isNaN(parsed) ? value : parsed;
932
981
  }
933
982
  return value;
934
- }, z2.number().int().nonnegative());
935
- var ledgerQuerySchema = z2.object({
983
+ }, z3.number().int().nonnegative());
984
+ var ledgerQuerySchema = z3.object({
936
985
  source: ledgerSourceSchema.optional(),
937
986
  since: timestampFilterSchema.optional()
938
987
  });
939
- var historyStateQuerySchema = z2.object({
940
- ledger_id: z2.string().trim().min(1).optional(),
988
+ var historyStateQuerySchema = z3.object({
989
+ ledger_id: z3.string().trim().min(1).optional(),
941
990
  ts: timestampFilterSchema.optional()
942
991
  }).superRefine((value, ctx) => {
943
992
  const provided = [value.ledger_id, value.ts].filter((entry) => entry !== void 0);
944
993
  if (provided.length !== 1) {
945
994
  ctx.addIssue({
946
- code: z2.ZodIssueCode.custom,
995
+ code: z3.ZodIssueCode.custom,
947
996
  message: "Provide exactly one of ledger_id or ts.",
948
997
  path: ["ledger_id"]
949
998
  });
950
999
  }
951
1000
  });
952
- var humanLockApproveRequestSchema = z2.object({
953
- file: z2.string().min(1),
954
- start_line: z2.number().int().positive(),
955
- end_line: z2.number().int().positive(),
956
- new_hash: z2.string().min(1)
1001
+ var humanLockApproveRequestSchema = z3.object({
1002
+ file: z3.string().min(1),
1003
+ start_line: z3.number().int().positive(),
1004
+ end_line: z3.number().int().positive(),
1005
+ new_hash: z3.string().min(1)
957
1006
  });
958
- var humanLockFileParamsSchema = z2.object({
959
- file: z2.string().min(1)
1007
+ var humanLockFileParamsSchema = z3.object({
1008
+ file: z3.string().min(1)
960
1009
  });
961
- var annotateIntentRequestSchema = z2.object({
962
- ledger_entry_id: z2.string().min(1),
963
- annotation: z2.string().trim().min(1)
1010
+ var annotateIntentRequestSchema = z3.object({
1011
+ ledger_entry_id: z3.string().min(1),
1012
+ annotation: z3.string().trim().min(1)
964
1013
  });
965
- var KnowledgeTypeSchema = z2.enum([
1014
+ var KnowledgeTypeSchema = z3.enum([
966
1015
  "models",
967
1016
  // entities, data structures, relationships
968
1017
  "decisions",
@@ -974,10 +1023,10 @@ var KnowledgeTypeSchema = z2.enum([
974
1023
  "processes"
975
1024
  // workflows, state machines, operational steps
976
1025
  ]);
977
- var MaturitySchema = z2.enum(["draft", "verified", "proven"]);
978
- var LayerSchema = z2.enum(["personal", "team"]);
979
- var StableIdSchema = z2.string().regex(/^K[PT]-(MOD|DEC|GLD|PIT|PRO)-\d{4,}$/);
980
- var KnowledgeEntryFrontmatterSchema = z2.object({
1026
+ var MaturitySchema = z3.enum(["draft", "verified", "proven"]);
1027
+ var LayerSchema = z3.enum(["personal", "team"]);
1028
+ var StableIdSchema = z3.string().regex(/^K[PT]-(MOD|DEC|GLD|PIT|PRO)-\d{4,}$/);
1029
+ var KnowledgeEntryFrontmatterSchema = z3.object({
981
1030
  id: StableIdSchema,
982
1031
  // e.g., "KT-DEC-0042"
983
1032
  type: KnowledgeTypeSchema,
@@ -986,9 +1035,9 @@ var KnowledgeEntryFrontmatterSchema = z2.object({
986
1035
  // draft | verified | proven
987
1036
  layer: LayerSchema,
988
1037
  // personal | team
989
- layer_reason: z2.string().optional(),
1038
+ layer_reason: z3.string().optional(),
990
1039
  // why this layer (for ambiguous cases)
991
- created_at: z2.string()
1040
+ created_at: z3.string()
992
1041
  // ISO 8601 timestamp
993
1042
  // Note: 'tags' and other fields can be added later but core schema is these 6
994
1043
  });
@@ -1019,6 +1068,13 @@ export {
1019
1068
  ONBOARD_SLOT_NAMES,
1020
1069
  onboardSlotSchema,
1021
1070
  ONBOARD_SLOT_TOTAL,
1071
+ PERSONAL_SCOPE,
1072
+ KNOWN_SCOPE_PREFIXES,
1073
+ SCOPE_COORDINATE_PATTERN,
1074
+ scopeCoordinateSchema,
1075
+ scopeRoot,
1076
+ isPersonalScope,
1077
+ entryScopeMetadataSchema,
1022
1078
  structuredWarningSchema,
1023
1079
  planContextInputSchema,
1024
1080
  planContextOutputSchema,
@@ -1035,7 +1091,7 @@ export {
1035
1091
  archiveScanOutputSchema,
1036
1092
  archiveScanAnnotations,
1037
1093
  ProposedReasonSchema,
1038
- PROPOSED_REASON_DESCRIPTIONS,
1094
+ PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE,
1039
1095
  FabExtractKnowledgeInputSchema,
1040
1096
  FabExtractKnowledgeInputShape,
1041
1097
  FabExtractKnowledgeOutputSchema,