oh-my-claude-sisyphus 3.6.3 → 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 +40 -1
- package/commands/hud.md +37 -5
- package/commands/omc-setup.md +105 -0
- package/dist/__tests__/delegation-enforcement-levels.test.d.ts +9 -0
- package/dist/__tests__/delegation-enforcement-levels.test.d.ts.map +1 -0
- package/dist/__tests__/delegation-enforcement-levels.test.js +550 -0
- package/dist/__tests__/delegation-enforcement-levels.test.js.map +1 -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/__tests__/installer.test.js +1 -1
- package/dist/__tests__/rate-limit-wait/daemon.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.js +313 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/integration.test.d.ts +8 -0
- package/dist/__tests__/rate-limit-wait/integration.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/integration.test.js +329 -0
- package/dist/__tests__/rate-limit-wait/integration.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js +167 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.js +295 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.js.map +1 -0
- package/dist/cli/commands/wait.d.ts +52 -0
- package/dist/cli/commands/wait.d.ts.map +1 -0
- package/dist/cli/commands/wait.js +229 -0
- package/dist/cli/commands/wait.js.map +1 -0
- package/dist/cli/index.js +54 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/features/rate-limit-wait/daemon.d.ts +52 -0
- package/dist/features/rate-limit-wait/daemon.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/daemon.js +585 -0
- package/dist/features/rate-limit-wait/daemon.js.map +1 -0
- package/dist/features/rate-limit-wait/index.d.ts +16 -0
- package/dist/features/rate-limit-wait/index.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/index.js +18 -0
- package/dist/features/rate-limit-wait/index.js.map +1 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.d.ts +22 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.js +99 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.js.map +1 -0
- package/dist/features/rate-limit-wait/tmux-detector.d.ts +59 -0
- package/dist/features/rate-limit-wait/tmux-detector.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/tmux-detector.js +304 -0
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -0
- package/dist/features/rate-limit-wait/types.d.ts +121 -0
- package/dist/features/rate-limit-wait/types.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/types.js +8 -0
- package/dist/features/rate-limit-wait/types.js.map +1 -0
- 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/bridge.d.ts +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +50 -4
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/omc-orchestrator/audit.d.ts +2 -1
- package/dist/hooks/omc-orchestrator/audit.d.ts.map +1 -1
- package/dist/hooks/omc-orchestrator/audit.js.map +1 -1
- package/dist/hooks/omc-orchestrator/index.d.ts +7 -0
- package/dist/hooks/omc-orchestrator/index.d.ts.map +1 -1
- package/dist/hooks/omc-orchestrator/index.js +95 -8
- package/dist/hooks/omc-orchestrator/index.js.map +1 -1
- package/dist/hooks/permission-handler/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/permission-handler/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/permission-handler/__tests__/index.test.js +291 -0
- package/dist/hooks/permission-handler/__tests__/index.test.js.map +1 -0
- package/dist/hooks/permission-handler/index.d.ts +42 -0
- package/dist/hooks/permission-handler/index.d.ts.map +1 -0
- package/dist/hooks/permission-handler/index.js +107 -0
- package/dist/hooks/permission-handler/index.js.map +1 -0
- 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/pre-compact/index.d.ts +82 -0
- package/dist/hooks/pre-compact/index.d.ts.map +1 -0
- package/dist/hooks/pre-compact/index.js +265 -0
- package/dist/hooks/pre-compact/index.js.map +1 -0
- package/dist/hooks/session-end/index.d.ts +42 -0
- package/dist/hooks/session-end/index.d.ts.map +1 -0
- package/dist/hooks/session-end/index.js +200 -0
- package/dist/hooks/session-end/index.js.map +1 -0
- package/dist/hooks/setup/index.d.ts +66 -0
- package/dist/hooks/setup/index.d.ts.map +1 -0
- package/dist/hooks/setup/index.js +299 -0
- package/dist/hooks/setup/index.js.map +1 -0
- package/dist/hooks/setup/types.d.ts +25 -0
- package/dist/hooks/setup/types.d.ts.map +1 -0
- package/dist/hooks/setup/types.js +5 -0
- package/dist/hooks/setup/types.js.map +1 -0
- package/dist/hooks/subagent-tracker/index.d.ts +68 -29
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/index.js +316 -131
- 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 +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +4 -3
- package/dist/installer/index.js.map +1 -1
- package/hooks/hooks.json +83 -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 +3 -1
- package/scripts/keyword-detector.mjs +4 -4
- package/scripts/permission-handler.mjs +23 -0
- package/scripts/persistent-mode.mjs +6 -6
- package/scripts/persistent-mode.sh +10 -10
- package/scripts/pre-compact.mjs +23 -0
- package/scripts/session-end.mjs +23 -0
- package/scripts/session-start.mjs +4 -4
- package/scripts/setup-init.mjs +23 -0
- package/scripts/setup-maintenance.mjs +23 -0
- package/scripts/subagent-tracker.mjs +35 -0
- 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.mjs +198 -0
- package/templates/hooks/keyword-detector.sh +102 -0
- package/templates/hooks/persistent-mode.mjs +249 -0
- package/templates/hooks/persistent-mode.sh +187 -0
- package/templates/hooks/post-tool-use.mjs +133 -0
- package/templates/hooks/post-tool-use.sh +90 -0
- package/templates/hooks/pre-tool-use.mjs +145 -0
- package/templates/hooks/pre-tool-use.sh +113 -0
- package/templates/hooks/session-start.mjs +100 -0
- package/templates/hooks/session-start.sh +62 -0
- package/templates/hooks/stop-continuation.mjs +80 -0
- package/templates/hooks/stop-continuation.sh +40 -0
- package/templates/rules/README.md +40 -0
- package/templates/rules/coding-style.md +74 -0
- package/templates/rules/git-workflow.md +41 -0
- package/templates/rules/performance.md +40 -0
- package/templates/rules/security.md +41 -0
- package/templates/rules/testing.md +42 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character Tracking Module
|
|
3
|
+
* 캐릭터 추적 및 검증 시스템
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadMemory, saveMemory, generateId, now } from './memory-manager';
|
|
7
|
+
import type { Character, EmotionPoint, SpeechLevel, WriterMemory } from './memory-manager';
|
|
8
|
+
|
|
9
|
+
// === Helper to find character ===
|
|
10
|
+
function findCharacter(memory: WriterMemory, nameOrAlias: string): Character | null {
|
|
11
|
+
// Direct lookup
|
|
12
|
+
if (memory.characters[nameOrAlias]) {
|
|
13
|
+
return memory.characters[nameOrAlias];
|
|
14
|
+
}
|
|
15
|
+
// Alias lookup
|
|
16
|
+
for (const char of Object.values(memory.characters)) {
|
|
17
|
+
if (char.aliases?.includes(nameOrAlias)) {
|
|
18
|
+
return char;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// === Character CRUD ===
|
|
25
|
+
|
|
26
|
+
export function addCharacter(name: string, options?: {
|
|
27
|
+
arc?: string;
|
|
28
|
+
tone?: string;
|
|
29
|
+
speechLevel?: SpeechLevel;
|
|
30
|
+
attitude?: string;
|
|
31
|
+
keywords?: string[];
|
|
32
|
+
notes?: string;
|
|
33
|
+
}): Character | null {
|
|
34
|
+
const memory = loadMemory();
|
|
35
|
+
if (!memory) return null;
|
|
36
|
+
|
|
37
|
+
if (memory.characters[name]) {
|
|
38
|
+
return null; // Already exists
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const character: Character = {
|
|
42
|
+
id: generateId('char'),
|
|
43
|
+
name,
|
|
44
|
+
aliases: [],
|
|
45
|
+
arc: options?.arc || '',
|
|
46
|
+
tone: options?.tone || '',
|
|
47
|
+
speechLevel: options?.speechLevel || '반말',
|
|
48
|
+
attitude: options?.attitude || '',
|
|
49
|
+
keywords: options?.keywords || [],
|
|
50
|
+
timeline: [],
|
|
51
|
+
notes: options?.notes || '',
|
|
52
|
+
created: now(),
|
|
53
|
+
updated: now()
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
memory.characters[name] = character;
|
|
57
|
+
saveMemory(memory);
|
|
58
|
+
return character;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function updateCharacter(name: string, updates: Partial<Character>): Character | null {
|
|
62
|
+
const memory = loadMemory();
|
|
63
|
+
if (!memory) return null;
|
|
64
|
+
|
|
65
|
+
const character = findCharacter(memory, name);
|
|
66
|
+
if (!character) return null;
|
|
67
|
+
|
|
68
|
+
// Apply updates (excluding id, name, created)
|
|
69
|
+
const { id, name: _, created, ...allowedUpdates } = updates as any;
|
|
70
|
+
Object.assign(character, allowedUpdates, { updated: now() });
|
|
71
|
+
|
|
72
|
+
saveMemory(memory);
|
|
73
|
+
return character;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function removeCharacter(name: string): boolean {
|
|
77
|
+
const memory = loadMemory();
|
|
78
|
+
if (!memory) return false;
|
|
79
|
+
|
|
80
|
+
const character = findCharacter(memory, name);
|
|
81
|
+
if (!character) return false;
|
|
82
|
+
|
|
83
|
+
delete memory.characters[character.name];
|
|
84
|
+
saveMemory(memory);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CharacterSummary {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
arc: string;
|
|
92
|
+
tone: string;
|
|
93
|
+
emotionCount: number;
|
|
94
|
+
lastUpdated: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function listCharacters(): CharacterSummary[] {
|
|
98
|
+
const memory = loadMemory();
|
|
99
|
+
if (!memory) return [];
|
|
100
|
+
|
|
101
|
+
return Object.values(memory.characters).map(c => ({
|
|
102
|
+
id: c.id,
|
|
103
|
+
name: c.name,
|
|
104
|
+
arc: c.arc,
|
|
105
|
+
tone: c.tone,
|
|
106
|
+
emotionCount: c.timeline?.length || 0,
|
|
107
|
+
lastUpdated: c.updated
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// === Alias Management ===
|
|
112
|
+
|
|
113
|
+
export function addAlias(characterName: string, alias: string): boolean {
|
|
114
|
+
const memory = loadMemory();
|
|
115
|
+
if (!memory) return false;
|
|
116
|
+
|
|
117
|
+
const character = findCharacter(memory, characterName);
|
|
118
|
+
if (!character) return false;
|
|
119
|
+
|
|
120
|
+
if (!character.aliases.includes(alias)) {
|
|
121
|
+
character.aliases.push(alias);
|
|
122
|
+
character.updated = now();
|
|
123
|
+
saveMemory(memory);
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function removeAlias(characterName: string, alias: string): boolean {
|
|
129
|
+
const memory = loadMemory();
|
|
130
|
+
if (!memory) return false;
|
|
131
|
+
|
|
132
|
+
const character = findCharacter(memory, characterName);
|
|
133
|
+
if (!character) return false;
|
|
134
|
+
|
|
135
|
+
const idx = character.aliases.indexOf(alias);
|
|
136
|
+
if (idx !== -1) {
|
|
137
|
+
character.aliases.splice(idx, 1);
|
|
138
|
+
character.updated = now();
|
|
139
|
+
saveMemory(memory);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function resolveCharacter(nameOrAlias: string): Character | null {
|
|
146
|
+
const memory = loadMemory();
|
|
147
|
+
if (!memory) return null;
|
|
148
|
+
return findCharacter(memory, nameOrAlias);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// === Emotion Timeline ===
|
|
152
|
+
|
|
153
|
+
export function addEmotionPoint(characterName: string, emotion: string, trigger: string, options?: {
|
|
154
|
+
sceneId?: string;
|
|
155
|
+
intensity?: 1 | 2 | 3 | 4 | 5;
|
|
156
|
+
}): boolean {
|
|
157
|
+
const memory = loadMemory();
|
|
158
|
+
if (!memory) return false;
|
|
159
|
+
|
|
160
|
+
const character = findCharacter(memory, characterName);
|
|
161
|
+
if (!character) return false;
|
|
162
|
+
|
|
163
|
+
const point: EmotionPoint = {
|
|
164
|
+
timestamp: now(),
|
|
165
|
+
sceneId: options?.sceneId,
|
|
166
|
+
emotion,
|
|
167
|
+
trigger,
|
|
168
|
+
intensity: options?.intensity || 3
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
character.timeline.push(point);
|
|
172
|
+
character.updated = now();
|
|
173
|
+
saveMemory(memory);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getEmotionTimeline(characterName: string): EmotionPoint[] {
|
|
178
|
+
const character = resolveCharacter(characterName);
|
|
179
|
+
return character?.timeline || [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getLatestEmotion(characterName: string): EmotionPoint | null {
|
|
183
|
+
const timeline = getEmotionTimeline(characterName);
|
|
184
|
+
return timeline.length > 0 ? timeline[timeline.length - 1] : null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function getEmotionArc(characterName: string): string {
|
|
188
|
+
const timeline = getEmotionTimeline(characterName);
|
|
189
|
+
if (timeline.length === 0) return '';
|
|
190
|
+
return timeline.map(e => e.emotion).join(' → ');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// === Dialogue Validation ===
|
|
194
|
+
|
|
195
|
+
export interface ValidationResult {
|
|
196
|
+
status: 'PASS' | 'WARN' | 'FAIL';
|
|
197
|
+
character: string;
|
|
198
|
+
checks: {
|
|
199
|
+
toneMatch: { passed: boolean; detail: string };
|
|
200
|
+
speechLevelMatch: { passed: boolean; detail: string };
|
|
201
|
+
keywordConsistency: { passed: boolean; detail: string };
|
|
202
|
+
};
|
|
203
|
+
suggestion: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function detectSpeechLevel(text: string): SpeechLevel {
|
|
207
|
+
// 존댓말 patterns
|
|
208
|
+
const formal = /요$|습니다$|세요$|십시오$/;
|
|
209
|
+
// 반말 patterns
|
|
210
|
+
const informal = /야$|아$|어$|지$|는데$/;
|
|
211
|
+
// 해체 patterns
|
|
212
|
+
const casual = /임$|음$|ㅋ|ㅎ$/;
|
|
213
|
+
|
|
214
|
+
const sentences = text.split(/[.!?]/).filter(s => s.trim());
|
|
215
|
+
let formalCnt = 0, informalCnt = 0, casualCnt = 0;
|
|
216
|
+
|
|
217
|
+
for (const s of sentences) {
|
|
218
|
+
const t = s.trim();
|
|
219
|
+
if (formal.test(t)) formalCnt++;
|
|
220
|
+
if (informal.test(t)) informalCnt++;
|
|
221
|
+
if (casual.test(t)) casualCnt++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (formalCnt > informalCnt && formalCnt > casualCnt) return '존댓말';
|
|
225
|
+
if (casualCnt > informalCnt) return '해체';
|
|
226
|
+
if (informalCnt > 0) return '반말';
|
|
227
|
+
return '혼합';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function validateDialogue(characterName: string, dialogue: string): ValidationResult {
|
|
231
|
+
const character = resolveCharacter(characterName);
|
|
232
|
+
|
|
233
|
+
if (!character) {
|
|
234
|
+
return {
|
|
235
|
+
status: 'FAIL',
|
|
236
|
+
character: characterName,
|
|
237
|
+
checks: {
|
|
238
|
+
toneMatch: { passed: false, detail: '캐릭터를 찾을 수 없음' },
|
|
239
|
+
speechLevelMatch: { passed: false, detail: '캐릭터를 찾을 수 없음' },
|
|
240
|
+
keywordConsistency: { passed: false, detail: '캐릭터를 찾을 수 없음' }
|
|
241
|
+
},
|
|
242
|
+
suggestion: `"${characterName}" 캐릭터가 메모리에 없습니다.`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check tone
|
|
247
|
+
const exclamations = (dialogue.match(/!/g) || []).length;
|
|
248
|
+
const toneCheck = { passed: true, detail: '톤 일치' };
|
|
249
|
+
if (character.tone.includes('담백') && exclamations > 1) {
|
|
250
|
+
toneCheck.passed = false;
|
|
251
|
+
toneCheck.detail = `담백한 톤에 느낌표 ${exclamations}개는 과함`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check speech level
|
|
255
|
+
const detected = detectSpeechLevel(dialogue);
|
|
256
|
+
const speechCheck = {
|
|
257
|
+
passed: detected === character.speechLevel || detected === '혼합',
|
|
258
|
+
detail: detected === character.speechLevel ? '말투 일치' : `기대: ${character.speechLevel}, 감지: ${detected}`
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Check keywords
|
|
262
|
+
const keywordCheck = { passed: true, detail: '키워드 없음 (검사 생략)' };
|
|
263
|
+
if (character.keywords.length > 0) {
|
|
264
|
+
const hasKeyword = character.keywords.some(kw => dialogue.includes(kw));
|
|
265
|
+
keywordCheck.passed = hasKeyword;
|
|
266
|
+
keywordCheck.detail = hasKeyword ? '특징 키워드 포함' : `키워드 미포함: ${character.keywords.slice(0, 2).join(', ')}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const failCount = [toneCheck, speechCheck, keywordCheck].filter(c => !c.passed).length;
|
|
270
|
+
const status: 'PASS' | 'WARN' | 'FAIL' = failCount === 0 ? 'PASS' : failCount >= 2 ? 'FAIL' : 'WARN';
|
|
271
|
+
|
|
272
|
+
const suggestions: string[] = [];
|
|
273
|
+
if (!toneCheck.passed) suggestions.push(`톤 조정 필요`);
|
|
274
|
+
if (!speechCheck.passed) suggestions.push(`${character.speechLevel}로 말투 수정`);
|
|
275
|
+
if (!keywordCheck.passed) suggestions.push(`특징 키워드 사용 고려`);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
status,
|
|
279
|
+
character: character.name,
|
|
280
|
+
checks: {
|
|
281
|
+
toneMatch: toneCheck,
|
|
282
|
+
speechLevelMatch: speechCheck,
|
|
283
|
+
keywordConsistency: keywordCheck
|
|
284
|
+
},
|
|
285
|
+
suggestion: suggestions.length > 0 ? suggestions.join('. ') : '대사가 캐릭터와 잘 어울립니다.'
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// === Profile Generation ===
|
|
290
|
+
|
|
291
|
+
export function generateCharacterProfile(characterName: string): string {
|
|
292
|
+
const character = resolveCharacter(characterName);
|
|
293
|
+
|
|
294
|
+
if (!character) {
|
|
295
|
+
return `# "${characterName}" 캐릭터를 찾을 수 없습니다`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const latest = getLatestEmotion(characterName);
|
|
299
|
+
const arc = getEmotionArc(characterName);
|
|
300
|
+
|
|
301
|
+
let profile = `# ${character.name}\n\n`;
|
|
302
|
+
|
|
303
|
+
if (character.aliases.length > 0) {
|
|
304
|
+
profile += `**별칭**: ${character.aliases.join(', ')}\n\n`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (character.arc) {
|
|
308
|
+
profile += `**캐릭터 아크**: ${character.arc}\n\n`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (character.tone) {
|
|
312
|
+
profile += `**대사 톤**: ${character.tone}\n\n`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
profile += `**말투**: ${character.speechLevel}\n\n`;
|
|
316
|
+
|
|
317
|
+
if (character.keywords.length > 0) {
|
|
318
|
+
profile += `**핵심 키워드**: ${character.keywords.join(', ')}\n\n`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (latest) {
|
|
322
|
+
profile += `**현재 감정**: ${latest.emotion} (강도: ${latest.intensity}/5)\n\n`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (character.attitude) {
|
|
326
|
+
profile += `**태도**: ${character.attitude}\n\n`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (arc) {
|
|
330
|
+
profile += `**감정 궤도**: ${arc}\n\n`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (character.notes) {
|
|
334
|
+
profile += `**메모**: ${character.notes}\n\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return profile.trim();
|
|
338
|
+
}
|