@fenglimg/fabric-shared 2.0.0 → 2.1.0-rc.2
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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/chunk-MDWTGOAY.js +101 -0
- package/dist/chunk-R2J7DAED.js +2043 -0
- package/dist/chunk-WVPDH4BF.js +952 -0
- package/dist/i18n/index.d.ts +22 -2
- package/dist/i18n/index.js +3 -1
- package/dist/index-GQpaWTm-.d.ts +328 -0
- package/dist/index.d.ts +4922 -1998
- package/dist/index.js +2114 -372
- package/dist/node/atomic-write.d.ts +16 -0
- package/dist/node/atomic-write.js +19 -10
- package/dist/node/mcp-payload-guard.d.ts +3 -1
- package/dist/node/mcp-payload-guard.js +6 -2
- package/dist/schemas/api-contracts.d.ts +1562 -815
- package/dist/schemas/api-contracts.js +23 -7
- package/dist/templates/bootstrap-canonical.d.ts +56 -0
- package/dist/templates/bootstrap-canonical.js +18 -0
- package/dist/types/index.d.ts +2 -123
- package/package.json +32 -4
- package/dist/chunk-U2SR2M4L.js +0 -958
- package/dist/chunk-VQDCDCJA.js +0 -555
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
// src/schemas/api-contracts.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/onboard-slots.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var ONBOARD_SLOT_NAMES = [
|
|
7
|
+
"tech-stack-decision",
|
|
8
|
+
"architecture-pattern",
|
|
9
|
+
"code-style-tone",
|
|
10
|
+
"build-system-idiom",
|
|
11
|
+
"domain-vocabulary"
|
|
12
|
+
];
|
|
13
|
+
var onboardSlotSchema = z.enum(ONBOARD_SLOT_NAMES);
|
|
14
|
+
var ONBOARD_SLOT_TOTAL = ONBOARD_SLOT_NAMES.length;
|
|
15
|
+
|
|
16
|
+
// 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()
|
|
22
|
+
});
|
|
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(),
|
|
33
|
+
// v2.0: optional knowledge-entry fields. Absent for v1.x rules; present for
|
|
34
|
+
// entries that declare frontmatter `id/type/maturity/layer`.
|
|
35
|
+
id: z2.string().optional(),
|
|
36
|
+
knowledge_type: _knowledgeTypeEnum.optional(),
|
|
37
|
+
maturity: _maturityEnum.optional(),
|
|
38
|
+
knowledge_layer: _layerEnum.optional(),
|
|
39
|
+
layer_reason: z2.string().optional(),
|
|
40
|
+
created_at: z2.string().optional(),
|
|
41
|
+
// v2.0.0-rc.38 UX-3 (D-MCP fold ③): these three were previously carried ONLY
|
|
42
|
+
// as top-level mirrors on the index item. With the mirrors removed,
|
|
43
|
+
// `description` becomes their canonical (and only) home, so the schema must
|
|
44
|
+
// validate them here. Optional + default-safe (tags/[]/broad) so legacy
|
|
45
|
+
// 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()
|
|
49
|
+
});
|
|
50
|
+
var _descriptionIndexItemSchema = z2.object({
|
|
51
|
+
stable_id: z2.string(),
|
|
52
|
+
description: _ruleDescriptionSchema
|
|
53
|
+
});
|
|
54
|
+
var _requirementProfileSchema = z2.object({
|
|
55
|
+
target_path: z2.string(),
|
|
56
|
+
known_tech: z2.array(z2.string()),
|
|
57
|
+
user_intent: z2.string(),
|
|
58
|
+
detected_entities: z2.array(z2.string())
|
|
59
|
+
});
|
|
60
|
+
var planContextInputSchema = z2.object({
|
|
61
|
+
paths: z2.array(z2.string()).min(1).describe("Candidate file paths to build neutral rule selection context for"),
|
|
62
|
+
intent: z2.string().optional().describe("User-stated requirement or implementation intent; used only to build a neutral requirement profile"),
|
|
63
|
+
known_tech: z2.array(z2.string()).optional().describe("Known technologies involved in the requirement profile"),
|
|
64
|
+
detected_entities: z2.record(z2.array(z2.string())).optional().describe("Optional path-keyed detected entities for the requirement profile"),
|
|
65
|
+
client_hash: z2.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection"),
|
|
66
|
+
correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
|
|
67
|
+
session_id: z2.string().optional().describe(
|
|
68
|
+
"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."
|
|
69
|
+
),
|
|
70
|
+
// v2.0-rc.5 A3 (TASK-007): `include_deprecated` removed — it was a no-op
|
|
71
|
+
// placeholder (MaturitySchema has no `deprecated` value). When the maturity
|
|
72
|
+
// enum widens we re-introduce the flag as part of that protocol bump.
|
|
73
|
+
// v2/rc.2 (Q6): client-supplied layer scope. When omitted, the server
|
|
74
|
+
// falls back to fabric-config.default_layer_filter (TASK-002) so a single
|
|
75
|
+
// workspace policy controls the default. Explicit values override.
|
|
76
|
+
layer_filter: z2.enum(["team", "personal", "both"]).optional().describe(
|
|
77
|
+
"Restrict description_index to the named layer. Default: fabric-config.default_layer_filter (TASK-002)."
|
|
78
|
+
),
|
|
79
|
+
// v2.0-rc.5 C3 (TASK-012): explicit path context for `narrow` relevance
|
|
80
|
+
// filtering. When omitted, the server falls back to `paths` so existing
|
|
81
|
+
// callers see narrowing against the requested paths. When the resolved
|
|
82
|
+
// list is empty, the narrow filter fails open (every narrow entry passes).
|
|
83
|
+
target_paths: z2.array(z2.string()).optional().describe(
|
|
84
|
+
"Path context for narrow-scope relevance filtering. Defaults to `paths`; empty = no filter."
|
|
85
|
+
)
|
|
86
|
+
});
|
|
87
|
+
var _preflightDiagnosticSchema = z2.object({
|
|
88
|
+
// v2.0.0-rc.38 UX-2: `empty_shell_suppressed` surfaces draft entries whose
|
|
89
|
+
// description carries no selection signal (summary === stable_id + empty
|
|
90
|
+
// intent_clues/tech_stack/impact). They are filtered out of `candidates` to
|
|
91
|
+
// cut noise; this diagnostic names them so `fabric doctor` /
|
|
92
|
+
// --enrich-descriptions can prompt enrichment.
|
|
93
|
+
code: z2.enum(["missing_description", "empty_shell_suppressed"]),
|
|
94
|
+
severity: z2.literal("warn"),
|
|
95
|
+
message: z2.string(),
|
|
96
|
+
stable_ids: z2.array(z2.string()).optional(),
|
|
97
|
+
path: z2.string().optional()
|
|
98
|
+
});
|
|
99
|
+
var planContextOutputSchema = z2.object({
|
|
100
|
+
revision_hash: z2.string(),
|
|
101
|
+
stale: z2.boolean(),
|
|
102
|
+
selection_token: z2.string(),
|
|
103
|
+
entries: z2.array(
|
|
104
|
+
z2.object({
|
|
105
|
+
path: z2.string(),
|
|
106
|
+
requirement_profile: _requirementProfileSchema
|
|
107
|
+
})
|
|
108
|
+
),
|
|
109
|
+
candidates: z2.array(_descriptionIndexItemSchema),
|
|
110
|
+
preflight_diagnostics: z2.array(_preflightDiagnosticSchema),
|
|
111
|
+
warnings: z2.array(structuredWarningSchema).optional(),
|
|
112
|
+
// v2.0.0-rc.22 Scope D T-D2: optional auto-heal banner fields. Surfaced
|
|
113
|
+
// ONLY when the loadActiveMetaOrStale call detected drift and rebuilt the
|
|
114
|
+
// meta in-place. Downstream CLI / hint renderers use this pair to render a
|
|
115
|
+
// "knowledge meta auto-healed (was <prev>, now <curr>)" notice without
|
|
116
|
+
// having to query the event ledger.
|
|
117
|
+
auto_healed: z2.boolean().optional(),
|
|
118
|
+
previous_revision_hash: z2.string().optional(),
|
|
119
|
+
// v2.0.0-rc.37 NEW-24: stale-id redirect map. Populated when one or more
|
|
120
|
+
// recent fab_review modify-layer flips reassigned a canonical stable_id
|
|
121
|
+
// and the NEW id is in this response's description_index. Callers that
|
|
122
|
+
// cached the OLD id from a prior session look it up here and substitute
|
|
123
|
+
// the new id before issuing fab_get_knowledge_sections / fab_recall. Empty
|
|
124
|
+
// (field omitted) when no actionable redirects exist for the surfaced
|
|
125
|
+
// candidate set. See packages/server/src/services/id-redirect.ts.
|
|
126
|
+
redirects: z2.record(z2.string()).optional()
|
|
127
|
+
});
|
|
128
|
+
var planContextAnnotations = {
|
|
129
|
+
readOnlyHint: true,
|
|
130
|
+
idempotentHint: true,
|
|
131
|
+
destructiveHint: false,
|
|
132
|
+
openWorldHint: false,
|
|
133
|
+
title: "Plan rule context"
|
|
134
|
+
};
|
|
135
|
+
var planContextHintNarrowEntrySchema = z2.object({
|
|
136
|
+
id: z2.string(),
|
|
137
|
+
type: z2.string(),
|
|
138
|
+
maturity: z2.string(),
|
|
139
|
+
summary: z2.string()
|
|
140
|
+
});
|
|
141
|
+
var planContextHintOutputSchema = z2.object({
|
|
142
|
+
version: z2.literal(1),
|
|
143
|
+
revision_hash: z2.string(),
|
|
144
|
+
target_paths: z2.array(z2.string()),
|
|
145
|
+
narrow: z2.array(planContextHintNarrowEntrySchema),
|
|
146
|
+
broad_count: z2.number().int().nonnegative()
|
|
147
|
+
});
|
|
148
|
+
var knowledgeSectionsInputSchema = z2.object({
|
|
149
|
+
selection_token: z2.string().min(1).describe("Selection token returned by fab_plan_context"),
|
|
150
|
+
ai_selected_stable_ids: z2.array(z2.string()).describe(
|
|
151
|
+
"Stable ids picked from fab_plan_context candidates[].stable_id; choose 1..N to fetch bodies for"
|
|
152
|
+
),
|
|
153
|
+
ai_selection_reasons: z2.record(z2.string().min(1)).describe("Reason for each AI-selected L1 stable_id"),
|
|
154
|
+
correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
|
|
155
|
+
session_id: z2.string().optional().describe("Optional caller-provided session id for Event Ledger records"),
|
|
156
|
+
// v2.0 rc.5 TASK-014 (C5): optional client identity hash propagated into
|
|
157
|
+
// knowledge_consumed events. Falls back to empty string when unset — full
|
|
158
|
+
// client-identity propagation deferred to rc.6.
|
|
159
|
+
client_hash: z2.string().optional().describe("Optional caller-provided client hash propagated into knowledge_consumed events")
|
|
160
|
+
});
|
|
161
|
+
var knowledgeSectionsOutputSchema = z2.object({
|
|
162
|
+
revision_hash: z2.string(),
|
|
163
|
+
// v2.0.0-rc.38 UX-13 (D-MCP step-2 audit): the deprecated `precedence`
|
|
164
|
+
// L2/L1/L0 tuple (flagged "removed in rc.24" but still emitted) is gone — it
|
|
165
|
+
// was a constant 3-string field on every response read by no production
|
|
166
|
+
// consumer. Use rules[].level for ordering.
|
|
167
|
+
selected_stable_ids: z2.array(z2.string()),
|
|
168
|
+
rules: z2.array(
|
|
169
|
+
z2.object({
|
|
170
|
+
stable_id: z2.string(),
|
|
171
|
+
level: z2.enum(["L0", "L1", "L2"]),
|
|
172
|
+
path: z2.string(),
|
|
173
|
+
// v2.0.0-rc.23 TASK-013 (F8b): replaced the legacy
|
|
174
|
+
// `sections: Record<string,string>` (keyed by the 4-element A-set enum)
|
|
175
|
+
// with the full markdown body (frontmatter stripped). Callers scan the
|
|
176
|
+
// body for whichever B-set heading they need (Summary / Why proposed /
|
|
177
|
+
// Session context / Evidence) — section-name discipline is now a writer
|
|
178
|
+
// convention, not an API contract.
|
|
179
|
+
body: z2.string()
|
|
180
|
+
})
|
|
181
|
+
),
|
|
182
|
+
diagnostics: z2.array(
|
|
183
|
+
// v2.0.0-rc.23 TASK-013 (F8b): `missing_section` was removed alongside the
|
|
184
|
+
// A-set enum. `missing_knowledge_metadata` stays as the warn-level signal
|
|
185
|
+
// for un-migrated v1.x entries (no knowledge_type AND no knowledge_layer
|
|
186
|
+
// in frontmatter). Does NOT block selection.
|
|
187
|
+
z2.object({
|
|
188
|
+
code: z2.literal("missing_knowledge_metadata"),
|
|
189
|
+
severity: z2.literal("warn"),
|
|
190
|
+
stable_id: z2.string(),
|
|
191
|
+
message: z2.string()
|
|
192
|
+
})
|
|
193
|
+
),
|
|
194
|
+
// v2/rc.3 (Q6) + v2.0.0-rc.37 NEW-24: present iff at least one stable_id in
|
|
195
|
+
// the caller-supplied ai_selected_stable_ids was rewritten by the layer-flip
|
|
196
|
+
// redirect resolver. Pre-rc.37: this was a single { stable_id } object set
|
|
197
|
+
// only on the rare token-mint-vs-flip race. rc.37+: also accepts a map of
|
|
198
|
+
// (old_id → new_id) when multiple rewrites fire in one fetch. Both shapes
|
|
199
|
+
// are accepted for forward-compat; readers should branch on shape and
|
|
200
|
+
// refresh their cached ids accordingly.
|
|
201
|
+
redirect_to: z2.union([
|
|
202
|
+
z2.object({ stable_id: z2.string() }),
|
|
203
|
+
z2.record(z2.string())
|
|
204
|
+
]).optional().describe(
|
|
205
|
+
"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."
|
|
206
|
+
),
|
|
207
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
208
|
+
});
|
|
209
|
+
var knowledgeSectionsAnnotations = {
|
|
210
|
+
readOnlyHint: true,
|
|
211
|
+
idempotentHint: true,
|
|
212
|
+
destructiveHint: false,
|
|
213
|
+
openWorldHint: false,
|
|
214
|
+
title: "Filter rule sections"
|
|
215
|
+
};
|
|
216
|
+
var recallInputSchema = z2.object({
|
|
217
|
+
paths: z2.array(z2.string()).min(1).describe(
|
|
218
|
+
"Candidate file paths to recall Fabric rules for. Same semantics as fab_plan_context.paths."
|
|
219
|
+
),
|
|
220
|
+
intent: z2.string().optional().describe("User-stated requirement or implementation intent; used to build a neutral requirement profile."),
|
|
221
|
+
known_tech: z2.array(z2.string()).optional().describe("Known technologies involved."),
|
|
222
|
+
detected_entities: z2.record(z2.array(z2.string())).optional().describe("Optional path-keyed detected entities."),
|
|
223
|
+
client_hash: z2.string().optional().describe("Revision hash from a prior call; enables stale detection."),
|
|
224
|
+
correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
|
|
225
|
+
session_id: z2.string().optional().describe(
|
|
226
|
+
"Current client session id (Claude Code: $session_id; Codex: corresponding identifier). Enables cross-session debt tracking. Falls back gracefully if omitted."
|
|
227
|
+
),
|
|
228
|
+
layer_filter: z2.enum(["team", "personal", "both"]).optional().describe(
|
|
229
|
+
"Restrict recall to the named layer. Default: fabric-config.default_layer_filter."
|
|
230
|
+
),
|
|
231
|
+
target_paths: z2.array(z2.string()).optional().describe(
|
|
232
|
+
"Path context for narrow-scope relevance filtering. Defaults to `paths`; empty = no filter."
|
|
233
|
+
),
|
|
234
|
+
ids: z2.array(z2.string()).optional().describe(
|
|
235
|
+
"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."
|
|
236
|
+
)
|
|
237
|
+
});
|
|
238
|
+
var recallOutputSchema = z2.object({
|
|
239
|
+
revision_hash: z2.string(),
|
|
240
|
+
stale: z2.boolean(),
|
|
241
|
+
// Selection token surfaced for callers who want to continue the conversation
|
|
242
|
+
// with fab_get_knowledge_sections (e.g. fetch additional ids later) — every
|
|
243
|
+
// recall response is still token-backed internally.
|
|
244
|
+
selection_token: z2.string(),
|
|
245
|
+
// v2.0.0-rc.38 UX-1/UX-4: mirrors planContextOutputSchema fold ① — per-path
|
|
246
|
+
// description_index collapsed into a single top-level `candidates`, and
|
|
247
|
+
// `preflight_diagnostics` lifted out of the removed `shared` wrapper.
|
|
248
|
+
entries: z2.array(
|
|
249
|
+
z2.object({
|
|
250
|
+
path: z2.string(),
|
|
251
|
+
requirement_profile: _requirementProfileSchema
|
|
252
|
+
})
|
|
253
|
+
),
|
|
254
|
+
candidates: z2.array(_descriptionIndexItemSchema),
|
|
255
|
+
preflight_diagnostics: z2.array(_preflightDiagnosticSchema),
|
|
256
|
+
// Same shape as knowledgeSectionsOutputSchema.rules — full body keyed by stable_id.
|
|
257
|
+
rules: z2.array(
|
|
258
|
+
z2.object({
|
|
259
|
+
stable_id: z2.string(),
|
|
260
|
+
level: z2.enum(["L0", "L1", "L2"]),
|
|
261
|
+
path: z2.string(),
|
|
262
|
+
body: z2.string()
|
|
263
|
+
})
|
|
264
|
+
),
|
|
265
|
+
selected_stable_ids: z2.array(z2.string()),
|
|
266
|
+
diagnostics: z2.array(
|
|
267
|
+
z2.object({
|
|
268
|
+
code: z2.literal("missing_knowledge_metadata"),
|
|
269
|
+
severity: z2.literal("warn"),
|
|
270
|
+
stable_id: z2.string(),
|
|
271
|
+
message: z2.string()
|
|
272
|
+
})
|
|
273
|
+
),
|
|
274
|
+
warnings: z2.array(structuredWarningSchema).optional(),
|
|
275
|
+
auto_healed: z2.boolean().optional(),
|
|
276
|
+
previous_revision_hash: z2.string().optional(),
|
|
277
|
+
// v2.0.0-rc.37 NEW-24: parallel to planContextOutputSchema.redirects — see
|
|
278
|
+
// that field for semantics. fab_recall transparently rewrites any old ids
|
|
279
|
+
// passed via `ids` before fetching bodies; the surfaced map still exposes
|
|
280
|
+
// the substitution to callers that want to refresh their cached state.
|
|
281
|
+
redirects: z2.record(z2.string()).optional()
|
|
282
|
+
});
|
|
283
|
+
var recallAnnotations = {
|
|
284
|
+
readOnlyHint: true,
|
|
285
|
+
idempotentHint: true,
|
|
286
|
+
destructiveHint: false,
|
|
287
|
+
openWorldHint: false,
|
|
288
|
+
title: "Recall Fabric knowledge (one-call)"
|
|
289
|
+
};
|
|
290
|
+
var archiveScanInputSchema = z2.object({
|
|
291
|
+
range: z2.union([z2.array(z2.string()).min(1), z2.literal("all")]).optional().describe(
|
|
292
|
+
"Phase 0 scope: explicit session_id[] to constrain the scan, or the 'all' sentinel. Omitted = scan everything since the last knowledge_proposed anchor."
|
|
293
|
+
),
|
|
294
|
+
now_ms: z2.number().int().nonnegative().optional().describe("Override for the anti-loop cooldown clock (testing). Defaults to Date.now()."),
|
|
295
|
+
correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records."),
|
|
296
|
+
session_id: z2.string().optional().describe("Current client session id; recorded for cross-session debt tracking.")
|
|
297
|
+
});
|
|
298
|
+
var archiveScanOutputSchema = z2.object({
|
|
299
|
+
// ts of the most recent knowledge_proposed event (the lower bound), or null
|
|
300
|
+
// when the workspace has never archived (scan everything).
|
|
301
|
+
anchor_ts: z2.number().nullable(),
|
|
302
|
+
// Distinct session_ids since the anchor that survived the outcome filter,
|
|
303
|
+
// in first-seen order — ready for the Skill to load digests + stitch.
|
|
304
|
+
session_ids: z2.array(z2.string()),
|
|
305
|
+
// Sessions dropped by the filter, with the rule that fired (audit/debug).
|
|
306
|
+
dropped: z2.array(
|
|
307
|
+
z2.object({
|
|
308
|
+
session_id: z2.string(),
|
|
309
|
+
reason: z2.enum(["user_dismissed", "cooldown", "no_new_signal"])
|
|
310
|
+
})
|
|
311
|
+
),
|
|
312
|
+
// max ts examined across the scan — becomes the next covered_through_ts.
|
|
313
|
+
covered_through_ts: z2.number().nullable(),
|
|
314
|
+
// Idempotency keys already proposed by prior archive runs but not yet
|
|
315
|
+
// reviewed (Phase 4.5 cross-session pending dedupe). Drop matching candidates.
|
|
316
|
+
already_proposed_keys: z2.array(z2.string()),
|
|
317
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
318
|
+
});
|
|
319
|
+
var archiveScanAnnotations = {
|
|
320
|
+
readOnlyHint: true,
|
|
321
|
+
idempotentHint: true,
|
|
322
|
+
destructiveHint: false,
|
|
323
|
+
openWorldHint: false,
|
|
324
|
+
title: "Scan event ledger for archive candidates (deterministic)"
|
|
325
|
+
};
|
|
326
|
+
var ProposedReasonSchema = z2.enum([
|
|
327
|
+
"explicit-user-mark",
|
|
328
|
+
"diagnostic-then-fix",
|
|
329
|
+
"decision-confirmation",
|
|
330
|
+
"wrong-turn-revert",
|
|
331
|
+
"new-dependency-or-pattern",
|
|
332
|
+
"dismissal-with-reason"
|
|
333
|
+
]);
|
|
334
|
+
var PROPOSED_REASON_DESCRIPTIONS = {
|
|
335
|
+
"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",
|
|
336
|
+
"diagnostic-then-fix": "\u8BCA\u65AD\u8FC7\u7A0B\u53D1\u73B0\u65B0\u6A21\u5F0F\u6216\u8E29\u5751\uFF0C\u4FEE\u590D\u540E\u503C\u5F97\u6C89\u6DC0\u3002",
|
|
337
|
+
"decision-confirmation": "\u22652 \u5019\u9009\u65B9\u6848\u7ECF\u6743\u8861\u540E\u786E\u8BA4\u9009\u578B\uFF0C\u9700\u4FDD\u7559 rationale\u3002",
|
|
338
|
+
"wrong-turn-revert": "\u5C1D\u8BD5\u67D0\u8DEF\u5F84\u540E\u56DE\u9000\uFF0C\u9519\u8BEF\u8DEF\u5F84\u672C\u8EAB\u662F\u503C\u5F97\u8BB0\u5F55\u7684 pitfall\u3002",
|
|
339
|
+
"new-dependency-or-pattern": "\u5F15\u5165\u65B0\u4F9D\u8D56 / \u65B0\u6A21\u5F0F / \u65B0\u547D\u540D\u7EA6\u5B9A\u3002",
|
|
340
|
+
"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"
|
|
341
|
+
};
|
|
342
|
+
var _sourceSessionsField = z2.array(z2.string().min(1)).min(1);
|
|
343
|
+
var _FabExtractKnowledgeInputBaseSchema = z2.object({
|
|
344
|
+
// v2.0.0-rc.7 T5: array form. rc.23 dropped the legacy single-string alias.
|
|
345
|
+
source_sessions: _sourceSessionsField.optional().describe(
|
|
346
|
+
"Originating session ids; correlates with Event Ledger records. Array form (T5+, rc.23 made it the sole accepted shape)."
|
|
347
|
+
),
|
|
348
|
+
recent_paths: z2.array(z2.string()).describe("Workspace paths recently touched in the source session \u2014 used as scope hints"),
|
|
349
|
+
user_messages_summary: z2.string().describe("Skill-side summary of the user's intent/messages, kept compact"),
|
|
350
|
+
type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).describe("Knowledge type bucket (plural form, mirrors directory layout)"),
|
|
351
|
+
slug: z2.string().describe("URL-safe short identifier proposed by the Skill; server may sanitize"),
|
|
352
|
+
// rc.5 B1: dual pending root. When 'personal', the server writes to
|
|
353
|
+
// ~/.fabric/knowledge/pending/<type>/; otherwise to .fabric/knowledge/pending/<type>/.
|
|
354
|
+
// Defaults to 'team' to preserve existing call sites (Skill bumps as needed).
|
|
355
|
+
layer: z2.enum(["team", "personal"]).optional().describe(
|
|
356
|
+
"Storage layer for the pending entry. 'team' writes under the workspace; 'personal' writes under the user's home. Defaults to 'team'."
|
|
357
|
+
),
|
|
358
|
+
// v2.0.0-rc.7 T6: proposed_reason — required enum that drives `## Why
|
|
359
|
+
// proposed` rendering. Skills (archive / import / review) infer the
|
|
360
|
+
// appropriate reason per their semantics (see each SKILL.md).
|
|
361
|
+
proposed_reason: ProposedReasonSchema.describe(
|
|
362
|
+
"Why this entry is being proposed. Drives `## Why proposed` rendering and enables future maturity-promotion scoring."
|
|
363
|
+
),
|
|
364
|
+
// v2.0.0-rc.7 T6: session_context — required 3-5 line markdown blob that
|
|
365
|
+
// captures the session goal + key turning point. Future-self review reads
|
|
366
|
+
// this without conversation transcript access. Min length guards against
|
|
367
|
+
// empty placeholders; cap is soft (no max), Skill caps at ~600 chars.
|
|
368
|
+
session_context: z2.string().min(20, { message: "session_context must be \u226520 chars (3-5 lines describing goal + turning point)" }).describe(
|
|
369
|
+
"3-5 line markdown blob \u2014 session goal + key turning point. Reviewed by future-self without transcript access."
|
|
370
|
+
),
|
|
371
|
+
// v2.0.0-rc.8 A1 (skill-contract-fix): relevance scope/paths on the
|
|
372
|
+
// creation surface. Mirrors `_fabReviewModifyChangesSchema.relevance_*`
|
|
373
|
+
// (L518-533) verbatim so callers can declare scope at archive time
|
|
374
|
+
// instead of waiting for a fab_review.modify follow-up. Both fields are
|
|
375
|
+
// optional — when omitted, the pending file omits the YAML lines entirely
|
|
376
|
+
// (knowledge-meta-builder defaults to broad + [] at parse time, see
|
|
377
|
+
// L1007-1021). Personal + narrow is silently degraded to broad + [] at
|
|
378
|
+
// service entry, mirroring the rc.5 review.ts:725-739 behaviour, and emits
|
|
379
|
+
// a `knowledge_scope_degraded` event keyed by `pending:<idempotency_key>`.
|
|
380
|
+
// NOTE: these fields MUST NOT be part of the idempotency hash inputs at
|
|
381
|
+
// extract-knowledge.ts:78 — preserves rc.5→rc.7 collision detection.
|
|
382
|
+
relevance_scope: z2.enum(["narrow", "broad"]).optional().describe(
|
|
383
|
+
"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 + []."
|
|
384
|
+
),
|
|
385
|
+
relevance_paths: z2.array(z2.string()).optional().describe(
|
|
386
|
+
"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)."
|
|
387
|
+
),
|
|
388
|
+
// v2.0.0-rc.23 TASK-006 (a-C1): four optional structured fields that the
|
|
389
|
+
// skill-side LLM populates from raw observations. The same information
|
|
390
|
+
// historically lived only in `## Session context` prose, forcing future-self
|
|
391
|
+
// reviewers / plan-context retrievers to re-read the entire body to decide
|
|
392
|
+
// relevance. Lifting them into structured frontmatter lets downstream
|
|
393
|
+
// surfaces (description_index, scoring, relevance triage) consume them
|
|
394
|
+
// directly. ALL FOUR ARE STRICTLY OPTIONAL — skills that cannot infer them
|
|
395
|
+
// confidently must omit, not guess.
|
|
396
|
+
//
|
|
397
|
+
// IMPORTANT: these fields MUST NOT participate in the idempotency_key hash
|
|
398
|
+
// (see rc.8 A1 convention at extract-knowledge.ts — relevance_scope /
|
|
399
|
+
// relevance_paths follow the same rule). Including them would let an LLM
|
|
400
|
+
// re-roll of the same observation create a second pending file just because
|
|
401
|
+
// its inferred metadata wording drifted.
|
|
402
|
+
intent_clues: z2.array(z2.string()).optional().describe(
|
|
403
|
+
"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."
|
|
404
|
+
),
|
|
405
|
+
tech_stack: z2.array(z2.string()).optional().describe(
|
|
406
|
+
"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."
|
|
407
|
+
),
|
|
408
|
+
impact: z2.array(z2.string()).optional().describe(
|
|
409
|
+
"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."
|
|
410
|
+
),
|
|
411
|
+
must_read_if: z2.string().optional().describe(
|
|
412
|
+
"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."
|
|
413
|
+
),
|
|
414
|
+
// v2.0.0-rc.37 NEW-37 (werewolf dogfood remediation): optional tags array.
|
|
415
|
+
// Werewolf实测发现 100% canonical entries 的 `tags: []` 为空,主题聚类与
|
|
416
|
+
// 跨条目检索退化。Skills (fabric-archive / fabric-import) 应每个 entry 产
|
|
417
|
+
// 2-4 个 kebab-case 主题词。Server 写入时直接落 frontmatter `tags: [...]`;
|
|
418
|
+
// empty array 仍然合法(skill 无法 confident 推断时显式空)。
|
|
419
|
+
// IDEMPOTENCY: tags MUST NOT 参与 idempotency_key hash(同 relevance_*
|
|
420
|
+
// / intent_clues 等可变字段一致),re-extract 时 tags 调整不应产生重复
|
|
421
|
+
// pending file。
|
|
422
|
+
tags: z2.array(z2.string()).optional().describe(
|
|
423
|
+
"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)."
|
|
424
|
+
),
|
|
425
|
+
// v2.0.0-rc.23 TASK-014 (F8c): optional onboard-slot tag. The S5 slot
|
|
426
|
+
// mechanism reintroduces a Skill-orchestrated "project tone" capture
|
|
427
|
+
// surface after F8a deleted the auto-`fabric scan` baseline pipeline.
|
|
428
|
+
// fabric-archive's first-run phase reads `fabric onboard-coverage` to
|
|
429
|
+
// discover unclaimed slots, then propagates the chosen slot label here
|
|
430
|
+
// so the resulting pending entry counts toward coverage.
|
|
431
|
+
//
|
|
432
|
+
// STRICT optionality: every non-onboard fab_extract_knowledge call MUST
|
|
433
|
+
// omit this field. The skill is the only producer; downstream consumers
|
|
434
|
+
// (plan_context retrieval, doctor lints) treat missing as a steady-state
|
|
435
|
+
// signal that the entry was NOT part of an onboard pass.
|
|
436
|
+
//
|
|
437
|
+
// IDEMPOTENCY: like the four a-C1 fields and the rc.8 A1 relevance pair,
|
|
438
|
+
// `onboard_slot` MUST NOT participate in the idempotency_key hash at
|
|
439
|
+
// extract-knowledge.ts:100-106. An LLM that re-rolls the same observation
|
|
440
|
+
// with a different (or absent) slot must still collapse onto the same
|
|
441
|
+
// pending file — otherwise the slot mechanic itself could spawn
|
|
442
|
+
// duplicate entries.
|
|
443
|
+
onboard_slot: onboardSlotSchema.optional().describe(
|
|
444
|
+
"Optional slot tag from the S5 onboarding set (tech-stack-decision / architecture-pattern / code-style-tone / build-system-idiom / domain-vocabulary); lets fabric-archive's first-run phase claim a project-tone slot. Skill propose-time only; never required."
|
|
445
|
+
),
|
|
446
|
+
// v2.0.0-rc.37 NEW-7: read-only evidence paths lifted from the legacy
|
|
447
|
+
// body `## Evidence` markdown block into structured frontmatter. These are
|
|
448
|
+
// paths the agent CONSULTED while building this knowledge but never
|
|
449
|
+
// modified — they document context without participating in the
|
|
450
|
+
// activation gate (relevance_paths does that). Splitting evidence into a
|
|
451
|
+
// first-class frontmatter array lets future plan-context retrieval read
|
|
452
|
+
// it as data (intersect with current paths to surface high-recall hits)
|
|
453
|
+
// instead of re-parsing markdown. Optional; omit when no read-only
|
|
454
|
+
// signal was captured. Like relevance_paths it MUST NOT participate in
|
|
455
|
+
// the idempotency_key hash (an idempotent re-extract may surface a
|
|
456
|
+
// slightly different read set without spawning a duplicate pending).
|
|
457
|
+
evidence_paths: z2.array(z2.string()).optional().describe(
|
|
458
|
+
"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."
|
|
459
|
+
)
|
|
460
|
+
});
|
|
461
|
+
var FabExtractKnowledgeInputSchema = _FabExtractKnowledgeInputBaseSchema.superRefine(
|
|
462
|
+
(value, ctx) => {
|
|
463
|
+
const hasArray = Array.isArray(value.source_sessions) && value.source_sessions.length > 0;
|
|
464
|
+
if (!hasArray) {
|
|
465
|
+
ctx.addIssue({
|
|
466
|
+
code: z2.ZodIssueCode.custom,
|
|
467
|
+
message: "source_sessions (non-empty string array) is required",
|
|
468
|
+
path: ["source_sessions"]
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
var FabExtractKnowledgeInputShape = _FabExtractKnowledgeInputBaseSchema.shape;
|
|
474
|
+
var FabExtractKnowledgeOutputSchema = z2.object({
|
|
475
|
+
pending_path: z2.string().describe("Workspace-relative path to the persisted pending entry"),
|
|
476
|
+
idempotency_key: z2.string().describe("Stable key derived from inputs; identical inputs yield identical key"),
|
|
477
|
+
// v2.0.0-rc.23 TASK-009 (d): optional warnings surface for the first-reconcile
|
|
478
|
+
// gate (`meta_stale` / `reconcile_failed`). Absent on the steady-state path.
|
|
479
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
480
|
+
});
|
|
481
|
+
var fabExtractKnowledgeAnnotations = {
|
|
482
|
+
readOnlyHint: false,
|
|
483
|
+
idempotentHint: true,
|
|
484
|
+
destructiveHint: false,
|
|
485
|
+
openWorldHint: false,
|
|
486
|
+
title: "Extract pending knowledge entry"
|
|
487
|
+
};
|
|
488
|
+
var _fabReviewFiltersSchema = z2.object({
|
|
489
|
+
type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]).optional(),
|
|
490
|
+
layer: z2.enum(["team", "personal", "both"]).optional(),
|
|
491
|
+
maturity: z2.enum(["draft", "verified", "proven"]).optional(),
|
|
492
|
+
tags: z2.array(z2.string()).optional(),
|
|
493
|
+
// rc.4 TASK-006 fix (c): ISO-8601 lower bound on entry created_at; entries
|
|
494
|
+
// strictly older than this threshold are excluded from list / search
|
|
495
|
+
// results. Additive optional field — existing callers unaffected.
|
|
496
|
+
created_after: z2.string().datetime().optional(),
|
|
497
|
+
// v2.0.0-rc.27 TASK-001 (§2.2/§2.3): opt-in surfacing of lifecycle-filtered
|
|
498
|
+
// entries. Default (omit both) hides rejected entries and deferred entries
|
|
499
|
+
// whose deferred_until is in the future. Pass true to include them — e.g.
|
|
500
|
+
// for vacuum tooling, audit dashboards, or "show me what I parked" UX.
|
|
501
|
+
include_rejected: z2.boolean().optional(),
|
|
502
|
+
include_deferred: z2.boolean().optional(),
|
|
503
|
+
// v2.0.0-rc.27 TASK-006 (audit §2.23): opt-in body inspection. Default
|
|
504
|
+
// list/search return only frontmatter-derived fields — a malicious
|
|
505
|
+
// pending entry could hide a prompt-injection payload under `## Evidence`
|
|
506
|
+
// body content that frontmatter inspection never surfaces. Setting
|
|
507
|
+
// `include_body: true` attaches the full post-frontmatter content to
|
|
508
|
+
// each item, and (for search) extends the haystack to body text. The
|
|
509
|
+
// default-off design keeps the wire payload small for routine list
|
|
510
|
+
// calls; reviewer workflows pass `true` before approving so the body
|
|
511
|
+
// is rendered into the reviewer's UI for visual scan.
|
|
512
|
+
include_body: z2.boolean().optional()
|
|
513
|
+
}).optional();
|
|
514
|
+
var _fabReviewModifyChangesSchema = z2.object({
|
|
515
|
+
title: z2.string().optional(),
|
|
516
|
+
summary: z2.string().optional(),
|
|
517
|
+
// Q7: writing `layer` here triggers a layer-flip; downstream callers may
|
|
518
|
+
// observe a redirect_to in fab_get_knowledge_sections if stable_id changes.
|
|
519
|
+
layer: z2.enum(["team", "personal"]).optional(),
|
|
520
|
+
maturity: z2.enum(["draft", "verified", "proven"]).optional(),
|
|
521
|
+
tags: z2.array(z2.string()).optional(),
|
|
522
|
+
// v2.0-rc.5 C3 (TASK-012): relevance scope/paths patches. Applied to
|
|
523
|
+
// pending AND canonical entries. When an explicit team→personal layer flip
|
|
524
|
+
// arrives on a narrow entry, the server auto-degrades to broad + [] and
|
|
525
|
+
// emits a `knowledge_scope_degraded` event regardless of what the caller
|
|
526
|
+
// sent in these fields (personal-implies-broad).
|
|
527
|
+
relevance_scope: z2.enum(["narrow", "broad"]).optional(),
|
|
528
|
+
relevance_paths: z2.array(z2.string()).optional()
|
|
529
|
+
});
|
|
530
|
+
var FabReviewInputSchema = z2.discriminatedUnion("action", [
|
|
531
|
+
z2.object({
|
|
532
|
+
action: z2.literal("list"),
|
|
533
|
+
filters: _fabReviewFiltersSchema
|
|
534
|
+
}),
|
|
535
|
+
z2.object({
|
|
536
|
+
action: z2.literal("approve"),
|
|
537
|
+
pending_paths: z2.array(z2.string()).min(1)
|
|
538
|
+
}),
|
|
539
|
+
z2.object({
|
|
540
|
+
action: z2.literal("reject"),
|
|
541
|
+
pending_paths: z2.array(z2.string()).min(1),
|
|
542
|
+
reason: z2.string().min(1)
|
|
543
|
+
}),
|
|
544
|
+
z2.object({
|
|
545
|
+
action: z2.literal("modify"),
|
|
546
|
+
pending_path: z2.string().min(1),
|
|
547
|
+
changes: _fabReviewModifyChangesSchema
|
|
548
|
+
}),
|
|
549
|
+
// v2.0.0-rc.37 NEW-12: explicit modify split. `modify-content` edits scalar
|
|
550
|
+
// frontmatter/body fields (title/summary/maturity/tags/relevance_*) and MUST
|
|
551
|
+
// NOT carry a layer change. `modify-layer` is the dedicated layer-flip path
|
|
552
|
+
// (changes.layer REQUIRED) which may reallocate the stable_id + emit an
|
|
553
|
+
// id-redirect (rc.37 NEW-24). Legacy `modify` stays for back-compat and
|
|
554
|
+
// routes by whether changes.layer is present.
|
|
555
|
+
z2.object({
|
|
556
|
+
action: z2.literal("modify-content"),
|
|
557
|
+
pending_path: z2.string().min(1),
|
|
558
|
+
changes: _fabReviewModifyChangesSchema
|
|
559
|
+
}),
|
|
560
|
+
z2.object({
|
|
561
|
+
action: z2.literal("modify-layer"),
|
|
562
|
+
pending_path: z2.string().min(1),
|
|
563
|
+
changes: _fabReviewModifyChangesSchema.extend({
|
|
564
|
+
layer: z2.enum(["team", "personal"])
|
|
565
|
+
})
|
|
566
|
+
}),
|
|
567
|
+
z2.object({
|
|
568
|
+
action: z2.literal("search"),
|
|
569
|
+
query: z2.string().min(1),
|
|
570
|
+
filters: _fabReviewFiltersSchema
|
|
571
|
+
}),
|
|
572
|
+
z2.object({
|
|
573
|
+
action: z2.literal("defer"),
|
|
574
|
+
pending_paths: z2.array(z2.string()).min(1),
|
|
575
|
+
until: z2.string().datetime().optional(),
|
|
576
|
+
reason: z2.string().optional()
|
|
577
|
+
})
|
|
578
|
+
]);
|
|
579
|
+
var FabReviewInputShape = {
|
|
580
|
+
action: z2.enum(["list", "approve", "reject", "modify", "modify-content", "modify-layer", "search", "defer"]).describe(
|
|
581
|
+
"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."
|
|
582
|
+
),
|
|
583
|
+
filters: _fabReviewFiltersSchema.describe(
|
|
584
|
+
"Optional filters (type/layer/maturity/tags/created_after). Used by action=list and action=search."
|
|
585
|
+
),
|
|
586
|
+
pending_paths: z2.array(z2.string()).min(1).optional().describe(
|
|
587
|
+
"Workspace-relative pending entry paths. Required when action=approve|reject|defer (non-empty array)."
|
|
588
|
+
),
|
|
589
|
+
pending_path: z2.string().min(1).optional().describe(
|
|
590
|
+
"Workspace-relative pending OR canonical entry path. Required when action=modify."
|
|
591
|
+
),
|
|
592
|
+
reason: z2.string().optional().describe(
|
|
593
|
+
"Reason string. Required (non-empty) when action=reject; optional when action=defer."
|
|
594
|
+
),
|
|
595
|
+
changes: _fabReviewModifyChangesSchema.optional().describe(
|
|
596
|
+
"Frontmatter scalar patches (title/summary/layer/maturity/tags/relevance_*). Required when action=modify."
|
|
597
|
+
),
|
|
598
|
+
query: z2.string().min(1).optional().describe(
|
|
599
|
+
"Substring query against title/summary/tags/path. Required (non-empty) when action=search."
|
|
600
|
+
),
|
|
601
|
+
until: z2.string().datetime().optional().describe(
|
|
602
|
+
"ISO-8601 datetime upper bound for the deferral. Optional; used only when action=defer."
|
|
603
|
+
)
|
|
604
|
+
};
|
|
605
|
+
var _fabReviewListItemSchema = z2.object({
|
|
606
|
+
pending_path: z2.string(),
|
|
607
|
+
// v2.0.0-rc.27 TASK-001 (§2.12): for personal-layer entries `pending_path`
|
|
608
|
+
// carries the human-friendly `~/...` form (legacy contract) while
|
|
609
|
+
// `pending_path_absolute` carries the os-expanded absolute path. Programmatic
|
|
610
|
+
// consumers (Read tool, fs.readFile, downstream MCP servers) should prefer
|
|
611
|
+
// the absolute variant — the `~` is a shell-only sigil that breaks every
|
|
612
|
+
// non-shell consumer. Team entries omit this field because their
|
|
613
|
+
// `pending_path` is already project-relative and unambiguous.
|
|
614
|
+
pending_path_absolute: z2.string().optional(),
|
|
615
|
+
type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
|
|
616
|
+
layer: z2.enum(["team", "personal"]),
|
|
617
|
+
maturity: z2.enum(["draft", "verified", "proven"]),
|
|
618
|
+
tags: z2.array(z2.string()).optional(),
|
|
619
|
+
title: z2.string().optional(),
|
|
620
|
+
summary: z2.string().optional(),
|
|
621
|
+
// rc.5 B1: dual pending root. 'team' = workspace .fabric/knowledge/pending,
|
|
622
|
+
// 'personal' = ~/.fabric/knowledge/pending. Distinct from `layer` (frontmatter):
|
|
623
|
+
// origin reflects where the pending file actually lives on disk; layer reflects
|
|
624
|
+
// the declared classification that will drive the approve destination.
|
|
625
|
+
origin: z2.enum(["team", "personal"]).optional(),
|
|
626
|
+
// v2.0.0-rc.27 TASK-001 (§2.2/§2.3): frontmatter status markers. Default
|
|
627
|
+
// "active" (or absent). `rejected` entries are excluded from list/search
|
|
628
|
+
// unless filters.include_rejected=true; `deferred` entries are excluded
|
|
629
|
+
// when deferred_until is in the future. Authored by reject/defer write
|
|
630
|
+
// paths — never by extract or approve.
|
|
631
|
+
status: z2.enum(["active", "rejected", "deferred"]).optional(),
|
|
632
|
+
deferred_until: z2.string().datetime().optional(),
|
|
633
|
+
// v2.0.0-rc.27 TASK-006 (audit §2.23): full body content (everything
|
|
634
|
+
// after the closing `---` of frontmatter). Surfaced only when caller
|
|
635
|
+
// passes `filters.include_body: true`. Default-omitted to keep payload
|
|
636
|
+
// small for routine list calls.
|
|
637
|
+
body: z2.string().optional()
|
|
638
|
+
});
|
|
639
|
+
var _fabReviewSearchItemSchema = z2.object({
|
|
640
|
+
// Search hits live in one of two trees:
|
|
641
|
+
// - "pending" → .fabric/knowledge/pending/ (or ~/.fabric/knowledge/pending/)
|
|
642
|
+
// - "canonical" → .fabric/knowledge/{decisions,pitfalls,...} (or personal mirror)
|
|
643
|
+
area: z2.enum(["pending", "canonical"]),
|
|
644
|
+
path: z2.string(),
|
|
645
|
+
path_absolute: z2.string().optional(),
|
|
646
|
+
type: z2.enum(["decisions", "pitfalls", "guidelines", "models", "processes"]),
|
|
647
|
+
layer: z2.enum(["team", "personal"]),
|
|
648
|
+
maturity: z2.enum(["draft", "verified", "proven"]),
|
|
649
|
+
tags: z2.array(z2.string()).optional(),
|
|
650
|
+
title: z2.string().optional(),
|
|
651
|
+
summary: z2.string().optional(),
|
|
652
|
+
origin: z2.enum(["team", "personal"]).optional(),
|
|
653
|
+
status: z2.enum(["active", "rejected", "deferred"]).optional(),
|
|
654
|
+
deferred_until: z2.string().datetime().optional(),
|
|
655
|
+
body: z2.string().optional(),
|
|
656
|
+
// For pending hits the upstream stable_id may still be unassigned — keep it
|
|
657
|
+
// optional so canonical hits (which always have one) parse alongside pending
|
|
658
|
+
// hits in the same array.
|
|
659
|
+
stable_id: z2.string().optional()
|
|
660
|
+
});
|
|
661
|
+
var FabReviewOutputSchema = z2.discriminatedUnion("action", [
|
|
662
|
+
z2.object({
|
|
663
|
+
action: z2.literal("list"),
|
|
664
|
+
items: z2.array(_fabReviewListItemSchema),
|
|
665
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
666
|
+
}),
|
|
667
|
+
z2.object({
|
|
668
|
+
action: z2.literal("approve"),
|
|
669
|
+
approved: z2.array(z2.object({ pending_path: z2.string(), stable_id: z2.string() })),
|
|
670
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
671
|
+
}),
|
|
672
|
+
z2.object({
|
|
673
|
+
action: z2.literal("reject"),
|
|
674
|
+
rejected: z2.array(z2.string()),
|
|
675
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
676
|
+
}),
|
|
677
|
+
z2.object({
|
|
678
|
+
action: z2.literal("modify"),
|
|
679
|
+
pending_path: z2.string(),
|
|
680
|
+
// When a layer-flip occurred, prior_stable_id and new_stable_id differ.
|
|
681
|
+
prior_stable_id: z2.string().optional(),
|
|
682
|
+
new_stable_id: z2.string().optional(),
|
|
683
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
684
|
+
}),
|
|
685
|
+
z2.object({
|
|
686
|
+
action: z2.literal("search"),
|
|
687
|
+
// v2.0.0-rc.29 TASK-007 (BUG-M4): search returns the new search-item
|
|
688
|
+
// shape with `area` discriminator + neutrally-named `path` field.
|
|
689
|
+
items: z2.array(_fabReviewSearchItemSchema),
|
|
690
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
691
|
+
}),
|
|
692
|
+
z2.object({
|
|
693
|
+
action: z2.literal("defer"),
|
|
694
|
+
deferred: z2.array(z2.string()),
|
|
695
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
696
|
+
})
|
|
697
|
+
]);
|
|
698
|
+
var FabReviewOutputShape = {
|
|
699
|
+
action: z2.enum(["list", "approve", "reject", "modify", "search", "defer"]).describe(
|
|
700
|
+
"Echoes the input action; clients can switch on it for per-variant fields below."
|
|
701
|
+
),
|
|
702
|
+
items: z2.array(z2.union([_fabReviewListItemSchema, _fabReviewSearchItemSchema])).optional().describe(
|
|
703
|
+
"Pending entries (action=list, `pending_path` shape) or pending+canonical entries (action=search, `area`+`path` shape)."
|
|
704
|
+
),
|
|
705
|
+
approved: z2.array(z2.object({ pending_path: z2.string(), stable_id: z2.string() })).optional().describe(
|
|
706
|
+
"Allocated stable ids paired with their original pending paths. Present when action=approve."
|
|
707
|
+
),
|
|
708
|
+
rejected: z2.array(z2.string()).optional().describe(
|
|
709
|
+
"Pending paths that were rejected (files retained on disk; doctor owns vacuum). Present when action=reject."
|
|
710
|
+
),
|
|
711
|
+
pending_path: z2.string().optional().describe(
|
|
712
|
+
"Echoed target path for the modification. Present when action=modify."
|
|
713
|
+
),
|
|
714
|
+
prior_stable_id: z2.string().optional().describe(
|
|
715
|
+
"Prior stable id. Present when action=modify AND a layer-flip reallocated the id."
|
|
716
|
+
),
|
|
717
|
+
new_stable_id: z2.string().optional().describe(
|
|
718
|
+
"New stable id after reallocation. Present when action=modify AND a layer-flip reallocated the id."
|
|
719
|
+
),
|
|
720
|
+
deferred: z2.array(z2.string()).optional().describe(
|
|
721
|
+
"Pending paths that were deferred (files retained on disk). Present when action=defer."
|
|
722
|
+
),
|
|
723
|
+
// v2.0.0-rc.23 TASK-009 (d): optional warnings surface for the first-reconcile
|
|
724
|
+
// gate (`meta_stale` / `reconcile_failed`). Absent on the steady-state path.
|
|
725
|
+
warnings: z2.array(structuredWarningSchema).optional()
|
|
726
|
+
};
|
|
727
|
+
var fabReviewAnnotations = {
|
|
728
|
+
readOnlyHint: false,
|
|
729
|
+
idempotentHint: false,
|
|
730
|
+
destructiveHint: false,
|
|
731
|
+
openWorldHint: false,
|
|
732
|
+
title: "Review pending knowledge entries"
|
|
733
|
+
};
|
|
734
|
+
var citeContractMetricsSchema = z2.object({
|
|
735
|
+
decisions_cited: z2.number().int().nonnegative(),
|
|
736
|
+
pitfalls_cited: z2.number().int().nonnegative(),
|
|
737
|
+
contract_with: z2.number().int().nonnegative(),
|
|
738
|
+
contract_missing: z2.number().int().nonnegative(),
|
|
739
|
+
hard_violated: z2.number().int().nonnegative(),
|
|
740
|
+
cite_id_unresolved: z2.number().int().nonnegative(),
|
|
741
|
+
skip_count: z2.record(z2.string(), z2.number().int().nonnegative())
|
|
742
|
+
});
|
|
743
|
+
var citeLayerTypeBreakdownSchema = z2.object({
|
|
744
|
+
team: z2.record(z2.string(), z2.number().int().nonnegative()),
|
|
745
|
+
personal: z2.record(z2.string(), z2.number().int().nonnegative())
|
|
746
|
+
});
|
|
747
|
+
var citeCoverageReportSchema = z2.object({
|
|
748
|
+
status: z2.enum(["ok", "skipped"]),
|
|
749
|
+
marker_ts: z2.number().int().nonnegative(),
|
|
750
|
+
marker_emitted_now: z2.boolean(),
|
|
751
|
+
since_ts: z2.number().int().nonnegative(),
|
|
752
|
+
client_filter: z2.enum(["cc", "codex", "cursor", "all"]),
|
|
753
|
+
// v2.0.0-rc.24 TASK-08: layer filter discriminator. Optional so pre-TASK-10
|
|
754
|
+
// CLI callers (which never set the flag) still parse. Defaults to "all" at
|
|
755
|
+
// the service layer.
|
|
756
|
+
layer_filter: z2.enum(["team", "personal", "all"]).optional(),
|
|
757
|
+
metrics: z2.object({
|
|
758
|
+
edits_touched: z2.number().int().nonnegative(),
|
|
759
|
+
qualifying_cites: z2.number().int().nonnegative(),
|
|
760
|
+
recalled_unverified: z2.number().int().nonnegative(),
|
|
761
|
+
expected_but_missed: z2.number().int().nonnegative(),
|
|
762
|
+
total_turns: z2.number().int().nonnegative(),
|
|
763
|
+
// v2.0.0-rc.38 UX-8 (C, user-authorized): cite-policy COMPLIANCE rate —
|
|
764
|
+
// the corrected G-CITE semantic. The legacy qualifying_cites/edits ratio
|
|
765
|
+
// measured "how often an applicable KB id existed" (a function of corpus
|
|
766
|
+
// density / soak), NOT "did the AI follow the cite policy". Compliance
|
|
767
|
+
// credits every valid cite line — `KB: <id> [applied|dismissed]` AND
|
|
768
|
+
// `KB: none [reason]` (the policy explicitly allows the none sentinel) —
|
|
769
|
+
// over the turns where a cite was expected. null when no cite-expected
|
|
770
|
+
// turns observed (avoids a misleading 0/0 → 0). Range [0,1].
|
|
771
|
+
cite_compliance_rate: z2.number().min(0).max(1).nullable().optional(),
|
|
772
|
+
compliant_cites: z2.number().int().nonnegative().optional(),
|
|
773
|
+
noncompliant_cites: z2.number().int().nonnegative().optional(),
|
|
774
|
+
// Edit signals lacking session_id → uncorrelatable, silently excluded from
|
|
775
|
+
// expected_but_missed. >0 typically means a stale pre-session_id hook is
|
|
776
|
+
// installed (run `fabric install`). Surfaced so the denominator gap is
|
|
777
|
+
// visible rather than a silent 100% confound.
|
|
778
|
+
uncorrelatable_edits: z2.number().int().nonnegative().optional()
|
|
779
|
+
}),
|
|
780
|
+
per_client: z2.record(
|
|
781
|
+
z2.string(),
|
|
782
|
+
z2.object({
|
|
783
|
+
edits_touched: z2.number().int().nonnegative().optional(),
|
|
784
|
+
qualifying_cites: z2.number().int().nonnegative().optional(),
|
|
785
|
+
recalled_unverified: z2.number().int().nonnegative().optional(),
|
|
786
|
+
expected_but_missed: z2.number().int().nonnegative().optional(),
|
|
787
|
+
total_turns: z2.number().int().nonnegative().optional()
|
|
788
|
+
})
|
|
789
|
+
).optional(),
|
|
790
|
+
dismissed_reason_histogram: z2.record(z2.string(), z2.number().int().nonnegative()).optional(),
|
|
791
|
+
none_reason_histogram: z2.record(z2.string(), z2.number().int().nonnegative()).optional(),
|
|
792
|
+
// v2.0.0-rc.24 TASK-08: contract-policy audit metrics. Status discriminates
|
|
793
|
+
// populated vs degraded modes. contract_metrics + per_layer_type are emitted
|
|
794
|
+
// (zeroed) in degraded modes so the renderer iterates one stable shape.
|
|
795
|
+
contract_metrics_status: z2.enum(["ok", "skipped:bootstrap_drift", "awaiting_marker"]).optional(),
|
|
796
|
+
contract_metrics: citeContractMetricsSchema.optional(),
|
|
797
|
+
per_layer_type: citeLayerTypeBreakdownSchema.optional(),
|
|
798
|
+
contract_marker_ts: z2.number().int().nonnegative().optional(),
|
|
799
|
+
generated_at: z2.string()
|
|
800
|
+
});
|
|
801
|
+
var ledgerSourceSchema = z2.enum(["ai", "human"]);
|
|
802
|
+
var timestampFilterSchema = z2.preprocess((value) => {
|
|
803
|
+
if (value === void 0 || value === null || value === "") {
|
|
804
|
+
return void 0;
|
|
805
|
+
}
|
|
806
|
+
if (typeof value === "number") {
|
|
807
|
+
return value;
|
|
808
|
+
}
|
|
809
|
+
if (typeof value === "string") {
|
|
810
|
+
const trimmed = value.trim();
|
|
811
|
+
if (trimmed.length === 0) {
|
|
812
|
+
return void 0;
|
|
813
|
+
}
|
|
814
|
+
if (/^\d+$/.test(trimmed)) {
|
|
815
|
+
return Number.parseInt(trimmed, 10);
|
|
816
|
+
}
|
|
817
|
+
const parsed = Date.parse(trimmed);
|
|
818
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
819
|
+
}
|
|
820
|
+
return value;
|
|
821
|
+
}, z2.number().int().nonnegative());
|
|
822
|
+
var ledgerQuerySchema = z2.object({
|
|
823
|
+
source: ledgerSourceSchema.optional(),
|
|
824
|
+
since: timestampFilterSchema.optional()
|
|
825
|
+
});
|
|
826
|
+
var historyStateQuerySchema = z2.object({
|
|
827
|
+
ledger_id: z2.string().trim().min(1).optional(),
|
|
828
|
+
ts: timestampFilterSchema.optional()
|
|
829
|
+
}).superRefine((value, ctx) => {
|
|
830
|
+
const provided = [value.ledger_id, value.ts].filter((entry) => entry !== void 0);
|
|
831
|
+
if (provided.length !== 1) {
|
|
832
|
+
ctx.addIssue({
|
|
833
|
+
code: z2.ZodIssueCode.custom,
|
|
834
|
+
message: "Provide exactly one of ledger_id or ts.",
|
|
835
|
+
path: ["ledger_id"]
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
var humanLockApproveRequestSchema = z2.object({
|
|
840
|
+
file: z2.string().min(1),
|
|
841
|
+
start_line: z2.number().int().positive(),
|
|
842
|
+
end_line: z2.number().int().positive(),
|
|
843
|
+
new_hash: z2.string().min(1)
|
|
844
|
+
});
|
|
845
|
+
var humanLockFileParamsSchema = z2.object({
|
|
846
|
+
file: z2.string().min(1)
|
|
847
|
+
});
|
|
848
|
+
var annotateIntentRequestSchema = z2.object({
|
|
849
|
+
ledger_entry_id: z2.string().min(1),
|
|
850
|
+
annotation: z2.string().trim().min(1)
|
|
851
|
+
});
|
|
852
|
+
var KnowledgeTypeSchema = z2.enum([
|
|
853
|
+
"models",
|
|
854
|
+
// entities, data structures, relationships
|
|
855
|
+
"decisions",
|
|
856
|
+
// architectural/technical choices with rationale
|
|
857
|
+
"guidelines",
|
|
858
|
+
// recommended practices (recommend) or anti-patterns (avoid)
|
|
859
|
+
"pitfalls",
|
|
860
|
+
// known risks, failure modes, troubleshooting
|
|
861
|
+
"processes"
|
|
862
|
+
// workflows, state machines, operational steps
|
|
863
|
+
]);
|
|
864
|
+
var MaturitySchema = z2.enum(["draft", "verified", "proven"]);
|
|
865
|
+
var LayerSchema = z2.enum(["personal", "team"]);
|
|
866
|
+
var StableIdSchema = z2.string().regex(/^K[PT]-(MOD|DEC|GLD|PIT|PRO)-\d{4,}$/);
|
|
867
|
+
var KnowledgeEntryFrontmatterSchema = z2.object({
|
|
868
|
+
id: StableIdSchema,
|
|
869
|
+
// e.g., "KT-DEC-0042"
|
|
870
|
+
type: KnowledgeTypeSchema,
|
|
871
|
+
// one of 5 types
|
|
872
|
+
maturity: MaturitySchema,
|
|
873
|
+
// draft | verified | proven
|
|
874
|
+
layer: LayerSchema,
|
|
875
|
+
// personal | team
|
|
876
|
+
layer_reason: z2.string().optional(),
|
|
877
|
+
// why this layer (for ambiguous cases)
|
|
878
|
+
created_at: z2.string()
|
|
879
|
+
// ISO 8601 timestamp
|
|
880
|
+
// Note: 'tags' and other fields can be added later but core schema is these 6
|
|
881
|
+
});
|
|
882
|
+
var KNOWLEDGE_TYPE_CODES = {
|
|
883
|
+
models: "MOD",
|
|
884
|
+
decisions: "DEC",
|
|
885
|
+
guidelines: "GLD",
|
|
886
|
+
pitfalls: "PIT",
|
|
887
|
+
processes: "PRO"
|
|
888
|
+
};
|
|
889
|
+
function formatKnowledgeId(layer, type, counter) {
|
|
890
|
+
const layerPrefix = layer === "personal" ? "KP" : "KT";
|
|
891
|
+
const typeCode = KNOWLEDGE_TYPE_CODES[type];
|
|
892
|
+
return `${layerPrefix}-${typeCode}-${String(counter).padStart(4, "0")}`;
|
|
893
|
+
}
|
|
894
|
+
function parseKnowledgeId(id) {
|
|
895
|
+
const match = id.match(/^(KP|KT)-(MOD|DEC|GLD|PIT|PRO)-(\d+)$/);
|
|
896
|
+
if (!match) return null;
|
|
897
|
+
const layer = match[1] === "KP" ? "personal" : "team";
|
|
898
|
+
const typeCode = match[2];
|
|
899
|
+
const entry = Object.entries(KNOWLEDGE_TYPE_CODES).find(([, code]) => code === typeCode);
|
|
900
|
+
if (!entry) return null;
|
|
901
|
+
const type = entry[0];
|
|
902
|
+
return { layer, type, counter: parseInt(match[3], 10) };
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export {
|
|
906
|
+
ONBOARD_SLOT_NAMES,
|
|
907
|
+
onboardSlotSchema,
|
|
908
|
+
ONBOARD_SLOT_TOTAL,
|
|
909
|
+
structuredWarningSchema,
|
|
910
|
+
planContextInputSchema,
|
|
911
|
+
planContextOutputSchema,
|
|
912
|
+
planContextAnnotations,
|
|
913
|
+
planContextHintNarrowEntrySchema,
|
|
914
|
+
planContextHintOutputSchema,
|
|
915
|
+
knowledgeSectionsInputSchema,
|
|
916
|
+
knowledgeSectionsOutputSchema,
|
|
917
|
+
knowledgeSectionsAnnotations,
|
|
918
|
+
recallInputSchema,
|
|
919
|
+
recallOutputSchema,
|
|
920
|
+
recallAnnotations,
|
|
921
|
+
archiveScanInputSchema,
|
|
922
|
+
archiveScanOutputSchema,
|
|
923
|
+
archiveScanAnnotations,
|
|
924
|
+
ProposedReasonSchema,
|
|
925
|
+
PROPOSED_REASON_DESCRIPTIONS,
|
|
926
|
+
FabExtractKnowledgeInputSchema,
|
|
927
|
+
FabExtractKnowledgeInputShape,
|
|
928
|
+
FabExtractKnowledgeOutputSchema,
|
|
929
|
+
fabExtractKnowledgeAnnotations,
|
|
930
|
+
FabReviewInputSchema,
|
|
931
|
+
FabReviewInputShape,
|
|
932
|
+
FabReviewOutputSchema,
|
|
933
|
+
FabReviewOutputShape,
|
|
934
|
+
fabReviewAnnotations,
|
|
935
|
+
citeContractMetricsSchema,
|
|
936
|
+
citeLayerTypeBreakdownSchema,
|
|
937
|
+
citeCoverageReportSchema,
|
|
938
|
+
ledgerSourceSchema,
|
|
939
|
+
ledgerQuerySchema,
|
|
940
|
+
historyStateQuerySchema,
|
|
941
|
+
humanLockApproveRequestSchema,
|
|
942
|
+
humanLockFileParamsSchema,
|
|
943
|
+
annotateIntentRequestSchema,
|
|
944
|
+
KnowledgeTypeSchema,
|
|
945
|
+
MaturitySchema,
|
|
946
|
+
LayerSchema,
|
|
947
|
+
StableIdSchema,
|
|
948
|
+
KnowledgeEntryFrontmatterSchema,
|
|
949
|
+
KNOWLEDGE_TYPE_CODES,
|
|
950
|
+
formatKnowledgeId,
|
|
951
|
+
parseKnowledgeId
|
|
952
|
+
};
|