@neuroverseos/governance 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLens
3
- } from "./chunk-VGFDMPVB.js";
3
+ } from "./chunk-F2LWMOM5.js";
4
4
  import {
5
5
  loadWorld
6
6
  } from "./chunk-I4RTIMLX.js";
@@ -8,91 +8,166 @@ import {
8
8
  evaluateGuard
9
9
  } from "./chunk-ZAF6JH23.js";
10
10
 
11
+ // src/radiant/core/compress.ts
12
+ function compressWorldmodel(content) {
13
+ const lines = [];
14
+ const missionMatch = content.match(/##\s*Mission\s*\n+(?:<!--[\s\S]*?-->\s*\n+)?(.*?)(?:\n\n|\n##|$)/s);
15
+ if (missionMatch) {
16
+ const mission = missionMatch[1].trim().split("\n")[0];
17
+ lines.push(`Mission: ${mission}`);
18
+ }
19
+ const domainMatches = content.matchAll(/###\s+([^\n]+)/g);
20
+ const domains = [];
21
+ for (const m of domainMatches) {
22
+ const name = m[1].trim();
23
+ if (name !== "Skills" && name !== "Values" && !name.startsWith("####")) {
24
+ domains.push(name);
25
+ }
26
+ }
27
+ if (domains.length > 0) {
28
+ lines.push(`Domains: ${domains.join(", ")}`);
29
+ }
30
+ const invariantSection = content.match(/(?:Invariants|## Invariants|invariants)([\s\S]*?)(?:\n#|\n---|\n\n\n)/i);
31
+ if (invariantSection) {
32
+ const invLines = invariantSection[1].match(/^[-*]\s+`?([^`\n]+)/gm);
33
+ if (invLines) {
34
+ lines.push("\nInvariants:");
35
+ for (const inv of invLines.slice(0, 10)) {
36
+ lines.push(inv.trim());
37
+ }
38
+ }
39
+ }
40
+ const prioritySection = content.match(/(?:Decision Priorities|## Decision Priorities)([\s\S]*?)(?:\n#|\n---|\n\n\n)/i);
41
+ if (prioritySection) {
42
+ const priLines = prioritySection[1].match(/^[-*]\s+.+>.+/gm);
43
+ if (priLines) {
44
+ lines.push("\nPriorities:");
45
+ for (const pri of priLines.slice(0, 10)) {
46
+ lines.push(pri.trim());
47
+ }
48
+ }
49
+ }
50
+ const signalSection = content.match(/(?:## Signals)([\s\S]*?)(?:\n#|\n---|\n\n\n)/i);
51
+ if (signalSection) {
52
+ const sigLines = signalSection[1].match(/^[-*]\s+(\w+)/gm);
53
+ if (sigLines) {
54
+ lines.push(`
55
+ Signals: ${sigLines.map((s) => s.replace(/^[-*]\s+/, "")).join(", ")}`);
56
+ }
57
+ }
58
+ const driftSection = content.match(/(?:Drift Behaviors|## Drift Behaviors)([\s\S]*?)(?:\n#|\n---|\n\n\n)/i);
59
+ if (driftSection) {
60
+ const driftLines = driftSection[1].match(/^[-*]\s+(.+)/gm);
61
+ if (driftLines) {
62
+ lines.push("\nDrift behaviors:");
63
+ for (const d of driftLines.slice(0, 5)) {
64
+ lines.push(d.trim());
65
+ }
66
+ }
67
+ }
68
+ const compressed = lines.join("\n");
69
+ if (compressed.length < 50) {
70
+ return content.slice(0, 2e3) + "\n[truncated]";
71
+ }
72
+ return compressed;
73
+ }
74
+ function compressExocortex(ctx) {
75
+ const lines = [];
76
+ if (ctx.attention) {
77
+ lines.push(`Attention: ${firstMeaningfulLine(ctx.attention)}`);
78
+ }
79
+ if (ctx.goals) {
80
+ lines.push(`Goals: ${firstNLines(ctx.goals, 3)}`);
81
+ }
82
+ if (ctx.sprint) {
83
+ lines.push(`Sprint: ${firstNLines(ctx.sprint, 3)}`);
84
+ }
85
+ if (ctx.identity) {
86
+ lines.push(`Identity: ${firstMeaningfulLine(ctx.identity)}`);
87
+ }
88
+ if (ctx.organization) {
89
+ lines.push(`Org: ${firstMeaningfulLine(ctx.organization)}`);
90
+ }
91
+ return lines.join("\n");
92
+ }
93
+ function compressLens(lens) {
94
+ return {
95
+ evaluationQuestions: lens.primary_frame.evaluation_questions.map((q, i) => `${i + 1}. ${q}`).join("\n"),
96
+ scoringRubric: lens.primary_frame.scoring_rubric,
97
+ forbiddenPhrases: lens.forbidden_phrases.join(", "),
98
+ jargonTranslations: Object.entries(lens.vocabulary.jargon_translations).map(([k, v]) => `${k} \u2192 ${v}`).join("; "),
99
+ strategicPatterns: lens.strategic_patterns.slice(0, 5).join("\n")
100
+ };
101
+ }
102
+ function compressPriorReads(reads) {
103
+ if (reads.length === 0) return "";
104
+ const patternCounts = /* @__PURE__ */ new Map();
105
+ for (const read of reads) {
106
+ for (const name of read.patternNames) {
107
+ patternCounts.set(name, (patternCounts.get(name) ?? 0) + 1);
108
+ }
109
+ }
110
+ const sorted = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]);
111
+ if (sorted.length === 0) {
112
+ return `${reads.length} prior reads, no patterns extracted.`;
113
+ }
114
+ const patternList = sorted.map(([name, count]) => `${name} (${count}x)`).join(", ");
115
+ return `${reads.length} prior reads. Patterns seen: ${patternList}. If these recur, note persistence.`;
116
+ }
117
+ function firstMeaningfulLine(text) {
118
+ const lines = text.split("\n").filter((l) => {
119
+ const t = l.trim();
120
+ return t.length > 0 && !t.startsWith("#") && !t.startsWith("<!--");
121
+ });
122
+ return lines[0]?.slice(0, 200) ?? "";
123
+ }
124
+ function firstNLines(text, n) {
125
+ const lines = text.split("\n").filter((l) => {
126
+ const t = l.trim();
127
+ return t.length > 0 && !t.startsWith("#") && !t.startsWith("<!--");
128
+ });
129
+ return lines.slice(0, n).map((l) => l.slice(0, 150)).join("; ");
130
+ }
131
+
11
132
  // src/radiant/core/prompt.ts
12
133
  function composeSystemPrompt(worldmodelContent, lens) {
13
- const sections = [];
14
- sections.push(
15
- `## Worldmodel
16
-
17
- You are operating inside a governed environment. The worldmodel below
18
- defines the invariants, signals, decision priorities, and behavioral
19
- expectations for this organization. Every response you produce must
20
- be grounded in this worldmodel.
21
-
22
- ` + worldmodelContent
23
- );
24
- const frame = lens.primary_frame;
25
- const questionsBlock = frame.evaluation_questions.map((q, i) => `${i + 1}. ${q}`).join("\n");
26
- const overlapsBlock = frame.overlaps.map(
27
- (o) => `- ${o.domains[0]} + ${o.domains[1]} = **${o.emergent_state}**: ${o.description}`
28
- ).join("\n");
29
- sections.push(
30
- `## How to Think (Analytical Frame: ${lens.name})
31
-
32
- ${frame.scoring_rubric}
33
-
34
- ### Evaluation questions to reason through
134
+ const compressedWorld = compressWorldmodel(worldmodelContent);
135
+ const cl = compressLens(lens);
136
+ const overlapsBlock = lens.primary_frame.overlaps.map((o) => `${o.domains[0]} + ${o.domains[1]} = ${o.emergent_state}`).join("\n");
137
+ return [
138
+ // Section 1: Compressed worldmodel
139
+ `## Worldmodel (compressed)
35
140
 
36
- ${questionsBlock}
141
+ ${compressedWorld}`,
142
+ // Section 2: Analytical frame (evaluation questions + rubric)
143
+ `## How to Think
37
144
 
38
- ### Overlap emergent states
145
+ ${cl.scoringRubric}
39
146
 
40
- ${overlapsBlock}
147
+ Questions:
148
+ ${cl.evaluationQuestions}
41
149
 
42
- ### Center identity
150
+ Overlaps: ${overlapsBlock}
151
+ Center: ${lens.primary_frame.center_identity}
43
152
 
44
- When all dimensions integrate fully: **${frame.center_identity}**. Surface this sparingly \u2014 only when the integration is genuinely complete.`
45
- );
46
- const vocabPreferred = Object.entries(lens.vocabulary.preferred).map(([generic, native]) => `- "${generic}" \u2192 **${native}**`).join("\n");
47
- const vocabArchitecture = lens.vocabulary.architecture.map((t) => `\`${t}\``).join(", ");
48
- const vocabProperNouns = lens.vocabulary.proper_nouns.map((n) => `**${n}**`).join(", ");
49
- const strategicBlock = lens.strategic_patterns.map((p) => `- ${p}`).join("\n");
50
- sections.push(
51
- `## How to Speak (Voice: ${lens.name})
153
+ Translate before output: ${cl.jargonTranslations}`,
154
+ // Section 3: Voice (compressed — register + key rules only)
155
+ `## Voice: ${lens.name}
52
156
 
53
157
  Register: ${lens.voice.register}
54
-
55
- Rules:
56
- - Active voice: ${lens.voice.active_voice}
57
- - Named specificity (people, places, numbers): ${lens.voice.specificity}
58
- - Hype vocabulary: ${lens.voice.hype_vocabulary}
59
- - Hedging / qualified phrasing: ${lens.voice.hedging}
60
- - Playfulness: ${lens.voice.playfulness}
61
- - Close with strategic frame: ${lens.voice.close_with_strategic_frame}
62
- - Honesty about failure: ${lens.voice.honesty_about_failure}
63
-
64
- ### Output translation discipline
158
+ Active voice: ${lens.voice.active_voice}. Specificity: ${lens.voice.specificity}. Hedging: ${lens.voice.hedging}. Hype: ${lens.voice.hype_vocabulary}. Honesty about failure: ${lens.voice.honesty_about_failure}.
65
159
 
66
160
  ${lens.voice.output_translation}
67
161
 
68
- ### Vocabulary
69
-
70
- Proper nouns (use literally): ${vocabProperNouns}
71
-
72
- Preferred term substitutions:
73
- ${vocabPreferred}
74
-
75
- Architecture vocabulary: ${vocabArchitecture}
76
-
77
- ### Strategic decision patterns
78
-
79
- When recommending action, these patterns reflect how this organization resolves tradeoffs:
80
-
81
- ${strategicBlock}`
82
- );
83
- const forbiddenBlock = lens.forbidden_phrases.map((p) => `- "${p}"`).join("\n");
84
- sections.push(
162
+ Strategic patterns:
163
+ ${cl.strategicPatterns}`,
164
+ // Section 4: Guardrails (forbidden phrases as comma-separated, not bulleted)
85
165
  `## Guardrails
86
166
 
87
- Do NOT use any of these phrases in your response. If you catch yourself
88
- reaching for one, rephrase in direct, active, specific language instead.
167
+ Do NOT use: ${cl.forbiddenPhrases}
89
168
 
90
- ${forbiddenBlock}
91
-
92
- If your response would violate a worldmodel invariant, state the conflict
93
- explicitly and propose an alternative that honors the invariant.`
94
- );
95
- return sections.join("\n\n---\n\n");
169
+ If a response would violate a worldmodel invariant, state the conflict and propose an alternative.`
170
+ ].join("\n\n---\n\n");
96
171
  }
97
172
 
98
173
  // src/radiant/core/voice-check.ts
@@ -145,17 +220,30 @@ function resolveLens(id) {
145
220
  }
146
221
 
147
222
  // src/radiant/core/scopes.ts
148
- function parseRepoScope(scope) {
223
+ function parseScope(scope) {
149
224
  const cleaned = scope.replace(/^https?:\/\//, "").replace(/^github\.com\//, "").replace(/\.git$/, "").replace(/\/$/, "");
150
- const parts = cleaned.split("/");
151
- if (parts.length < 2 || !parts[0] || !parts[1]) {
225
+ const parts = cleaned.split("/").filter(Boolean);
226
+ if (parts.length === 0 || !parts[0]) {
227
+ throw new Error(
228
+ `Cannot parse scope: "${scope}". Expected "owner/repo" or "owner".`
229
+ );
230
+ }
231
+ if (parts.length === 1) {
232
+ return { type: "org", owner: parts[0] };
233
+ }
234
+ return { type: "repo", owner: parts[0], repo: parts[1] };
235
+ }
236
+ function parseRepoScope(scope) {
237
+ const parsed = parseScope(scope);
238
+ if (parsed.type === "org") {
152
239
  throw new Error(
153
- `Cannot parse repo scope: "${scope}". Expected "owner/repo" or a GitHub URL.`
240
+ `Expected "owner/repo" but got org-level scope "${parsed.owner}". Use parseScope() for org-level.`
154
241
  );
155
242
  }
156
- return { owner: parts[0], repo: parts[1] };
243
+ return parsed;
157
244
  }
158
245
  function formatScope(scope) {
246
+ if (scope.type === "org") return `${scope.owner} (org)`;
159
247
  return `${scope.owner}/${scope.repo}`;
160
248
  }
161
249
 
@@ -339,22 +427,519 @@ async function fetchJSON(url, headers) {
339
427
  }
340
428
  return await res.json();
341
429
  }
430
+ async function fetchGitHubOrgActivity(scope, token, options = {}) {
431
+ const perPage = options.perPage ?? 100;
432
+ const headers = {
433
+ Authorization: `token ${token}`,
434
+ Accept: "application/vnd.github.v3+json",
435
+ "User-Agent": "neuroverseos-radiant"
436
+ };
437
+ const repos = await fetchJSON(
438
+ `https://api.github.com/orgs/${scope.owner}/repos?sort=pushed&direction=desc&per_page=${perPage}`,
439
+ headers
440
+ );
441
+ const windowDays = options.windowDays ?? 14;
442
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
443
+ const activeRepos = repos.filter(
444
+ (r) => new Date(r.pushed_at) >= since
445
+ );
446
+ const cappedRepos = activeRepos.slice(0, 10);
447
+ const allEvents = [];
448
+ const repoNames = [];
449
+ for (const repo of cappedRepos) {
450
+ const [owner, repoName] = repo.full_name.split("/");
451
+ try {
452
+ const repoScope = { type: "repo", owner, repo: repoName };
453
+ const events = await fetchGitHubActivity(repoScope, token, options);
454
+ allEvents.push(...events);
455
+ if (events.length > 0) repoNames.push(repo.full_name);
456
+ } catch {
457
+ }
458
+ }
459
+ allEvents.sort(
460
+ (a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)
461
+ );
462
+ return { events: allEvents, repos: repoNames };
463
+ }
342
464
  function createMockGitHubAdapter(fixedEvents) {
343
465
  return async () => fixedEvents;
344
466
  }
345
467
 
346
- // src/radiant/adapters/exocortex.ts
347
- import { readFileSync, existsSync } from "fs";
348
- import { join, resolve } from "path";
349
- function readExocortex(dirPath) {
468
+ // src/radiant/adapters/discord.ts
469
+ async function fetchDiscordActivity(guildId, token, options = {}) {
470
+ const windowDays = options.windowDays ?? 14;
471
+ const perChannel = options.perChannel ?? 100;
472
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
473
+ const headers = {
474
+ Authorization: `Bot ${token}`,
475
+ "Content-Type": "application/json"
476
+ };
477
+ const channels = await fetchJSON2(
478
+ `https://discord.com/api/v10/guilds/${guildId}/channels`,
479
+ headers
480
+ );
481
+ const textChannels = channels.filter((c) => {
482
+ if (c.type !== 0) return false;
483
+ if (options.channelIds && options.channelIds.length > 0) {
484
+ return options.channelIds.includes(c.id);
485
+ }
486
+ if (options.visibility === "public") {
487
+ return !c.name.startsWith("private-") && !c.nsfw;
488
+ }
489
+ return true;
490
+ });
491
+ const events = [];
492
+ let totalMessages = 0;
493
+ let helpRequests = 0;
494
+ let unresolvedThreads = 0;
495
+ let newcomerMessages = 0;
496
+ const responseTimes = [];
497
+ const participants = /* @__PURE__ */ new Set();
498
+ const knownParticipants = /* @__PURE__ */ new Set();
499
+ const topicCounts = /* @__PURE__ */ new Map();
500
+ for (const channel of textChannels.slice(0, 15)) {
501
+ try {
502
+ const messages = await fetchJSON2(
503
+ `https://discord.com/api/v10/channels/${channel.id}/messages?limit=${perChannel}`,
504
+ headers
505
+ );
506
+ const inWindow = messages.filter(
507
+ (m) => new Date(m.timestamp) >= since
508
+ );
509
+ totalMessages += inWindow.length;
510
+ const topic = channel.name.replace(/-/g, " ");
511
+ topicCounts.set(topic, (topicCounts.get(topic) ?? 0) + inWindow.length);
512
+ for (const msg of inWindow) {
513
+ const actor = mapDiscordUser(msg.author);
514
+ participants.add(actor.id);
515
+ const lowerContent = msg.content.toLowerCase();
516
+ if (lowerContent.includes("help") || lowerContent.includes("stuck") || lowerContent.includes("how do i") || lowerContent.includes("anyone know")) {
517
+ helpRequests++;
518
+ }
519
+ if (msg.referenced_message) {
520
+ const refTime = new Date(msg.referenced_message.timestamp).getTime();
521
+ const msgTime = new Date(msg.timestamp).getTime();
522
+ const diffMinutes = (msgTime - refTime) / 6e4;
523
+ if (diffMinutes > 0 && diffMinutes < 10080) {
524
+ responseTimes.push(diffMinutes);
525
+ }
526
+ }
527
+ events.push({
528
+ id: `discord-${msg.id}`,
529
+ timestamp: msg.timestamp,
530
+ actor,
531
+ kind: "discord_message",
532
+ content: msg.content.slice(0, 500),
533
+ respondsTo: msg.referenced_message ? {
534
+ eventId: `discord-${msg.referenced_message.id}`,
535
+ actor: mapDiscordUser(msg.referenced_message.author)
536
+ } : void 0,
537
+ metadata: {
538
+ channel: channel.name,
539
+ guildId
540
+ }
541
+ });
542
+ }
543
+ } catch {
544
+ }
545
+ }
546
+ const avgResponseMinutes = responseTimes.length > 0 ? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length : null;
547
+ const topTopics = [...topicCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t]) => t);
548
+ const signals = {
549
+ totalMessages,
550
+ activeChannels: textChannels.length,
551
+ uniqueParticipants: participants.size,
552
+ avgResponseMinutes: avgResponseMinutes ? Math.round(avgResponseMinutes) : null,
553
+ helpRequests,
554
+ unresolvedThreads,
555
+ topTopics,
556
+ newcomerMessages
557
+ };
558
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
559
+ return { events, signals };
560
+ }
561
+ function formatDiscordSignalsForPrompt(signals) {
562
+ if (signals.totalMessages === 0) return "";
563
+ const lines = [
564
+ "## Discord Activity (conversational behavior)",
565
+ "",
566
+ `${signals.totalMessages} messages across ${signals.activeChannels} channels.`,
567
+ `${signals.uniqueParticipants} unique participants.`
568
+ ];
569
+ if (signals.avgResponseMinutes !== null) {
570
+ lines.push(`Average response time: ${signals.avgResponseMinutes} minutes.`);
571
+ }
572
+ if (signals.helpRequests > 0) {
573
+ lines.push(`${signals.helpRequests} help requests detected.`);
574
+ }
575
+ if (signals.unresolvedThreads > 0) {
576
+ lines.push(`${signals.unresolvedThreads} unresolved threads.`);
577
+ }
578
+ if (signals.topTopics.length > 0) {
579
+ lines.push(`Top discussion topics: ${signals.topTopics.join(", ")}.`);
580
+ }
581
+ lines.push("");
582
+ lines.push("Compare conversational activity against GitHub shipping activity.");
583
+ lines.push("Where debates happen in Discord but nothing ships in GitHub, name the gap.");
584
+ lines.push("Where work ships in GitHub but nobody discusses it in Discord, name the visibility gap.");
585
+ return lines.join("\n");
586
+ }
587
+ function mapDiscordUser(user) {
588
+ return {
589
+ id: user.username,
590
+ kind: user.bot ? "bot" : "human",
591
+ name: user.username
592
+ };
593
+ }
594
+ async function fetchJSON2(url, headers) {
595
+ const res = await fetch(url, { headers });
596
+ if (!res.ok) {
597
+ if (res.status === 404 || res.status === 403) return [];
598
+ throw new Error(`Discord API error ${res.status}: ${(await res.text()).slice(0, 300)}`);
599
+ }
600
+ return await res.json();
601
+ }
602
+
603
+ // src/radiant/adapters/slack.ts
604
+ async function fetchSlackActivity(token, options = {}) {
605
+ const windowDays = options.windowDays ?? 14;
606
+ const perChannel = options.perChannel ?? 100;
607
+ const oldest = String(
608
+ Math.floor((Date.now() - windowDays * 24 * 60 * 60 * 1e3) / 1e3)
609
+ );
610
+ const headers = {
611
+ Authorization: `Bearer ${token}`,
612
+ "Content-Type": "application/json"
613
+ };
614
+ const channelsResponse = await fetchSlackAPI("https://slack.com/api/conversations.list?types=public_channel&limit=200", headers);
615
+ let channels = channelsResponse.channels ?? [];
616
+ if (options.channelIds && options.channelIds.length > 0) {
617
+ const ids = new Set(options.channelIds);
618
+ channels = channels.filter((c) => ids.has(c.id));
619
+ }
620
+ if (options.visibility === "public") {
621
+ channels = channels.filter((c) => !c.is_private && !c.is_archived);
622
+ }
623
+ const events = [];
624
+ let totalMessages = 0;
625
+ let reactionCount = 0;
626
+ let unresolvedThreads = 0;
627
+ const responseTimes = [];
628
+ const participants = /* @__PURE__ */ new Set();
629
+ const externalParticipants = /* @__PURE__ */ new Set();
630
+ const channelMessageCounts = /* @__PURE__ */ new Map();
631
+ for (const channel of channels.slice(0, 15)) {
632
+ try {
633
+ const historyResponse = await fetchSlackAPI(
634
+ `https://slack.com/api/conversations.history?channel=${channel.id}&limit=${perChannel}&oldest=${oldest}`,
635
+ headers
636
+ );
637
+ const messages = historyResponse.messages ?? [];
638
+ totalMessages += messages.length;
639
+ channelMessageCounts.set(channel.name, messages.length);
640
+ for (const msg of messages) {
641
+ if (msg.subtype === "channel_join" || msg.subtype === "channel_leave") continue;
642
+ const actor = mapSlackUser(msg.user ?? "unknown");
643
+ participants.add(actor.id);
644
+ if (msg.reactions) {
645
+ reactionCount += msg.reactions.reduce(
646
+ (sum, r) => sum + (r.count ?? 0),
647
+ 0
648
+ );
649
+ }
650
+ if (msg.thread_ts && msg.thread_ts !== msg.ts) {
651
+ const parentTs = parseFloat(msg.thread_ts) * 1e3;
652
+ const msgTs = parseFloat(msg.ts) * 1e3;
653
+ const diffMinutes = (msgTs - parentTs) / 6e4;
654
+ if (diffMinutes > 0 && diffMinutes < 10080) {
655
+ responseTimes.push(diffMinutes);
656
+ }
657
+ }
658
+ if (msg.thread_ts === msg.ts && (!msg.reply_count || msg.reply_count === 0)) {
659
+ if (msg.text && (msg.text.includes("?") || msg.text.toLowerCase().includes("help"))) {
660
+ unresolvedThreads++;
661
+ }
662
+ }
663
+ const timestamp = new Date(parseFloat(msg.ts) * 1e3).toISOString();
664
+ events.push({
665
+ id: `slack-${msg.ts}`,
666
+ timestamp,
667
+ actor,
668
+ kind: "slack_message",
669
+ content: (msg.text ?? "").slice(0, 500),
670
+ respondsTo: msg.thread_ts && msg.thread_ts !== msg.ts ? {
671
+ eventId: `slack-${msg.thread_ts}`,
672
+ actor: { id: "thread-parent", kind: "unknown" }
673
+ } : void 0,
674
+ metadata: {
675
+ channel: channel.name,
676
+ isPrivate: channel.is_private,
677
+ hasReactions: (msg.reactions?.length ?? 0) > 0
678
+ }
679
+ });
680
+ }
681
+ } catch {
682
+ }
683
+ }
684
+ const avgResponseMinutes = responseTimes.length > 0 ? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length) : null;
685
+ const topChannels = [...channelMessageCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
686
+ const signals = {
687
+ totalMessages,
688
+ activeChannels: channelMessageCounts.size,
689
+ uniqueParticipants: participants.size,
690
+ avgResponseMinutes,
691
+ externalParticipants: externalParticipants.size,
692
+ unresolvedThreads,
693
+ topChannels,
694
+ reactionCount
695
+ };
696
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
697
+ return { events, signals };
698
+ }
699
+ function formatSlackSignalsForPrompt(signals) {
700
+ if (signals.totalMessages === 0) return "";
701
+ const lines = [
702
+ "## Slack Activity (external coordination)",
703
+ "",
704
+ `${signals.totalMessages} messages across ${signals.activeChannels} channels.`,
705
+ `${signals.uniqueParticipants} unique participants.`
706
+ ];
707
+ if (signals.avgResponseMinutes !== null) {
708
+ lines.push(`Average thread response time: ${signals.avgResponseMinutes} minutes.`);
709
+ }
710
+ if (signals.unresolvedThreads > 0) {
711
+ lines.push(`${signals.unresolvedThreads} questions/threads with no reply.`);
712
+ }
713
+ if (signals.reactionCount > 0) {
714
+ lines.push(`${signals.reactionCount} reactions (engagement signal).`);
715
+ }
716
+ if (signals.topChannels.length > 0) {
717
+ lines.push(`Most active channels: ${signals.topChannels.join(", ")}.`);
718
+ }
719
+ lines.push("");
720
+ lines.push("Slack carries external coordination \u2014 partner and client communication.");
721
+ lines.push("Compare partner engagement against internal activity. Where partners are");
722
+ lines.push("active but internal follow-through is low, name the gap.");
723
+ return lines.join("\n");
724
+ }
725
+ function mapSlackUser(userId) {
726
+ return {
727
+ id: userId,
728
+ kind: "human",
729
+ name: userId
730
+ };
731
+ }
732
+ async function fetchSlackAPI(url, headers) {
733
+ const res = await fetch(url, { headers });
734
+ if (!res.ok) {
735
+ throw new Error(`Slack API error ${res.status}: ${(await res.text()).slice(0, 300)}`);
736
+ }
737
+ const data = await res.json();
738
+ if (!data.ok) {
739
+ throw new Error(`Slack API error: ${data.error ?? "unknown"}`);
740
+ }
741
+ return data;
742
+ }
743
+
744
+ // src/radiant/adapters/notion.ts
745
+ async function fetchNotionActivity(token, options = {}) {
746
+ const windowDays = options.windowDays ?? 14;
747
+ const maxPages = options.maxPages ?? 100;
748
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
749
+ const headers = {
750
+ Authorization: `Bearer ${token}`,
751
+ "Notion-Version": "2022-06-28",
752
+ "Content-Type": "application/json"
753
+ };
754
+ const searchResponse = await fetchNotionAPI("https://api.notion.com/v1/search", headers, {
755
+ method: "POST",
756
+ body: JSON.stringify({
757
+ filter: { property: "object", value: "page" },
758
+ sort: { direction: "descending", timestamp: "last_edited_time" },
759
+ page_size: maxPages
760
+ })
761
+ });
762
+ const pages = searchResponse.results ?? [];
763
+ const events = [];
764
+ const editors = /* @__PURE__ */ new Set();
765
+ let pagesCreated = 0;
766
+ let pagesUpdated = 0;
767
+ let stalePages = 0;
768
+ const editAges = [];
769
+ const topPages = [];
770
+ const now = Date.now();
771
+ for (const page of pages) {
772
+ const lastEdited = new Date(page.last_edited_time);
773
+ const created = new Date(page.created_time);
774
+ const daysSinceEdit = (now - lastEdited.getTime()) / (24 * 60 * 60 * 1e3);
775
+ editAges.push(daysSinceEdit);
776
+ if (daysSinceEdit > 30) stalePages++;
777
+ const title = extractTitle(page);
778
+ const editorId = page.last_edited_by?.id ?? "unknown";
779
+ editors.add(editorId);
780
+ if (lastEdited >= since) {
781
+ const isNew = created >= since;
782
+ if (isNew) pagesCreated++;
783
+ else pagesUpdated++;
784
+ topPages.push({ title, editedAt: page.last_edited_time });
785
+ events.push({
786
+ id: `notion-${page.id}`,
787
+ timestamp: page.last_edited_time,
788
+ actor: {
789
+ id: editorId,
790
+ kind: "human",
791
+ name: editorId
792
+ },
793
+ kind: isNew ? "doc_created" : "doc_updated",
794
+ content: `${isNew ? "Created" : "Updated"}: ${title}`,
795
+ metadata: {
796
+ pageId: page.id,
797
+ url: page.url,
798
+ createdAt: page.created_time
799
+ }
800
+ });
801
+ }
802
+ }
803
+ const avgDaysSinceEdit = editAges.length > 0 ? Math.round(editAges.reduce((a, b) => a + b, 0) / editAges.length) : null;
804
+ const signals = {
805
+ pagesActive: pagesCreated + pagesUpdated,
806
+ pagesCreated,
807
+ pagesUpdated,
808
+ uniqueEditors: editors.size,
809
+ stalePages,
810
+ avgDaysSinceEdit,
811
+ topPages: topPages.slice(0, 5).map((p) => p.title)
812
+ };
813
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
814
+ return { events, signals };
815
+ }
816
+ function formatNotionSignalsForPrompt(signals) {
817
+ if (signals.pagesActive === 0 && signals.stalePages === 0) return "";
818
+ const lines = [
819
+ "## Notion Activity (documentation behavior)",
820
+ "",
821
+ `${signals.pagesActive} pages active in window (${signals.pagesCreated} created, ${signals.pagesUpdated} updated).`,
822
+ `${signals.uniqueEditors} unique editors.`
823
+ ];
824
+ if (signals.stalePages > 0) {
825
+ lines.push(`${signals.stalePages} pages haven't been touched in 30+ days.`);
826
+ }
827
+ if (signals.avgDaysSinceEdit !== null) {
828
+ lines.push(`Average page age since last edit: ${signals.avgDaysSinceEdit} days.`);
829
+ }
830
+ if (signals.topPages.length > 0) {
831
+ lines.push(`Recently active pages: ${signals.topPages.join(", ")}.`);
832
+ }
833
+ lines.push("");
834
+ lines.push("Documentation is how the team crystallizes and shares knowledge.");
835
+ lines.push("High code velocity + low documentation = building without recording.");
836
+ lines.push("High documentation + low code = planning without shipping.");
837
+ lines.push("Compare Notion activity against GitHub and Discord to find the balance.");
838
+ return lines.join("\n");
839
+ }
840
+ function extractTitle(page) {
841
+ for (const prop of Object.values(page.properties)) {
842
+ if (prop.type === "title" && prop.title) {
843
+ return prop.title.map((t) => t.plain_text).join("") || "Untitled";
844
+ }
845
+ }
846
+ return "Untitled";
847
+ }
848
+ async function fetchNotionAPI(url, headers, init) {
849
+ const res = await fetch(url, {
850
+ method: init?.method ?? "GET",
851
+ headers,
852
+ body: init?.body
853
+ });
854
+ if (!res.ok) {
855
+ throw new Error(
856
+ `Notion API error ${res.status}: ${(await res.text()).slice(0, 300)}`
857
+ );
858
+ }
859
+ return await res.json();
860
+ }
861
+
862
+ // src/radiant/core/discovery.ts
863
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
864
+ import { join, resolve, basename } from "path";
865
+ import { homedir } from "os";
866
+ function discoverWorlds(options) {
867
+ const worlds = [];
868
+ const userDir = options?.userWorldsDir ?? join(homedir(), ".neuroverse", "worlds");
869
+ if (existsSync(userDir)) {
870
+ worlds.push(...loadWorldsFromDir(userDir, "user"));
871
+ }
872
+ if (options?.explicitWorldsDir) {
873
+ worlds.push(...loadWorldsFromDir(options.explicitWorldsDir, "repo"));
874
+ } else if (options?.repoDir) {
875
+ const repoPaths = [
876
+ join(options.repoDir, "worlds"),
877
+ join(options.repoDir, ".neuroverse", "worlds")
878
+ ];
879
+ for (const p of repoPaths) {
880
+ if (existsSync(p)) {
881
+ worlds.push(...loadWorldsFromDir(p, "repo"));
882
+ break;
883
+ }
884
+ }
885
+ }
886
+ const combinedContent = worlds.map((w) => `<!-- world: ${w.name} (${w.source}) -->
887
+ ${w.content}`).join("\n\n---\n\n");
888
+ const summary = worlds.length === 0 ? "no worlds discovered" : worlds.map((w) => `${w.name} (${w.source})`).join(", ");
889
+ return { worlds, combinedContent, summary };
890
+ }
891
+ function formatActiveWorlds(stack) {
892
+ if (stack.worlds.length === 0) return "No worlds loaded.";
893
+ const lines = ["ACTIVE WORLDS", ""];
894
+ for (const w of stack.worlds) {
895
+ const sourceLabel = w.source === "base" ? "universal" : w.source === "user" ? "personal" : "this repo";
896
+ lines.push(` ${w.name} (${sourceLabel})`);
897
+ }
898
+ return lines.join("\n");
899
+ }
900
+ function loadWorldsFromDir(dirPath, source) {
350
901
  const dir = resolve(dirPath);
902
+ if (!existsSync(dir)) return [];
903
+ const stat = statSync(dir);
904
+ if (stat.isFile() && dir.endsWith(".md")) {
905
+ try {
906
+ return [{
907
+ name: basename(dir).replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
908
+ source,
909
+ path: dir,
910
+ content: readFileSync(dir, "utf-8")
911
+ }];
912
+ } catch {
913
+ return [];
914
+ }
915
+ }
916
+ if (!stat.isDirectory()) return [];
917
+ const files = readdirSync(dir).filter(
918
+ (f) => f.endsWith(".worldmodel.md") || f.endsWith(".nv-world.md")
919
+ ).sort();
920
+ return files.map((f) => {
921
+ const fullPath = join(dir, f);
922
+ return {
923
+ name: f.replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
924
+ source,
925
+ path: fullPath,
926
+ content: readFileSync(fullPath, "utf-8")
927
+ };
928
+ });
929
+ }
930
+
931
+ // src/radiant/adapters/exocortex.ts
932
+ import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
933
+ import { join as join2, resolve as resolve2 } from "path";
934
+ function readExocortex(dirPath, repoName) {
935
+ const dir = resolve2(dirPath);
351
936
  let filesLoaded = 0;
352
937
  function tryRead(...paths) {
353
938
  for (const p of paths) {
354
- const full = join(dir, p);
355
- if (existsSync(full)) {
939
+ const full = join2(dir, p);
940
+ if (existsSync2(full)) {
356
941
  try {
357
- const content = readFileSync(full, "utf-8").trim();
942
+ const content = readFileSync2(full, "utf-8").trim();
358
943
  if (content) {
359
944
  filesLoaded++;
360
945
  return content;
@@ -365,16 +950,64 @@ function readExocortex(dirPath) {
365
950
  }
366
951
  return null;
367
952
  }
953
+ const attention = tryRead("attention.md");
954
+ const goals = tryRead("goals.md");
955
+ const identity = tryRead("identity.md", "user.md");
956
+ const organization = tryRead("org/organization.md", "org/src/organization.md");
957
+ const methods = tryRead("org/methods.md", "org/src/methods.md");
958
+ let sprint = null;
959
+ let projectContext = null;
960
+ if (repoName) {
961
+ const projectPaths = [
962
+ repoName,
963
+ repoName.toLowerCase(),
964
+ repoName.replace(/-/g, "_")
965
+ ];
966
+ for (const projectDir of projectPaths) {
967
+ const projectSprint = tryRead(
968
+ `${projectDir}/src/sprint.md`,
969
+ `${projectDir}/sprint.md`
970
+ );
971
+ if (projectSprint) {
972
+ sprint = projectSprint;
973
+ break;
974
+ }
975
+ }
976
+ for (const projectDir of projectPaths) {
977
+ const roadmap = tryRead(
978
+ `${projectDir}/roadmap.md`,
979
+ `${projectDir}/src/roadmap.md`
980
+ );
981
+ if (roadmap) {
982
+ projectContext = roadmap;
983
+ break;
984
+ }
985
+ }
986
+ }
987
+ if (!sprint) {
988
+ sprint = tryRead("sprint.md", "src/sprint.md");
989
+ }
368
990
  const ctx = {
369
- attention: tryRead("attention.md"),
370
- goals: tryRead("goals.md"),
371
- identity: tryRead("identity.md"),
372
- sprint: tryRead("sprint.md", "src/sprint.md"),
373
- organization: tryRead("org/organization.md", "org/src/organization.md"),
374
- methods: tryRead("org/methods.md", "org/src/methods.md"),
991
+ attention,
992
+ goals,
993
+ identity,
994
+ sprint,
995
+ organization,
996
+ methods,
375
997
  source: dir,
376
998
  filesLoaded
377
999
  };
1000
+ if (projectContext && ctx.sprint) {
1001
+ ctx.sprint = `${ctx.sprint}
1002
+
1003
+ ---
1004
+ Project roadmap:
1005
+ ${projectContext}`;
1006
+ } else if (projectContext) {
1007
+ ctx.sprint = `Project roadmap:
1008
+ ${projectContext}`;
1009
+ ctx.filesLoaded++;
1010
+ }
378
1011
  return ctx;
379
1012
  }
380
1013
  function formatExocortexForPrompt(ctx) {
@@ -415,6 +1048,46 @@ ${ctx.methods}`);
415
1048
  }
416
1049
  return sections.join("\n\n");
417
1050
  }
1051
+ function readTeamExocortices(teamDir) {
1052
+ const dir = resolve2(teamDir);
1053
+ if (!existsSync2(dir)) return [];
1054
+ const entries = readdirSync2(dir);
1055
+ const results = [];
1056
+ for (const entry of entries) {
1057
+ const entryPath = join2(dir, entry);
1058
+ try {
1059
+ const stat = statSync2(entryPath);
1060
+ if (stat.isDirectory()) {
1061
+ const ctx = readExocortex(entryPath);
1062
+ if (ctx.filesLoaded > 0) {
1063
+ results.push({ name: entry, context: ctx });
1064
+ }
1065
+ }
1066
+ } catch {
1067
+ }
1068
+ }
1069
+ return results;
1070
+ }
1071
+ function formatTeamExocorticesForPrompt(team) {
1072
+ if (team.length === 0) return "";
1073
+ const sections = [
1074
+ "## Team Intent (cross-exocortex read)",
1075
+ "",
1076
+ `Reading ${team.length} team members' exocortices. Compare each person's`,
1077
+ "stated intent against the observed activity AND against each other.",
1078
+ "Surface: duplicate focus, missing coverage, silent pivots,",
1079
+ "and areas where no one is carrying the work.",
1080
+ ""
1081
+ ];
1082
+ for (const { name, context } of team) {
1083
+ sections.push(`### ${name}`);
1084
+ if (context.attention) sections.push(`**Attention:** ${context.attention.split("\n")[0]}`);
1085
+ if (context.goals) sections.push(`**Goals:** ${context.goals.split("\n").slice(0, 3).join("; ")}`);
1086
+ if (context.sprint) sections.push(`**Sprint:** ${context.sprint.split("\n").slice(0, 3).join("; ")}`);
1087
+ sections.push("");
1088
+ }
1089
+ return sections.join("\n");
1090
+ }
418
1091
  function summarizeExocortex(ctx) {
419
1092
  if (ctx.filesLoaded === 0) return "no exocortex files found";
420
1093
  const loaded = [];
@@ -428,14 +1101,14 @@ function summarizeExocortex(ctx) {
428
1101
  }
429
1102
 
430
1103
  // src/radiant/memory/palace.ts
431
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync, existsSync as existsSync2 } from "fs";
432
- import { join as join2, resolve as resolve2 } from "path";
1104
+ import { readFileSync as readFileSync3, writeFileSync, mkdirSync, readdirSync as readdirSync3, existsSync as existsSync3 } from "fs";
1105
+ import { join as join3, resolve as resolve3 } from "path";
433
1106
  function writeRead(exocortexDir, frontmatter, text) {
434
- const dir = resolve2(exocortexDir, "radiant", "reads");
1107
+ const dir = resolve3(exocortexDir, "radiant", "reads");
435
1108
  mkdirSync(dir, { recursive: true });
436
1109
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
437
1110
  const filename = `${date}.md`;
438
- const filepath = join2(dir, filename);
1111
+ const filepath = join3(dir, filename);
439
1112
  const content = `${frontmatter}
440
1113
 
441
1114
  ${text}
@@ -444,9 +1117,9 @@ ${text}
444
1117
  return filepath;
445
1118
  }
446
1119
  function updateKnowledge(exocortexDir, persistence, options) {
447
- const dir = resolve2(exocortexDir, "radiant");
1120
+ const dir = resolve3(exocortexDir, "radiant");
448
1121
  mkdirSync(dir, { recursive: true });
449
- const filepath = join2(dir, "knowledge.md");
1122
+ const filepath = join3(dir, "knowledge.md");
450
1123
  const totalReads = options?.totalReads ?? 0;
451
1124
  const existingUntriggered = loadUntriggeredCounts(filepath);
452
1125
  const lines = [
@@ -538,9 +1211,9 @@ function updateKnowledge(exocortexDir, persistence, options) {
538
1211
  }
539
1212
  function loadUntriggeredCounts(filepath) {
540
1213
  const counts = /* @__PURE__ */ new Map();
541
- if (!existsSync2(filepath)) return counts;
1214
+ if (!existsSync3(filepath)) return counts;
542
1215
  try {
543
- const content = readFileSync2(filepath, "utf-8");
1216
+ const content = readFileSync3(filepath, "utf-8");
544
1217
  const match = content.match(
545
1218
  /<!-- untriggered_counts[\s\S]*?-->/
546
1219
  );
@@ -558,13 +1231,13 @@ function loadUntriggeredCounts(filepath) {
558
1231
  return counts;
559
1232
  }
560
1233
  function loadPriorReads(exocortexDir) {
561
- const dir = resolve2(exocortexDir, "radiant", "reads");
562
- if (!existsSync2(dir)) return [];
563
- const files = readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
1234
+ const dir = resolve3(exocortexDir, "radiant", "reads");
1235
+ if (!existsSync3(dir)) return [];
1236
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".md")).sort();
564
1237
  const reads = [];
565
1238
  for (const filename of files) {
566
- const filepath = join2(dir, filename);
567
- const content = readFileSync2(filepath, "utf-8");
1239
+ const filepath = join3(dir, filename);
1240
+ const content = readFileSync3(filepath, "utf-8");
568
1241
  const date = filename.replace(".md", "");
569
1242
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
570
1243
  const frontmatter = fmMatch ? fmMatch[1] : "";
@@ -941,15 +1614,17 @@ function buildInterpretationPrompt(input) {
941
1614
  const eventSample = formatEventSample(input.events, 30);
942
1615
  const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
943
1616
  ${input.canonicalPatterns.map((p) => `- ${p}`).join("\n")}` : "No patterns have been named yet. Everything you observe is new.";
1617
+ const compressedWorld = compressWorldmodel(input.worldmodelContent);
1618
+ const cl = compressLens(input.lens);
944
1619
  const frame = input.lens.primary_frame;
945
1620
  const evalQuestions = frame.evaluation_questions.map((q, i) => `${i + 1}. ${q}`).join("\n");
946
- const forbiddenList = input.lens.forbidden_phrases.map((p) => `- "${p}"`).join("\n");
947
- const jargonTable = Object.entries(input.lens.vocabulary.jargon_translations).map(([internal, plain]) => ` "${internal}" \u2192 "${plain}"`).join("\n");
1621
+ const forbiddenList = cl.forbiddenPhrases;
1622
+ const jargonTable = cl.jargonTranslations;
948
1623
  return `You are a behavioral intelligence system reading team activity and producing a read for the reader who needs to act on it.
949
1624
 
950
- ## Context the reader has loaded
1625
+ ## Worldmodel (compressed)
951
1626
 
952
- ${input.worldmodelContent}
1627
+ ${compressedWorld}
953
1628
 
954
1629
  ## What happened this window
955
1630
 
@@ -995,6 +1670,14 @@ ${jargonTable}
995
1670
 
996
1671
  For example: don't say "update the worldmodel." Say "add a line to your strategy file."
997
1672
 
1673
+ ## When the same invariant keeps firing
1674
+
1675
+ If the prior read history or the current evidence shows the same worldmodel invariant being triggered repeatedly (by the same side \u2014 human or AI), name it in MEANING and ask the real question:
1676
+
1677
+ "This invariant has been tested N times across M reads. Always on the [human/AI] side. Either the team needs alignment on WHY this rule exists \u2014 or the team is telling you something the worldmodel hasn't absorbed yet."
1678
+
1679
+ Don't just say "invariant held." Say what it means that people keep pushing against the same wall.
1680
+
998
1681
  ## Health is a valid read
999
1682
 
1000
1683
  If the activity is healthy and aligned with the worldmodel, SAY SO. Don't fabricate problems. Over-prescription is a voice failure. Legitimate outputs include:
@@ -1132,30 +1815,16 @@ Window: last ${input.windowDays} days \xB7 ${input.eventCount} events
1132
1815
  Lens: ${input.lens.name}`
1133
1816
  );
1134
1817
  if (input.patterns.length > 0) {
1135
- const canonical = input.patterns.filter((p) => p.type === "canonical");
1136
- const candidates = input.patterns.filter((p) => p.type === "candidate");
1137
1818
  let emergentBlock = "EMERGENT\n";
1138
- if (canonical.length > 0) {
1139
- for (const p of canonical) {
1140
- emergentBlock += `
1819
+ for (const p of input.patterns) {
1820
+ emergentBlock += `
1141
1821
  ${p.name}
1142
1822
  `;
1143
- emergentBlock += ` ${p.description}
1823
+ emergentBlock += ` ${p.description}
1144
1824
  `;
1145
- }
1146
- }
1147
- if (candidates.length > 0) {
1148
- emergentBlock += "\n Emergent (candidates \u2014 not yet in worldmodel)\n";
1149
- for (const p of candidates) {
1150
- emergentBlock += `
1151
- ${p.name} (candidate)
1152
- `;
1153
- emergentBlock += ` ${p.description}
1825
+ if (p.evidence.cited_invariant) {
1826
+ emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
1154
1827
  `;
1155
- if (p.evidence.cited_invariant) {
1156
- emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
1157
- `;
1158
- }
1159
1828
  }
1160
1829
  }
1161
1830
  sections.push(emergentBlock.trimEnd());
@@ -1367,21 +2036,80 @@ function serializeYAML(obj, indent = 0) {
1367
2036
  async function emergent(input) {
1368
2037
  const lens = resolveLens2(input.lensId);
1369
2038
  const windowDays = input.windowDays ?? 14;
2039
+ let worldStack;
2040
+ let worldmodelContent = input.worldmodelContent;
2041
+ if (!worldmodelContent || worldmodelContent.trim() === "") {
2042
+ worldStack = discoverWorlds({ explicitWorldsDir: input.worldPath });
2043
+ worldmodelContent = worldStack.combinedContent;
2044
+ }
1370
2045
  let statedIntent;
1371
2046
  let exocortexContext;
1372
2047
  let priorReadContext = "";
1373
2048
  if (input.exocortexPath) {
1374
- exocortexContext = readExocortex(input.exocortexPath);
1375
- const formatted = formatExocortexForPrompt(exocortexContext);
1376
- if (formatted) statedIntent = formatted;
2049
+ const repoName = input.scope.type === "repo" ? input.scope.repo : void 0;
2050
+ exocortexContext = readExocortex(input.exocortexPath, repoName);
2051
+ const compressed = compressExocortex(exocortexContext);
2052
+ if (compressed) {
2053
+ statedIntent = `## Stated Intent (from exocortex, compressed)
2054
+
2055
+ ${compressed}
2056
+
2057
+ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2058
+ }
1377
2059
  const priorReads = loadPriorReads(input.exocortexPath);
1378
2060
  if (priorReads.length > 0) {
1379
- priorReadContext = formatPriorReadsForPrompt(priorReads);
2061
+ priorReadContext = compressPriorReads(priorReads);
1380
2062
  }
1381
2063
  }
1382
- const events = await fetchGitHubActivity(input.scope, input.githubToken, {
1383
- windowDays
1384
- });
2064
+ let events;
2065
+ let orgRepos;
2066
+ if (input.scope.type === "org") {
2067
+ const orgResult = await fetchGitHubOrgActivity(
2068
+ input.scope,
2069
+ input.githubToken,
2070
+ { windowDays }
2071
+ );
2072
+ events = orgResult.events;
2073
+ orgRepos = orgResult.repos;
2074
+ } else {
2075
+ events = await fetchGitHubActivity(input.scope, input.githubToken, {
2076
+ windowDays
2077
+ });
2078
+ }
2079
+ let adapterSignals = "";
2080
+ const activeAdapters = ["github"];
2081
+ const discordToken = process.env.DISCORD_TOKEN;
2082
+ const discordGuild = process.env.DISCORD_GUILD_ID;
2083
+ if (discordToken && discordGuild) {
2084
+ try {
2085
+ const discord = await fetchDiscordActivity(discordGuild, discordToken, { windowDays });
2086
+ events.push(...discord.events);
2087
+ adapterSignals += "\n\n" + formatDiscordSignalsForPrompt(discord.signals);
2088
+ activeAdapters.push("discord");
2089
+ } catch {
2090
+ }
2091
+ }
2092
+ const slackToken = process.env.SLACK_TOKEN;
2093
+ if (slackToken) {
2094
+ try {
2095
+ const slack = await fetchSlackActivity(slackToken, { windowDays });
2096
+ events.push(...slack.events);
2097
+ adapterSignals += "\n\n" + formatSlackSignalsForPrompt(slack.signals);
2098
+ activeAdapters.push("slack");
2099
+ } catch {
2100
+ }
2101
+ }
2102
+ const notionToken = process.env.NOTION_TOKEN;
2103
+ if (notionToken) {
2104
+ try {
2105
+ const notion = await fetchNotionActivity(notionToken, { windowDays });
2106
+ events.push(...notion.events);
2107
+ adapterSignals += "\n\n" + formatNotionSignalsForPrompt(notion.signals);
2108
+ activeAdapters.push("notion");
2109
+ } catch {
2110
+ }
2111
+ }
2112
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
1385
2113
  const classified = classifyEvents(events);
1386
2114
  const signals = extractSignals(classified);
1387
2115
  const scores = computeScores(signals, input.worldmodelContent !== "");
@@ -1392,7 +2120,7 @@ async function emergent(input) {
1392
2120
  lens,
1393
2121
  ai: input.ai,
1394
2122
  canonicalPatterns: input.canonicalPatterns,
1395
- statedIntent: statedIntent ? statedIntent + (priorReadContext ? "\n\n" + priorReadContext : "") : priorReadContext || void 0
2123
+ statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
1396
2124
  });
1397
2125
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
1398
2126
  const allDescriptions = rewrittenPatterns.map((p) => p.description).join("\n");
@@ -1441,7 +2169,9 @@ async function emergent(input) {
1441
2169
  voiceClean: voiceViolations.length === 0,
1442
2170
  signals,
1443
2171
  scores,
1444
- eventCount: events.length
2172
+ eventCount: events.length,
2173
+ activeAdapters,
2174
+ worldStack
1445
2175
  };
1446
2176
  }
1447
2177
  function computeScores(signals, worldmodelLoaded) {
@@ -1534,15 +2264,31 @@ function createMockAI(fixedResponse) {
1534
2264
  }
1535
2265
 
1536
2266
  export {
2267
+ compressWorldmodel,
2268
+ compressExocortex,
2269
+ compressLens,
2270
+ compressPriorReads,
1537
2271
  composeSystemPrompt,
1538
2272
  checkForbiddenPhrases,
1539
2273
  think,
2274
+ parseScope,
1540
2275
  parseRepoScope,
1541
2276
  formatScope,
1542
2277
  fetchGitHubActivity,
2278
+ fetchGitHubOrgActivity,
1543
2279
  createMockGitHubAdapter,
2280
+ fetchDiscordActivity,
2281
+ formatDiscordSignalsForPrompt,
2282
+ fetchSlackActivity,
2283
+ formatSlackSignalsForPrompt,
2284
+ fetchNotionActivity,
2285
+ formatNotionSignalsForPrompt,
2286
+ discoverWorlds,
2287
+ formatActiveWorlds,
1544
2288
  readExocortex,
1545
2289
  formatExocortexForPrompt,
2290
+ readTeamExocortices,
2291
+ formatTeamExocorticesForPrompt,
1546
2292
  summarizeExocortex,
1547
2293
  writeRead,
1548
2294
  updateKnowledge,