@cyber-dash-tech/revela 0.15.4 → 0.16.1

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.
@@ -17,6 +17,13 @@ export interface CompileDeckPlanResult {
17
17
  narrativeHash: string
18
18
  slideCount: number
19
19
  slides: SlideSpec[]
20
+ qualityChecks?: DeckPlanQualityCheck[]
21
+ }
22
+
23
+ export interface DeckPlanQualityCheck {
24
+ id: string
25
+ status: "pass" | "warning" | "blocker"
26
+ message: string
20
27
  }
21
28
 
22
29
  export function compileDeckPlanFromNarrative(state: DecksState, options: CompileDeckPlanOptions = {}): { state: DecksState; result: CompileDeckPlanResult } {
@@ -41,6 +48,8 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
41
48
  const deck = deckKey ? state.decks[deckKey] : undefined
42
49
  const slug = deck?.slug ?? state.activeDeck ?? "deck"
43
50
  const slides = buildSlides(narrative)
51
+ const qualityChecks = checkPlanQuality(narrative, slides)
52
+ const planCoverage = deckPlanCoverage(narrative, slides)
44
53
  const requiredInputs: Partial<RequiredInputs> = {
45
54
  topicClarified: true,
46
55
  audienceClarified: Boolean(narrative.audience.primary),
@@ -70,6 +79,7 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
70
79
  status: "pending",
71
80
  narrativeHash,
72
81
  planHash: deckPlanHash(plannedDeck.slides),
82
+ qualityChecks,
73
83
  }
74
84
  plannedDeck.requiredInputs = { ...plannedDeck.requiredInputs, slidePlanConfirmed: false }
75
85
  plannedDeck.writeReadiness = { status: "blocked", blockers: [] }
@@ -81,6 +91,10 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
81
91
  ...(htmlTarget.data ?? {}),
82
92
  narrativeId: narrative.id,
83
93
  narrativeHash,
94
+ planQualityChecks: qualityChecks,
95
+ requiredClaimIds: planCoverage.requiredClaimIds,
96
+ coveredClaimIds: planCoverage.coveredClaimIds,
97
+ missingClaimIds: planCoverage.missingClaimIds,
84
98
  claimSlideRefs: getClaimSlideRefs(next).map((ref) => ({
85
99
  claimId: ref.claimId,
86
100
  claimText: ref.claimText,
@@ -95,35 +109,50 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
95
109
 
96
110
  return {
97
111
  state: next,
98
- result: {
99
- compiled: true,
100
- skipped: false,
101
- narrativeHash,
102
- slideCount: slides.length,
103
- slides,
104
- },
105
- }
112
+ result: {
113
+ compiled: true,
114
+ skipped: false,
115
+ narrativeHash,
116
+ slideCount: slides.length,
117
+ slides,
118
+ qualityChecks,
119
+ },
120
+ }
106
121
  }
107
122
 
108
123
  function buildSlides(narrative: NarrativeStateV1): SlideSpec[] {
109
124
  const slides: SlideSpec[] = []
110
- const centralClaims = narrative.claims.filter((claim) => claim.importance === "central")
111
- const supportingClaims = narrative.claims.filter((claim) => claim.importance !== "central")
112
- const evidenceByClaim = new Map<string, NarrativeEvidenceBinding[]>()
113
- for (const binding of narrative.evidenceBindings) {
114
- const list = evidenceByClaim.get(binding.claimId) ?? []
115
- list.push(binding)
116
- evidenceByClaim.set(binding.claimId, list)
125
+ const evidenceByClaim = evidenceBindingsByClaim(narrative.evidenceBindings)
126
+ const centralClaims = orderedClaims(narrative, (claim) => claim.importance === "central")
127
+ const supportingClaims = orderedClaims(narrative, (claim) => claim.importance !== "central")
128
+ const chapters = deriveChapters(narrative, centralClaims, supportingClaims)
129
+
130
+ slides.push(coverSlide(slides.length + 1, narrative))
131
+ slides.push(tocSlide(slides.length + 1, chapters))
132
+
133
+ for (const claim of centralClaims) {
134
+ slides.push(claimSlide(slides.length + 1, claim, evidenceByClaim.get(claim.id) ?? []))
117
135
  }
136
+ if (supportingClaims.length > 0) slides.push(supportingLogicSlide(slides.length + 1, supportingClaims, evidenceByClaim))
118
137
 
119
- slides.push({
120
- index: slides.length + 1,
138
+ if (narrative.risks.length > 0 || narrative.objections.length > 0) {
139
+ slides.push(riskObjectionSlide(slides.length + 1, narrative))
140
+ }
141
+
142
+ slides.push(decisionAskSlide(slides.length + 1, narrative))
143
+
144
+ return slides
145
+ }
146
+
147
+ function coverSlide(index: number, narrative: NarrativeStateV1): SlideSpec {
148
+ return {
149
+ index,
121
150
  title: "Decision Context",
122
151
  purpose: "Frame the audience belief shift and decision required before presenting the recommendation.",
123
152
  narrativeRole: "context",
124
153
  layout: "cover",
125
154
  qa: false,
126
- components: [],
155
+ components: ["hero", "text-panel"],
127
156
  content: {
128
157
  headline: narrative.thesis?.statement || narrative.decision.action || "Narrative context",
129
158
  body: [
@@ -134,76 +163,25 @@ function buildSlides(narrative: NarrativeStateV1): SlideSpec[] {
134
163
  },
135
164
  evidence: [],
136
165
  status: "planned",
137
- })
138
-
139
- for (const claim of centralClaims) slides.push(claimSlide(slides.length + 1, claim, evidenceByClaim.get(claim.id) ?? []))
140
- if (supportingClaims.length > 0) {
141
- const supportingBindings = supportingClaims.flatMap((claim) => evidenceByClaim.get(claim.id) ?? [])
142
- slides.push({
143
- index: slides.length + 1,
144
- title: "Supporting Logic",
145
- purpose: "Connect supporting claims to the central recommendation without overloading the main proof slides.",
146
- narrativeRole: "evidence",
147
- layout: "card-grid",
148
- qa: true,
149
- components: ["card"],
150
- claimIds: supportingClaims.map((claim) => claim.id),
151
- claimRefs: supportingClaims.map((claim) => ({ claimId: claim.id, role: "supporting" as const })),
152
- evidenceBindingIds: supportingBindings.map((binding) => binding.id),
153
- content: {
154
- headline: "Supporting claims and boundaries",
155
- bullets: supportingClaims.slice(0, 5).map((claim) => claim.text),
156
- },
157
- evidence: supportingBindings.map(evidenceRefFromBinding),
158
- status: "planned",
159
- })
160
- }
161
-
162
- if (narrative.risks.length > 0 || narrative.objections.length > 0) {
163
- const challengedClaimRefs = [
164
- ...narrative.risks.map((risk) => risk.claimId ? { claimId: risk.claimId, role: "risk" as const } : undefined).filter((ref): ref is { claimId: string; role: "risk" } => Boolean(ref)),
165
- ...narrative.objections.map((objection) => objection.claimId ? { claimId: objection.claimId, role: "objection" as const } : undefined).filter((ref): ref is { claimId: string; role: "objection" } => Boolean(ref)),
166
- ]
167
- const challengedClaimIds = [...new Set(challengedClaimRefs.map((ref) => ref.claimId))]
168
- slides.push({
169
- index: slides.length + 1,
170
- title: "Risks And Objections",
171
- purpose: "Make caveats and stakeholder objections visible before asking for a decision.",
172
- narrativeRole: "risk",
173
- layout: "two-col",
174
- qa: true,
175
- components: ["card"],
176
- claimIds: challengedClaimIds,
177
- claimRefs: dedupeClaimRefs(challengedClaimRefs),
178
- content: {
179
- headline: "What could break the recommendation",
180
- bullets: [
181
- ...narrative.risks.slice(0, 3).map((risk) => risk.mitigation ? `${risk.text} Mitigation: ${risk.mitigation}` : risk.text),
182
- ...narrative.objections.slice(0, 3).map((objection) => objection.response ? `${objection.text} Response: ${objection.response}` : objection.text),
183
- ],
184
- },
185
- evidence: [],
186
- status: "planned",
187
- })
188
166
  }
167
+ }
189
168
 
190
- slides.push({
191
- index: slides.length + 1,
192
- title: "Decision Ask",
193
- purpose: "Close with the explicit decision or action requested from the audience.",
194
- narrativeRole: "ask",
195
- layout: "closing",
169
+ function tocSlide(index: number, chapters: string[]): SlideSpec {
170
+ return {
171
+ index,
172
+ title: "Storyline",
173
+ purpose: "Preview the deterministic chapter structure compiled from the approved narrative state.",
174
+ narrativeRole: "context",
175
+ layout: "toc",
196
176
  qa: false,
197
- components: [],
177
+ components: ["toc", "text-panel"],
198
178
  content: {
199
- headline: narrative.decision.action || "Confirm the decision",
200
- bullets: narrative.decision.consequenceOfNoDecision ? [`If no decision: ${narrative.decision.consequenceOfNoDecision}`] : [],
179
+ headline: "How the decision story is organized",
180
+ bullets: chapters,
201
181
  },
202
182
  evidence: [],
203
183
  status: "planned",
204
- })
205
-
206
- return slides
184
+ }
207
185
  }
208
186
 
209
187
  function claimSlide(index: number, claim: NarrativeClaim, bindings: NarrativeEvidenceBinding[]): SlideSpec {
@@ -214,19 +192,162 @@ function claimSlide(index: number, claim: NarrativeClaim, bindings: NarrativeEvi
214
192
  narrativeRole: claim.kind === "risk" || claim.kind === "assumption" ? "risk" : claim.kind === "ask" ? "ask" : claim.kind === "recommendation" ? "recommendation" : "evidence",
215
193
  layout: "two-col",
216
194
  qa: true,
217
- components: ["card"],
195
+ components: claimComponents(claim, bindings),
218
196
  claimIds: [claim.id],
219
- claimRefs: [{ claimId: claim.id, role: "primary" }],
197
+ claimRefs: [{ claimId: claim.id, role: "primary", note: claimBoundaryNote(claim) }],
220
198
  evidenceBindingIds: bindings.map((binding) => binding.id),
221
199
  content: {
222
200
  headline: claim.text,
223
- bullets: [claim.supportedScope, claim.unsupportedScope ? `Unsupported scope: ${claim.unsupportedScope}` : undefined, ...(claim.caveats ?? [])].filter((item): item is string => Boolean(item)),
201
+ bullets: claimBullets(claim, bindings),
224
202
  },
225
203
  evidence: bindings.map(evidenceRefFromBinding),
226
204
  status: "planned",
227
205
  }
228
206
  }
229
207
 
208
+ function supportingLogicSlide(index: number, claims: NarrativeClaim[], evidenceByClaim: Map<string, NarrativeEvidenceBinding[]>): SlideSpec {
209
+ const supportingBindings = claims.flatMap((claim) => evidenceByClaim.get(claim.id) ?? [])
210
+ return {
211
+ index,
212
+ title: "Supporting Logic",
213
+ purpose: "Connect supporting and background claims to the central recommendation without overloading the main proof slides.",
214
+ narrativeRole: "evidence",
215
+ layout: "card-grid",
216
+ qa: true,
217
+ components: ["box", "text-panel"],
218
+ claimIds: claims.map((claim) => claim.id),
219
+ claimRefs: claims.map((claim) => ({ claimId: claim.id, role: "supporting" as const, note: claimBoundaryNote(claim) })),
220
+ evidenceBindingIds: supportingBindings.map((binding) => binding.id),
221
+ content: {
222
+ headline: "Supporting claims and boundaries",
223
+ bullets: claims.slice(0, 5).flatMap((claim) => [claim.text, ...claimBoundaryBullets(claim)]).slice(0, 8),
224
+ },
225
+ evidence: supportingBindings.map(evidenceRefFromBinding),
226
+ status: "planned",
227
+ }
228
+ }
229
+
230
+ function riskObjectionSlide(index: number, narrative: NarrativeStateV1): SlideSpec {
231
+ const challengedClaimRefs = [
232
+ ...narrative.risks.map((risk) => risk.claimId ? { claimId: risk.claimId, role: "risk" as const } : undefined).filter((ref): ref is { claimId: string; role: "risk" } => Boolean(ref)),
233
+ ...narrative.objections.map((objection) => objection.claimId ? { claimId: objection.claimId, role: "objection" as const } : undefined).filter((ref): ref is { claimId: string; role: "objection" } => Boolean(ref)),
234
+ ]
235
+ const challengedClaimIds = [...new Set(challengedClaimRefs.map((ref) => ref.claimId))]
236
+ return {
237
+ index,
238
+ title: "Risks And Objections",
239
+ purpose: "Make caveats and stakeholder objections visible before asking for a decision.",
240
+ narrativeRole: "risk",
241
+ layout: "two-col",
242
+ qa: true,
243
+ components: ["box", "text-panel"],
244
+ claimIds: challengedClaimIds,
245
+ claimRefs: dedupeClaimRefs(challengedClaimRefs),
246
+ content: {
247
+ headline: "What could break the recommendation",
248
+ bullets: [
249
+ ...narrative.risks.slice(0, 3).map((risk) => risk.mitigation ? `${risk.text} Mitigation: ${risk.mitigation}` : risk.text),
250
+ ...narrative.objections.slice(0, 3).map((objection) => objection.response ? `${objection.text} Response: ${objection.response}` : objection.text),
251
+ ],
252
+ },
253
+ evidence: [],
254
+ status: "planned",
255
+ }
256
+ }
257
+
258
+ function decisionAskSlide(index: number, narrative: NarrativeStateV1): SlideSpec {
259
+ const askClaims = orderedClaims(narrative, (claim) => claim.kind === "ask" || claim.kind === "recommendation")
260
+ return {
261
+ index,
262
+ title: "Decision Ask",
263
+ purpose: "Close with the explicit decision or action requested from the audience.",
264
+ narrativeRole: "ask",
265
+ layout: "closing",
266
+ qa: false,
267
+ components: ["hero", "text-panel"],
268
+ claimIds: askClaims.map((claim) => claim.id),
269
+ claimRefs: askClaims.map((claim) => ({ claimId: claim.id, role: "primary" as const, note: claimBoundaryNote(claim) })),
270
+ content: {
271
+ headline: narrative.decision.action || "Confirm the decision",
272
+ bullets: [
273
+ narrative.decision.owner ? `Owner: ${narrative.decision.owner}` : undefined,
274
+ narrative.decision.deadline ? `Deadline: ${narrative.decision.deadline}` : undefined,
275
+ narrative.decision.consequenceOfNoDecision ? `If no decision: ${narrative.decision.consequenceOfNoDecision}` : undefined,
276
+ ].filter((item): item is string => Boolean(item)),
277
+ },
278
+ evidence: [],
279
+ status: "planned",
280
+ }
281
+ }
282
+
283
+ function evidenceBindingsByClaim(bindings: NarrativeEvidenceBinding[]): Map<string, NarrativeEvidenceBinding[]> {
284
+ const evidenceByClaim = new Map<string, NarrativeEvidenceBinding[]>()
285
+ for (const binding of bindings) {
286
+ const list = evidenceByClaim.get(binding.claimId) ?? []
287
+ list.push(binding)
288
+ evidenceByClaim.set(binding.claimId, list)
289
+ }
290
+ return evidenceByClaim
291
+ }
292
+
293
+ function orderedClaims(narrative: NarrativeStateV1, predicate: (claim: NarrativeClaim) => boolean): NarrativeClaim[] {
294
+ const sourceOrder = new Map(narrative.claims.map((claim, index) => [claim.id, index]))
295
+ const relationScore = new Map<string, number>()
296
+ for (const relation of narrative.claimRelations ?? []) {
297
+ const delta = relation.relation === "leads_to" ? 3 : relation.relation === "supports" ? 2 : relation.relation === "depends_on" || relation.relation === "constrains" ? 1 : 0
298
+ relationScore.set(relation.toClaimId, (relationScore.get(relation.toClaimId) ?? 0) + delta)
299
+ }
300
+ return narrative.claims
301
+ .filter(predicate)
302
+ .sort((a, b) => (relationScore.get(b.id) ?? 0) - (relationScore.get(a.id) ?? 0) || (sourceOrder.get(a.id) ?? 0) - (sourceOrder.get(b.id) ?? 0))
303
+ }
304
+
305
+ function deriveChapters(narrative: NarrativeStateV1, centralClaims: NarrativeClaim[], supportingClaims: NarrativeClaim[]): string[] {
306
+ const chapters: string[] = []
307
+ addUnique(chapters, narrative.audience.decisionContext ? "Decision context" : "Context and belief shift")
308
+ if (hasClaimKind([...centralClaims, ...supportingClaims], ["problem", "opportunity"])) addUnique(chapters, "Tension and opportunity")
309
+ if (centralClaims.some((claim) => claim.kind === "evidence") || supportingClaims.some((claim) => claim.kind === "evidence")) addUnique(chapters, "Evidence and proof")
310
+ if (centralClaims.some((claim) => claim.kind === "recommendation" || claim.kind === "ask") || narrative.decision.action) addUnique(chapters, "Recommendation and decision")
311
+ if (narrative.risks.length > 0 || narrative.objections.length > 0 || centralClaims.some((claim) => claim.unsupportedScope || (claim.caveats ?? []).length > 0)) addUnique(chapters, "Risks and boundaries")
312
+ addUnique(chapters, "Decision ask")
313
+ if (chapters.length < 3) addUnique(chapters, "Evidence and proof")
314
+ return chapters.slice(0, 5)
315
+ }
316
+
317
+ function addUnique(items: string[], item: string): void {
318
+ if (!items.includes(item)) items.push(item)
319
+ }
320
+
321
+ function hasClaimKind(claims: NarrativeClaim[], kinds: NarrativeClaim["kind"][]): boolean {
322
+ return claims.some((claim) => kinds.includes(claim.kind))
323
+ }
324
+
325
+ function claimComponents(claim: NarrativeClaim, bindings: NarrativeEvidenceBinding[]): string[] {
326
+ if (bindings.some((binding) => binding.quote?.trim())) return ["box", "text-panel", "quote"]
327
+ if (claim.kind === "recommendation" || claim.kind === "ask") return ["box", "text-panel", "steps"]
328
+ return ["box", "text-panel"]
329
+ }
330
+
331
+ function claimBullets(claim: NarrativeClaim, bindings: NarrativeEvidenceBinding[]): string[] {
332
+ return [
333
+ ...claimBoundaryBullets(claim),
334
+ ...bindings.slice(0, 2).map((binding) => binding.supportScope ? `Evidence supports: ${binding.supportScope}` : undefined),
335
+ ].filter((item): item is string => Boolean(item))
336
+ }
337
+
338
+ function claimBoundaryBullets(claim: NarrativeClaim): string[] {
339
+ return [
340
+ claim.supportedScope ? `Supported scope: ${claim.supportedScope}` : undefined,
341
+ claim.unsupportedScope ? `Unsupported scope: ${claim.unsupportedScope}` : undefined,
342
+ ...(claim.caveats ?? []).map((caveat) => `Caveat: ${caveat}`),
343
+ ].filter((item): item is string => Boolean(item))
344
+ }
345
+
346
+ function claimBoundaryNote(claim: NarrativeClaim): string | undefined {
347
+ const notes = claimBoundaryBullets(claim)
348
+ return notes.length > 0 ? notes.join(" ") : undefined
349
+ }
350
+
230
351
  function dedupeClaimRefs<T extends { claimId: string; role: "risk" | "objection" }>(refs: T[]): T[] {
231
352
  const seen = new Set<string>()
232
353
  return refs.filter((ref) => {
@@ -249,6 +370,55 @@ function evidenceRefFromBinding(binding: NarrativeEvidenceBinding): EvidenceRef
249
370
  }
250
371
  }
251
372
 
373
+ function checkPlanQuality(narrative: NarrativeStateV1, slides: SlideSpec[]): DeckPlanQualityCheck[] {
374
+ const coverage = deckPlanCoverage(narrative, slides)
375
+ const centralClaimIds = narrative.claims.filter((claim) => claim.importance === "central").map((claim) => claim.id)
376
+ const missingCentralClaims = centralClaimIds.filter((claimId) => coverage.missingClaimIds.includes(claimId))
377
+ const incompatibleComponents = [...new Set(slides.flatMap((slide) => slide.components).filter((component) => component === "card"))]
378
+
379
+ return [
380
+ {
381
+ id: "toc_present",
382
+ status: slides.some((slide) => slide.components.includes("toc")) ? "pass" : "blocker",
383
+ message: slides.some((slide) => slide.components.includes("toc")) ? "Deck plan includes a deterministic TOC slide." : "Deck plan is missing a TOC slide.",
384
+ },
385
+ {
386
+ id: "closing_ask_present",
387
+ status: slides.some((slide) => slide.narrativeRole === "ask" && slide.title === "Decision Ask") ? "pass" : "blocker",
388
+ message: slides.some((slide) => slide.narrativeRole === "ask" && slide.title === "Decision Ask") ? "Deck plan includes a closing Decision Ask slide." : "Deck plan is missing a closing Decision Ask slide.",
389
+ },
390
+ {
391
+ id: "central_claims_covered",
392
+ status: missingCentralClaims.length === 0 ? "pass" : "blocker",
393
+ message: missingCentralClaims.length === 0 ? "All central claims are covered by planned slides." : `Central claims missing from planned slides: ${missingCentralClaims.join(", ")}`,
394
+ },
395
+ {
396
+ id: "unsupported_central_claims_visible",
397
+ status: narrative.claims.some((claim) => claim.importance === "central" && (claim.unsupportedScope || (claim.caveats ?? []).length > 0)) ? "warning" : "pass",
398
+ message: narrative.claims.some((claim) => claim.importance === "central" && (claim.unsupportedScope || (claim.caveats ?? []).length > 0)) ? "Central claim boundaries are visible and should remain explicit in the rendered artifact." : "No unsupported central claim boundaries were found.",
399
+ },
400
+ {
401
+ id: "simplified_design_grammar",
402
+ status: incompatibleComponents.length === 0 ? "pass" : "blocker",
403
+ message: incompatibleComponents.length === 0 ? "Planned slides use the simplified design grammar." : `Deck plan uses incompatible primary components: ${incompatibleComponents.join(", ")}`,
404
+ },
405
+ ]
406
+ }
407
+
408
+ function deckPlanCoverage(narrative: NarrativeStateV1, slides: SlideSpec[]): { requiredClaimIds: string[]; coveredClaimIds: string[]; missingClaimIds: string[] } {
409
+ const requiredClaimIds = narrative.claims
410
+ .filter((claim) => claim.importance === "central" || claim.evidenceRequired)
411
+ .map((claim) => claim.id)
412
+ .sort()
413
+ const required = new Set(requiredClaimIds)
414
+ const coveredClaimIds = [...new Set(slides.flatMap((slide) => [
415
+ ...(slide.claimIds ?? []),
416
+ ...(slide.claimRefs ?? []).map((ref) => ref.claimId),
417
+ ]).filter((claimId) => required.has(claimId)))].sort()
418
+ const missingClaimIds = requiredClaimIds.filter((claimId) => !coveredClaimIds.includes(claimId))
419
+ return { requiredClaimIds, coveredClaimIds, missingClaimIds }
420
+ }
421
+
252
422
  function titleFromClaim(claim: NarrativeClaim): string {
253
423
  const words = claim.text.split(/\s+/).filter(Boolean).slice(0, 6).join(" ")
254
424
  return words || claim.kind
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.15.4",
3
+ "version": "0.16.1",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -34,6 +34,16 @@ export default tool({
34
34
  risks: tool.schema.string().optional(),
35
35
  researchGaps: tool.schema.string().optional(),
36
36
  coveredSlides: tool.schema.string().optional(),
37
+ storyWorkbench: tool.schema.string().optional(),
38
+ workbenchNote: tool.schema.string().optional(),
39
+ artifactCoverage: tool.schema.string().optional(),
40
+ noRenderTargets: tool.schema.string().optional(),
41
+ nextActions: tool.schema.string().optional(),
42
+ missingClaims: tool.schema.string().optional(),
43
+ affectedClaims: tool.schema.string().optional(),
44
+ affectedSlides: tool.schema.string().optional(),
45
+ notes: tool.schema.string().optional(),
46
+ recommendedNextCommand: tool.schema.string().optional(),
37
47
  noClaims: tool.schema.string().optional(),
38
48
  none: tool.schema.string().optional(),
39
49
  }).optional(),
@@ -49,7 +59,7 @@ export default tool({
49
59
  fromClaimId: tool.schema.string(),
50
60
  toClaimId: tool.schema.string(),
51
61
  relation: tool.schema.enum(["leads_to", "supports", "depends_on", "contrasts_with", "constrains", "answers"]),
52
- displayLabel: tool.schema.string().optional().describe("Display-only localization of an existing canonical relation label. Omit for inferred relations."),
62
+ displayLabel: tool.schema.string().optional().describe("Display-only localization of an existing canonical relation label. Omit for inferred relations."),
53
63
  displayRationale: tool.schema.string().optional().describe("Display-only localization of an existing canonical rationale. Omit when canonical rationale is missing or the relation is inferred."),
54
64
  })).optional(),
55
65
  }).optional().describe("Localized and organized display-only projection. It must not add facts or alter IDs."),