@neuroverseos/governance 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -4
- package/dist/{chunk-T6EQ7ZBG.js → chunk-MC6O5GV5.js} +122 -11
- package/dist/cli/neuroverse.cjs +106 -21
- package/dist/cli/radiant.cjs +97 -12
- package/dist/cli/radiant.js +23 -5
- package/dist/radiant/index.cjs +531 -10
- package/dist/radiant/index.d.cts +247 -14
- package/dist/radiant/index.d.ts +247 -14
- package/dist/radiant/index.js +409 -1
- package/dist/{server-BXMC5NOE.js → server-DFNY5N5A.js} +1 -1
- package/package.json +1 -1
package/dist/radiant/index.cjs
CHANGED
|
@@ -46,10 +46,18 @@ __export(radiant_exports, {
|
|
|
46
46
|
createMockGitHubAdapter: () => createMockGitHubAdapter,
|
|
47
47
|
emergent: () => emergent,
|
|
48
48
|
extractSignals: () => extractSignals,
|
|
49
|
+
fetchDiscordActivity: () => fetchDiscordActivity,
|
|
49
50
|
fetchGitHubActivity: () => fetchGitHubActivity,
|
|
51
|
+
fetchGitHubOrgActivity: () => fetchGitHubOrgActivity,
|
|
52
|
+
fetchNotionActivity: () => fetchNotionActivity,
|
|
53
|
+
fetchSlackActivity: () => fetchSlackActivity,
|
|
54
|
+
formatDiscordSignalsForPrompt: () => formatDiscordSignalsForPrompt,
|
|
50
55
|
formatExocortexForPrompt: () => formatExocortexForPrompt,
|
|
56
|
+
formatNotionSignalsForPrompt: () => formatNotionSignalsForPrompt,
|
|
51
57
|
formatPriorReadsForPrompt: () => formatPriorReadsForPrompt,
|
|
52
58
|
formatScope: () => formatScope,
|
|
59
|
+
formatSlackSignalsForPrompt: () => formatSlackSignalsForPrompt,
|
|
60
|
+
formatTeamExocorticesForPrompt: () => formatTeamExocorticesForPrompt,
|
|
53
61
|
getLens: () => getLens,
|
|
54
62
|
interpretPatterns: () => interpretPatterns,
|
|
55
63
|
isPresent: () => isPresent,
|
|
@@ -58,8 +66,10 @@ __export(radiant_exports, {
|
|
|
58
66
|
listLenses: () => listLenses,
|
|
59
67
|
loadPriorReads: () => loadPriorReads,
|
|
60
68
|
parseRepoScope: () => parseRepoScope,
|
|
69
|
+
parseScope: () => parseScope,
|
|
61
70
|
presenceAverage: () => presenceAverage,
|
|
62
71
|
readExocortex: () => readExocortex,
|
|
72
|
+
readTeamExocortices: () => readTeamExocortices,
|
|
63
73
|
render: () => render,
|
|
64
74
|
scoreComposite: () => scoreComposite,
|
|
65
75
|
scoreCyber: () => scoreCyber,
|
|
@@ -847,17 +857,30 @@ function createMockAI(fixedResponse) {
|
|
|
847
857
|
}
|
|
848
858
|
|
|
849
859
|
// src/radiant/core/scopes.ts
|
|
850
|
-
function
|
|
860
|
+
function parseScope(scope) {
|
|
851
861
|
const cleaned = scope.replace(/^https?:\/\//, "").replace(/^github\.com\//, "").replace(/\.git$/, "").replace(/\/$/, "");
|
|
852
|
-
const parts = cleaned.split("/");
|
|
853
|
-
if (parts.length
|
|
862
|
+
const parts = cleaned.split("/").filter(Boolean);
|
|
863
|
+
if (parts.length === 0 || !parts[0]) {
|
|
864
|
+
throw new Error(
|
|
865
|
+
`Cannot parse scope: "${scope}". Expected "owner/repo" or "owner".`
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (parts.length === 1) {
|
|
869
|
+
return { type: "org", owner: parts[0] };
|
|
870
|
+
}
|
|
871
|
+
return { type: "repo", owner: parts[0], repo: parts[1] };
|
|
872
|
+
}
|
|
873
|
+
function parseRepoScope(scope) {
|
|
874
|
+
const parsed = parseScope(scope);
|
|
875
|
+
if (parsed.type === "org") {
|
|
854
876
|
throw new Error(
|
|
855
|
-
`
|
|
877
|
+
`Expected "owner/repo" but got org-level scope "${parsed.owner}". Use parseScope() for org-level.`
|
|
856
878
|
);
|
|
857
879
|
}
|
|
858
|
-
return
|
|
880
|
+
return parsed;
|
|
859
881
|
}
|
|
860
882
|
function formatScope(scope) {
|
|
883
|
+
if (scope.type === "org") return `${scope.owner} (org)`;
|
|
861
884
|
return `${scope.owner}/${scope.repo}`;
|
|
862
885
|
}
|
|
863
886
|
|
|
@@ -1041,6 +1064,40 @@ async function fetchJSON(url, headers) {
|
|
|
1041
1064
|
}
|
|
1042
1065
|
return await res.json();
|
|
1043
1066
|
}
|
|
1067
|
+
async function fetchGitHubOrgActivity(scope, token, options = {}) {
|
|
1068
|
+
const perPage = options.perPage ?? 100;
|
|
1069
|
+
const headers = {
|
|
1070
|
+
Authorization: `token ${token}`,
|
|
1071
|
+
Accept: "application/vnd.github.v3+json",
|
|
1072
|
+
"User-Agent": "neuroverseos-radiant"
|
|
1073
|
+
};
|
|
1074
|
+
const repos = await fetchJSON(
|
|
1075
|
+
`https://api.github.com/orgs/${scope.owner}/repos?sort=pushed&direction=desc&per_page=${perPage}`,
|
|
1076
|
+
headers
|
|
1077
|
+
);
|
|
1078
|
+
const windowDays = options.windowDays ?? 14;
|
|
1079
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
1080
|
+
const activeRepos = repos.filter(
|
|
1081
|
+
(r) => new Date(r.pushed_at) >= since
|
|
1082
|
+
);
|
|
1083
|
+
const cappedRepos = activeRepos.slice(0, 10);
|
|
1084
|
+
const allEvents = [];
|
|
1085
|
+
const repoNames = [];
|
|
1086
|
+
for (const repo of cappedRepos) {
|
|
1087
|
+
const [owner, repoName] = repo.full_name.split("/");
|
|
1088
|
+
try {
|
|
1089
|
+
const repoScope = { type: "repo", owner, repo: repoName };
|
|
1090
|
+
const events = await fetchGitHubActivity(repoScope, token, options);
|
|
1091
|
+
allEvents.push(...events);
|
|
1092
|
+
if (events.length > 0) repoNames.push(repo.full_name);
|
|
1093
|
+
} catch {
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
allEvents.sort(
|
|
1097
|
+
(a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)
|
|
1098
|
+
);
|
|
1099
|
+
return { events: allEvents, repos: repoNames };
|
|
1100
|
+
}
|
|
1044
1101
|
function createMockGitHubAdapter(fixedEvents) {
|
|
1045
1102
|
return async () => fixedEvents;
|
|
1046
1103
|
}
|
|
@@ -1117,6 +1174,46 @@ ${ctx.methods}`);
|
|
|
1117
1174
|
}
|
|
1118
1175
|
return sections.join("\n\n");
|
|
1119
1176
|
}
|
|
1177
|
+
function readTeamExocortices(teamDir) {
|
|
1178
|
+
const dir = (0, import_path.resolve)(teamDir);
|
|
1179
|
+
if (!(0, import_fs.existsSync)(dir)) return [];
|
|
1180
|
+
const entries = (0, import_fs.readdirSync)(dir);
|
|
1181
|
+
const results = [];
|
|
1182
|
+
for (const entry of entries) {
|
|
1183
|
+
const entryPath = (0, import_path.join)(dir, entry);
|
|
1184
|
+
try {
|
|
1185
|
+
const stat = (0, import_fs.statSync)(entryPath);
|
|
1186
|
+
if (stat.isDirectory()) {
|
|
1187
|
+
const ctx = readExocortex(entryPath);
|
|
1188
|
+
if (ctx.filesLoaded > 0) {
|
|
1189
|
+
results.push({ name: entry, context: ctx });
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
} catch {
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return results;
|
|
1196
|
+
}
|
|
1197
|
+
function formatTeamExocorticesForPrompt(team) {
|
|
1198
|
+
if (team.length === 0) return "";
|
|
1199
|
+
const sections = [
|
|
1200
|
+
"## Team Intent (cross-exocortex read)",
|
|
1201
|
+
"",
|
|
1202
|
+
`Reading ${team.length} team members' exocortices. Compare each person's`,
|
|
1203
|
+
"stated intent against the observed activity AND against each other.",
|
|
1204
|
+
"Surface: duplicate focus, missing coverage, silent pivots,",
|
|
1205
|
+
"and areas where no one is carrying the work.",
|
|
1206
|
+
""
|
|
1207
|
+
];
|
|
1208
|
+
for (const { name, context } of team) {
|
|
1209
|
+
sections.push(`### ${name}`);
|
|
1210
|
+
if (context.attention) sections.push(`**Attention:** ${context.attention.split("\n")[0]}`);
|
|
1211
|
+
if (context.goals) sections.push(`**Goals:** ${context.goals.split("\n").slice(0, 3).join("; ")}`);
|
|
1212
|
+
if (context.sprint) sections.push(`**Sprint:** ${context.sprint.split("\n").slice(0, 3).join("; ")}`);
|
|
1213
|
+
sections.push("");
|
|
1214
|
+
}
|
|
1215
|
+
return sections.join("\n");
|
|
1216
|
+
}
|
|
1120
1217
|
function summarizeExocortex(ctx) {
|
|
1121
1218
|
if (ctx.filesLoaded === 0) return "no exocortex files found";
|
|
1122
1219
|
const loaded = [];
|
|
@@ -1129,6 +1226,400 @@ function summarizeExocortex(ctx) {
|
|
|
1129
1226
|
return `${loaded.join(", ")} (${ctx.filesLoaded} files)`;
|
|
1130
1227
|
}
|
|
1131
1228
|
|
|
1229
|
+
// src/radiant/adapters/discord.ts
|
|
1230
|
+
async function fetchDiscordActivity(guildId, token, options = {}) {
|
|
1231
|
+
const windowDays = options.windowDays ?? 14;
|
|
1232
|
+
const perChannel = options.perChannel ?? 100;
|
|
1233
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
1234
|
+
const headers = {
|
|
1235
|
+
Authorization: `Bot ${token}`,
|
|
1236
|
+
"Content-Type": "application/json"
|
|
1237
|
+
};
|
|
1238
|
+
const channels = await fetchJSON2(
|
|
1239
|
+
`https://discord.com/api/v10/guilds/${guildId}/channels`,
|
|
1240
|
+
headers
|
|
1241
|
+
);
|
|
1242
|
+
const textChannels = channels.filter((c) => {
|
|
1243
|
+
if (c.type !== 0) return false;
|
|
1244
|
+
if (options.channelIds && options.channelIds.length > 0) {
|
|
1245
|
+
return options.channelIds.includes(c.id);
|
|
1246
|
+
}
|
|
1247
|
+
if (options.visibility === "public") {
|
|
1248
|
+
return !c.name.startsWith("private-") && !c.nsfw;
|
|
1249
|
+
}
|
|
1250
|
+
return true;
|
|
1251
|
+
});
|
|
1252
|
+
const events = [];
|
|
1253
|
+
let totalMessages = 0;
|
|
1254
|
+
let helpRequests = 0;
|
|
1255
|
+
let unresolvedThreads = 0;
|
|
1256
|
+
let newcomerMessages = 0;
|
|
1257
|
+
const responseTimes = [];
|
|
1258
|
+
const participants = /* @__PURE__ */ new Set();
|
|
1259
|
+
const knownParticipants = /* @__PURE__ */ new Set();
|
|
1260
|
+
const topicCounts = /* @__PURE__ */ new Map();
|
|
1261
|
+
for (const channel of textChannels.slice(0, 15)) {
|
|
1262
|
+
try {
|
|
1263
|
+
const messages = await fetchJSON2(
|
|
1264
|
+
`https://discord.com/api/v10/channels/${channel.id}/messages?limit=${perChannel}`,
|
|
1265
|
+
headers
|
|
1266
|
+
);
|
|
1267
|
+
const inWindow = messages.filter(
|
|
1268
|
+
(m) => new Date(m.timestamp) >= since
|
|
1269
|
+
);
|
|
1270
|
+
totalMessages += inWindow.length;
|
|
1271
|
+
const topic = channel.name.replace(/-/g, " ");
|
|
1272
|
+
topicCounts.set(topic, (topicCounts.get(topic) ?? 0) + inWindow.length);
|
|
1273
|
+
for (const msg of inWindow) {
|
|
1274
|
+
const actor = mapDiscordUser(msg.author);
|
|
1275
|
+
participants.add(actor.id);
|
|
1276
|
+
const lowerContent = msg.content.toLowerCase();
|
|
1277
|
+
if (lowerContent.includes("help") || lowerContent.includes("stuck") || lowerContent.includes("how do i") || lowerContent.includes("anyone know")) {
|
|
1278
|
+
helpRequests++;
|
|
1279
|
+
}
|
|
1280
|
+
if (msg.referenced_message) {
|
|
1281
|
+
const refTime = new Date(msg.referenced_message.timestamp).getTime();
|
|
1282
|
+
const msgTime = new Date(msg.timestamp).getTime();
|
|
1283
|
+
const diffMinutes = (msgTime - refTime) / 6e4;
|
|
1284
|
+
if (diffMinutes > 0 && diffMinutes < 10080) {
|
|
1285
|
+
responseTimes.push(diffMinutes);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
events.push({
|
|
1289
|
+
id: `discord-${msg.id}`,
|
|
1290
|
+
timestamp: msg.timestamp,
|
|
1291
|
+
actor,
|
|
1292
|
+
kind: "discord_message",
|
|
1293
|
+
content: msg.content.slice(0, 500),
|
|
1294
|
+
respondsTo: msg.referenced_message ? {
|
|
1295
|
+
eventId: `discord-${msg.referenced_message.id}`,
|
|
1296
|
+
actor: mapDiscordUser(msg.referenced_message.author)
|
|
1297
|
+
} : void 0,
|
|
1298
|
+
metadata: {
|
|
1299
|
+
channel: channel.name,
|
|
1300
|
+
guildId
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
} catch {
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const avgResponseMinutes = responseTimes.length > 0 ? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length : null;
|
|
1308
|
+
const topTopics = [...topicCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t]) => t);
|
|
1309
|
+
const signals = {
|
|
1310
|
+
totalMessages,
|
|
1311
|
+
activeChannels: textChannels.length,
|
|
1312
|
+
uniqueParticipants: participants.size,
|
|
1313
|
+
avgResponseMinutes: avgResponseMinutes ? Math.round(avgResponseMinutes) : null,
|
|
1314
|
+
helpRequests,
|
|
1315
|
+
unresolvedThreads,
|
|
1316
|
+
topTopics,
|
|
1317
|
+
newcomerMessages
|
|
1318
|
+
};
|
|
1319
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
1320
|
+
return { events, signals };
|
|
1321
|
+
}
|
|
1322
|
+
function formatDiscordSignalsForPrompt(signals) {
|
|
1323
|
+
if (signals.totalMessages === 0) return "";
|
|
1324
|
+
const lines = [
|
|
1325
|
+
"## Discord Activity (conversational behavior)",
|
|
1326
|
+
"",
|
|
1327
|
+
`${signals.totalMessages} messages across ${signals.activeChannels} channels.`,
|
|
1328
|
+
`${signals.uniqueParticipants} unique participants.`
|
|
1329
|
+
];
|
|
1330
|
+
if (signals.avgResponseMinutes !== null) {
|
|
1331
|
+
lines.push(`Average response time: ${signals.avgResponseMinutes} minutes.`);
|
|
1332
|
+
}
|
|
1333
|
+
if (signals.helpRequests > 0) {
|
|
1334
|
+
lines.push(`${signals.helpRequests} help requests detected.`);
|
|
1335
|
+
}
|
|
1336
|
+
if (signals.unresolvedThreads > 0) {
|
|
1337
|
+
lines.push(`${signals.unresolvedThreads} unresolved threads.`);
|
|
1338
|
+
}
|
|
1339
|
+
if (signals.topTopics.length > 0) {
|
|
1340
|
+
lines.push(`Top discussion topics: ${signals.topTopics.join(", ")}.`);
|
|
1341
|
+
}
|
|
1342
|
+
lines.push("");
|
|
1343
|
+
lines.push("Compare conversational activity against GitHub shipping activity.");
|
|
1344
|
+
lines.push("Where debates happen in Discord but nothing ships in GitHub, name the gap.");
|
|
1345
|
+
lines.push("Where work ships in GitHub but nobody discusses it in Discord, name the visibility gap.");
|
|
1346
|
+
return lines.join("\n");
|
|
1347
|
+
}
|
|
1348
|
+
function mapDiscordUser(user) {
|
|
1349
|
+
return {
|
|
1350
|
+
id: user.username,
|
|
1351
|
+
kind: user.bot ? "bot" : "human",
|
|
1352
|
+
name: user.username
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
async function fetchJSON2(url, headers) {
|
|
1356
|
+
const res = await fetch(url, { headers });
|
|
1357
|
+
if (!res.ok) {
|
|
1358
|
+
if (res.status === 404 || res.status === 403) return [];
|
|
1359
|
+
throw new Error(`Discord API error ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
1360
|
+
}
|
|
1361
|
+
return await res.json();
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/radiant/adapters/slack.ts
|
|
1365
|
+
async function fetchSlackActivity(token, options = {}) {
|
|
1366
|
+
const windowDays = options.windowDays ?? 14;
|
|
1367
|
+
const perChannel = options.perChannel ?? 100;
|
|
1368
|
+
const oldest = String(
|
|
1369
|
+
Math.floor((Date.now() - windowDays * 24 * 60 * 60 * 1e3) / 1e3)
|
|
1370
|
+
);
|
|
1371
|
+
const headers = {
|
|
1372
|
+
Authorization: `Bearer ${token}`,
|
|
1373
|
+
"Content-Type": "application/json"
|
|
1374
|
+
};
|
|
1375
|
+
const channelsResponse = await fetchSlackAPI("https://slack.com/api/conversations.list?types=public_channel&limit=200", headers);
|
|
1376
|
+
let channels = channelsResponse.channels ?? [];
|
|
1377
|
+
if (options.channelIds && options.channelIds.length > 0) {
|
|
1378
|
+
const ids = new Set(options.channelIds);
|
|
1379
|
+
channels = channels.filter((c) => ids.has(c.id));
|
|
1380
|
+
}
|
|
1381
|
+
if (options.visibility === "public") {
|
|
1382
|
+
channels = channels.filter((c) => !c.is_private && !c.is_archived);
|
|
1383
|
+
}
|
|
1384
|
+
const events = [];
|
|
1385
|
+
let totalMessages = 0;
|
|
1386
|
+
let reactionCount = 0;
|
|
1387
|
+
let unresolvedThreads = 0;
|
|
1388
|
+
const responseTimes = [];
|
|
1389
|
+
const participants = /* @__PURE__ */ new Set();
|
|
1390
|
+
const externalParticipants = /* @__PURE__ */ new Set();
|
|
1391
|
+
const channelMessageCounts = /* @__PURE__ */ new Map();
|
|
1392
|
+
for (const channel of channels.slice(0, 15)) {
|
|
1393
|
+
try {
|
|
1394
|
+
const historyResponse = await fetchSlackAPI(
|
|
1395
|
+
`https://slack.com/api/conversations.history?channel=${channel.id}&limit=${perChannel}&oldest=${oldest}`,
|
|
1396
|
+
headers
|
|
1397
|
+
);
|
|
1398
|
+
const messages = historyResponse.messages ?? [];
|
|
1399
|
+
totalMessages += messages.length;
|
|
1400
|
+
channelMessageCounts.set(channel.name, messages.length);
|
|
1401
|
+
for (const msg of messages) {
|
|
1402
|
+
if (msg.subtype === "channel_join" || msg.subtype === "channel_leave") continue;
|
|
1403
|
+
const actor = mapSlackUser(msg.user ?? "unknown");
|
|
1404
|
+
participants.add(actor.id);
|
|
1405
|
+
if (msg.reactions) {
|
|
1406
|
+
reactionCount += msg.reactions.reduce(
|
|
1407
|
+
(sum, r) => sum + (r.count ?? 0),
|
|
1408
|
+
0
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
if (msg.thread_ts && msg.thread_ts !== msg.ts) {
|
|
1412
|
+
const parentTs = parseFloat(msg.thread_ts) * 1e3;
|
|
1413
|
+
const msgTs = parseFloat(msg.ts) * 1e3;
|
|
1414
|
+
const diffMinutes = (msgTs - parentTs) / 6e4;
|
|
1415
|
+
if (diffMinutes > 0 && diffMinutes < 10080) {
|
|
1416
|
+
responseTimes.push(diffMinutes);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (msg.thread_ts === msg.ts && (!msg.reply_count || msg.reply_count === 0)) {
|
|
1420
|
+
if (msg.text && (msg.text.includes("?") || msg.text.toLowerCase().includes("help"))) {
|
|
1421
|
+
unresolvedThreads++;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
const timestamp = new Date(parseFloat(msg.ts) * 1e3).toISOString();
|
|
1425
|
+
events.push({
|
|
1426
|
+
id: `slack-${msg.ts}`,
|
|
1427
|
+
timestamp,
|
|
1428
|
+
actor,
|
|
1429
|
+
kind: "slack_message",
|
|
1430
|
+
content: (msg.text ?? "").slice(0, 500),
|
|
1431
|
+
respondsTo: msg.thread_ts && msg.thread_ts !== msg.ts ? {
|
|
1432
|
+
eventId: `slack-${msg.thread_ts}`,
|
|
1433
|
+
actor: { id: "thread-parent", kind: "unknown" }
|
|
1434
|
+
} : void 0,
|
|
1435
|
+
metadata: {
|
|
1436
|
+
channel: channel.name,
|
|
1437
|
+
isPrivate: channel.is_private,
|
|
1438
|
+
hasReactions: (msg.reactions?.length ?? 0) > 0
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
} catch {
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const avgResponseMinutes = responseTimes.length > 0 ? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length) : null;
|
|
1446
|
+
const topChannels = [...channelMessageCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
|
|
1447
|
+
const signals = {
|
|
1448
|
+
totalMessages,
|
|
1449
|
+
activeChannels: channelMessageCounts.size,
|
|
1450
|
+
uniqueParticipants: participants.size,
|
|
1451
|
+
avgResponseMinutes,
|
|
1452
|
+
externalParticipants: externalParticipants.size,
|
|
1453
|
+
unresolvedThreads,
|
|
1454
|
+
topChannels,
|
|
1455
|
+
reactionCount
|
|
1456
|
+
};
|
|
1457
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
1458
|
+
return { events, signals };
|
|
1459
|
+
}
|
|
1460
|
+
function formatSlackSignalsForPrompt(signals) {
|
|
1461
|
+
if (signals.totalMessages === 0) return "";
|
|
1462
|
+
const lines = [
|
|
1463
|
+
"## Slack Activity (external coordination)",
|
|
1464
|
+
"",
|
|
1465
|
+
`${signals.totalMessages} messages across ${signals.activeChannels} channels.`,
|
|
1466
|
+
`${signals.uniqueParticipants} unique participants.`
|
|
1467
|
+
];
|
|
1468
|
+
if (signals.avgResponseMinutes !== null) {
|
|
1469
|
+
lines.push(`Average thread response time: ${signals.avgResponseMinutes} minutes.`);
|
|
1470
|
+
}
|
|
1471
|
+
if (signals.unresolvedThreads > 0) {
|
|
1472
|
+
lines.push(`${signals.unresolvedThreads} questions/threads with no reply.`);
|
|
1473
|
+
}
|
|
1474
|
+
if (signals.reactionCount > 0) {
|
|
1475
|
+
lines.push(`${signals.reactionCount} reactions (engagement signal).`);
|
|
1476
|
+
}
|
|
1477
|
+
if (signals.topChannels.length > 0) {
|
|
1478
|
+
lines.push(`Most active channels: ${signals.topChannels.join(", ")}.`);
|
|
1479
|
+
}
|
|
1480
|
+
lines.push("");
|
|
1481
|
+
lines.push("Slack carries external coordination \u2014 partner and client communication.");
|
|
1482
|
+
lines.push("Compare partner engagement against internal activity. Where partners are");
|
|
1483
|
+
lines.push("active but internal follow-through is low, name the gap.");
|
|
1484
|
+
return lines.join("\n");
|
|
1485
|
+
}
|
|
1486
|
+
function mapSlackUser(userId) {
|
|
1487
|
+
return {
|
|
1488
|
+
id: userId,
|
|
1489
|
+
kind: "human",
|
|
1490
|
+
name: userId
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
async function fetchSlackAPI(url, headers) {
|
|
1494
|
+
const res = await fetch(url, { headers });
|
|
1495
|
+
if (!res.ok) {
|
|
1496
|
+
throw new Error(`Slack API error ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
1497
|
+
}
|
|
1498
|
+
const data = await res.json();
|
|
1499
|
+
if (!data.ok) {
|
|
1500
|
+
throw new Error(`Slack API error: ${data.error ?? "unknown"}`);
|
|
1501
|
+
}
|
|
1502
|
+
return data;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/radiant/adapters/notion.ts
|
|
1506
|
+
async function fetchNotionActivity(token, options = {}) {
|
|
1507
|
+
const windowDays = options.windowDays ?? 14;
|
|
1508
|
+
const maxPages = options.maxPages ?? 100;
|
|
1509
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
1510
|
+
const headers = {
|
|
1511
|
+
Authorization: `Bearer ${token}`,
|
|
1512
|
+
"Notion-Version": "2022-06-28",
|
|
1513
|
+
"Content-Type": "application/json"
|
|
1514
|
+
};
|
|
1515
|
+
const searchResponse = await fetchNotionAPI("https://api.notion.com/v1/search", headers, {
|
|
1516
|
+
method: "POST",
|
|
1517
|
+
body: JSON.stringify({
|
|
1518
|
+
filter: { property: "object", value: "page" },
|
|
1519
|
+
sort: { direction: "descending", timestamp: "last_edited_time" },
|
|
1520
|
+
page_size: maxPages
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1523
|
+
const pages = searchResponse.results ?? [];
|
|
1524
|
+
const events = [];
|
|
1525
|
+
const editors = /* @__PURE__ */ new Set();
|
|
1526
|
+
let pagesCreated = 0;
|
|
1527
|
+
let pagesUpdated = 0;
|
|
1528
|
+
let stalePages = 0;
|
|
1529
|
+
const editAges = [];
|
|
1530
|
+
const topPages = [];
|
|
1531
|
+
const now = Date.now();
|
|
1532
|
+
for (const page of pages) {
|
|
1533
|
+
const lastEdited = new Date(page.last_edited_time);
|
|
1534
|
+
const created = new Date(page.created_time);
|
|
1535
|
+
const daysSinceEdit = (now - lastEdited.getTime()) / (24 * 60 * 60 * 1e3);
|
|
1536
|
+
editAges.push(daysSinceEdit);
|
|
1537
|
+
if (daysSinceEdit > 30) stalePages++;
|
|
1538
|
+
const title = extractTitle(page);
|
|
1539
|
+
const editorId = page.last_edited_by?.id ?? "unknown";
|
|
1540
|
+
editors.add(editorId);
|
|
1541
|
+
if (lastEdited >= since) {
|
|
1542
|
+
const isNew = created >= since;
|
|
1543
|
+
if (isNew) pagesCreated++;
|
|
1544
|
+
else pagesUpdated++;
|
|
1545
|
+
topPages.push({ title, editedAt: page.last_edited_time });
|
|
1546
|
+
events.push({
|
|
1547
|
+
id: `notion-${page.id}`,
|
|
1548
|
+
timestamp: page.last_edited_time,
|
|
1549
|
+
actor: {
|
|
1550
|
+
id: editorId,
|
|
1551
|
+
kind: "human",
|
|
1552
|
+
name: editorId
|
|
1553
|
+
},
|
|
1554
|
+
kind: isNew ? "doc_created" : "doc_updated",
|
|
1555
|
+
content: `${isNew ? "Created" : "Updated"}: ${title}`,
|
|
1556
|
+
metadata: {
|
|
1557
|
+
pageId: page.id,
|
|
1558
|
+
url: page.url,
|
|
1559
|
+
createdAt: page.created_time
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
const avgDaysSinceEdit = editAges.length > 0 ? Math.round(editAges.reduce((a, b) => a + b, 0) / editAges.length) : null;
|
|
1565
|
+
const signals = {
|
|
1566
|
+
pagesActive: pagesCreated + pagesUpdated,
|
|
1567
|
+
pagesCreated,
|
|
1568
|
+
pagesUpdated,
|
|
1569
|
+
uniqueEditors: editors.size,
|
|
1570
|
+
stalePages,
|
|
1571
|
+
avgDaysSinceEdit,
|
|
1572
|
+
topPages: topPages.slice(0, 5).map((p) => p.title)
|
|
1573
|
+
};
|
|
1574
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
1575
|
+
return { events, signals };
|
|
1576
|
+
}
|
|
1577
|
+
function formatNotionSignalsForPrompt(signals) {
|
|
1578
|
+
if (signals.pagesActive === 0 && signals.stalePages === 0) return "";
|
|
1579
|
+
const lines = [
|
|
1580
|
+
"## Notion Activity (documentation behavior)",
|
|
1581
|
+
"",
|
|
1582
|
+
`${signals.pagesActive} pages active in window (${signals.pagesCreated} created, ${signals.pagesUpdated} updated).`,
|
|
1583
|
+
`${signals.uniqueEditors} unique editors.`
|
|
1584
|
+
];
|
|
1585
|
+
if (signals.stalePages > 0) {
|
|
1586
|
+
lines.push(`${signals.stalePages} pages haven't been touched in 30+ days.`);
|
|
1587
|
+
}
|
|
1588
|
+
if (signals.avgDaysSinceEdit !== null) {
|
|
1589
|
+
lines.push(`Average page age since last edit: ${signals.avgDaysSinceEdit} days.`);
|
|
1590
|
+
}
|
|
1591
|
+
if (signals.topPages.length > 0) {
|
|
1592
|
+
lines.push(`Recently active pages: ${signals.topPages.join(", ")}.`);
|
|
1593
|
+
}
|
|
1594
|
+
lines.push("");
|
|
1595
|
+
lines.push("Documentation is how the team crystallizes and shares knowledge.");
|
|
1596
|
+
lines.push("High code velocity + low documentation = building without recording.");
|
|
1597
|
+
lines.push("High documentation + low code = planning without shipping.");
|
|
1598
|
+
lines.push("Compare Notion activity against GitHub and Discord to find the balance.");
|
|
1599
|
+
return lines.join("\n");
|
|
1600
|
+
}
|
|
1601
|
+
function extractTitle(page) {
|
|
1602
|
+
for (const prop of Object.values(page.properties)) {
|
|
1603
|
+
if (prop.type === "title" && prop.title) {
|
|
1604
|
+
return prop.title.map((t) => t.plain_text).join("") || "Untitled";
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return "Untitled";
|
|
1608
|
+
}
|
|
1609
|
+
async function fetchNotionAPI(url, headers, init) {
|
|
1610
|
+
const res = await fetch(url, {
|
|
1611
|
+
method: init?.method ?? "GET",
|
|
1612
|
+
headers,
|
|
1613
|
+
body: init?.body
|
|
1614
|
+
});
|
|
1615
|
+
if (!res.ok) {
|
|
1616
|
+
throw new Error(
|
|
1617
|
+
`Notion API error ${res.status}: ${(await res.text()).slice(0, 300)}`
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
return await res.json();
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1132
1623
|
// src/radiant/core/patterns.ts
|
|
1133
1624
|
async function interpretPatterns(input) {
|
|
1134
1625
|
const prompt = buildInterpretationPrompt(input);
|
|
@@ -1200,6 +1691,14 @@ ${jargonTable}
|
|
|
1200
1691
|
|
|
1201
1692
|
For example: don't say "update the worldmodel." Say "add a line to your strategy file."
|
|
1202
1693
|
|
|
1694
|
+
## When the same invariant keeps firing
|
|
1695
|
+
|
|
1696
|
+
If the prior read history or the current evidence shows the same worldmodel invariant being triggered repeatedly (by the same side \u2014 human or AI), name it in MEANING and ask the real question:
|
|
1697
|
+
|
|
1698
|
+
"This invariant has been tested N times across M reads. Always on the [human/AI] side. Either the team needs alignment on WHY this rule exists \u2014 or the team is telling you something the worldmodel hasn't absorbed yet."
|
|
1699
|
+
|
|
1700
|
+
Don't just say "invariant held." Say what it means that people keep pushing against the same wall.
|
|
1701
|
+
|
|
1203
1702
|
## Health is a valid read
|
|
1204
1703
|
|
|
1205
1704
|
If the activity is healthy and aligned with the worldmodel, SAY SO. Don't fabricate problems. Over-prescription is a voice failure. Legitimate outputs include:
|
|
@@ -2655,7 +3154,7 @@ function verdictToEvent(status, intent) {
|
|
|
2655
3154
|
async function loadWorldFromDirectory(dirPath) {
|
|
2656
3155
|
const { readFile } = await import("fs/promises");
|
|
2657
3156
|
const { join: join3 } = await import("path");
|
|
2658
|
-
const { readdirSync:
|
|
3157
|
+
const { readdirSync: readdirSync3 } = await import("fs");
|
|
2659
3158
|
async function readJson(filename) {
|
|
2660
3159
|
const filePath = join3(dirPath, filename);
|
|
2661
3160
|
try {
|
|
@@ -2688,7 +3187,7 @@ async function loadWorldFromDirectory(dirPath) {
|
|
|
2688
3187
|
const rules = [];
|
|
2689
3188
|
try {
|
|
2690
3189
|
const rulesDir = join3(dirPath, "rules");
|
|
2691
|
-
const ruleFiles =
|
|
3190
|
+
const ruleFiles = readdirSync3(rulesDir).filter((f) => f.endsWith(".json")).sort();
|
|
2692
3191
|
for (const file of ruleFiles) {
|
|
2693
3192
|
try {
|
|
2694
3193
|
const content = await readFile(join3(rulesDir, file), "utf-8");
|
|
@@ -3090,9 +3589,21 @@ async function emergent(input) {
|
|
|
3090
3589
|
priorReadContext = formatPriorReadsForPrompt(priorReads);
|
|
3091
3590
|
}
|
|
3092
3591
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3592
|
+
let events;
|
|
3593
|
+
let orgRepos;
|
|
3594
|
+
if (input.scope.type === "org") {
|
|
3595
|
+
const orgResult = await fetchGitHubOrgActivity(
|
|
3596
|
+
input.scope,
|
|
3597
|
+
input.githubToken,
|
|
3598
|
+
{ windowDays }
|
|
3599
|
+
);
|
|
3600
|
+
events = orgResult.events;
|
|
3601
|
+
orgRepos = orgResult.repos;
|
|
3602
|
+
} else {
|
|
3603
|
+
events = await fetchGitHubActivity(input.scope, input.githubToken, {
|
|
3604
|
+
windowDays
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
3096
3607
|
const classified = classifyEvents(events);
|
|
3097
3608
|
const signals = extractSignals(classified);
|
|
3098
3609
|
const scores = computeScores(signals, input.worldmodelContent !== "");
|
|
@@ -3223,10 +3734,18 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
|
3223
3734
|
createMockGitHubAdapter,
|
|
3224
3735
|
emergent,
|
|
3225
3736
|
extractSignals,
|
|
3737
|
+
fetchDiscordActivity,
|
|
3226
3738
|
fetchGitHubActivity,
|
|
3739
|
+
fetchGitHubOrgActivity,
|
|
3740
|
+
fetchNotionActivity,
|
|
3741
|
+
fetchSlackActivity,
|
|
3742
|
+
formatDiscordSignalsForPrompt,
|
|
3227
3743
|
formatExocortexForPrompt,
|
|
3744
|
+
formatNotionSignalsForPrompt,
|
|
3228
3745
|
formatPriorReadsForPrompt,
|
|
3229
3746
|
formatScope,
|
|
3747
|
+
formatSlackSignalsForPrompt,
|
|
3748
|
+
formatTeamExocorticesForPrompt,
|
|
3230
3749
|
getLens,
|
|
3231
3750
|
interpretPatterns,
|
|
3232
3751
|
isPresent,
|
|
@@ -3235,8 +3754,10 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
|
|
|
3235
3754
|
listLenses,
|
|
3236
3755
|
loadPriorReads,
|
|
3237
3756
|
parseRepoScope,
|
|
3757
|
+
parseScope,
|
|
3238
3758
|
presenceAverage,
|
|
3239
3759
|
readExocortex,
|
|
3760
|
+
readTeamExocortices,
|
|
3240
3761
|
render,
|
|
3241
3762
|
scoreComposite,
|
|
3242
3763
|
scoreCyber,
|