返回 oh-my-ppt
source-grounding.test.ts
根目录 / tests / unit / prompt / source-grounding.test.ts
1 import { describe, expect, it } from 'vitest'
2 import fs from 'fs'
3 import path from 'path'
4
5 const readSource = (relativePath: string): string =>
6 fs.readFileSync(path.join(process.cwd(), relativePath), 'utf-8')
7
8 describe('source-grounded prompt rules', () => {
9 it('parse plan uses single-shot model and outline scan', () => {
10 const source = readSource('src/main/io/document-parse-handlers.ts')
11 const outlineScan = readSource('src/main/io/document-outline-scan.ts')
12
13 expect(source).toContain('single-shot document parsing task')
14 expect(source).toContain('You have no filesystem tools in this call')
15 expect(source).toContain('bounded source preview')
16 expect(source).toContain('MAX_PARSE_SOURCE_PREVIEW_CHARS')
17 expect(source).not.toContain('attachProductSkillsBackend')
18 expect(source).not.toContain('createDeepAgent')
19 expect(source).not.toContain('product_skill_read_file')
20 expect(source).toContain('Do not ask to read the file')
21 expect(source).toContain('Do not write detailed facts')
22 expect(source).toContain('hasOutlinePageCandidateSkeleton')
23 expect(source).not.toContain('rawPageCountInput')
24 expect(source).not.toContain('requestedPageCount')
25 expect(source).not.toContain('userPageCount')
26 expect(source).not.toContain('User-provided page count')
27 expect(source).toContain('runSingleShotDocumentPlanModel')
28 expect(source).toContain('single-shot model invoke')
29 expect(source).toContain('sourcePreviewLength')
30 expect(source).toContain('[documents:parsePlan] end')
31 expect(source).toContain('durationMs')
32 expect(source).toContain('csv converted for reading')
33 expect(source).toContain('normalized candidate plan')
34 expect(source).toContain('document outline page-count estimate')
35 expect(source).toContain('deterministic source-structure page-count estimate')
36 expect(source).toContain('outline quality check failed after retry, rejecting plan')
37 expect(source).toContain('isDocumentOutlineQualityError')
38 expect(source).toContain('Source document path for later generation only')
39 expect(outlineScan).toContain('Document structure scan')
40 expect(outlineScan).toContain('Heading map truncated')
41 expect(outlineScan).toContain('Deterministic slide-count estimate')
42 expect(outlineScan).toContain('deriveOutlinePageCandidates')
43 expect(outlineScan).toContain('Page candidate skeleton')
44 expect(source).toContain('page candidate skeleton')
45 expect(source).toContain('skeleton count')
46 expect(source).toContain('compact page skeleton')
47 expect(source).toContain('source line range')
48 expect(source).toContain('chapter divider slides')
49 expect(source).toContain('页面角色:章节页')
50 expect(source).toContain('assertPlanMatchesDocumentOutline')
51 })
52
53 it('frontend lets document parse infer pageCount from source structure', () => {
54 const sessionCreate = readSource('src/renderer/src/pages/session-create.tsx')
55 const templateUseDialog = readSource(
56 'src/renderer/src/components/templates/TemplateUseDialog.tsx'
57 )
58 const sessionParseCall = sessionCreate.slice(
59 sessionCreate.indexOf('const result = await ipc.parseDocumentPlan({'),
60 sessionCreate.indexOf('const nextSuggestion = {')
61 )
62 const templateParseCall = templateUseDialog.slice(
63 templateUseDialog.indexOf('const result = await ipc.parseDocumentPlan({'),
64 templateUseDialog.indexOf('const referenceFile = result.files[0] || attachedReferenceFile')
65 )
66
67 expect(sessionParseCall).toContain('ipc.parseDocumentPlan')
68 expect(templateParseCall).toContain('ipc.parseDocumentPlan')
69 expect(sessionParseCall).not.toContain('pageCount:')
70 expect(sessionParseCall).not.toContain('resolvePageCount')
71 expect(templateParseCall).not.toContain('pageCount:')
72 expect(templateParseCall).not.toContain('resolvePageCount')
73 })
74
75 it('template document analysis reuses the shared suggestion dialog', () => {
76 const templateUseDialog = readSource(
77 'src/renderer/src/components/templates/TemplateUseDialog.tsx'
78 )
79
80 expect(templateUseDialog).toContain('SessionCreateSuggestionDialog')
81 expect(templateUseDialog).not.toContain('updateDraftSourcePlanItems')
82 expect(templateUseDialog).not.toContain('editingOutlineIndex')
83 expect(templateUseDialog).not.toContain('suggestionCardClass')
84 })
85
86 it('keeps source documents for edits and retries but excludes them from generated add-page', () => {
87 const generationContext = readSource('src/main/generation/context.ts')
88 const sourceDocuments = readSource('src/main/generation/source-documents.ts')
89 const editFlow = readSource('src/main/generation/edit-flow.ts')
90 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
91 const addPageFlow = readSource('src/main/generation/add-page-flow.ts')
92 const retrySinglePageFlow = readSource('src/main/generation/retry-single-page-flow.ts')
93
94 expect(generationContext).toContain(
95 "export { resolveSourceDocuments } from './source-documents'"
96 )
97 expect(sourceDocuments).toContain('appendSourceDocumentPath(resolveExistingSessionDoc')
98 expect(sourceDocuments).toContain('appendSourceDocumentPath(`/docs/${safeName}`)')
99 expect(generationContext).not.toContain("mode === 'edit') return []")
100 expect(generationContext).not.toContain('isFirstDeckGeneration')
101 expect(editFlow).toContain('resolveSourceDocuments')
102 expect(editFlow).toContain('sourceDocumentPaths: context.sourceDocumentPaths')
103 expect(deckAllPageEditFlow).toContain('sourceDocumentPaths: context.sourceDocumentPaths')
104 expect(addPageFlow).not.toContain('resolveSourceDocuments')
105 expect(addPageFlow).not.toContain('context.sourceDocumentPaths')
106 expect(addPageFlow.match(/sourceDocumentPaths: \[\]/g)).toHaveLength(3)
107 expect(retrySinglePageFlow).toContain('resolveSourceDocuments')
108 expect(retrySinglePageFlow).toContain('sourceDocumentPaths: context.sourceDocumentPaths')
109 expect(retrySinglePageFlow).not.toContain('sourceDocumentPaths: []')
110 })
111
112 it('deck all-page edit selected page ids only match file slugs', () => {
113 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
114
115 expect(deckAllPageEditFlow).toContain('requestedPageIdSet.has(ref.pageId)')
116 expect(deckAllPageEditFlow).not.toContain('requestedPageIdSet.has(ref.id)')
117 })
118
119 it('main-session deck edit enforces the shared selected-page limit', () => {
120 const sharedGeneration = readSource('src/shared/generation.ts')
121 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
122 const chatPanel = readSource(
123 'src/renderer/src/components/session-detail/ai-panel/ChatPanel.tsx'
124 )
125
126 expect(sharedGeneration).toContain('export const MAX_SELECTED_PAGES = 50')
127 expect(sharedGeneration).toContain('export const MAX_STYLE_SWITCH_PAGES = 500')
128 expect(sharedGeneration).not.toContain('.slice(0, 200)')
129 expect(deckAllPageEditFlow).toContain(
130 'context.resetVisualStyle ? MAX_STYLE_SWITCH_PAGES : MAX_SELECTED_PAGES'
131 )
132 expect(chatPanel).toContain('effectiveMainPageIds.length >= MAX_SELECTED_PAGES')
133 expect(chatPanel).toContain('pageIds.length > MAX_SELECTED_PAGES')
134 })
135
136 it('deck edit batch aborts finalize the run before rethrowing', () => {
137 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
138
139 expect(deckAllPageEditFlow).toContain('batchResults = await executeDeckEditBatchFlow')
140 expect(deckAllPageEditFlow).toContain(
141 "await db.updateGenerationRunStatus(context.runId, 'failed', message)"
142 )
143 expect(deckAllPageEditFlow).toContain("type: 'run_error'")
144 expect(deckAllPageEditFlow).toContain('throw error')
145 })
146
147 it('deck edit staggers three independent page agents to reduce rate-limit bursts', () => {
148 const batchFlow = readSource('src/main/generation/edit-deck-batch-flow.ts')
149 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
150 const engine = readSource('src/main/generation/agent-runner.ts')
151
152 expect(batchFlow).toContain('export const BATCH_EDIT_LAUNCH_STAGGER_MS = 100')
153 expect(batchFlow).toContain('import pLimit from')
154 expect(batchFlow).toContain('pLimit(BATCH_EDIT_CHUNK_SIZE)')
155 expect(batchFlow).toContain('pageIndex % BATCH_EDIT_CHUNK_SIZE')
156 expect(batchFlow).toContain('Promise.allSettled')
157 expect(deckAllPageEditFlow).toContain('runPageAttempt')
158 expect(deckAllPageEditFlow).toContain('selectPageIds: [pageId]')
159 expect(deckAllPageEditFlow).toContain('isDeckEditRateLimitRetryableError(error)')
160 expect(engine).toContain('setPageAgent(args.sessionId, concurrentDeckPageId, editAgent)')
161 })
162
163 it('keeps generation progress in the local page surfaces without a modal', () => {
164 const chatPanel = readSource(
165 'src/renderer/src/components/session-detail/ai-panel/ChatPanel.tsx'
166 )
167 const previewStage = readSource(
168 'src/renderer/src/components/session-detail/preview/PreviewStage.tsx'
169 )
170 const sessionDetail = readSource('src/renderer/src/pages/session-detail.tsx')
171
172 expect(chatPanel).not.toContain('<Progress value={progress.progress}')
173 expect(chatPanel).toContain('(isPageEditing || isDeckEditing)')
174 expect(previewStage).not.toContain('useGenerationLoading')
175 expect(previewStage).not.toContain('generationLoading')
176 expect(previewStage).toContain('pageEditJob && isPageEditing')
177 expect(sessionDetail).not.toContain('GenerationActivityDialog')
178 expect(sessionDetail).not.toContain('onStyleSwitchCompleted')
179 expect(sessionDetail).not.toContain('<PageProgressOverlay')
180 })
181
182 it('preserves chat messages when generation is cancelled', () => {
183 const sessionStore = readSource('src/renderer/src/store/sessionStore.ts')
184 const sessionDetail = readSource('src/renderer/src/pages/session-detail.tsx')
185 const loadSessionSource = sessionStore.slice(
186 sessionStore.indexOf('loadSession: async'),
187 sessionStore.indexOf('loadMessages: async')
188 )
189
190 expect(loadSessionSource).not.toContain('currentMessages: []')
191 expect(sessionDetail).toContain('if (payload.cancelled)')
192 expect(sessionDetail).toContain('cancelGeneration(payload.message)')
193 })
194
195 it('keeps concurrent deck-page progress on the active page instead of resetting to understanding', () => {
196 const deckAllPageEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
197 const batchFlow = readSource('src/main/generation/edit-deck-batch-flow.ts')
198 const engine = readSource('src/main/generation/agent-runner.ts')
199
200 expect(deckAllPageEditFlow).toContain("'正在准备批量编辑'")
201 expect(engine).toContain('`正在编辑页面 ${concurrentDeckPageId}`')
202 expect(engine).toContain("'正在生成并校验当前页面'")
203 expect(batchFlow).toContain('`正在编辑 P${args.pageNumber}`')
204 })
205
206 it('uses operation-specific progress copy for add-page and failed-page retries', () => {
207 const engine = readSource('src/main/generation/agent-runner.ts')
208 const addPageFlow = readSource('src/main/generation/add-page-flow.ts')
209 const retrySinglePageFlow = readSource('src/main/generation/retry-single-page-flow.ts')
210 const retryFlow = readSource('src/main/generation/retry-flow.ts')
211
212 expect(engine).toContain('renderingLabel?: string')
213 expect(engine).toContain(
214 "const renderingLabel = args.renderingLabel || progressText(args.appLocale, 'generating')"
215 )
216 expect(addPageFlow).toContain("'正在规划新增页面'")
217 expect(addPageFlow).toContain("'正在生成新增页面'")
218 expect(retrySinglePageFlow).toContain('`正在重新生成第 ${context.pageNumber} 页`')
219 expect(retryFlow).toContain('`正在重新生成 ${retryPages.length} 个失败页面`')
220 })
221
222 it('uses successful edit facts for edit replies instead of raw agent/tool output', () => {
223 const deckFlow = readSource('src/main/generation/deck-flow.ts')
224 const editFlow = readSource('src/main/generation/edit-flow.ts')
225 const batchEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
226 const addPageFlow = readSource('src/main/generation/add-page-flow.ts')
227 const retryFlow = readSource('src/main/generation/retry-flow.ts')
228 const retrySinglePageFlow = readSource('src/main/generation/retry-single-page-flow.ts')
229
230 expect(deckFlow).toContain('agentSummary.trim() || fallbackCompletionSummary')
231 expect(editFlow).toContain('emitSuccessfulEditSummary(context, editSummary, emitAssistant)')
232 expect(editFlow).not.toContain('editSummaryFromEngine')
233 expect(batchEditFlow).toContain(
234 'emitSuccessfulEditSummary(context, fallbackEditSummary, emitAssistant)'
235 )
236 expect(batchEditFlow).not.toContain('result.summary')
237 expect(editFlow.lastIndexOf('await db.updateGenerationRunStatus(')).toBeLessThan(
238 editFlow.indexOf('await emitSuccessfulEditSummary(context, editSummary, emitAssistant)')
239 )
240 expect(batchEditFlow.lastIndexOf('await db.updateGenerationRunStatus(')).toBeLessThan(
241 batchEditFlow.indexOf(
242 'await emitSuccessfulEditSummary(context, fallbackEditSummary, emitAssistant)'
243 )
244 )
245 expect(addPageFlow).toContain('agentSummary ||')
246 expect(retryFlow).toContain('agentSummary.trim() || fallbackCompletionSummary')
247 expect(retrySinglePageFlow).toContain('generationResult.summary.trim() ||')
248 expect(editFlow).not.toContain('我准备开始调整')
249 expect(batchEditFlow).not.toContain('我准备按主会话指令调整')
250 })
251
252 it('publishes durable batch page results without stealing preview focus or duplicating summaries', () => {
253 const sharedGeneration = readSource('src/shared/generation.ts')
254 const sessionDetail = readSource('src/renderer/src/pages/session-detail.tsx')
255 const batchEditFlow = readSource('src/main/generation/edit-deck-allpage-flow.ts')
256
257 expect(sharedGeneration).toContain('focusPage?: boolean')
258 expect(sessionDetail).toContain('if (payload.focusPage !== false)')
259 expect(batchEditFlow).toContain('focusPage: false')
260 expect(batchEditFlow).toContain(
261 'emitSuccessfulEditSummary(context, fallbackEditSummary, emitAssistant)'
262 )
263 expect(batchEditFlow.indexOf('await db.upsertSessionPage({')).toBeLessThan(
264 batchEditFlow.indexOf("type: isExisting ? 'page_updated' : 'page_generated'")
265 )
266 expect(batchEditFlow).toContain("status: existing?.status || 'failed'")
267 expect(batchEditFlow).toContain('error: existing?.error || null')
268 expect(batchEditFlow).toContain('resolveRemainingFailedPageInfo({')
269 })
270
271 it('main-session page scope is visible and resets after a successful send', () => {
272 const chatPanel = readSource(
273 'src/renderer/src/components/session-detail/ai-panel/ChatPanel.tsx'
274 )
275 const chatController = readSource(
276 'src/renderer/src/components/session-detail/hooks/useChatPanelController.ts'
277 )
278
279 expect(chatPanel).toContain('mainPageScopeConflictWarning')
280 expect(chatPanel).toContain('if (started) setSelectedMainPageIds([])')
281 expect(chatController).toContain('mainPageScopeMessagePrefix')
282 expect(chatController).toContain('userMessage: scopedMessageContent')
283 expect(chatController).toContain('content: scopedMessageContent')
284 })
285
286 it('deck edit selected single-page scope still uses deck edit tools', () => {
287 const agent = readSource('src/main/agent-runtime/agent/factory.ts')
288 const editSystem = readSource('src/main/agent-runtime/prompt/composers/edit-system.ts')
289 const deckSystem = readSource('src/main/agent-runtime/prompt/composers/deck-system.ts')
290 const deckTools = readSource('src/main/agent-runtime/tools/deck-tools.ts')
291 const editTemplates = [
292 'container.md',
293 'selector.md',
294 'single-page.md',
295 'deck.md'
296 ].map((fileName) =>
297 readSource(`src/main/agent-runtime/prompt/templates/edit-system/${fileName}`)
298 )
299 const templateSource = editTemplates.join('\n')
300
301 expect(agent).toContain("return context.mode === 'edit'")
302 expect(agent).toContain('当前编辑任务禁止使用 write_file')
303 expect(editSystem).toContain('createPromptCatalog<EditSystemTemplateVars>')
304 expect(templateSource).toContain('仅允许调用 set_index_transition(type, durationMs)')
305 expect(templateSource).toContain('read_file target page + grep to locate target → edit_file')
306 expect(templateSource).toContain('update_single_page_file(pageId="{{targetPageId}}"')
307 expect(templateSource).toContain('For each target page: update_page_file(pageId, content)')
308 expect(deckSystem).toContain("context.mode !== 'edit'")
309 expect(deckTools).toContain('!isEditMode &&')
310 })
311
312 it('deck edit prompt applies UI-selected page ids only to deck scope', () => {
313 const editSystem = readSource('src/main/agent-runtime/prompt/composers/edit-system.ts')
314 const selectorPromptSource = editSystem.slice(
315 editSystem.indexOf('function buildSelectorEditPrompt('),
316 editSystem.indexOf('function buildSinglePageEditPrompt(')
317 )
318 const deckPromptSource = editSystem.slice(editSystem.indexOf('function buildDeckEditPrompt('))
319
320 expect(selectorPromptSource).not.toContain('explicitTargetInfo')
321 expect(selectorPromptSource).not.toContain('Selected page ids from UI (hard target)')
322 expect(deckPromptSource).toContain('const explicitTargetInfo =')
323 expect(deckPromptSource).toContain('context.selectPageIds?.length')
324 expect(deckPromptSource).toContain(
325 'Selected page ids from UI (hard target): ${context.selectPageIds.join'
326 )
327 expect(deckPromptSource).toContain("'Target pages: all relevant /<pageId>.html files'")
328 expect(deckPromptSource).toContain(' explicitTargetInfo,')
329 })
330
331 it('edit prompt injects source document rules', () => {
332 const editSystem = readSource('src/main/agent-runtime/prompt/composers/edit-system.ts')
333
334 expect(editSystem).toContain('Source documents (content evidence)')
335 expect(editSystem).toContain('SOURCE_DOCUMENT_READ_STRATEGY')
336 expect(editSystem).toContain('sourceDocumentPaths:')
337 expect(editSystem).toContain('For pure visual/style-only edits')
338 })
339
340 it('planNewPage includes source document context', () => {
341 const engineGenerate = readSource('src/main/generation/agent-runner.ts')
342
343 expect(engineGenerate).toContain('sourceDocumentPaths?: string[]')
344 expect(engineGenerate).toContain('Source document context:')
345 })
346
347 it('single-slide planning can preserve explicit topic lists', () => {
348 const engineGenerate = readSource('src/main/generation/agent-runner.ts')
349 const generationUser = readSource('src/main/agent-runtime/prompt/composers/generation-user.ts')
350 const planningComposer = readSource('src/main/agent-runtime/prompt/composers/planning.ts')
351 const planningTemplate = readSource(
352 'src/main/agent-runtime/prompt/templates/planning/system.md'
353 )
354 const runtimeUserSource = readSource('src/main/agent-runtime/prompt/composers/runtime-user.ts')
355
356 expect(engineGenerate).toContain('keyPoints must contain 1-10 short phrases')
357 expect(engineGenerate).toContain('preserve each listed topic as a separate key point')
358 expect(generationUser).not.toContain('final slide should cover all of them')
359 expect(generationUser).toContain('not as a checklist')
360 expect(generationUser).toContain('Do not duplicate the same source facts')
361 expect(generationUser).toContain('grouping related points')
362 expect(generationUser).toContain('keep them distinct only where the layout allows')
363 expect(planningComposer).toContain('planningPromptCatalog.render')
364 expect(planningTemplate).toContain('Provide 1-10 key points per slide')
365 expect(runtimeUserSource).toContain('keyPoints must contain 1-10 strings')
366 })
367
368 it('blocks generic filler slides during planning', () => {
369 const sharedSource = readSource('src/main/agent-runtime/prompt/composers/shared.ts')
370 const planningComposer = readSource('src/main/agent-runtime/prompt/composers/planning.ts')
371 const planningTemplate = readSource(
372 'src/main/agent-runtime/prompt/templates/planning/system.md'
373 )
374 const runtimeUserSource = readSource('src/main/agent-runtime/prompt/composers/runtime-user.ts')
375
376 expect(planningComposer).toContain('SOURCE_MATERIAL_PLANNING_RULES')
377 expect(sharedSource).toContain('Apply these rules only when source documents')
378 expect(sharedSource).toContain('Stay source-grounded and avoid creative drift')
379 expect(sharedSource).toContain('evidence, not a slide checklist')
380 expect(sharedSource).toContain(
381 'split into multiple slides when one page would become a data dump'
382 )
383 expect(sharedSource).toContain('split source-backed sections')
384 expect(sharedSource).toContain('deepen each slide from the available material')
385 expect(sharedSource).toContain('SOURCE_GROUNDED_EXPANSION_RULES')
386 expect(sharedSource).toContain('actively enrich the slide from the material')
387 expect(sharedSource).toContain('source-grounded does not mean exhaustive')
388 expect(sharedSource).toContain('Do not add generic agenda')
389 expect(planningTemplate).toContain('For open-ended topics without source materials')
390 expect(planningTemplate).not.toContain('split or merge')
391 expect(runtimeUserSource).toContain('hasSourceMaterialCue')
392 expect(runtimeUserSource).toContain('hasSourceMaterials?: boolean')
393 expect(runtimeUserSource).toContain('args.hasSourceMaterials || hasSourceMaterialCue')
394 expect(runtimeUserSource).toContain('SOURCE_MATERIAL_PLANNING_RULES')
395 expect(runtimeUserSource).not.toContain(
396 'Do not reinterpret the reference document into a new creative storyline'
397 )
398 })
399
400 it('requires source inspection before source-backed slide generation', () => {
401 const sharedSource = readSource('src/main/agent-runtime/prompt/composers/shared.ts')
402 const source = readSource('src/main/agent-runtime/prompt/composers/generation-user.ts')
403 const sourceReadingSkill = readSource('resources/skills/oh-my-ppt-source-reading/SKILL.md')
404
405 expect(sharedSource).toContain('SOURCE_READING_SKILL_NAME')
406 expect(sharedSource).toContain('Before using source documents')
407 expect(sharedSource).toContain('Grounding forbids inventing facts the source lacks')
408 expect(sharedSource).not.toContain('Before writing source-backed content')
409 expect(sharedSource).not.toContain('Do not read entire long documents into context at once')
410 expect(sourceReadingSkill).toContain(
411 'Use the DeepAgents filesystem tool `grep(pattern, path, glob)`'
412 )
413 expect(sourceReadingSkill).toContain('Use the DeepAgents filesystem tool `glob(pattern, path)`')
414 expect(sourceReadingSkill).toContain('`pattern` is a literal string')
415 expect(sourceReadingSkill).toContain('Use `read_file` only on targeted sections')
416 expect(sourceReadingSkill).toContain('repeat grep -> targeted read')
417 expect(sourceReadingSkill).toContain('retrieved snippet conflicts with the source passage')
418 expect(sourceReadingSkill).toContain('Slide title: "Q3 Revenue Highlights"')
419 expect(sourceReadingSkill).toContain('Prefer 50-80 lines around grep matches')
420 expect(source).toContain('expansion must be source-grounded')
421 expect(source).toContain('SOURCE_GROUNDED_EXPANSION_RULES')
422 expect(source).toContain('if inspected material is thin, enrich the slide')
423 expect(readSource('src/main/agent-runtime/prompt/composers/deck-system.ts')).toContain(
424 'SOURCE_GROUNDED_EXPANSION_RULES'
425 )
426 expect(readSource('src/main/agent-runtime/prompt/composers/edit-system.ts')).toContain(
427 'SOURCE_GROUNDED_EXPANSION_RULES'
428 )
429 expect(source).toContain('SOURCE_DOCUMENT_FACT_RULE')
430 expect(sharedSource).toContain('examples, risks, decisions, or conclusions')
431 expect(source).not.toContain('first use grep or glob')
432 expect(source).not.toContain('you do not need to reread')
433 })
434
435 it('source-reading skill expands thin pages instead of over-suppressing into sparse slides', () => {
436 const sourceReadingSkill = readSource('resources/skills/oh-my-ppt-source-reading/SKILL.md')
437
438 // The old wording ("build ONLY from inspected passages" / "do not fill gaps")
439 // over-suppressed: with a reference doc present the model rendered bare source
440 // (chart + a couple facts) and left the page blank, because it read the skill as
441 // forbidding any addition. Those over-strict lines are gone.
442 expect(sourceReadingSkill).not.toContain('Build slide content only from inspected')
443 expect(sourceReadingSkill).not.toContain('Do not fill gaps with plausible-sounding')
444
445 // The skill now tells the model to expand a thin page into a full argument with
446 // analytical structure derived from the inspected material.
447 expect(sourceReadingSkill).toContain('A half-empty slide is a failure')
448 expect(sourceReadingSkill).toContain('Expand the slide into a complete argument')
449 expect(sourceReadingSkill).toContain('comparison dimensions')
450
451 // ...while keeping the anti-hallucination core: never invent EXACT facts.
452 expect(sourceReadingSkill).toContain('Do not invent exact facts')
453 })
454 })
455
455 lines TYPESCRIPT