@neuroverseos/governance 0.8.0 → 0.9.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLens
3
- } from "./chunk-VGFDMPVB.js";
3
+ } from "./chunk-TCGGED4G.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}
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)
33
140
 
34
- ### Evaluation questions to reason through
141
+ ${compressedWorld}`,
142
+ // Section 2: Analytical frame (evaluation questions + rubric)
143
+ `## How to Think
35
144
 
36
- ${questionsBlock}
145
+ ${cl.scoringRubric}
37
146
 
38
- ### Overlap emergent states
147
+ Questions:
148
+ ${cl.evaluationQuestions}
39
149
 
40
- ${overlapsBlock}
150
+ Overlaps: ${overlapsBlock}
151
+ Center: ${lens.primary_frame.center_identity}
41
152
 
42
- ### Center identity
43
-
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
@@ -390,18 +465,758 @@ function createMockGitHubAdapter(fixedEvents) {
390
465
  return async () => fixedEvents;
391
466
  }
392
467
 
393
- // src/radiant/adapters/exocortex.ts
394
- import { readFileSync, existsSync, readdirSync, statSync } from "fs";
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/git-remote.ts
863
+ import { existsSync, readFileSync, statSync } from "fs";
395
864
  import { join, resolve } from "path";
396
- function readExocortex(dirPath) {
397
- const dir = resolve(dirPath);
865
+ function resolveGitConfigPath(repoDir) {
866
+ const dotGit = join(repoDir, ".git");
867
+ if (!existsSync(dotGit)) return null;
868
+ try {
869
+ const stat = statSync(dotGit);
870
+ if (stat.isDirectory()) {
871
+ return join(dotGit, "config");
872
+ }
873
+ if (stat.isFile()) {
874
+ const content = readFileSync(dotGit, "utf-8");
875
+ const match = /^gitdir:\s*(.+)$/m.exec(content);
876
+ if (!match) return null;
877
+ const gitDir = resolve(repoDir, match[1].trim());
878
+ const configPath = join(gitDir, "config");
879
+ return existsSync(configPath) ? configPath : null;
880
+ }
881
+ } catch {
882
+ return null;
883
+ }
884
+ return null;
885
+ }
886
+ function readOriginRemote(repoDir) {
887
+ const configPath = resolveGitConfigPath(repoDir);
888
+ if (!configPath) return null;
889
+ try {
890
+ const raw = readFileSync(configPath, "utf-8");
891
+ const sectionRe = /\[remote "origin"\]\s*\n((?:(?!\[)[^\n]*\n?)*)/;
892
+ const section = sectionRe.exec(raw);
893
+ if (!section) return null;
894
+ const urlRe = /^\s*url\s*=\s*(.+?)\s*$/m;
895
+ const url = urlRe.exec(section[1]);
896
+ return url ? url[1] : null;
897
+ } catch {
898
+ return null;
899
+ }
900
+ }
901
+ function parseRemoteUrl(url) {
902
+ const trimmed = url.trim();
903
+ if (!trimmed) return null;
904
+ const ssh = /^git@([^:]+):([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
905
+ if (ssh) return { host: ssh[1], owner: ssh[2], repo: ssh[3] };
906
+ const sshProto = /^ssh:\/\/git@([^/]+)\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
907
+ if (sshProto) return { host: sshProto[1], owner: sshProto[2], repo: sshProto[3] };
908
+ const https = /^https?:\/\/(?:[^@/]+@)?([^/]+)\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
909
+ if (https) return { host: https[1], owner: https[2], repo: https[3] };
910
+ return null;
911
+ }
912
+ function getRepoOrigin(repoDir) {
913
+ const url = readOriginRemote(repoDir);
914
+ if (!url) return null;
915
+ return parseRemoteUrl(url);
916
+ }
917
+
918
+ // src/radiant/core/extends.ts
919
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync } from "fs";
920
+ import { join as join2, resolve as resolve2, isAbsolute } from "path";
921
+ import { homedir } from "os";
922
+ import { createHash } from "crypto";
923
+ import { execFileSync } from "child_process";
924
+ function loadExtendsConfig(repoDir) {
925
+ const configPath = join2(repoDir, ".neuroverse", "config.json");
926
+ if (!existsSync2(configPath)) return null;
927
+ try {
928
+ const raw = readFileSync2(configPath, "utf-8");
929
+ const parsed = JSON.parse(raw);
930
+ return parsed;
931
+ } catch {
932
+ return null;
933
+ }
934
+ }
935
+ function parseExtendsSpec(raw) {
936
+ const trimmed = raw.trim();
937
+ if (!trimmed) return null;
938
+ if (trimmed.startsWith("github:")) {
939
+ const rest = trimmed.slice("github:".length);
940
+ const match = /^([^/]+)\/([^@:]+)(?:@([^:]+))?(?::(.+))?$/.exec(rest);
941
+ if (!match) return null;
942
+ return {
943
+ raw: trimmed,
944
+ kind: "github",
945
+ owner: match[1],
946
+ repo: match[2],
947
+ ref: match[3] ?? "HEAD",
948
+ subpath: match[4] ?? ""
949
+ };
950
+ }
951
+ if (trimmed.startsWith("./") || trimmed.startsWith("../") || isAbsolute(trimmed)) {
952
+ return { raw: trimmed, kind: "local", path: trimmed };
953
+ }
954
+ return null;
955
+ }
956
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
957
+ function getCacheDir(spec, baseCacheDir) {
958
+ const root = baseCacheDir ?? join2(homedir(), ".neuroverse", "cache", "extends");
959
+ const key = createHash("sha256").update(spec.raw).digest("hex").slice(0, 16);
960
+ return join2(root, key);
961
+ }
962
+ function isCacheFresh(cacheDir, ttlMs) {
963
+ const stampPath = join2(cacheDir, ".neuroverse-fetched");
964
+ if (!existsSync2(stampPath)) return false;
965
+ try {
966
+ const stamp = statSync2(stampPath);
967
+ return Date.now() - stamp.mtimeMs < ttlMs;
968
+ } catch {
969
+ return false;
970
+ }
971
+ }
972
+ function markCacheFresh(cacheDir) {
973
+ const stampPath = join2(cacheDir, ".neuroverse-fetched");
974
+ try {
975
+ mkdirSync(cacheDir, { recursive: true });
976
+ writeFileSync(stampPath, (/* @__PURE__ */ new Date()).toISOString());
977
+ } catch {
978
+ }
979
+ }
980
+ var defaultGitFetcher = (spec, destDir) => {
981
+ if (spec.kind !== "github") return;
982
+ const url = `https://github.com/${spec.owner}/${spec.repo}.git`;
983
+ const parent = resolve2(destDir, "..");
984
+ mkdirSync(parent, { recursive: true });
985
+ if (existsSync2(destDir)) {
986
+ rmSync(destDir, { recursive: true, force: true });
987
+ }
988
+ const args = ["clone", "--depth", "1", "--filter=blob:none"];
989
+ if (spec.ref && spec.ref !== "HEAD") {
990
+ args.push("--branch", spec.ref);
991
+ }
992
+ args.push(url, destDir);
993
+ execFileSync("git", args, { stdio: "pipe" });
994
+ };
995
+ function resolveExtendsSpec(spec, repoDir, options) {
996
+ if (spec.kind === "local") {
997
+ const full = isAbsolute(spec.path) ? spec.path : resolve2(repoDir, spec.path);
998
+ if (!existsSync2(full)) {
999
+ return { spec, dir: null, warning: `local extends path not found: ${full}` };
1000
+ }
1001
+ return { spec, dir: full };
1002
+ }
1003
+ const cacheRoot = options?.cacheDir;
1004
+ const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
1005
+ const cacheDir = getCacheDir(spec, cacheRoot);
1006
+ const fresh = isCacheFresh(cacheDir, ttl);
1007
+ const needsFetch = options?.forceRefresh || !fresh || !existsSync2(cacheDir);
1008
+ if (needsFetch && options?.noFetch) {
1009
+ if (existsSync2(cacheDir) && existsSync2(join2(cacheDir, ".neuroverse-fetched"))) {
1010
+ return resolveSubpath(spec, cacheDir);
1011
+ }
1012
+ return options?.silentOnMissing ? { spec, dir: null } : { spec, dir: null, warning: `NEUROVERSE_NO_FETCH set and no cache for ${spec.raw}` };
1013
+ }
1014
+ if (needsFetch) {
1015
+ const fetcher = options?.fetcher ?? defaultGitFetcher;
1016
+ try {
1017
+ fetcher(spec, cacheDir);
1018
+ markCacheFresh(cacheDir);
1019
+ } catch (err) {
1020
+ if (existsSync2(cacheDir) && existsSync2(join2(cacheDir, ".neuroverse-fetched"))) {
1021
+ const result = resolveSubpath(spec, cacheDir);
1022
+ return options?.silentOnMissing ? result : { ...result, warning: `fetch failed for ${spec.raw}, using stale cache: ${err.message}` };
1023
+ }
1024
+ return options?.silentOnMissing ? { spec, dir: null } : { spec, dir: null, warning: `fetch failed for ${spec.raw}: ${err.message}` };
1025
+ }
1026
+ }
1027
+ return resolveSubpath(spec, cacheDir);
1028
+ }
1029
+ function resolveSubpath(spec, cacheDir) {
1030
+ const target = spec.subpath ? join2(cacheDir, spec.subpath) : cacheDir;
1031
+ if (!existsSync2(target)) {
1032
+ return { spec, dir: null, warning: `subpath not found in ${spec.raw}: ${spec.subpath}` };
1033
+ }
1034
+ if (!spec.subpath) {
1035
+ const candidates = [
1036
+ join2(target, "worlds"),
1037
+ join2(target, ".neuroverse", "worlds")
1038
+ ];
1039
+ for (const c of candidates) {
1040
+ if (existsSync2(c)) return { spec, dir: c };
1041
+ }
1042
+ }
1043
+ return { spec, dir: target };
1044
+ }
1045
+ function detectOrgExtendsSpec(repoDir) {
1046
+ const origin = getRepoOrigin(repoDir);
1047
+ if (!origin) return null;
1048
+ if (origin.host !== "github.com") return null;
1049
+ if (origin.repo === "worlds") return null;
1050
+ return {
1051
+ raw: `github:${origin.owner}/worlds`,
1052
+ kind: "github",
1053
+ owner: origin.owner,
1054
+ repo: "worlds",
1055
+ ref: "HEAD",
1056
+ subpath: ""
1057
+ };
1058
+ }
1059
+ function resolveAllExtends(repoDir, options) {
1060
+ const config = options?.config !== void 0 ? options.config : loadExtendsConfig(repoDir);
1061
+ if (!config?.extends || config.extends.length === 0) return [];
1062
+ const results = [];
1063
+ for (const raw of config.extends) {
1064
+ const spec = parseExtendsSpec(raw);
1065
+ if (!spec) {
1066
+ results.push({
1067
+ spec: { raw, kind: "local" },
1068
+ dir: null,
1069
+ warning: `unparseable extends spec: ${raw}`
1070
+ });
1071
+ continue;
1072
+ }
1073
+ results.push(resolveExtendsSpec(spec, repoDir, options));
1074
+ }
1075
+ return results;
1076
+ }
1077
+
1078
+ // src/radiant/core/discovery.ts
1079
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
1080
+ import { join as join3, resolve as resolve3, basename } from "path";
1081
+ import { homedir as homedir2 } from "os";
1082
+ function discoverWorlds(options) {
1083
+ const worlds = [];
1084
+ const warnings = [];
1085
+ const forceRefresh = process.env.NEUROVERSE_REFRESH === "1";
1086
+ const noFetch = process.env.NEUROVERSE_NO_FETCH === "1";
1087
+ const noOrg = options?.disableOrg || process.env.NEUROVERSE_NO_ORG === "1";
1088
+ const userDir = options?.userWorldsDir ?? join3(homedir2(), ".neuroverse", "worlds");
1089
+ if (existsSync3(userDir)) {
1090
+ worlds.push(...loadWorldsFromDir(userDir, "user"));
1091
+ }
1092
+ if (!noOrg && !options?.explicitWorldsDir) {
1093
+ const specs = [];
1094
+ if (options?.repoDir) {
1095
+ const fromGit = detectOrgExtendsSpec(options.repoDir);
1096
+ if (fromGit) specs.push(fromGit);
1097
+ }
1098
+ if (options?.scopeOwner) {
1099
+ const already = specs.some(
1100
+ (s) => s.owner?.toLowerCase() === options.scopeOwner.toLowerCase()
1101
+ );
1102
+ if (!already) {
1103
+ specs.push({
1104
+ raw: `github:${options.scopeOwner}/worlds`,
1105
+ kind: "github",
1106
+ owner: options.scopeOwner,
1107
+ repo: "worlds"
1108
+ });
1109
+ }
1110
+ }
1111
+ const baseDir = options?.repoDir ?? process.cwd();
1112
+ for (const spec of specs) {
1113
+ const result = resolveExtendsSpec(spec, baseDir, {
1114
+ cacheDir: options?.extendsCacheDir,
1115
+ fetcher: options?.extendsFetcher,
1116
+ ttlMs: options?.extendsTtlMs,
1117
+ forceRefresh,
1118
+ noFetch,
1119
+ silentOnMissing: true
1120
+ });
1121
+ worlds.push(...loadExtendsWorlds(result, "org"));
1122
+ }
1123
+ }
1124
+ if (options?.repoDir && !options.disableExtends && !options.explicitWorldsDir) {
1125
+ const results = resolveAllExtends(options.repoDir, {
1126
+ cacheDir: options.extendsCacheDir,
1127
+ fetcher: options.extendsFetcher,
1128
+ ttlMs: options.extendsTtlMs,
1129
+ forceRefresh,
1130
+ noFetch
1131
+ });
1132
+ for (const result of results) {
1133
+ worlds.push(...loadExtendsWorlds(result, "extends"));
1134
+ if (result.warning) warnings.push(result.warning);
1135
+ }
1136
+ }
1137
+ if (options?.explicitWorldsDir) {
1138
+ worlds.push(...loadWorldsFromDir(options.explicitWorldsDir, "repo"));
1139
+ } else if (options?.repoDir) {
1140
+ const repoPaths = [
1141
+ join3(options.repoDir, "worlds"),
1142
+ join3(options.repoDir, ".neuroverse", "worlds")
1143
+ ];
1144
+ for (const p of repoPaths) {
1145
+ if (existsSync3(p)) {
1146
+ worlds.push(...loadWorldsFromDir(p, "repo"));
1147
+ break;
1148
+ }
1149
+ }
1150
+ }
1151
+ const combinedContent = worlds.map((w) => {
1152
+ const tag = w.extendsFrom ? `<!-- world: ${w.name} (${w.source} ${w.extendsFrom}) -->` : `<!-- world: ${w.name} (${w.source}) -->`;
1153
+ return `${tag}
1154
+ ${w.content}`;
1155
+ }).join("\n\n---\n\n");
1156
+ const summary = worlds.length === 0 ? "no worlds discovered" : worlds.map((w) => `${w.name} (${w.source})`).join(", ");
1157
+ return { worlds, combinedContent, summary, warnings };
1158
+ }
1159
+ function formatActiveWorlds(stack) {
1160
+ if (stack.worlds.length === 0) return "No worlds loaded.";
1161
+ const lines = ["ACTIVE WORLDS", ""];
1162
+ for (const w of stack.worlds) {
1163
+ const sourceLabel = w.source === "base" ? "universal" : w.source === "user" ? "personal" : w.source === "org" ? `org (${w.extendsFrom ?? "auto"})` : w.source === "extends" ? `shared (${w.extendsFrom ?? "extends"})` : "this repo";
1164
+ lines.push(` ${w.name} (${sourceLabel})`);
1165
+ }
1166
+ if (stack.warnings.length > 0) {
1167
+ lines.push("", "WARNINGS");
1168
+ for (const w of stack.warnings) lines.push(` ${w}`);
1169
+ }
1170
+ return lines.join("\n");
1171
+ }
1172
+ function loadExtendsWorlds(result, source) {
1173
+ if (!result.dir) return [];
1174
+ const loaded = loadWorldsFromDir(result.dir, source);
1175
+ return loaded.map((w) => ({ ...w, extendsFrom: result.spec.raw }));
1176
+ }
1177
+ function loadWorldsFromDir(dirPath, source) {
1178
+ const dir = resolve3(dirPath);
1179
+ if (!existsSync3(dir)) return [];
1180
+ const stat = statSync3(dir);
1181
+ if (stat.isFile() && dir.endsWith(".md")) {
1182
+ try {
1183
+ return [{
1184
+ name: basename(dir).replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
1185
+ source,
1186
+ path: dir,
1187
+ content: readFileSync3(dir, "utf-8")
1188
+ }];
1189
+ } catch {
1190
+ return [];
1191
+ }
1192
+ }
1193
+ if (!stat.isDirectory()) return [];
1194
+ const files = readdirSync(dir).filter(
1195
+ (f) => f.endsWith(".worldmodel.md") || f.endsWith(".nv-world.md")
1196
+ ).sort();
1197
+ return files.map((f) => {
1198
+ const fullPath = join3(dir, f);
1199
+ return {
1200
+ name: f.replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
1201
+ source,
1202
+ path: fullPath,
1203
+ content: readFileSync3(fullPath, "utf-8")
1204
+ };
1205
+ });
1206
+ }
1207
+
1208
+ // src/radiant/adapters/exocortex.ts
1209
+ import { readFileSync as readFileSync4, existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
1210
+ import { join as join4, resolve as resolve4 } from "path";
1211
+ function readExocortex(dirPath, repoName) {
1212
+ const dir = resolve4(dirPath);
398
1213
  let filesLoaded = 0;
399
1214
  function tryRead(...paths) {
400
1215
  for (const p of paths) {
401
- const full = join(dir, p);
402
- if (existsSync(full)) {
1216
+ const full = join4(dir, p);
1217
+ if (existsSync4(full)) {
403
1218
  try {
404
- const content = readFileSync(full, "utf-8").trim();
1219
+ const content = readFileSync4(full, "utf-8").trim();
405
1220
  if (content) {
406
1221
  filesLoaded++;
407
1222
  return content;
@@ -412,16 +1227,64 @@ function readExocortex(dirPath) {
412
1227
  }
413
1228
  return null;
414
1229
  }
1230
+ const attention = tryRead("attention.md");
1231
+ const goals = tryRead("goals.md");
1232
+ const identity = tryRead("identity.md", "user.md");
1233
+ const organization = tryRead("org/organization.md", "org/src/organization.md");
1234
+ const methods = tryRead("org/methods.md", "org/src/methods.md");
1235
+ let sprint = null;
1236
+ let projectContext = null;
1237
+ if (repoName) {
1238
+ const projectPaths = [
1239
+ repoName,
1240
+ repoName.toLowerCase(),
1241
+ repoName.replace(/-/g, "_")
1242
+ ];
1243
+ for (const projectDir of projectPaths) {
1244
+ const projectSprint = tryRead(
1245
+ `${projectDir}/src/sprint.md`,
1246
+ `${projectDir}/sprint.md`
1247
+ );
1248
+ if (projectSprint) {
1249
+ sprint = projectSprint;
1250
+ break;
1251
+ }
1252
+ }
1253
+ for (const projectDir of projectPaths) {
1254
+ const roadmap = tryRead(
1255
+ `${projectDir}/roadmap.md`,
1256
+ `${projectDir}/src/roadmap.md`
1257
+ );
1258
+ if (roadmap) {
1259
+ projectContext = roadmap;
1260
+ break;
1261
+ }
1262
+ }
1263
+ }
1264
+ if (!sprint) {
1265
+ sprint = tryRead("sprint.md", "src/sprint.md");
1266
+ }
415
1267
  const ctx = {
416
- attention: tryRead("attention.md"),
417
- goals: tryRead("goals.md"),
418
- identity: tryRead("identity.md"),
419
- sprint: tryRead("sprint.md", "src/sprint.md"),
420
- organization: tryRead("org/organization.md", "org/src/organization.md"),
421
- methods: tryRead("org/methods.md", "org/src/methods.md"),
1268
+ attention,
1269
+ goals,
1270
+ identity,
1271
+ sprint,
1272
+ organization,
1273
+ methods,
422
1274
  source: dir,
423
1275
  filesLoaded
424
1276
  };
1277
+ if (projectContext && ctx.sprint) {
1278
+ ctx.sprint = `${ctx.sprint}
1279
+
1280
+ ---
1281
+ Project roadmap:
1282
+ ${projectContext}`;
1283
+ } else if (projectContext) {
1284
+ ctx.sprint = `Project roadmap:
1285
+ ${projectContext}`;
1286
+ ctx.filesLoaded++;
1287
+ }
425
1288
  return ctx;
426
1289
  }
427
1290
  function formatExocortexForPrompt(ctx) {
@@ -463,14 +1326,14 @@ ${ctx.methods}`);
463
1326
  return sections.join("\n\n");
464
1327
  }
465
1328
  function readTeamExocortices(teamDir) {
466
- const dir = resolve(teamDir);
467
- if (!existsSync(dir)) return [];
468
- const entries = readdirSync(dir);
1329
+ const dir = resolve4(teamDir);
1330
+ if (!existsSync4(dir)) return [];
1331
+ const entries = readdirSync2(dir);
469
1332
  const results = [];
470
1333
  for (const entry of entries) {
471
- const entryPath = join(dir, entry);
1334
+ const entryPath = join4(dir, entry);
472
1335
  try {
473
- const stat = statSync(entryPath);
1336
+ const stat = statSync4(entryPath);
474
1337
  if (stat.isDirectory()) {
475
1338
  const ctx = readExocortex(entryPath);
476
1339
  if (ctx.filesLoaded > 0) {
@@ -515,25 +1378,25 @@ function summarizeExocortex(ctx) {
515
1378
  }
516
1379
 
517
1380
  // src/radiant/memory/palace.ts
518
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
519
- import { join as join2, resolve as resolve2 } from "path";
1381
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
1382
+ import { join as join5, resolve as resolve5 } from "path";
520
1383
  function writeRead(exocortexDir, frontmatter, text) {
521
- const dir = resolve2(exocortexDir, "radiant", "reads");
522
- mkdirSync(dir, { recursive: true });
1384
+ const dir = resolve5(exocortexDir, "radiant", "reads");
1385
+ mkdirSync2(dir, { recursive: true });
523
1386
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
524
1387
  const filename = `${date}.md`;
525
- const filepath = join2(dir, filename);
1388
+ const filepath = join5(dir, filename);
526
1389
  const content = `${frontmatter}
527
1390
 
528
1391
  ${text}
529
1392
  `;
530
- writeFileSync(filepath, content, "utf-8");
1393
+ writeFileSync2(filepath, content, "utf-8");
531
1394
  return filepath;
532
1395
  }
533
1396
  function updateKnowledge(exocortexDir, persistence, options) {
534
- const dir = resolve2(exocortexDir, "radiant");
535
- mkdirSync(dir, { recursive: true });
536
- const filepath = join2(dir, "knowledge.md");
1397
+ const dir = resolve5(exocortexDir, "radiant");
1398
+ mkdirSync2(dir, { recursive: true });
1399
+ const filepath = join5(dir, "knowledge.md");
537
1400
  const totalReads = options?.totalReads ?? 0;
538
1401
  const existingUntriggered = loadUntriggeredCounts(filepath);
539
1402
  const lines = [
@@ -620,14 +1483,14 @@ function updateKnowledge(exocortexDir, persistence, options) {
620
1483
  lines.push(`${name}=${count}`);
621
1484
  }
622
1485
  lines.push("-->");
623
- writeFileSync(filepath, lines.join("\n"), "utf-8");
1486
+ writeFileSync2(filepath, lines.join("\n"), "utf-8");
624
1487
  return filepath;
625
1488
  }
626
1489
  function loadUntriggeredCounts(filepath) {
627
1490
  const counts = /* @__PURE__ */ new Map();
628
- if (!existsSync2(filepath)) return counts;
1491
+ if (!existsSync5(filepath)) return counts;
629
1492
  try {
630
- const content = readFileSync2(filepath, "utf-8");
1493
+ const content = readFileSync5(filepath, "utf-8");
631
1494
  const match = content.match(
632
1495
  /<!-- untriggered_counts[\s\S]*?-->/
633
1496
  );
@@ -645,13 +1508,13 @@ function loadUntriggeredCounts(filepath) {
645
1508
  return counts;
646
1509
  }
647
1510
  function loadPriorReads(exocortexDir) {
648
- const dir = resolve2(exocortexDir, "radiant", "reads");
649
- if (!existsSync2(dir)) return [];
650
- const files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
1511
+ const dir = resolve5(exocortexDir, "radiant", "reads");
1512
+ if (!existsSync5(dir)) return [];
1513
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".md")).sort();
651
1514
  const reads = [];
652
1515
  for (const filename of files) {
653
- const filepath = join2(dir, filename);
654
- const content = readFileSync2(filepath, "utf-8");
1516
+ const filepath = join5(dir, filename);
1517
+ const content = readFileSync5(filepath, "utf-8");
655
1518
  const date = filename.replace(".md", "");
656
1519
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
657
1520
  const frontmatter = fmMatch ? fmMatch[1] : "";
@@ -1028,15 +1891,17 @@ function buildInterpretationPrompt(input) {
1028
1891
  const eventSample = formatEventSample(input.events, 30);
1029
1892
  const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
1030
1893
  ${input.canonicalPatterns.map((p) => `- ${p}`).join("\n")}` : "No patterns have been named yet. Everything you observe is new.";
1894
+ const compressedWorld = compressWorldmodel(input.worldmodelContent);
1895
+ const cl = compressLens(input.lens);
1031
1896
  const frame = input.lens.primary_frame;
1032
1897
  const evalQuestions = frame.evaluation_questions.map((q, i) => `${i + 1}. ${q}`).join("\n");
1033
- const forbiddenList = input.lens.forbidden_phrases.map((p) => `- "${p}"`).join("\n");
1034
- const jargonTable = Object.entries(input.lens.vocabulary.jargon_translations).map(([internal, plain]) => ` "${internal}" \u2192 "${plain}"`).join("\n");
1898
+ const forbiddenList = cl.forbiddenPhrases;
1899
+ const jargonTable = cl.jargonTranslations;
1035
1900
  return `You are a behavioral intelligence system reading team activity and producing a read for the reader who needs to act on it.
1036
1901
 
1037
- ## Context the reader has loaded
1902
+ ## Worldmodel (compressed)
1038
1903
 
1039
- ${input.worldmodelContent}
1904
+ ${compressedWorld}
1040
1905
 
1041
1906
  ## What happened this window
1042
1907
 
@@ -1227,30 +2092,16 @@ Window: last ${input.windowDays} days \xB7 ${input.eventCount} events
1227
2092
  Lens: ${input.lens.name}`
1228
2093
  );
1229
2094
  if (input.patterns.length > 0) {
1230
- const canonical = input.patterns.filter((p) => p.type === "canonical");
1231
- const candidates = input.patterns.filter((p) => p.type === "candidate");
1232
2095
  let emergentBlock = "EMERGENT\n";
1233
- if (canonical.length > 0) {
1234
- for (const p of canonical) {
1235
- emergentBlock += `
2096
+ for (const p of input.patterns) {
2097
+ emergentBlock += `
1236
2098
  ${p.name}
1237
2099
  `;
1238
- emergentBlock += ` ${p.description}
1239
- `;
1240
- }
1241
- }
1242
- if (candidates.length > 0) {
1243
- emergentBlock += "\n Emergent (candidates \u2014 not yet in worldmodel)\n";
1244
- for (const p of candidates) {
1245
- emergentBlock += `
1246
- ${p.name} (candidate)
1247
- `;
1248
- emergentBlock += ` ${p.description}
2100
+ emergentBlock += ` ${p.description}
1249
2101
  `;
1250
- if (p.evidence.cited_invariant) {
1251
- emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
2102
+ if (p.evidence.cited_invariant) {
2103
+ emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
1252
2104
  `;
1253
- }
1254
2105
  }
1255
2106
  }
1256
2107
  sections.push(emergentBlock.trimEnd());
@@ -1462,16 +2313,29 @@ function serializeYAML(obj, indent = 0) {
1462
2313
  async function emergent(input) {
1463
2314
  const lens = resolveLens2(input.lensId);
1464
2315
  const windowDays = input.windowDays ?? 14;
2316
+ let worldStack;
2317
+ let worldmodelContent = input.worldmodelContent;
2318
+ if (!worldmodelContent || worldmodelContent.trim() === "") {
2319
+ worldStack = discoverWorlds({ explicitWorldsDir: input.worldPath });
2320
+ worldmodelContent = worldStack.combinedContent;
2321
+ }
1465
2322
  let statedIntent;
1466
2323
  let exocortexContext;
1467
2324
  let priorReadContext = "";
1468
2325
  if (input.exocortexPath) {
1469
- exocortexContext = readExocortex(input.exocortexPath);
1470
- const formatted = formatExocortexForPrompt(exocortexContext);
1471
- if (formatted) statedIntent = formatted;
2326
+ const repoName = input.scope.type === "repo" ? input.scope.repo : void 0;
2327
+ exocortexContext = readExocortex(input.exocortexPath, repoName);
2328
+ const compressed = compressExocortex(exocortexContext);
2329
+ if (compressed) {
2330
+ statedIntent = `## Stated Intent (from exocortex, compressed)
2331
+
2332
+ ${compressed}
2333
+
2334
+ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2335
+ }
1472
2336
  const priorReads = loadPriorReads(input.exocortexPath);
1473
2337
  if (priorReads.length > 0) {
1474
- priorReadContext = formatPriorReadsForPrompt(priorReads);
2338
+ priorReadContext = compressPriorReads(priorReads);
1475
2339
  }
1476
2340
  }
1477
2341
  let events;
@@ -1489,6 +2353,40 @@ async function emergent(input) {
1489
2353
  windowDays
1490
2354
  });
1491
2355
  }
2356
+ let adapterSignals = "";
2357
+ const activeAdapters = ["github"];
2358
+ const discordToken = process.env.DISCORD_TOKEN;
2359
+ const discordGuild = process.env.DISCORD_GUILD_ID;
2360
+ if (discordToken && discordGuild) {
2361
+ try {
2362
+ const discord = await fetchDiscordActivity(discordGuild, discordToken, { windowDays });
2363
+ events.push(...discord.events);
2364
+ adapterSignals += "\n\n" + formatDiscordSignalsForPrompt(discord.signals);
2365
+ activeAdapters.push("discord");
2366
+ } catch {
2367
+ }
2368
+ }
2369
+ const slackToken = process.env.SLACK_TOKEN;
2370
+ if (slackToken) {
2371
+ try {
2372
+ const slack = await fetchSlackActivity(slackToken, { windowDays });
2373
+ events.push(...slack.events);
2374
+ adapterSignals += "\n\n" + formatSlackSignalsForPrompt(slack.signals);
2375
+ activeAdapters.push("slack");
2376
+ } catch {
2377
+ }
2378
+ }
2379
+ const notionToken = process.env.NOTION_TOKEN;
2380
+ if (notionToken) {
2381
+ try {
2382
+ const notion = await fetchNotionActivity(notionToken, { windowDays });
2383
+ events.push(...notion.events);
2384
+ adapterSignals += "\n\n" + formatNotionSignalsForPrompt(notion.signals);
2385
+ activeAdapters.push("notion");
2386
+ } catch {
2387
+ }
2388
+ }
2389
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
1492
2390
  const classified = classifyEvents(events);
1493
2391
  const signals = extractSignals(classified);
1494
2392
  const scores = computeScores(signals, input.worldmodelContent !== "");
@@ -1499,7 +2397,7 @@ async function emergent(input) {
1499
2397
  lens,
1500
2398
  ai: input.ai,
1501
2399
  canonicalPatterns: input.canonicalPatterns,
1502
- statedIntent: statedIntent ? statedIntent + (priorReadContext ? "\n\n" + priorReadContext : "") : priorReadContext || void 0
2400
+ statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
1503
2401
  });
1504
2402
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
1505
2403
  const allDescriptions = rewrittenPatterns.map((p) => p.description).join("\n");
@@ -1548,7 +2446,9 @@ async function emergent(input) {
1548
2446
  voiceClean: voiceViolations.length === 0,
1549
2447
  signals,
1550
2448
  scores,
1551
- eventCount: events.length
2449
+ eventCount: events.length,
2450
+ activeAdapters,
2451
+ worldStack
1552
2452
  };
1553
2453
  }
1554
2454
  function computeScores(signals, worldmodelLoaded) {
@@ -1641,6 +2541,10 @@ function createMockAI(fixedResponse) {
1641
2541
  }
1642
2542
 
1643
2543
  export {
2544
+ compressWorldmodel,
2545
+ compressExocortex,
2546
+ compressLens,
2547
+ compressPriorReads,
1644
2548
  composeSystemPrompt,
1645
2549
  checkForbiddenPhrases,
1646
2550
  think,
@@ -1650,6 +2554,23 @@ export {
1650
2554
  fetchGitHubActivity,
1651
2555
  fetchGitHubOrgActivity,
1652
2556
  createMockGitHubAdapter,
2557
+ fetchDiscordActivity,
2558
+ formatDiscordSignalsForPrompt,
2559
+ fetchSlackActivity,
2560
+ formatSlackSignalsForPrompt,
2561
+ fetchNotionActivity,
2562
+ formatNotionSignalsForPrompt,
2563
+ readOriginRemote,
2564
+ parseRemoteUrl,
2565
+ getRepoOrigin,
2566
+ loadExtendsConfig,
2567
+ parseExtendsSpec,
2568
+ getCacheDir,
2569
+ resolveExtendsSpec,
2570
+ detectOrgExtendsSpec,
2571
+ resolveAllExtends,
2572
+ discoverWorlds,
2573
+ formatActiveWorlds,
1653
2574
  readExocortex,
1654
2575
  formatExocortexForPrompt,
1655
2576
  readTeamExocortices,