@neuroverseos/governance 0.8.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.
- package/README.md +61 -9
- package/dist/{chunk-MC6O5GV5.js → chunk-ETDIEVAX.js} +766 -131
- package/dist/{chunk-VGFDMPVB.js → chunk-F2LWMOM5.js} +283 -1
- package/dist/cli/neuroverse.cjs +1500 -346
- package/dist/cli/radiant.cjs +1095 -217
- package/dist/cli/radiant.js +4 -4
- package/dist/cli/worldmodel.cjs +300 -21
- package/dist/cli/worldmodel.js +76 -1
- package/dist/{lenses-K5FVSALR.js → lenses-YDMKSXDL.js} +5 -3
- package/dist/radiant/index.cjs +654 -130
- package/dist/radiant/index.d.cts +136 -10
- package/dist/radiant/index.d.ts +136 -10
- package/dist/radiant/index.js +23 -397
- package/dist/{server-DFNY5N5A.js → server-ZSQ6DRSN.js} +2 -2
- package/dist/worldmodel-create-5SWHVNMQ.js +195 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLens
|
|
3
|
-
} from "./chunk-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
141
|
+
${compressedWorld}`,
|
|
142
|
+
// Section 2: Analytical frame (evaluation questions + rubric)
|
|
143
|
+
`## How to Think
|
|
35
144
|
|
|
36
|
-
${
|
|
145
|
+
${cl.scoringRubric}
|
|
37
146
|
|
|
38
|
-
|
|
147
|
+
Questions:
|
|
148
|
+
${cl.evaluationQuestions}
|
|
39
149
|
|
|
40
|
-
${overlapsBlock}
|
|
150
|
+
Overlaps: ${overlapsBlock}
|
|
151
|
+
Center: ${lens.primary_frame.center_identity}
|
|
41
152
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
88
|
-
reaching for one, rephrase in direct, active, specific language instead.
|
|
167
|
+
Do NOT use: ${cl.forbiddenPhrases}
|
|
89
168
|
|
|
90
|
-
|
|
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,481 @@ function createMockGitHubAdapter(fixedEvents) {
|
|
|
390
465
|
return async () => fixedEvents;
|
|
391
466
|
}
|
|
392
467
|
|
|
393
|
-
// src/radiant/adapters/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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) {
|
|
397
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);
|
|
398
936
|
let filesLoaded = 0;
|
|
399
937
|
function tryRead(...paths) {
|
|
400
938
|
for (const p of paths) {
|
|
401
|
-
const full =
|
|
402
|
-
if (
|
|
939
|
+
const full = join2(dir, p);
|
|
940
|
+
if (existsSync2(full)) {
|
|
403
941
|
try {
|
|
404
|
-
const content =
|
|
942
|
+
const content = readFileSync2(full, "utf-8").trim();
|
|
405
943
|
if (content) {
|
|
406
944
|
filesLoaded++;
|
|
407
945
|
return content;
|
|
@@ -412,16 +950,64 @@ function readExocortex(dirPath) {
|
|
|
412
950
|
}
|
|
413
951
|
return null;
|
|
414
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
|
+
}
|
|
415
990
|
const ctx = {
|
|
416
|
-
attention
|
|
417
|
-
goals
|
|
418
|
-
identity
|
|
419
|
-
sprint
|
|
420
|
-
organization
|
|
421
|
-
methods
|
|
991
|
+
attention,
|
|
992
|
+
goals,
|
|
993
|
+
identity,
|
|
994
|
+
sprint,
|
|
995
|
+
organization,
|
|
996
|
+
methods,
|
|
422
997
|
source: dir,
|
|
423
998
|
filesLoaded
|
|
424
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
|
+
}
|
|
425
1011
|
return ctx;
|
|
426
1012
|
}
|
|
427
1013
|
function formatExocortexForPrompt(ctx) {
|
|
@@ -463,14 +1049,14 @@ ${ctx.methods}`);
|
|
|
463
1049
|
return sections.join("\n\n");
|
|
464
1050
|
}
|
|
465
1051
|
function readTeamExocortices(teamDir) {
|
|
466
|
-
const dir =
|
|
467
|
-
if (!
|
|
468
|
-
const entries =
|
|
1052
|
+
const dir = resolve2(teamDir);
|
|
1053
|
+
if (!existsSync2(dir)) return [];
|
|
1054
|
+
const entries = readdirSync2(dir);
|
|
469
1055
|
const results = [];
|
|
470
1056
|
for (const entry of entries) {
|
|
471
|
-
const entryPath =
|
|
1057
|
+
const entryPath = join2(dir, entry);
|
|
472
1058
|
try {
|
|
473
|
-
const stat =
|
|
1059
|
+
const stat = statSync2(entryPath);
|
|
474
1060
|
if (stat.isDirectory()) {
|
|
475
1061
|
const ctx = readExocortex(entryPath);
|
|
476
1062
|
if (ctx.filesLoaded > 0) {
|
|
@@ -515,14 +1101,14 @@ function summarizeExocortex(ctx) {
|
|
|
515
1101
|
}
|
|
516
1102
|
|
|
517
1103
|
// src/radiant/memory/palace.ts
|
|
518
|
-
import { readFileSync as
|
|
519
|
-
import { join as
|
|
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";
|
|
520
1106
|
function writeRead(exocortexDir, frontmatter, text) {
|
|
521
|
-
const dir =
|
|
1107
|
+
const dir = resolve3(exocortexDir, "radiant", "reads");
|
|
522
1108
|
mkdirSync(dir, { recursive: true });
|
|
523
1109
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
524
1110
|
const filename = `${date}.md`;
|
|
525
|
-
const filepath =
|
|
1111
|
+
const filepath = join3(dir, filename);
|
|
526
1112
|
const content = `${frontmatter}
|
|
527
1113
|
|
|
528
1114
|
${text}
|
|
@@ -531,9 +1117,9 @@ ${text}
|
|
|
531
1117
|
return filepath;
|
|
532
1118
|
}
|
|
533
1119
|
function updateKnowledge(exocortexDir, persistence, options) {
|
|
534
|
-
const dir =
|
|
1120
|
+
const dir = resolve3(exocortexDir, "radiant");
|
|
535
1121
|
mkdirSync(dir, { recursive: true });
|
|
536
|
-
const filepath =
|
|
1122
|
+
const filepath = join3(dir, "knowledge.md");
|
|
537
1123
|
const totalReads = options?.totalReads ?? 0;
|
|
538
1124
|
const existingUntriggered = loadUntriggeredCounts(filepath);
|
|
539
1125
|
const lines = [
|
|
@@ -625,9 +1211,9 @@ function updateKnowledge(exocortexDir, persistence, options) {
|
|
|
625
1211
|
}
|
|
626
1212
|
function loadUntriggeredCounts(filepath) {
|
|
627
1213
|
const counts = /* @__PURE__ */ new Map();
|
|
628
|
-
if (!
|
|
1214
|
+
if (!existsSync3(filepath)) return counts;
|
|
629
1215
|
try {
|
|
630
|
-
const content =
|
|
1216
|
+
const content = readFileSync3(filepath, "utf-8");
|
|
631
1217
|
const match = content.match(
|
|
632
1218
|
/<!-- untriggered_counts[\s\S]*?-->/
|
|
633
1219
|
);
|
|
@@ -645,13 +1231,13 @@ function loadUntriggeredCounts(filepath) {
|
|
|
645
1231
|
return counts;
|
|
646
1232
|
}
|
|
647
1233
|
function loadPriorReads(exocortexDir) {
|
|
648
|
-
const dir =
|
|
649
|
-
if (!
|
|
650
|
-
const files =
|
|
1234
|
+
const dir = resolve3(exocortexDir, "radiant", "reads");
|
|
1235
|
+
if (!existsSync3(dir)) return [];
|
|
1236
|
+
const files = readdirSync3(dir).filter((f) => f.endsWith(".md")).sort();
|
|
651
1237
|
const reads = [];
|
|
652
1238
|
for (const filename of files) {
|
|
653
|
-
const filepath =
|
|
654
|
-
const content =
|
|
1239
|
+
const filepath = join3(dir, filename);
|
|
1240
|
+
const content = readFileSync3(filepath, "utf-8");
|
|
655
1241
|
const date = filename.replace(".md", "");
|
|
656
1242
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
657
1243
|
const frontmatter = fmMatch ? fmMatch[1] : "";
|
|
@@ -1028,15 +1614,17 @@ function buildInterpretationPrompt(input) {
|
|
|
1028
1614
|
const eventSample = formatEventSample(input.events, 30);
|
|
1029
1615
|
const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
|
|
1030
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);
|
|
1031
1619
|
const frame = input.lens.primary_frame;
|
|
1032
1620
|
const evalQuestions = frame.evaluation_questions.map((q, i) => `${i + 1}. ${q}`).join("\n");
|
|
1033
|
-
const forbiddenList =
|
|
1034
|
-
const jargonTable =
|
|
1621
|
+
const forbiddenList = cl.forbiddenPhrases;
|
|
1622
|
+
const jargonTable = cl.jargonTranslations;
|
|
1035
1623
|
return `You are a behavioral intelligence system reading team activity and producing a read for the reader who needs to act on it.
|
|
1036
1624
|
|
|
1037
|
-
##
|
|
1625
|
+
## Worldmodel (compressed)
|
|
1038
1626
|
|
|
1039
|
-
${
|
|
1627
|
+
${compressedWorld}
|
|
1040
1628
|
|
|
1041
1629
|
## What happened this window
|
|
1042
1630
|
|
|
@@ -1227,30 +1815,16 @@ Window: last ${input.windowDays} days \xB7 ${input.eventCount} events
|
|
|
1227
1815
|
Lens: ${input.lens.name}`
|
|
1228
1816
|
);
|
|
1229
1817
|
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
1818
|
let emergentBlock = "EMERGENT\n";
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
emergentBlock += `
|
|
1819
|
+
for (const p of input.patterns) {
|
|
1820
|
+
emergentBlock += `
|
|
1236
1821
|
${p.name}
|
|
1237
1822
|
`;
|
|
1238
|
-
|
|
1823
|
+
emergentBlock += ` ${p.description}
|
|
1239
1824
|
`;
|
|
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}
|
|
1825
|
+
if (p.evidence.cited_invariant) {
|
|
1826
|
+
emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
|
|
1249
1827
|
`;
|
|
1250
|
-
if (p.evidence.cited_invariant) {
|
|
1251
|
-
emergentBlock += ` Cited invariant: ${p.evidence.cited_invariant}
|
|
1252
|
-
`;
|
|
1253
|
-
}
|
|
1254
1828
|
}
|
|
1255
1829
|
}
|
|
1256
1830
|
sections.push(emergentBlock.trimEnd());
|
|
@@ -1462,16 +2036,29 @@ function serializeYAML(obj, indent = 0) {
|
|
|
1462
2036
|
async function emergent(input) {
|
|
1463
2037
|
const lens = resolveLens2(input.lensId);
|
|
1464
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
|
+
}
|
|
1465
2045
|
let statedIntent;
|
|
1466
2046
|
let exocortexContext;
|
|
1467
2047
|
let priorReadContext = "";
|
|
1468
2048
|
if (input.exocortexPath) {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
+
}
|
|
1472
2059
|
const priorReads = loadPriorReads(input.exocortexPath);
|
|
1473
2060
|
if (priorReads.length > 0) {
|
|
1474
|
-
priorReadContext =
|
|
2061
|
+
priorReadContext = compressPriorReads(priorReads);
|
|
1475
2062
|
}
|
|
1476
2063
|
}
|
|
1477
2064
|
let events;
|
|
@@ -1489,6 +2076,40 @@ async function emergent(input) {
|
|
|
1489
2076
|
windowDays
|
|
1490
2077
|
});
|
|
1491
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));
|
|
1492
2113
|
const classified = classifyEvents(events);
|
|
1493
2114
|
const signals = extractSignals(classified);
|
|
1494
2115
|
const scores = computeScores(signals, input.worldmodelContent !== "");
|
|
@@ -1499,7 +2120,7 @@ async function emergent(input) {
|
|
|
1499
2120
|
lens,
|
|
1500
2121
|
ai: input.ai,
|
|
1501
2122
|
canonicalPatterns: input.canonicalPatterns,
|
|
1502
|
-
statedIntent: statedIntent
|
|
2123
|
+
statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
|
|
1503
2124
|
});
|
|
1504
2125
|
const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
|
|
1505
2126
|
const allDescriptions = rewrittenPatterns.map((p) => p.description).join("\n");
|
|
@@ -1548,7 +2169,9 @@ async function emergent(input) {
|
|
|
1548
2169
|
voiceClean: voiceViolations.length === 0,
|
|
1549
2170
|
signals,
|
|
1550
2171
|
scores,
|
|
1551
|
-
eventCount: events.length
|
|
2172
|
+
eventCount: events.length,
|
|
2173
|
+
activeAdapters,
|
|
2174
|
+
worldStack
|
|
1552
2175
|
};
|
|
1553
2176
|
}
|
|
1554
2177
|
function computeScores(signals, worldmodelLoaded) {
|
|
@@ -1641,6 +2264,10 @@ function createMockAI(fixedResponse) {
|
|
|
1641
2264
|
}
|
|
1642
2265
|
|
|
1643
2266
|
export {
|
|
2267
|
+
compressWorldmodel,
|
|
2268
|
+
compressExocortex,
|
|
2269
|
+
compressLens,
|
|
2270
|
+
compressPriorReads,
|
|
1644
2271
|
composeSystemPrompt,
|
|
1645
2272
|
checkForbiddenPhrases,
|
|
1646
2273
|
think,
|
|
@@ -1650,6 +2277,14 @@ export {
|
|
|
1650
2277
|
fetchGitHubActivity,
|
|
1651
2278
|
fetchGitHubOrgActivity,
|
|
1652
2279
|
createMockGitHubAdapter,
|
|
2280
|
+
fetchDiscordActivity,
|
|
2281
|
+
formatDiscordSignalsForPrompt,
|
|
2282
|
+
fetchSlackActivity,
|
|
2283
|
+
formatSlackSignalsForPrompt,
|
|
2284
|
+
fetchNotionActivity,
|
|
2285
|
+
formatNotionSignalsForPrompt,
|
|
2286
|
+
discoverWorlds,
|
|
2287
|
+
formatActiveWorlds,
|
|
1653
2288
|
readExocortex,
|
|
1654
2289
|
formatExocortexForPrompt,
|
|
1655
2290
|
readTeamExocortices,
|