oh-my-claude-sisyphus 3.7.0 → 3.7.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/README.md +24 -1
- package/commands/hud.md +37 -5
- package/commands/omc-setup.md +105 -0
- package/dist/__tests__/hud/analytics-display.test.js +137 -1
- package/dist/__tests__/hud/analytics-display.test.js.map +1 -1
- package/dist/__tests__/hud-windows.test.d.ts +2 -0
- package/dist/__tests__/hud-windows.test.d.ts.map +1 -0
- package/dist/__tests__/hud-windows.test.js +91 -0
- package/dist/__tests__/hud-windows.test.js.map +1 -0
- package/dist/features/rate-limit-wait/daemon.d.ts.map +1 -1
- package/dist/features/rate-limit-wait/daemon.js +41 -1
- package/dist/features/rate-limit-wait/daemon.js.map +1 -1
- package/dist/features/state-manager/index.d.ts.map +1 -1
- package/dist/features/state-manager/index.js +4 -1
- package/dist/features/state-manager/index.js.map +1 -1
- package/dist/hooks/permission-handler/__tests__/index.test.js +47 -0
- package/dist/hooks/permission-handler/__tests__/index.test.js.map +1 -1
- package/dist/hooks/permission-handler/index.d.ts +1 -1
- package/dist/hooks/permission-handler/index.d.ts.map +1 -1
- package/dist/hooks/permission-handler/index.js +11 -15
- package/dist/hooks/permission-handler/index.js.map +1 -1
- package/dist/hooks/plugin-patterns/index.d.ts +5 -0
- package/dist/hooks/plugin-patterns/index.d.ts.map +1 -1
- package/dist/hooks/plugin-patterns/index.js +26 -1
- package/dist/hooks/plugin-patterns/index.js.map +1 -1
- package/dist/hooks/session-end/index.d.ts +0 -8
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +5 -12
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/index.js +32 -17
- package/dist/hooks/subagent-tracker/index.js.map +1 -1
- package/dist/hud/analytics-display.d.ts +16 -0
- package/dist/hud/analytics-display.d.ts.map +1 -1
- package/dist/hud/analytics-display.js +35 -9
- package/dist/hud/analytics-display.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +49 -18
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/types.d.ts +2 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +14 -0
- package/dist/hud/types.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +3 -2
- package/dist/installer/index.js.map +1 -1
- package/hooks/keyword-detector.sh +4 -4
- package/hooks/persistent-mode.sh +10 -10
- package/hooks/session-start.sh +4 -4
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +4 -4
- package/scripts/persistent-mode.mjs +6 -6
- package/scripts/persistent-mode.sh +10 -10
- package/scripts/session-start.mjs +4 -4
- package/skills/hud/SKILL.md +37 -5
- package/skills/omc-setup/SKILL.md +162 -4
- package/skills/writer-memory/SKILL.md +443 -0
- package/skills/writer-memory/lib/character-tracker.ts +338 -0
- package/skills/writer-memory/lib/memory-manager.ts +804 -0
- package/skills/writer-memory/lib/relationship-graph.ts +400 -0
- package/skills/writer-memory/lib/scene-organizer.ts +544 -0
- package/skills/writer-memory/lib/synopsis-builder.ts +339 -0
- package/skills/writer-memory/templates/synopsis-template.md +46 -0
- package/templates/hooks/keyword-detector.sh +4 -4
- package/templates/hooks/persistent-mode.sh +10 -10
- package/templates/hooks/session-start.sh +4 -4
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { loadMemory, saveMemory, findSceneById, findScenesByCharacter, generateId, now } from './memory-manager';
|
|
2
|
+
import type { Scene, Cut, WriterMemory } from './memory-manager';
|
|
3
|
+
|
|
4
|
+
// === Korean Emotion Vocabulary ===
|
|
5
|
+
const EMOTION_VOCABULARY: string[] = [
|
|
6
|
+
"긴장", "설렘", "불안", "평온", "갈등",
|
|
7
|
+
"슬픔", "기쁨", "분노", "체념", "희망",
|
|
8
|
+
"외로움", "그리움", "애틋함", "당혹", "환희",
|
|
9
|
+
"공포", "안도", "후회", "결의", "허탈"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const CUT_TYPE_LABELS: Record<string, string> = {
|
|
13
|
+
dialogue: "대사",
|
|
14
|
+
narration: "내레이션",
|
|
15
|
+
action: "액션",
|
|
16
|
+
internal: "내면"
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// === Type Definitions ===
|
|
20
|
+
export interface SceneSummary {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
chapter?: string;
|
|
24
|
+
order: number;
|
|
25
|
+
characterCount: number;
|
|
26
|
+
cutCount: number;
|
|
27
|
+
emotionTags: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SceneFlowEntry {
|
|
31
|
+
order: number;
|
|
32
|
+
title: string;
|
|
33
|
+
chapter?: string;
|
|
34
|
+
primaryEmotion: string;
|
|
35
|
+
characters: string[];
|
|
36
|
+
cutCount: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// === Scene CRUD ===
|
|
40
|
+
|
|
41
|
+
export function addScene(title: string, options?: {
|
|
42
|
+
chapter?: string;
|
|
43
|
+
characters?: string[];
|
|
44
|
+
emotionTags?: string[];
|
|
45
|
+
narrationTone?: string;
|
|
46
|
+
notes?: string;
|
|
47
|
+
}): Scene | null {
|
|
48
|
+
try {
|
|
49
|
+
const memory = loadMemory();
|
|
50
|
+
|
|
51
|
+
const newScene: Scene = {
|
|
52
|
+
id: generateId('scene'),
|
|
53
|
+
title,
|
|
54
|
+
chapter: options?.chapter,
|
|
55
|
+
characters: options?.characters || [],
|
|
56
|
+
emotionTags: options?.emotionTags || [],
|
|
57
|
+
cuts: [],
|
|
58
|
+
narrationTone: options?.narrationTone || '',
|
|
59
|
+
notes: options?.notes || '',
|
|
60
|
+
order: memory.scenes.length,
|
|
61
|
+
created: now()
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
memory.scenes.push(newScene);
|
|
65
|
+
saveMemory(memory);
|
|
66
|
+
|
|
67
|
+
return newScene;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Failed to add scene:', error);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function updateScene(sceneId: string, updates: Partial<Scene>): Scene | null {
|
|
75
|
+
try {
|
|
76
|
+
const memory = loadMemory();
|
|
77
|
+
const scene = findSceneById(memory, sceneId);
|
|
78
|
+
|
|
79
|
+
if (!scene) {
|
|
80
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Apply updates (preserve immutable fields)
|
|
85
|
+
Object.assign(scene, {
|
|
86
|
+
...updates,
|
|
87
|
+
id: scene.id,
|
|
88
|
+
created: scene.created
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
saveMemory(memory);
|
|
92
|
+
return scene;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Failed to update scene:', error);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function removeScene(sceneId: string): boolean {
|
|
100
|
+
try {
|
|
101
|
+
const memory = loadMemory();
|
|
102
|
+
const index = memory.scenes.findIndex(s => s.id === sceneId);
|
|
103
|
+
|
|
104
|
+
if (index === -1) {
|
|
105
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
memory.scenes.splice(index, 1);
|
|
110
|
+
|
|
111
|
+
// Reorder remaining scenes
|
|
112
|
+
memory.scenes.forEach((scene, idx) => {
|
|
113
|
+
scene.order = idx;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
saveMemory(memory);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Failed to remove scene:', error);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getScene(sceneId: string): Scene | null {
|
|
125
|
+
try {
|
|
126
|
+
const memory = loadMemory();
|
|
127
|
+
return findSceneById(memory, sceneId);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to get scene:', error);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function listScenes(options?: {
|
|
135
|
+
chapter?: string;
|
|
136
|
+
character?: string;
|
|
137
|
+
emotionTag?: string;
|
|
138
|
+
}): SceneSummary[] {
|
|
139
|
+
try {
|
|
140
|
+
const memory = loadMemory();
|
|
141
|
+
let scenes = [...memory.scenes];
|
|
142
|
+
|
|
143
|
+
// Apply filters
|
|
144
|
+
if (options?.chapter) {
|
|
145
|
+
scenes = scenes.filter(s => s.chapter === options.chapter);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (options?.character) {
|
|
149
|
+
scenes = scenes.filter(s => s.characters.includes(options.character!));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options?.emotionTag) {
|
|
153
|
+
scenes = scenes.filter(s => s.emotionTags.includes(options.emotionTag!));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Sort by order
|
|
157
|
+
scenes.sort((a, b) => a.order - b.order);
|
|
158
|
+
|
|
159
|
+
// Convert to summaries
|
|
160
|
+
return scenes.map(scene => ({
|
|
161
|
+
id: scene.id,
|
|
162
|
+
title: scene.title,
|
|
163
|
+
chapter: scene.chapter,
|
|
164
|
+
order: scene.order,
|
|
165
|
+
characterCount: scene.characters.length,
|
|
166
|
+
cutCount: scene.cuts.length,
|
|
167
|
+
emotionTags: scene.emotionTags
|
|
168
|
+
}));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to list scenes:', error);
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// === Cut Management (콘티 컷) ===
|
|
176
|
+
|
|
177
|
+
export function addCut(sceneId: string, cut: {
|
|
178
|
+
type: "dialogue" | "narration" | "action" | "internal";
|
|
179
|
+
content: string;
|
|
180
|
+
character?: string;
|
|
181
|
+
emotionTag?: string;
|
|
182
|
+
}): boolean {
|
|
183
|
+
try {
|
|
184
|
+
const memory = loadMemory();
|
|
185
|
+
const scene = findSceneById(memory, sceneId);
|
|
186
|
+
|
|
187
|
+
if (!scene) {
|
|
188
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const newCut: Cut = {
|
|
193
|
+
order: scene.cuts.length,
|
|
194
|
+
type: cut.type,
|
|
195
|
+
content: cut.content,
|
|
196
|
+
character: cut.character,
|
|
197
|
+
emotionTag: cut.emotionTag
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
scene.cuts.push(newCut);
|
|
201
|
+
|
|
202
|
+
saveMemory(memory);
|
|
203
|
+
return true;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('Failed to add cut:', error);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function updateCut(sceneId: string, cutOrder: number, updates: Partial<Cut>): boolean {
|
|
211
|
+
try {
|
|
212
|
+
const memory = loadMemory();
|
|
213
|
+
const scene = findSceneById(memory, sceneId);
|
|
214
|
+
|
|
215
|
+
if (!scene) {
|
|
216
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const cut = scene.cuts.find(c => c.order === cutOrder);
|
|
221
|
+
|
|
222
|
+
if (!cut) {
|
|
223
|
+
console.error(`Cut not found: order ${cutOrder} in scene ${sceneId}`);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Apply updates (preserve order)
|
|
228
|
+
Object.assign(cut, {
|
|
229
|
+
...updates,
|
|
230
|
+
order: cut.order
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
saveMemory(memory);
|
|
234
|
+
return true;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('Failed to update cut:', error);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function removeCut(sceneId: string, cutOrder: number): boolean {
|
|
242
|
+
try {
|
|
243
|
+
const memory = loadMemory();
|
|
244
|
+
const scene = findSceneById(memory, sceneId);
|
|
245
|
+
|
|
246
|
+
if (!scene) {
|
|
247
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const index = scene.cuts.findIndex(c => c.order === cutOrder);
|
|
252
|
+
|
|
253
|
+
if (index === -1) {
|
|
254
|
+
console.error(`Cut not found: order ${cutOrder} in scene ${sceneId}`);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
scene.cuts.splice(index, 1);
|
|
259
|
+
|
|
260
|
+
// Reorder remaining cuts
|
|
261
|
+
scene.cuts.forEach((cut, idx) => {
|
|
262
|
+
cut.order = idx;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
saveMemory(memory);
|
|
266
|
+
return true;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Failed to remove cut:', error);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function reorderCuts(sceneId: string, newOrder: number[]): boolean {
|
|
274
|
+
try {
|
|
275
|
+
const memory = loadMemory();
|
|
276
|
+
const scene = findSceneById(memory, sceneId);
|
|
277
|
+
|
|
278
|
+
if (!scene) {
|
|
279
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (newOrder.length !== scene.cuts.length) {
|
|
284
|
+
console.error('New order length does not match cuts length');
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate all indices are present
|
|
289
|
+
const sortedOrder = [...newOrder].sort((a, b) => a - b);
|
|
290
|
+
for (let i = 0; i < sortedOrder.length; i++) {
|
|
291
|
+
if (sortedOrder[i] !== i) {
|
|
292
|
+
console.error('Invalid order array: must contain all indices 0 to n-1');
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Reorder cuts
|
|
298
|
+
const reorderedCuts: Cut[] = newOrder.map(oldIdx => scene.cuts[oldIdx]);
|
|
299
|
+
reorderedCuts.forEach((cut, newIdx) => {
|
|
300
|
+
cut.order = newIdx;
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
scene.cuts = reorderedCuts;
|
|
304
|
+
|
|
305
|
+
saveMemory(memory);
|
|
306
|
+
return true;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error('Failed to reorder cuts:', error);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// === Emotion Tags ===
|
|
314
|
+
|
|
315
|
+
export function addEmotionTag(sceneId: string, tag: string): boolean {
|
|
316
|
+
try {
|
|
317
|
+
const memory = loadMemory();
|
|
318
|
+
const scene = findSceneById(memory, sceneId);
|
|
319
|
+
|
|
320
|
+
if (!scene) {
|
|
321
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (scene.emotionTags.includes(tag)) {
|
|
326
|
+
console.warn(`Emotion tag already exists: ${tag}`);
|
|
327
|
+
return true; // Not an error
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
scene.emotionTags.push(tag);
|
|
331
|
+
|
|
332
|
+
saveMemory(memory);
|
|
333
|
+
return true;
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('Failed to add emotion tag:', error);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function removeEmotionTag(sceneId: string, tag: string): boolean {
|
|
341
|
+
try {
|
|
342
|
+
const memory = loadMemory();
|
|
343
|
+
const scene = findSceneById(memory, sceneId);
|
|
344
|
+
|
|
345
|
+
if (!scene) {
|
|
346
|
+
console.error(`Scene not found: ${sceneId}`);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const index = scene.emotionTags.indexOf(tag);
|
|
351
|
+
|
|
352
|
+
if (index === -1) {
|
|
353
|
+
console.warn(`Emotion tag not found: ${tag}`);
|
|
354
|
+
return true; // Not an error
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
scene.emotionTags.splice(index, 1);
|
|
358
|
+
|
|
359
|
+
saveMemory(memory);
|
|
360
|
+
return true;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Failed to remove emotion tag:', error);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function getScenesByEmotion(emotionTag: string): Scene[] {
|
|
368
|
+
try {
|
|
369
|
+
const memory = loadMemory();
|
|
370
|
+
return memory.scenes
|
|
371
|
+
.filter(scene => scene.emotionTags.includes(emotionTag))
|
|
372
|
+
.sort((a, b) => a.order - b.order);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error('Failed to get scenes by emotion:', error);
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getAllEmotionTags(): { tag: string; count: number }[] {
|
|
380
|
+
try {
|
|
381
|
+
const memory = loadMemory();
|
|
382
|
+
const tagCounts = new Map<string, number>();
|
|
383
|
+
|
|
384
|
+
memory.scenes.forEach(scene => {
|
|
385
|
+
scene.emotionTags.forEach(tag => {
|
|
386
|
+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return Array.from(tagCounts.entries())
|
|
391
|
+
.map(([tag, count]) => ({ tag, count }))
|
|
392
|
+
.sort((a, b) => b.count - a.count);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error('Failed to get all emotion tags:', error);
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// === Scene Organization ===
|
|
400
|
+
|
|
401
|
+
export function reorderScenes(sceneIds: string[]): boolean {
|
|
402
|
+
try {
|
|
403
|
+
const memory = loadMemory();
|
|
404
|
+
|
|
405
|
+
if (sceneIds.length !== memory.scenes.length) {
|
|
406
|
+
console.error('Scene IDs length does not match scenes length');
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Validate all IDs exist
|
|
411
|
+
const sceneMap = new Map(memory.scenes.map(s => [s.id, s]));
|
|
412
|
+
for (const id of sceneIds) {
|
|
413
|
+
if (!sceneMap.has(id)) {
|
|
414
|
+
console.error(`Scene not found: ${id}`);
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Reorder scenes
|
|
420
|
+
memory.scenes = sceneIds.map(id => sceneMap.get(id)!);
|
|
421
|
+
memory.scenes.forEach((scene, idx) => {
|
|
422
|
+
scene.order = idx;
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
saveMemory(memory);
|
|
426
|
+
return true;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error('Failed to reorder scenes:', error);
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function getSceneFlow(): SceneFlowEntry[] {
|
|
434
|
+
try {
|
|
435
|
+
const memory = loadMemory();
|
|
436
|
+
|
|
437
|
+
return memory.scenes
|
|
438
|
+
.sort((a, b) => a.order - b.order)
|
|
439
|
+
.map(scene => ({
|
|
440
|
+
order: scene.order + 1, // 1-indexed for display
|
|
441
|
+
title: scene.title,
|
|
442
|
+
chapter: scene.chapter,
|
|
443
|
+
primaryEmotion: scene.emotionTags[0] || "감정 미설정",
|
|
444
|
+
characters: scene.characters,
|
|
445
|
+
cutCount: scene.cuts.length
|
|
446
|
+
}));
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error('Failed to get scene flow:', error);
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// === Scene Profile Generation ===
|
|
454
|
+
|
|
455
|
+
export function generateSceneProfile(sceneId: string): string {
|
|
456
|
+
try {
|
|
457
|
+
const scene = getScene(sceneId);
|
|
458
|
+
|
|
459
|
+
if (!scene) {
|
|
460
|
+
return `# 오류: 장면을 찾을 수 없습니다 (${sceneId})`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let profile = `# 장면: ${scene.title}\n\n`;
|
|
464
|
+
|
|
465
|
+
if (scene.chapter) {
|
|
466
|
+
profile += `**챕터**: ${scene.chapter}\n`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (scene.characters.length > 0) {
|
|
470
|
+
profile += `**등장인물**: ${scene.characters.join(', ')}\n`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (scene.emotionTags.length > 0) {
|
|
474
|
+
profile += `**감정 태그**: ${scene.emotionTags.join(', ')}\n`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (scene.narrationTone) {
|
|
478
|
+
profile += `**내레이션 톤**: ${scene.narrationTone}\n`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (scene.notes) {
|
|
482
|
+
profile += `\n**노트**: ${scene.notes}\n`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
profile += `\n## 컷 구성\n\n`;
|
|
486
|
+
|
|
487
|
+
if (scene.cuts.length === 0) {
|
|
488
|
+
profile += `*(컷이 아직 추가되지 않았습니다)*\n`;
|
|
489
|
+
} else {
|
|
490
|
+
scene.cuts.forEach(cut => {
|
|
491
|
+
const typeLabel = CUT_TYPE_LABELS[cut.type] || cut.type;
|
|
492
|
+
const charPart = cut.character ? `/${cut.character}` : '';
|
|
493
|
+
const emotionPart = cut.emotionTag ? ` (감정: ${cut.emotionTag})` : '';
|
|
494
|
+
|
|
495
|
+
profile += `${cut.order + 1}. [${typeLabel}${charPart}] ${cut.content}${emotionPart}\n`;
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return profile;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error('Failed to generate scene profile:', error);
|
|
502
|
+
return `# 오류: 장면 프로필 생성 실패`;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function generateSceneList(): string {
|
|
507
|
+
try {
|
|
508
|
+
const memory = loadMemory();
|
|
509
|
+
|
|
510
|
+
let list = `## 전체 장면 목록\n\n`;
|
|
511
|
+
list += `| # | 제목 | 챕터 | 감정 | 등장인물 | 컷 수 |\n`;
|
|
512
|
+
list += `|---|------|------|------|---------|-------|\n`;
|
|
513
|
+
|
|
514
|
+
if (memory.scenes.length === 0) {
|
|
515
|
+
list += `| - | *(장면이 아직 추가되지 않았습니다)* | - | - | - | - |\n`;
|
|
516
|
+
return list;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const sortedScenes = [...memory.scenes].sort((a, b) => a.order - b.order);
|
|
520
|
+
|
|
521
|
+
sortedScenes.forEach(scene => {
|
|
522
|
+
const sceneNum = scene.order + 1;
|
|
523
|
+
const title = scene.title;
|
|
524
|
+
const chapter = scene.chapter || '-';
|
|
525
|
+
const emotions = scene.emotionTags.length > 0
|
|
526
|
+
? scene.emotionTags.join(', ')
|
|
527
|
+
: '-';
|
|
528
|
+
const characters = scene.characters.length > 0
|
|
529
|
+
? scene.characters.join(', ')
|
|
530
|
+
: '-';
|
|
531
|
+
const cutCount = scene.cuts.length;
|
|
532
|
+
|
|
533
|
+
list += `| ${sceneNum} | ${title} | ${chapter} | ${emotions} | ${characters} | ${cutCount} |\n`;
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
return list;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error('Failed to generate scene list:', error);
|
|
539
|
+
return `## 오류: 장면 목록 생성 실패`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Export emotion vocabulary for external use
|
|
544
|
+
export { EMOTION_VOCABULARY, CUT_TYPE_LABELS };
|