ava-langgraph-narrative-intelligence 0.1.0
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 +268 -0
- package/dist/graphs/index.cjs +1511 -0
- package/dist/graphs/index.cjs.map +1 -0
- package/dist/graphs/index.d.cts +2 -0
- package/dist/graphs/index.d.ts +2 -0
- package/dist/graphs/index.js +1468 -0
- package/dist/graphs/index.js.map +1 -0
- package/dist/index-Btxk3nQm.d.cts +430 -0
- package/dist/index-CgXXxuIH.d.ts +430 -0
- package/dist/index-CweT-D3c.d.cts +122 -0
- package/dist/index-D-zWH42e.d.cts +66 -0
- package/dist/index-D71kh3nE.d.cts +213 -0
- package/dist/index-DApls3w2.d.ts +66 -0
- package/dist/index-UamXITgg.d.ts +122 -0
- package/dist/index-v9AlRC0M.d.ts +213 -0
- package/dist/index.cjs +2753 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2654 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/index.cjs +654 -0
- package/dist/integrations/index.cjs.map +1 -0
- package/dist/integrations/index.d.cts +2 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.js +614 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/ncp-tXS9Jr9e.d.cts +132 -0
- package/dist/ncp-tXS9Jr9e.d.ts +132 -0
- package/dist/nodes/index.cjs +226 -0
- package/dist/nodes/index.cjs.map +1 -0
- package/dist/nodes/index.d.cts +2 -0
- package/dist/nodes/index.d.ts +2 -0
- package/dist/nodes/index.js +196 -0
- package/dist/nodes/index.js.map +1 -0
- package/dist/schemas/index.cjs +550 -0
- package/dist/schemas/index.cjs.map +1 -0
- package/dist/schemas/index.d.cts +2 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +484 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/unified_state_bridge-CIDm1kuf.d.cts +266 -0
- package/dist/unified_state_bridge-CIDm1kuf.d.ts +266 -0
- package/package.json +91 -0
- package/src/graphs/coherence_engine.ts +1027 -0
- package/src/graphs/index.ts +47 -0
- package/src/graphs/three_universe_processor.ts +1136 -0
- package/src/index.ts +181 -0
- package/src/integrations/index.ts +17 -0
- package/src/integrations/redis_state.ts +691 -0
- package/src/nodes/emotional_classifier.ts +289 -0
- package/src/nodes/index.ts +17 -0
- package/src/schemas/index.ts +75 -0
- package/src/schemas/ncp.ts +312 -0
- package/src/schemas/unified_state_bridge.ts +681 -0
- package/src/tests/coherence_engine.test.ts +273 -0
- package/src/tests/three_universe_processor.test.ts +309 -0
- package/src/tests/unified_state_bridge.test.ts +360 -0
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-Universe Processor
|
|
3
|
+
*
|
|
4
|
+
* Processes events through all three universe lenses:
|
|
5
|
+
* - Engineer World (Mia) - Technical precision
|
|
6
|
+
* - Ceremony World (Ava8) - Relational protocols
|
|
7
|
+
* - Story Engine World (Miette) - Narrative patterns
|
|
8
|
+
*
|
|
9
|
+
* This produces a ThreeUniverseAnalysis with:
|
|
10
|
+
* - Individual perspectives from each universe
|
|
11
|
+
* - Lead universe determination
|
|
12
|
+
* - Coherence score
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
Universe,
|
|
17
|
+
UniversePerspective,
|
|
18
|
+
ThreeUniverseAnalysis,
|
|
19
|
+
NarrativeFunction,
|
|
20
|
+
StoryBeat,
|
|
21
|
+
createUniversePerspective,
|
|
22
|
+
createThreeUniverseAnalysis,
|
|
23
|
+
createStoryBeat,
|
|
24
|
+
} from "../schemas/unified_state_bridge.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Types of events that can be processed.
|
|
28
|
+
*/
|
|
29
|
+
export enum EventType {
|
|
30
|
+
GITHUB_PUSH = "github.push",
|
|
31
|
+
GITHUB_ISSUE = "github.issue",
|
|
32
|
+
GITHUB_PR = "github.pull_request",
|
|
33
|
+
GITHUB_COMMENT = "github.comment",
|
|
34
|
+
GITHUB_REVIEW = "github.review",
|
|
35
|
+
USER_INPUT = "user.input",
|
|
36
|
+
AGENT_ACTION = "agent.action",
|
|
37
|
+
SYSTEM_EVENT = "system.event",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An event ready for three-universe processing.
|
|
42
|
+
*/
|
|
43
|
+
export interface ProcessedEvent {
|
|
44
|
+
eventId: string;
|
|
45
|
+
eventType: EventType;
|
|
46
|
+
content: string;
|
|
47
|
+
metadata: Record<string, unknown>;
|
|
48
|
+
timestamp: Date;
|
|
49
|
+
source?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* State for the three-universe processor.
|
|
54
|
+
*/
|
|
55
|
+
export interface ThreeUniverseState {
|
|
56
|
+
// Input
|
|
57
|
+
event: Record<string, unknown>;
|
|
58
|
+
eventType: string;
|
|
59
|
+
|
|
60
|
+
// Processing state
|
|
61
|
+
engineerPerspective?: UniversePerspective;
|
|
62
|
+
ceremonyPerspective?: UniversePerspective;
|
|
63
|
+
storyEnginePerspective?: UniversePerspective;
|
|
64
|
+
|
|
65
|
+
// Output
|
|
66
|
+
analysis?: ThreeUniverseAnalysis;
|
|
67
|
+
leadUniverse?: Universe;
|
|
68
|
+
coherenceScore?: number;
|
|
69
|
+
|
|
70
|
+
// Error handling
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Protocol for callbacks that receive three-universe analysis results.
|
|
76
|
+
*/
|
|
77
|
+
export type AnalysisCallback = (
|
|
78
|
+
eventId: string,
|
|
79
|
+
eventContent: string,
|
|
80
|
+
engineerResult: Record<string, unknown>,
|
|
81
|
+
ceremonyResult: Record<string, unknown>,
|
|
82
|
+
storyEngineResult: Record<string, unknown>,
|
|
83
|
+
leadUniverse: string,
|
|
84
|
+
coherenceScore: number
|
|
85
|
+
) => void;
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Engineer World (Mia) - The Builder
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Keywords that indicate different engineering intents.
|
|
93
|
+
*/
|
|
94
|
+
export function engineerIntentKeywords(): Record<string, string[]> {
|
|
95
|
+
return {
|
|
96
|
+
feature_implementation: [
|
|
97
|
+
"feat:",
|
|
98
|
+
"feature",
|
|
99
|
+
"add",
|
|
100
|
+
"implement",
|
|
101
|
+
"create",
|
|
102
|
+
"new",
|
|
103
|
+
],
|
|
104
|
+
bug_fix: ["fix:", "bug", "hotfix", "patch", "resolve", "correct"],
|
|
105
|
+
refactor: ["refactor", "refact:", "cleanup", "restructure", "reorganize"],
|
|
106
|
+
documentation: ["docs:", "doc:", "documentation", "readme", "comment"],
|
|
107
|
+
testing: ["test:", "tests:", "testing", "spec", "coverage"],
|
|
108
|
+
dependency: ["deps:", "dependency", "upgrade", "update", "bump"],
|
|
109
|
+
configuration: ["config:", "configure", "settings", "env"],
|
|
110
|
+
performance: ["perf:", "performance", "optimize", "speed", "cache"],
|
|
111
|
+
security: ["security", "sec:", "vulnerability", "auth", "permission"],
|
|
112
|
+
ci_cd: ["ci:", "cd:", "pipeline", "workflow", "build"],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mia's perspective: The Builder (Engineer-world)
|
|
118
|
+
*
|
|
119
|
+
* Focuses on:
|
|
120
|
+
* - What was built/changed
|
|
121
|
+
* - Technical impact
|
|
122
|
+
* - System architecture implications
|
|
123
|
+
* - Flow routing for technical actions
|
|
124
|
+
*/
|
|
125
|
+
export function analyzeEngineerPerspective(
|
|
126
|
+
state: ThreeUniverseState
|
|
127
|
+
): ThreeUniverseState {
|
|
128
|
+
const event = state.event;
|
|
129
|
+
const eventType = state.eventType;
|
|
130
|
+
|
|
131
|
+
// Extract relevant content
|
|
132
|
+
let content = "";
|
|
133
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
134
|
+
|
|
135
|
+
if (payload) {
|
|
136
|
+
const commits = payload.commits as Array<Record<string, string>> | undefined;
|
|
137
|
+
if (commits) {
|
|
138
|
+
content = commits.map((c) => c.message || "").join(" ");
|
|
139
|
+
} else if (payload.issue) {
|
|
140
|
+
const issue = payload.issue as Record<string, string>;
|
|
141
|
+
content = (issue.title || "") + " " + (issue.body || "");
|
|
142
|
+
} else if (payload.pull_request) {
|
|
143
|
+
const pr = payload.pull_request as Record<string, string>;
|
|
144
|
+
content = (pr.title || "") + " " + (pr.body || "");
|
|
145
|
+
}
|
|
146
|
+
} else if (event.content) {
|
|
147
|
+
content = event.content as string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const contentLower = content.toLowerCase();
|
|
151
|
+
|
|
152
|
+
// Analyze intent based on keywords
|
|
153
|
+
const keywords = engineerIntentKeywords();
|
|
154
|
+
const intentScores: Record<string, number> = {};
|
|
155
|
+
|
|
156
|
+
for (const [intent, terms] of Object.entries(keywords)) {
|
|
157
|
+
const score = terms.filter((term) =>
|
|
158
|
+
contentLower.includes(term.toLowerCase())
|
|
159
|
+
).length;
|
|
160
|
+
if (score > 0) {
|
|
161
|
+
intentScores[intent] = score / terms.length;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Determine primary intent
|
|
166
|
+
let intent: string;
|
|
167
|
+
let confidence: number;
|
|
168
|
+
|
|
169
|
+
if (Object.keys(intentScores).length > 0) {
|
|
170
|
+
intent = Object.entries(intentScores).reduce((a, b) =>
|
|
171
|
+
a[1] > b[1] ? a : b
|
|
172
|
+
)[0];
|
|
173
|
+
confidence = Math.min(0.95, 0.6 + intentScores[intent] * 0.4);
|
|
174
|
+
} else {
|
|
175
|
+
intent = "maintenance";
|
|
176
|
+
confidence = 0.5;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Map intents to suggested flows
|
|
180
|
+
const flowMap: Record<string, string[]> = {
|
|
181
|
+
feature_implementation: [
|
|
182
|
+
"code_review",
|
|
183
|
+
"integration_test",
|
|
184
|
+
"documentation_update",
|
|
185
|
+
],
|
|
186
|
+
bug_fix: ["regression_test", "root_cause_analysis", "changelog_update"],
|
|
187
|
+
refactor: ["architecture_review", "performance_test", "code_quality"],
|
|
188
|
+
documentation: ["doc_review", "example_validation"],
|
|
189
|
+
testing: ["coverage_analysis", "test_quality_review"],
|
|
190
|
+
dependency: ["security_scan", "compatibility_test"],
|
|
191
|
+
configuration: ["validation_test", "rollback_plan"],
|
|
192
|
+
performance: ["benchmark", "profiling", "optimization_review"],
|
|
193
|
+
security: ["security_audit", "penetration_test", "credential_scan"],
|
|
194
|
+
ci_cd: ["pipeline_validation", "deployment_test"],
|
|
195
|
+
maintenance: ["standard_ci"],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const suggestedFlows = flowMap[intent] || ["standard_ci"];
|
|
199
|
+
|
|
200
|
+
// Build context
|
|
201
|
+
const context: Record<string, unknown> = {
|
|
202
|
+
detectedKeywords: Object.entries(intentScores)
|
|
203
|
+
.filter(([, v]) => v > 0)
|
|
204
|
+
.map(([k]) => k),
|
|
205
|
+
contentLength: content.length,
|
|
206
|
+
eventType,
|
|
207
|
+
technicalScope: determineTechnicalScope(content, event),
|
|
208
|
+
estimatedComplexity: estimateComplexity(content, event),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const perspective = createUniversePerspective(
|
|
212
|
+
Universe.ENGINEER,
|
|
213
|
+
intent,
|
|
214
|
+
confidence,
|
|
215
|
+
{ suggestedFlows, context }
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return { ...state, engineerPerspective: perspective };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function determineTechnicalScope(
|
|
222
|
+
content: string,
|
|
223
|
+
_event: Record<string, unknown>
|
|
224
|
+
): string {
|
|
225
|
+
const contentLower = content.toLowerCase();
|
|
226
|
+
|
|
227
|
+
if (["api", "endpoint", "route"].some((kw) => contentLower.includes(kw))) {
|
|
228
|
+
return "api_layer";
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
["database", "schema", "migration"].some((kw) => contentLower.includes(kw))
|
|
232
|
+
) {
|
|
233
|
+
return "data_layer";
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
["ui", "component", "frontend"].some((kw) => contentLower.includes(kw))
|
|
237
|
+
) {
|
|
238
|
+
return "presentation_layer";
|
|
239
|
+
}
|
|
240
|
+
if (["test", "spec"].some((kw) => contentLower.includes(kw))) {
|
|
241
|
+
return "testing";
|
|
242
|
+
}
|
|
243
|
+
if (["config", "env", "settings"].some((kw) => contentLower.includes(kw))) {
|
|
244
|
+
return "configuration";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return "general";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function estimateComplexity(
|
|
251
|
+
content: string,
|
|
252
|
+
event: Record<string, unknown>
|
|
253
|
+
): string {
|
|
254
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
255
|
+
if (payload) {
|
|
256
|
+
const commits = payload.commits as unknown[] | undefined;
|
|
257
|
+
if (commits) {
|
|
258
|
+
if (commits.length > 5) return "high";
|
|
259
|
+
if (commits.length > 2) return "medium";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (content.length > 500) return "high";
|
|
264
|
+
if (content.length > 100) return "medium";
|
|
265
|
+
|
|
266
|
+
return "low";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// =============================================================================
|
|
270
|
+
// Ceremony World (Ava8) - The Keeper
|
|
271
|
+
// =============================================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Keywords that indicate different ceremonial intents.
|
|
275
|
+
*/
|
|
276
|
+
export function ceremonyIntentKeywords(): Record<string, string[]> {
|
|
277
|
+
return {
|
|
278
|
+
co_creation: ["we", "together", "team", "pair", "collaborate", "co-author"],
|
|
279
|
+
gratitude_expression: [
|
|
280
|
+
"thanks",
|
|
281
|
+
"thank you",
|
|
282
|
+
"grateful",
|
|
283
|
+
"appreciate",
|
|
284
|
+
"credit",
|
|
285
|
+
],
|
|
286
|
+
witnessing: ["witness", "observe", "acknowledge", "see", "recognize"],
|
|
287
|
+
sacred_pause: ["pause", "reflect", "consider", "contemplate", "breathe"],
|
|
288
|
+
relationship_building: [
|
|
289
|
+
"connect",
|
|
290
|
+
"relationship",
|
|
291
|
+
"community",
|
|
292
|
+
"support",
|
|
293
|
+
],
|
|
294
|
+
healing: ["heal", "restore", "repair", "reconcile", "mend"],
|
|
295
|
+
celebration: [
|
|
296
|
+
"celebrate",
|
|
297
|
+
"milestone",
|
|
298
|
+
"achievement",
|
|
299
|
+
"success",
|
|
300
|
+
"complete",
|
|
301
|
+
],
|
|
302
|
+
offering: ["offer", "gift", "contribute", "share", "give"],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Ava8's perspective: The Keeper (Ceremony-world)
|
|
308
|
+
*
|
|
309
|
+
* Focuses on:
|
|
310
|
+
* - Who contributed and their state
|
|
311
|
+
* - Relational dynamics (K'é)
|
|
312
|
+
* - Witnessing and acknowledgment
|
|
313
|
+
* - Seven-generation awareness
|
|
314
|
+
*/
|
|
315
|
+
export function analyzeCeremonyPerspective(
|
|
316
|
+
state: ThreeUniverseState
|
|
317
|
+
): ThreeUniverseState {
|
|
318
|
+
const event = state.event;
|
|
319
|
+
|
|
320
|
+
// Extract contributor information
|
|
321
|
+
const contributors = extractContributors(event);
|
|
322
|
+
|
|
323
|
+
// Extract content for analysis
|
|
324
|
+
const content = extractContent(event);
|
|
325
|
+
const contentLower = content.toLowerCase();
|
|
326
|
+
|
|
327
|
+
// Analyze relational intent
|
|
328
|
+
const keywords = ceremonyIntentKeywords();
|
|
329
|
+
const intentScores: Record<string, number> = {};
|
|
330
|
+
|
|
331
|
+
for (const [intent, terms] of Object.entries(keywords)) {
|
|
332
|
+
const score = terms.filter((term) => contentLower.includes(term)).length;
|
|
333
|
+
if (score > 0) {
|
|
334
|
+
intentScores[intent] = score / terms.length;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Special case: multiple contributors = co_creation
|
|
339
|
+
if (contributors.length > 1) {
|
|
340
|
+
intentScores.co_creation = (intentScores.co_creation || 0) + 0.5;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Determine primary intent
|
|
344
|
+
let intent: string;
|
|
345
|
+
let confidence: number;
|
|
346
|
+
|
|
347
|
+
if (Object.keys(intentScores).length > 0) {
|
|
348
|
+
intent = Object.entries(intentScores).reduce((a, b) =>
|
|
349
|
+
a[1] > b[1] ? a : b
|
|
350
|
+
)[0];
|
|
351
|
+
confidence = Math.min(0.95, 0.5 + intentScores[intent] * 0.4);
|
|
352
|
+
} else {
|
|
353
|
+
intent = "individual_offering";
|
|
354
|
+
confidence = 0.6;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Map intents to ceremonial flows
|
|
358
|
+
const flowMap: Record<string, string[]> = {
|
|
359
|
+
co_creation: ["witness_collaboration", "honor_contributions", "amplify_voices"],
|
|
360
|
+
gratitude_expression: [
|
|
361
|
+
"amplify_acknowledgment",
|
|
362
|
+
"record_connection",
|
|
363
|
+
"reciprocity_check",
|
|
364
|
+
],
|
|
365
|
+
witnessing: ["hold_space", "reflect_back", "presence"],
|
|
366
|
+
sacred_pause: ["create_silence", "contemplation_prompt", "breathing_space"],
|
|
367
|
+
relationship_building: [
|
|
368
|
+
"map_connections",
|
|
369
|
+
"strengthen_ties",
|
|
370
|
+
"introduce_support",
|
|
371
|
+
],
|
|
372
|
+
healing: ["compassion_response", "restoration_path", "forgiveness_space"],
|
|
373
|
+
celebration: ["amplify_joy", "community_acknowledgment", "gratitude_circle"],
|
|
374
|
+
offering: ["receive_gracefully", "honor_gift", "share_forward"],
|
|
375
|
+
individual_offering: [
|
|
376
|
+
"witness_work",
|
|
377
|
+
"hold_space",
|
|
378
|
+
"gentle_acknowledgment",
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const suggestedFlows = flowMap[intent] || ["witness_work", "hold_space"];
|
|
383
|
+
|
|
384
|
+
// Build ceremonial context
|
|
385
|
+
const context: Record<string, unknown> = {
|
|
386
|
+
contributors,
|
|
387
|
+
isCollaborative: contributors.length > 1,
|
|
388
|
+
senderEnergy: assessEnergy(content),
|
|
389
|
+
witnessingNeeded: needsWitnessing(content, event),
|
|
390
|
+
relationshipDepth: assessRelationshipDepth(contributors, event),
|
|
391
|
+
sevenGenerationRelevance: assessLongTermImpact(content, event),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const perspective = createUniversePerspective(
|
|
395
|
+
Universe.CEREMONY,
|
|
396
|
+
intent,
|
|
397
|
+
confidence,
|
|
398
|
+
{ suggestedFlows, context }
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
return { ...state, ceremonyPerspective: perspective };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function extractContributors(event: Record<string, unknown>): string[] {
|
|
405
|
+
const contributors: string[] = [];
|
|
406
|
+
|
|
407
|
+
if (event.sender) {
|
|
408
|
+
contributors.push(event.sender as string);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
412
|
+
if (payload) {
|
|
413
|
+
// Git commits
|
|
414
|
+
const commits = payload.commits as Array<Record<string, unknown>> | undefined;
|
|
415
|
+
if (commits) {
|
|
416
|
+
for (const commit of commits) {
|
|
417
|
+
const author = (commit.author as Record<string, string> | undefined)?.name;
|
|
418
|
+
if (author && !contributors.includes(author)) {
|
|
419
|
+
contributors.push(author);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Issue/PR author
|
|
425
|
+
const issue = payload.issue as Record<string, unknown> | undefined;
|
|
426
|
+
if (issue) {
|
|
427
|
+
const user = issue.user as Record<string, string> | undefined;
|
|
428
|
+
if (user?.login && !contributors.includes(user.login)) {
|
|
429
|
+
contributors.push(user.login);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const pr = payload.pull_request as Record<string, unknown> | undefined;
|
|
434
|
+
if (pr) {
|
|
435
|
+
const user = pr.user as Record<string, string> | undefined;
|
|
436
|
+
if (user?.login && !contributors.includes(user.login)) {
|
|
437
|
+
contributors.push(user.login);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return contributors.length > 0 ? contributors : ["unknown"];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function extractContent(event: Record<string, unknown>): string {
|
|
446
|
+
if (event.content) {
|
|
447
|
+
return event.content as string;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
451
|
+
if (payload) {
|
|
452
|
+
const parts: string[] = [];
|
|
453
|
+
|
|
454
|
+
const commits = payload.commits as Array<Record<string, string>> | undefined;
|
|
455
|
+
if (commits) {
|
|
456
|
+
parts.push(...commits.map((c) => c.message || ""));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const issue = payload.issue as Record<string, string> | undefined;
|
|
460
|
+
if (issue) {
|
|
461
|
+
parts.push(issue.title || "");
|
|
462
|
+
parts.push(issue.body || "");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const pr = payload.pull_request as Record<string, string> | undefined;
|
|
466
|
+
if (pr) {
|
|
467
|
+
parts.push(pr.title || "");
|
|
468
|
+
parts.push(pr.body || "");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const comment = payload.comment as Record<string, string> | undefined;
|
|
472
|
+
if (comment) {
|
|
473
|
+
parts.push(comment.body || "");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return parts.filter(Boolean).join(" ");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return "";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function assessEnergy(content: string): string {
|
|
483
|
+
const contentLower = content.toLowerCase();
|
|
484
|
+
|
|
485
|
+
if (["urgent", "critical", "asap", "emergency"].some((w) =>
|
|
486
|
+
contentLower.includes(w)
|
|
487
|
+
)) {
|
|
488
|
+
return "urgent_flow";
|
|
489
|
+
}
|
|
490
|
+
if (["excited", "happy", "great", "awesome"].some((w) =>
|
|
491
|
+
contentLower.includes(w)
|
|
492
|
+
)) {
|
|
493
|
+
return "joyful_flow";
|
|
494
|
+
}
|
|
495
|
+
if (["stuck", "blocked", "help", "issue"].some((w) =>
|
|
496
|
+
contentLower.includes(w)
|
|
497
|
+
)) {
|
|
498
|
+
return "seeking_support";
|
|
499
|
+
}
|
|
500
|
+
if (["thoughtful", "consider", "reflect"].some((w) =>
|
|
501
|
+
contentLower.includes(w)
|
|
502
|
+
)) {
|
|
503
|
+
return "contemplative_flow";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return "steady_flow";
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function needsWitnessing(
|
|
510
|
+
content: string,
|
|
511
|
+
_event: Record<string, unknown>
|
|
512
|
+
): boolean {
|
|
513
|
+
const contentLower = content.toLowerCase();
|
|
514
|
+
|
|
515
|
+
// Vulnerable sharing needs witnessing
|
|
516
|
+
if (["first", "new", "trying", "learning", "help"].some((w) =>
|
|
517
|
+
contentLower.includes(w)
|
|
518
|
+
)) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Significant achievements need witnessing
|
|
523
|
+
if (["complete", "achieve", "milestone", "done"].some((w) =>
|
|
524
|
+
contentLower.includes(w)
|
|
525
|
+
)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function assessRelationshipDepth(
|
|
533
|
+
contributors: string[],
|
|
534
|
+
_event: Record<string, unknown>
|
|
535
|
+
): string {
|
|
536
|
+
if (contributors.length > 2) return "community";
|
|
537
|
+
if (contributors.length > 1) return "pair";
|
|
538
|
+
return "individual";
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function assessLongTermImpact(
|
|
542
|
+
content: string,
|
|
543
|
+
_event: Record<string, unknown>
|
|
544
|
+
): number {
|
|
545
|
+
const contentLower = content.toLowerCase();
|
|
546
|
+
let score = 0.3; // Base score
|
|
547
|
+
|
|
548
|
+
// Infrastructure changes have long-term impact
|
|
549
|
+
if (["architecture", "foundation", "core", "framework"].some((w) =>
|
|
550
|
+
contentLower.includes(w)
|
|
551
|
+
)) {
|
|
552
|
+
score += 0.3;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Documentation affects future generations
|
|
556
|
+
if (["document", "guide", "tutorial", "example"].some((w) =>
|
|
557
|
+
contentLower.includes(w)
|
|
558
|
+
)) {
|
|
559
|
+
score += 0.2;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Breaking changes affect the future
|
|
563
|
+
if (["breaking", "migration", "deprecate"].some((w) =>
|
|
564
|
+
contentLower.includes(w)
|
|
565
|
+
)) {
|
|
566
|
+
score += 0.2;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return Math.min(1.0, score);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// =============================================================================
|
|
573
|
+
// Story Engine World (Miette) - The Weaver
|
|
574
|
+
// =============================================================================
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Keywords that indicate different narrative functions.
|
|
578
|
+
*/
|
|
579
|
+
export function storyEngineIntentKeywords(): Record<string, string[]> {
|
|
580
|
+
return {
|
|
581
|
+
inciting_incident: ["init", "start", "begin", "new", "first", "introduce"],
|
|
582
|
+
rising_action: ["add", "implement", "build", "develop", "progress", "continue"],
|
|
583
|
+
turning_point: ["feat:", "major", "significant", "pivot", "change", "transform"],
|
|
584
|
+
complication: ["issue", "problem", "bug", "error", "conflict", "challenge"],
|
|
585
|
+
crisis: ["critical", "urgent", "breaking", "emergency", "blocker"],
|
|
586
|
+
climax: ["complete", "finish", "final", "release", "launch", "deploy"],
|
|
587
|
+
resolution: ["fix", "resolve", "close", "merge", "done"],
|
|
588
|
+
denouement: ["cleanup", "refactor", "optimize", "polish", "improve"],
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Miette's perspective: The Weaver (Story-engine-world)
|
|
594
|
+
*
|
|
595
|
+
* Focuses on:
|
|
596
|
+
* - Narrative position (which act/phase)
|
|
597
|
+
* - Dramatic function
|
|
598
|
+
* - Story arc progression
|
|
599
|
+
* - Character development
|
|
600
|
+
*/
|
|
601
|
+
export function analyzeStoryEnginePerspective(
|
|
602
|
+
state: ThreeUniverseState
|
|
603
|
+
): ThreeUniverseState {
|
|
604
|
+
const event = state.event;
|
|
605
|
+
const content = extractContent(event);
|
|
606
|
+
const contentLower = content.toLowerCase();
|
|
607
|
+
|
|
608
|
+
// Analyze narrative function
|
|
609
|
+
const keywords = storyEngineIntentKeywords();
|
|
610
|
+
const intentScores: Record<string, number> = {};
|
|
611
|
+
|
|
612
|
+
for (const [intent, terms] of Object.entries(keywords)) {
|
|
613
|
+
const score = terms.filter((term) => contentLower.includes(term)).length;
|
|
614
|
+
if (score > 0) {
|
|
615
|
+
intentScores[intent] = score / terms.length;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Determine primary intent
|
|
620
|
+
let intent: string;
|
|
621
|
+
let confidence: number;
|
|
622
|
+
|
|
623
|
+
if (Object.keys(intentScores).length > 0) {
|
|
624
|
+
intent = Object.entries(intentScores).reduce((a, b) =>
|
|
625
|
+
a[1] > b[1] ? a : b
|
|
626
|
+
)[0];
|
|
627
|
+
confidence = Math.min(0.95, 0.55 + intentScores[intent] * 0.4);
|
|
628
|
+
} else {
|
|
629
|
+
intent = "rising_action";
|
|
630
|
+
confidence = 0.5;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Map intent to act
|
|
634
|
+
const actMap: Record<string, number> = {
|
|
635
|
+
inciting_incident: 1,
|
|
636
|
+
rising_action: 2,
|
|
637
|
+
turning_point: 2,
|
|
638
|
+
complication: 2,
|
|
639
|
+
crisis: 2,
|
|
640
|
+
climax: 3,
|
|
641
|
+
resolution: 3,
|
|
642
|
+
denouement: 3,
|
|
643
|
+
};
|
|
644
|
+
const act = actMap[intent] || 2;
|
|
645
|
+
|
|
646
|
+
// Map intent to narrative function
|
|
647
|
+
const functionMap: Record<string, string> = {
|
|
648
|
+
inciting_incident: "inciting_incident",
|
|
649
|
+
rising_action: "rising_action",
|
|
650
|
+
turning_point: "turning_point",
|
|
651
|
+
complication: "complication",
|
|
652
|
+
crisis: "crisis",
|
|
653
|
+
climax: "climax",
|
|
654
|
+
resolution: "resolution",
|
|
655
|
+
denouement: "denouement",
|
|
656
|
+
};
|
|
657
|
+
const narrativeFunction = functionMap[intent] || "beat";
|
|
658
|
+
|
|
659
|
+
// Suggested flows for story engine
|
|
660
|
+
const flowMap: Record<string, string[]> = {
|
|
661
|
+
inciting_incident: ["establish_stakes", "introduce_characters", "set_tone"],
|
|
662
|
+
rising_action: ["advance_narrative", "develop_characters", "build_tension"],
|
|
663
|
+
turning_point: ["mark_pivot", "shift_perspective", "update_arc"],
|
|
664
|
+
complication: ["deepen_conflict", "raise_stakes", "add_obstacle"],
|
|
665
|
+
crisis: ["peak_tension", "force_decision", "approach_climax"],
|
|
666
|
+
climax: ["resolve_main_conflict", "character_transformation", "theme_revelation"],
|
|
667
|
+
resolution: ["tie_loose_ends", "show_consequences", "new_equilibrium"],
|
|
668
|
+
denouement: ["reflect_journey", "hint_future", "final_image"],
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const suggestedFlows = flowMap[intent] || [
|
|
672
|
+
"advance_narrative",
|
|
673
|
+
"update_arc_position",
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
// Calculate dramatic tension
|
|
677
|
+
const dramaticTension = calculateDramaticTension(intent, content);
|
|
678
|
+
|
|
679
|
+
// Build story context
|
|
680
|
+
const context: Record<string, unknown> = {
|
|
681
|
+
act,
|
|
682
|
+
narrativeFunction,
|
|
683
|
+
dramaticTension,
|
|
684
|
+
suggestedNextBeat: suggestNextBeat(intent),
|
|
685
|
+
characterImpact: assessCharacterImpact(content),
|
|
686
|
+
themeResonance: assessThemeResonance(content),
|
|
687
|
+
pacingSuggestion: suggestPacing(intent, dramaticTension),
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const perspective = createUniversePerspective(
|
|
691
|
+
Universe.STORY_ENGINE,
|
|
692
|
+
intent,
|
|
693
|
+
confidence,
|
|
694
|
+
{ suggestedFlows, context }
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
return { ...state, storyEnginePerspective: perspective };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function calculateDramaticTension(intent: string, content: string): number {
|
|
701
|
+
const baseTension: Record<string, number> = {
|
|
702
|
+
inciting_incident: 0.4,
|
|
703
|
+
rising_action: 0.5,
|
|
704
|
+
turning_point: 0.7,
|
|
705
|
+
complication: 0.6,
|
|
706
|
+
crisis: 0.9,
|
|
707
|
+
climax: 1.0,
|
|
708
|
+
resolution: 0.4,
|
|
709
|
+
denouement: 0.2,
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
let tension = baseTension[intent] || 0.5;
|
|
713
|
+
const contentLower = content.toLowerCase();
|
|
714
|
+
|
|
715
|
+
// Adjust based on content intensity
|
|
716
|
+
if (["urgent", "critical", "breaking"].some((w) => contentLower.includes(w))) {
|
|
717
|
+
tension = Math.min(1.0, tension + 0.2);
|
|
718
|
+
}
|
|
719
|
+
if (["minor", "small", "trivial"].some((w) => contentLower.includes(w))) {
|
|
720
|
+
tension = Math.max(0.1, tension - 0.2);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return Math.round(tension * 100) / 100;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function suggestNextBeat(currentIntent: string): string {
|
|
727
|
+
const nextBeatMap: Record<string, string> = {
|
|
728
|
+
inciting_incident: "rising_action",
|
|
729
|
+
rising_action: "complication",
|
|
730
|
+
turning_point: "rising_action",
|
|
731
|
+
complication: "crisis",
|
|
732
|
+
crisis: "climax",
|
|
733
|
+
climax: "resolution",
|
|
734
|
+
resolution: "denouement",
|
|
735
|
+
denouement: "inciting_incident", // New cycle
|
|
736
|
+
};
|
|
737
|
+
return nextBeatMap[currentIntent] || "rising_action";
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function assessCharacterImpact(content: string): string {
|
|
741
|
+
const contentLower = content.toLowerCase();
|
|
742
|
+
|
|
743
|
+
if (["transform", "change", "grow", "learn"].some((w) =>
|
|
744
|
+
contentLower.includes(w)
|
|
745
|
+
)) {
|
|
746
|
+
return "transformative";
|
|
747
|
+
}
|
|
748
|
+
if (["challenge", "struggle", "overcome"].some((w) =>
|
|
749
|
+
contentLower.includes(w)
|
|
750
|
+
)) {
|
|
751
|
+
return "character_testing";
|
|
752
|
+
}
|
|
753
|
+
if (["connect", "relationship", "team"].some((w) => contentLower.includes(w))) {
|
|
754
|
+
return "relational";
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return "incremental";
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function assessThemeResonance(content: string): string {
|
|
761
|
+
const contentLower = content.toLowerCase();
|
|
762
|
+
const themes: string[] = [];
|
|
763
|
+
|
|
764
|
+
if (["together", "team", "collaborate"].some((w) => contentLower.includes(w))) {
|
|
765
|
+
themes.push("collaboration");
|
|
766
|
+
}
|
|
767
|
+
if (["integrate", "connect", "bridge"].some((w) => contentLower.includes(w))) {
|
|
768
|
+
themes.push("integration");
|
|
769
|
+
}
|
|
770
|
+
if (["coherent", "consistent", "unified"].some((w) =>
|
|
771
|
+
contentLower.includes(w)
|
|
772
|
+
)) {
|
|
773
|
+
themes.push("coherence");
|
|
774
|
+
}
|
|
775
|
+
if (["transform", "change", "evolve"].some((w) => contentLower.includes(w))) {
|
|
776
|
+
themes.push("transformation");
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return themes.length > 0 ? themes.join(", ") : "development";
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function suggestPacing(intent: string, tension: number): string {
|
|
783
|
+
if (tension > 0.8) return "accelerate";
|
|
784
|
+
if (tension < 0.3) return "breathe";
|
|
785
|
+
if (intent === "inciting_incident" || intent === "climax") return "emphasize";
|
|
786
|
+
return "steady";
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// =============================================================================
|
|
790
|
+
// Synthesis - Combining All Three Perspectives
|
|
791
|
+
// =============================================================================
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Combine all three universe perspectives into a unified analysis.
|
|
795
|
+
*/
|
|
796
|
+
export function synthesizePerspectives(
|
|
797
|
+
state: ThreeUniverseState
|
|
798
|
+
): ThreeUniverseState {
|
|
799
|
+
const engineer = state.engineerPerspective;
|
|
800
|
+
const ceremony = state.ceremonyPerspective;
|
|
801
|
+
const storyEngine = state.storyEnginePerspective;
|
|
802
|
+
|
|
803
|
+
if (!engineer || !ceremony || !storyEngine) {
|
|
804
|
+
return {
|
|
805
|
+
...state,
|
|
806
|
+
error: "Missing one or more perspectives",
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Determine lead universe based on confidence and special conditions
|
|
811
|
+
const lead = determineLeadUniverse(engineer, ceremony, storyEngine);
|
|
812
|
+
|
|
813
|
+
// Calculate coherence
|
|
814
|
+
const coherence = calculateCoherence(engineer, ceremony, storyEngine);
|
|
815
|
+
|
|
816
|
+
// Build the analysis
|
|
817
|
+
const analysis = createThreeUniverseAnalysis(
|
|
818
|
+
engineer,
|
|
819
|
+
ceremony,
|
|
820
|
+
storyEngine,
|
|
821
|
+
lead,
|
|
822
|
+
coherence
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
...state,
|
|
827
|
+
analysis,
|
|
828
|
+
leadUniverse: lead,
|
|
829
|
+
coherenceScore: coherence,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Determine which universe should lead the response.
|
|
835
|
+
*
|
|
836
|
+
* Priority logic:
|
|
837
|
+
* 1. CEREMONY leads if: new contributor, sacred pause needed, relational obligation
|
|
838
|
+
* 2. STORY_ENGINE leads if: narrative coherence critical, character arc in focus
|
|
839
|
+
* 3. ENGINEER leads if: technical precision critical, schema validation required
|
|
840
|
+
* 4. Otherwise: highest confidence wins
|
|
841
|
+
*/
|
|
842
|
+
function determineLeadUniverse(
|
|
843
|
+
engineer: UniversePerspective,
|
|
844
|
+
ceremony: UniversePerspective,
|
|
845
|
+
storyEngine: UniversePerspective
|
|
846
|
+
): Universe {
|
|
847
|
+
const ceremonyContext = ceremony.context;
|
|
848
|
+
|
|
849
|
+
// Check ceremony priority conditions
|
|
850
|
+
if (ceremonyContext.witnessingNeeded) {
|
|
851
|
+
return Universe.CEREMONY;
|
|
852
|
+
}
|
|
853
|
+
if (ceremonyContext.isCollaborative) {
|
|
854
|
+
// Collaborative work honors the ceremony world
|
|
855
|
+
return Universe.CEREMONY;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Check story engine priority conditions
|
|
859
|
+
const storyContext = storyEngine.context;
|
|
860
|
+
if ((storyContext.dramaticTension as number) > 0.8) {
|
|
861
|
+
// High drama moments are led by story engine
|
|
862
|
+
return Universe.STORY_ENGINE;
|
|
863
|
+
}
|
|
864
|
+
if (
|
|
865
|
+
storyContext.narrativeFunction === "climax" ||
|
|
866
|
+
storyContext.narrativeFunction === "turning_point"
|
|
867
|
+
) {
|
|
868
|
+
return Universe.STORY_ENGINE;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Check engineer priority conditions
|
|
872
|
+
const engineerContext = engineer.context;
|
|
873
|
+
if (engineerContext.estimatedComplexity === "high") {
|
|
874
|
+
return Universe.ENGINEER;
|
|
875
|
+
}
|
|
876
|
+
if (engineer.intent === "security" || engineer.intent === "bug_fix") {
|
|
877
|
+
// Technical urgency
|
|
878
|
+
return Universe.ENGINEER;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Default: highest confidence
|
|
882
|
+
const perspectives: [UniversePerspective, Universe][] = [
|
|
883
|
+
[engineer, Universe.ENGINEER],
|
|
884
|
+
[ceremony, Universe.CEREMONY],
|
|
885
|
+
[storyEngine, Universe.STORY_ENGINE],
|
|
886
|
+
];
|
|
887
|
+
|
|
888
|
+
return perspectives.reduce((a, b) =>
|
|
889
|
+
a[0].confidence > b[0].confidence ? a : b
|
|
890
|
+
)[1];
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Calculate how well the three perspectives align.
|
|
895
|
+
*
|
|
896
|
+
* Higher coherence means the perspectives are complementary.
|
|
897
|
+
* Lower coherence might indicate conflicting interpretations.
|
|
898
|
+
*/
|
|
899
|
+
function calculateCoherence(
|
|
900
|
+
engineer: UniversePerspective,
|
|
901
|
+
ceremony: UniversePerspective,
|
|
902
|
+
storyEngine: UniversePerspective
|
|
903
|
+
): number {
|
|
904
|
+
// Base: average confidence
|
|
905
|
+
const avgConfidence =
|
|
906
|
+
(engineer.confidence + ceremony.confidence + storyEngine.confidence) / 3;
|
|
907
|
+
|
|
908
|
+
// Bonus for alignment
|
|
909
|
+
let bonus = 0.0;
|
|
910
|
+
|
|
911
|
+
// If all suggest similar urgency
|
|
912
|
+
const engineerUrgent = ["security", "bug_fix", "performance"].includes(
|
|
913
|
+
engineer.intent
|
|
914
|
+
);
|
|
915
|
+
const ceremonyUrgent = ceremony.context.senderEnergy === "urgent_flow";
|
|
916
|
+
const storyUrgent =
|
|
917
|
+
(storyEngine.context.dramaticTension as number) > 0.7;
|
|
918
|
+
|
|
919
|
+
if ([engineerUrgent, ceremonyUrgent, storyUrgent].filter(Boolean).length >= 2) {
|
|
920
|
+
bonus += 0.1; // Aligned on urgency
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Penalty for very different confidences (might indicate conflict)
|
|
924
|
+
const confidences = [
|
|
925
|
+
engineer.confidence,
|
|
926
|
+
ceremony.confidence,
|
|
927
|
+
storyEngine.confidence,
|
|
928
|
+
];
|
|
929
|
+
const confidenceSpread = Math.max(...confidences) - Math.min(...confidences);
|
|
930
|
+
const penalty = confidenceSpread * 0.2;
|
|
931
|
+
|
|
932
|
+
const coherence = avgConfidence + bonus - penalty;
|
|
933
|
+
return Math.round(Math.max(0.0, Math.min(1.0, coherence)) * 100) / 100;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// =============================================================================
|
|
937
|
+
// Main Processor Class
|
|
938
|
+
// =============================================================================
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* High-level interface for three-universe event processing.
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* const processor = new ThreeUniverseProcessor();
|
|
945
|
+
* const result = processor.process(event);
|
|
946
|
+
* console.log(result.leadUniverse); // "ceremony"
|
|
947
|
+
*
|
|
948
|
+
* @example With tracing callback
|
|
949
|
+
* const handler = new NarrativeTracingHandler({ storyId: "story_123" });
|
|
950
|
+
* const bridge = new LangGraphBridge(handler);
|
|
951
|
+
* const processor = new ThreeUniverseProcessor({
|
|
952
|
+
* tracingCallback: bridge.createThreeUniverseCallback()
|
|
953
|
+
* });
|
|
954
|
+
* const result = processor.process(event); // Automatically traced to Langfuse
|
|
955
|
+
*/
|
|
956
|
+
export class ThreeUniverseProcessor {
|
|
957
|
+
private tracingCallback?: AnalysisCallback;
|
|
958
|
+
|
|
959
|
+
constructor(options: { tracingCallback?: AnalysisCallback } = {}) {
|
|
960
|
+
this.tracingCallback = options.tracingCallback;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Process an event through all three universes.
|
|
965
|
+
*
|
|
966
|
+
* @param event The event data (webhook payload, user input, etc.)
|
|
967
|
+
* @param eventType Type of event (e.g., "github.push", "user.input")
|
|
968
|
+
* @returns ThreeUniverseAnalysis with all perspectives and synthesis
|
|
969
|
+
*/
|
|
970
|
+
process(
|
|
971
|
+
event: Record<string, unknown>,
|
|
972
|
+
eventType: string = "unknown"
|
|
973
|
+
): ThreeUniverseAnalysis {
|
|
974
|
+
// Initialize state
|
|
975
|
+
let state: ThreeUniverseState = {
|
|
976
|
+
event,
|
|
977
|
+
eventType,
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// Process through each universe
|
|
981
|
+
state = analyzeEngineerPerspective(state);
|
|
982
|
+
state = analyzeCeremonyPerspective(state);
|
|
983
|
+
state = analyzeStoryEnginePerspective(state);
|
|
984
|
+
state = synthesizePerspectives(state);
|
|
985
|
+
|
|
986
|
+
// Check for errors
|
|
987
|
+
if (state.error) {
|
|
988
|
+
throw new Error(`Processing error: ${state.error}`);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const analysis = state.analysis!;
|
|
992
|
+
|
|
993
|
+
// Call tracing callback if configured
|
|
994
|
+
if (this.tracingCallback && analysis) {
|
|
995
|
+
const eventId =
|
|
996
|
+
(event.eventId as string) ||
|
|
997
|
+
(event.id as string) ||
|
|
998
|
+
`${eventType}_${Date.now()}`;
|
|
999
|
+
const eventContent = this.extractEventContent(event);
|
|
1000
|
+
|
|
1001
|
+
this.tracingCallback(
|
|
1002
|
+
eventId,
|
|
1003
|
+
eventContent,
|
|
1004
|
+
perspectiveToRecord(analysis.engineer),
|
|
1005
|
+
perspectiveToRecord(analysis.ceremony),
|
|
1006
|
+
perspectiveToRecord(analysis.storyEngine),
|
|
1007
|
+
analysis.leadUniverse,
|
|
1008
|
+
analysis.coherenceScore
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return analysis;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
private extractEventContent(event: Record<string, unknown>): string {
|
|
1016
|
+
// Try common content locations
|
|
1017
|
+
if (event.content) {
|
|
1018
|
+
return String(event.content).slice(0, 500);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
1022
|
+
if (payload && typeof payload === "object") {
|
|
1023
|
+
const issue = payload.issue as Record<string, string> | undefined;
|
|
1024
|
+
if (issue?.title) return issue.title;
|
|
1025
|
+
|
|
1026
|
+
const pr = payload.pull_request as Record<string, string> | undefined;
|
|
1027
|
+
if (pr?.title) return pr.title;
|
|
1028
|
+
|
|
1029
|
+
const comment = payload.comment as Record<string, string> | undefined;
|
|
1030
|
+
if (comment?.body) return comment.body.slice(0, 500);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (event.message) {
|
|
1034
|
+
return String(event.message).slice(0, 500);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
return `Event: ${event.eventType || "unknown"}`;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Convenience method for processing GitHub webhooks.
|
|
1042
|
+
*/
|
|
1043
|
+
processWebhook(webhookPayload: Record<string, unknown>): ThreeUniverseAnalysis {
|
|
1044
|
+
// Determine event type from webhook
|
|
1045
|
+
let eventType = "github.push"; // Default
|
|
1046
|
+
|
|
1047
|
+
const payload = webhookPayload.payload as Record<string, unknown> | undefined;
|
|
1048
|
+
if (payload) {
|
|
1049
|
+
if (payload.issue) {
|
|
1050
|
+
eventType = "github.issue";
|
|
1051
|
+
} else if (payload.pull_request) {
|
|
1052
|
+
eventType = "github.pull_request";
|
|
1053
|
+
} else if (payload.comment) {
|
|
1054
|
+
eventType = "github.comment";
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return this.process(webhookPayload, eventType);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Create a story beat from event and analysis.
|
|
1063
|
+
*/
|
|
1064
|
+
createBeatFromAnalysis(
|
|
1065
|
+
event: Record<string, unknown>,
|
|
1066
|
+
analysis: ThreeUniverseAnalysis,
|
|
1067
|
+
sequence: number
|
|
1068
|
+
): StoryBeat {
|
|
1069
|
+
// Map story engine intent to NarrativeFunction
|
|
1070
|
+
const functionMap: Record<string, NarrativeFunction> = {
|
|
1071
|
+
inciting_incident: NarrativeFunction.INCITING_INCIDENT,
|
|
1072
|
+
rising_action: NarrativeFunction.RISING_ACTION,
|
|
1073
|
+
turning_point: NarrativeFunction.TURNING_POINT,
|
|
1074
|
+
complication: NarrativeFunction.COMPLICATION,
|
|
1075
|
+
crisis: NarrativeFunction.CRISIS,
|
|
1076
|
+
climax: NarrativeFunction.CLIMAX,
|
|
1077
|
+
resolution: NarrativeFunction.RESOLUTION,
|
|
1078
|
+
denouement: NarrativeFunction.DENOUEMENT,
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const storyIntent = analysis.storyEngine.intent;
|
|
1082
|
+
const narrativeFunc =
|
|
1083
|
+
functionMap[storyIntent] || NarrativeFunction.BEAT;
|
|
1084
|
+
|
|
1085
|
+
// Extract act
|
|
1086
|
+
const act = (analysis.storyEngine.context.act as number) || 2;
|
|
1087
|
+
|
|
1088
|
+
// Build content
|
|
1089
|
+
let content = extractContent(event);
|
|
1090
|
+
if (!content) {
|
|
1091
|
+
content = String(event.eventType || "event");
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Generate beat ID
|
|
1095
|
+
const timestamp = new Date().toISOString();
|
|
1096
|
+
const beatId = `beat_${timestamp}`;
|
|
1097
|
+
|
|
1098
|
+
// Source event ID
|
|
1099
|
+
let sourceEventId: string | undefined;
|
|
1100
|
+
const payload = event.payload as Record<string, unknown> | undefined;
|
|
1101
|
+
if (payload) {
|
|
1102
|
+
const headCommit = payload.head_commit as Record<string, string> | undefined;
|
|
1103
|
+
if (headCommit?.id) {
|
|
1104
|
+
sourceEventId = headCommit.id;
|
|
1105
|
+
} else if (payload.issue) {
|
|
1106
|
+
const issue = payload.issue as Record<string, unknown>;
|
|
1107
|
+
sourceEventId = String(issue.id);
|
|
1108
|
+
} else if (payload.pull_request) {
|
|
1109
|
+
const pr = payload.pull_request as Record<string, unknown>;
|
|
1110
|
+
sourceEventId = String(pr.id);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
return createStoryBeat(beatId, sequence, content.slice(0, 500), narrativeFunc, act, {
|
|
1115
|
+
universeAnalysis: analysis,
|
|
1116
|
+
leadUniverse: analysis.leadUniverse,
|
|
1117
|
+
source: "processor",
|
|
1118
|
+
sourceEventId,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Convert a UniversePerspective to a plain record.
|
|
1125
|
+
*/
|
|
1126
|
+
function perspectiveToRecord(
|
|
1127
|
+
perspective: UniversePerspective
|
|
1128
|
+
): Record<string, unknown> {
|
|
1129
|
+
return {
|
|
1130
|
+
universe: perspective.universe,
|
|
1131
|
+
intent: perspective.intent,
|
|
1132
|
+
confidence: perspective.confidence,
|
|
1133
|
+
suggestedFlows: perspective.suggestedFlows,
|
|
1134
|
+
context: perspective.context,
|
|
1135
|
+
};
|
|
1136
|
+
}
|