@neuroverseos/governance 0.9.0 → 0.10.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.
@@ -51,15 +51,19 @@ __export(radiant_exports, {
51
51
  detectOrgExtendsSpec: () => detectOrgExtendsSpec,
52
52
  discoverWorlds: () => discoverWorlds,
53
53
  emergent: () => emergent,
54
+ extractDeclaredVocabulary: () => extractDeclaredVocabulary,
54
55
  extractSignals: () => extractSignals,
55
56
  fetchDiscordActivity: () => fetchDiscordActivity,
56
57
  fetchGitHubActivity: () => fetchGitHubActivity,
57
58
  fetchGitHubOrgActivity: () => fetchGitHubOrgActivity,
59
+ fetchLinearActivity: () => fetchLinearActivity,
58
60
  fetchNotionActivity: () => fetchNotionActivity,
59
61
  fetchSlackActivity: () => fetchSlackActivity,
62
+ filterEventsByUser: () => filterEventsByUser,
60
63
  formatActiveWorlds: () => formatActiveWorlds,
61
64
  formatDiscordSignalsForPrompt: () => formatDiscordSignalsForPrompt,
62
65
  formatExocortexForPrompt: () => formatExocortexForPrompt,
66
+ formatLinearSignalsForPrompt: () => formatLinearSignalsForPrompt,
63
67
  formatNotionSignalsForPrompt: () => formatNotionSignalsForPrompt,
64
68
  formatPriorReadsForPrompt: () => formatPriorReadsForPrompt,
65
69
  formatScope: () => formatScope,
@@ -75,6 +79,7 @@ __export(radiant_exports, {
75
79
  listLenses: () => listLenses,
76
80
  loadExtendsConfig: () => loadExtendsConfig,
77
81
  loadPriorReads: () => loadPriorReads,
82
+ matchDeclaredPattern: () => matchDeclaredPattern,
78
83
  parseExtendsSpec: () => parseExtendsSpec,
79
84
  parseRemoteUrl: () => parseRemoteUrl,
80
85
  parseRepoScope: () => parseRepoScope,
@@ -2040,11 +2045,398 @@ async function fetchNotionAPI(url, headers, init) {
2040
2045
  return await res.json();
2041
2046
  }
2042
2047
 
2048
+ // src/radiant/adapters/linear.ts
2049
+ async function fetchLinearActivity(apiKey, options = {}) {
2050
+ const windowDays = options.windowDays ?? 14;
2051
+ const maxIssues = options.maxIssues ?? 200;
2052
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
2053
+ const sinceIso = since.toISOString();
2054
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2055
+ const teamFilter = options.teamIds && options.teamIds.length > 0 ? `team: { id: { in: [${options.teamIds.map((t) => JSON.stringify(t)).join(", ")}] } }` : "";
2056
+ const issuesQuery = `
2057
+ query RadiantIssues($since: DateTimeOrDuration!, $first: Int!) {
2058
+ issues(
2059
+ filter: {
2060
+ updatedAt: { gte: $since }
2061
+ ${teamFilter}
2062
+ }
2063
+ first: $first
2064
+ orderBy: updatedAt
2065
+ ) {
2066
+ nodes {
2067
+ id
2068
+ identifier
2069
+ title
2070
+ url
2071
+ createdAt
2072
+ updatedAt
2073
+ completedAt
2074
+ canceledAt
2075
+ state { name type }
2076
+ assignee { id name email }
2077
+ creator { id name }
2078
+ team { id name }
2079
+ project { id name }
2080
+ cycle { id number startsAt endsAt }
2081
+ comments(first: 20) {
2082
+ nodes { id body createdAt user { id name } }
2083
+ }
2084
+ }
2085
+ }
2086
+ }
2087
+ `;
2088
+ const cyclesQuery = `
2089
+ query RadiantCycles($since: DateTimeOrDuration!, $now: DateTimeOrDuration!) {
2090
+ cycles(
2091
+ filter: { endsAt: { gte: $since, lte: $now } }
2092
+ first: 20
2093
+ ) {
2094
+ nodes {
2095
+ id
2096
+ number
2097
+ startsAt
2098
+ endsAt
2099
+ issueCountHistory
2100
+ completedIssueCountHistory
2101
+ team { id name }
2102
+ }
2103
+ }
2104
+ }
2105
+ `;
2106
+ const [issuesResponse, cyclesResponse] = await Promise.all([
2107
+ fetchLinearGraphQL(apiKey, issuesQuery, {
2108
+ since: sinceIso,
2109
+ first: maxIssues
2110
+ }),
2111
+ fetchLinearGraphQL(apiKey, cyclesQuery, {
2112
+ since: sinceIso,
2113
+ now: nowIso
2114
+ })
2115
+ ]);
2116
+ const rawIssues = issuesResponse.issues?.nodes ?? [];
2117
+ const rawCycles = cyclesResponse.cycles?.nodes ?? [];
2118
+ const events = [];
2119
+ const assignees = /* @__PURE__ */ new Set();
2120
+ const projects = /* @__PURE__ */ new Map();
2121
+ let issuesCreated = 0;
2122
+ let issuesCompleted = 0;
2123
+ let issuesOpen = 0;
2124
+ let issuesStalled = 0;
2125
+ let commentsTotal = 0;
2126
+ const now = Date.now();
2127
+ const stallThresholdMs = 14 * 24 * 60 * 60 * 1e3;
2128
+ for (const issue of rawIssues) {
2129
+ const created = new Date(issue.createdAt);
2130
+ const updated = new Date(issue.updatedAt);
2131
+ const completed = issue.completedAt ? new Date(issue.completedAt) : null;
2132
+ const assigneeId = issue.assignee?.id ?? "unassigned";
2133
+ if (assigneeId !== "unassigned") assignees.add(assigneeId);
2134
+ if (issue.project) {
2135
+ projects.set(issue.project.name, (projects.get(issue.project.name) ?? 0) + 1);
2136
+ }
2137
+ const actor = {
2138
+ id: assigneeId,
2139
+ kind: "human",
2140
+ name: issue.assignee?.name ?? "unassigned"
2141
+ };
2142
+ if (created >= since) {
2143
+ issuesCreated++;
2144
+ events.push({
2145
+ id: `linear-created-${issue.id}`,
2146
+ timestamp: issue.createdAt,
2147
+ actor: {
2148
+ id: issue.creator?.id ?? "unknown",
2149
+ kind: "human",
2150
+ name: issue.creator?.name ?? "unknown"
2151
+ },
2152
+ kind: "issue_created",
2153
+ content: `[${issue.identifier}] ${issue.title}`,
2154
+ metadata: {
2155
+ issueId: issue.id,
2156
+ url: issue.url,
2157
+ team: issue.team?.name,
2158
+ project: issue.project?.name,
2159
+ state: issue.state?.name
2160
+ }
2161
+ });
2162
+ }
2163
+ if (completed && completed >= since) {
2164
+ issuesCompleted++;
2165
+ events.push({
2166
+ id: `linear-completed-${issue.id}`,
2167
+ timestamp: issue.completedAt,
2168
+ actor,
2169
+ kind: "issue_completed",
2170
+ content: `[${issue.identifier}] ${issue.title}`,
2171
+ metadata: {
2172
+ issueId: issue.id,
2173
+ url: issue.url,
2174
+ team: issue.team?.name,
2175
+ project: issue.project?.name,
2176
+ cycleDays: issue.cycle?.startsAt && issue.completedAt ? Math.round(
2177
+ (new Date(issue.completedAt).getTime() - new Date(issue.cycle.startsAt).getTime()) / (24 * 60 * 60 * 1e3)
2178
+ ) : null
2179
+ }
2180
+ });
2181
+ }
2182
+ if (!completed && !issue.canceledAt) {
2183
+ issuesOpen++;
2184
+ const isInProgress = issue.state?.type === "started";
2185
+ const idleMs = now - updated.getTime();
2186
+ if (isInProgress && idleMs > stallThresholdMs) issuesStalled++;
2187
+ }
2188
+ for (const comment of issue.comments?.nodes ?? []) {
2189
+ const commentedAt = new Date(comment.createdAt);
2190
+ if (commentedAt < since) continue;
2191
+ commentsTotal++;
2192
+ events.push({
2193
+ id: `linear-comment-${comment.id}`,
2194
+ timestamp: comment.createdAt,
2195
+ actor: {
2196
+ id: comment.user?.id ?? "unknown",
2197
+ kind: "human",
2198
+ name: comment.user?.name ?? "unknown"
2199
+ },
2200
+ kind: "issue_comment",
2201
+ content: comment.body.slice(0, 280),
2202
+ metadata: {
2203
+ issueId: issue.id,
2204
+ issueIdentifier: issue.identifier,
2205
+ url: issue.url
2206
+ }
2207
+ });
2208
+ }
2209
+ }
2210
+ let cycleCompletionRate = null;
2211
+ if (rawCycles.length > 0) {
2212
+ const rates = [];
2213
+ for (const cycle of rawCycles) {
2214
+ const committed = cycle.issueCountHistory?.at(0) ?? 0;
2215
+ const completed = cycle.completedIssueCountHistory?.at(-1) ?? 0;
2216
+ if (committed > 0) rates.push(completed / committed);
2217
+ }
2218
+ if (rates.length > 0) {
2219
+ cycleCompletionRate = Math.round(rates.reduce((a, b) => a + b, 0) / rates.length * 100) / 100;
2220
+ }
2221
+ }
2222
+ const topProjects = [...projects.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
2223
+ const signals = {
2224
+ issuesCreated,
2225
+ issuesCompleted,
2226
+ issuesOpen,
2227
+ issuesStalled,
2228
+ cycleCompletionRate,
2229
+ uniqueAssignees: assignees.size,
2230
+ commentsTotal,
2231
+ topProjects
2232
+ };
2233
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
2234
+ return { events, signals };
2235
+ }
2236
+ function formatLinearSignalsForPrompt(signals) {
2237
+ if (signals.issuesCreated === 0 && signals.issuesCompleted === 0 && signals.issuesOpen === 0) {
2238
+ return "";
2239
+ }
2240
+ const lines = [
2241
+ "## Linear Activity (planned work vs. shipped outcome)",
2242
+ "",
2243
+ `${signals.issuesCreated} issues created, ${signals.issuesCompleted} completed in window.`,
2244
+ `${signals.issuesOpen} issues still open.`
2245
+ ];
2246
+ if (signals.issuesStalled > 0) {
2247
+ lines.push(
2248
+ `${signals.issuesStalled} in-progress issues haven't moved in 14+ days (stalled).`
2249
+ );
2250
+ }
2251
+ if (signals.cycleCompletionRate !== null) {
2252
+ const pct = Math.round(signals.cycleCompletionRate * 100);
2253
+ lines.push(`Cycles ended in window completed ${pct}% of what was committed.`);
2254
+ }
2255
+ if (signals.uniqueAssignees > 0) {
2256
+ lines.push(`${signals.uniqueAssignees} unique assignees active.`);
2257
+ }
2258
+ if (signals.commentsTotal > 0) {
2259
+ lines.push(`${signals.commentsTotal} comments across issues in window.`);
2260
+ }
2261
+ if (signals.topProjects.length > 0) {
2262
+ lines.push(`Most active projects: ${signals.topProjects.join(", ")}.`);
2263
+ }
2264
+ lines.push("");
2265
+ lines.push("Linear is where the team states what it will build.");
2266
+ lines.push("GitHub is where the team reveals what actually got built.");
2267
+ lines.push("Low completion rate + high creation rate = planning faster than shipping.");
2268
+ lines.push("High stalled count = commitments made but not honored.");
2269
+ lines.push("Compare Linear signals against GitHub to find the stated-vs-shipped gap.");
2270
+ return lines.join("\n");
2271
+ }
2272
+ async function fetchLinearGraphQL(apiKey, query, variables) {
2273
+ const res = await fetch("https://api.linear.app/graphql", {
2274
+ method: "POST",
2275
+ headers: {
2276
+ // Linear accepts the raw API key in Authorization with no "Bearer" prefix.
2277
+ Authorization: apiKey,
2278
+ "Content-Type": "application/json"
2279
+ },
2280
+ body: JSON.stringify({ query, variables })
2281
+ });
2282
+ if (!res.ok) {
2283
+ throw new Error(
2284
+ `Linear API error ${res.status}: ${(await res.text()).slice(0, 300)}`
2285
+ );
2286
+ }
2287
+ const json = await res.json();
2288
+ if (json.errors && json.errors.length > 0) {
2289
+ throw new Error(
2290
+ `Linear GraphQL errors: ${json.errors.map((e) => e.message).join("; ")}`
2291
+ );
2292
+ }
2293
+ if (!json.data) {
2294
+ throw new Error("Linear API returned no data");
2295
+ }
2296
+ return json.data;
2297
+ }
2298
+
2299
+ // src/radiant/core/vocabulary.ts
2300
+ function extractDeclaredVocabulary(worldmodelContent) {
2301
+ const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
2302
+ (b) => parseBehavior(b, "aligned")
2303
+ );
2304
+ const drift = extractSection(worldmodelContent, "Drift Behaviors").map(
2305
+ (b) => parseBehavior(b, "drift")
2306
+ );
2307
+ const allNames = [...aligned, ...drift].map((p) => p.name);
2308
+ return { aligned, drift, allNames };
2309
+ }
2310
+ function extractSection(content, header) {
2311
+ const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2312
+ const pattern = new RegExp(
2313
+ `##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
2314
+ "i"
2315
+ );
2316
+ const match = content.match(pattern);
2317
+ if (!match) return [];
2318
+ const body = match[1];
2319
+ const bullets = body.match(/^[-*]\s+.+$/gm);
2320
+ if (!bullets) return [];
2321
+ return bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()).filter((b) => b.length > 0 && !b.startsWith("<!--"));
2322
+ }
2323
+ function parseBehavior(bullet, kind) {
2324
+ const explicit = bullet.match(
2325
+ /^`?([a-z][a-z0-9_]*)`?\s+[—\u2014-]\s+(.+)$/i
2326
+ );
2327
+ if (explicit && isSnakeCase(explicit[1])) {
2328
+ return {
2329
+ name: explicit[1].toLowerCase(),
2330
+ prose: explicit[2].trim(),
2331
+ kind
2332
+ };
2333
+ }
2334
+ return { name: snakeCaseName(bullet), prose: bullet, kind };
2335
+ }
2336
+ function isSnakeCase(s) {
2337
+ return /^[a-z][a-z0-9_]*$/.test(s);
2338
+ }
2339
+ function snakeCaseName(s) {
2340
+ const base = s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2341
+ if (base.length <= 60) return base;
2342
+ const truncated = base.slice(0, 60);
2343
+ const lastUnderscore = truncated.lastIndexOf("_");
2344
+ return lastUnderscore > 20 ? truncated.slice(0, lastUnderscore) : truncated;
2345
+ }
2346
+ function matchDeclaredPattern(candidateName, candidateDescription, vocabulary) {
2347
+ const candidateText = `${candidateName.replace(/_/g, " ")} ${candidateDescription}`;
2348
+ const candidateWords = contentWords(candidateText);
2349
+ if (candidateWords.size === 0) return null;
2350
+ let best = null;
2351
+ for (const pattern of [...vocabulary.aligned, ...vocabulary.drift]) {
2352
+ const proseWords = contentWords(pattern.prose);
2353
+ if (proseWords.size === 0) continue;
2354
+ let shared = 0;
2355
+ for (const w of proseWords) {
2356
+ if (candidateWords.has(w)) shared++;
2357
+ }
2358
+ const coverage = shared / proseWords.size;
2359
+ if (shared >= 2 && coverage >= 0.3) {
2360
+ if (!best || coverage > best.score) {
2361
+ best = { pattern, score: coverage };
2362
+ }
2363
+ }
2364
+ }
2365
+ return best?.pattern ?? null;
2366
+ }
2367
+ var STOPWORDS = /* @__PURE__ */ new Set([
2368
+ "about",
2369
+ "after",
2370
+ "against",
2371
+ "among",
2372
+ "around",
2373
+ "because",
2374
+ "been",
2375
+ "before",
2376
+ "being",
2377
+ "between",
2378
+ "both",
2379
+ "could",
2380
+ "does",
2381
+ "doing",
2382
+ "during",
2383
+ "each",
2384
+ "from",
2385
+ "further",
2386
+ "have",
2387
+ "having",
2388
+ "into",
2389
+ "itself",
2390
+ "most",
2391
+ "nor",
2392
+ "only",
2393
+ "other",
2394
+ "over",
2395
+ "same",
2396
+ "should",
2397
+ "some",
2398
+ "such",
2399
+ "than",
2400
+ "that",
2401
+ "their",
2402
+ "them",
2403
+ "then",
2404
+ "there",
2405
+ "these",
2406
+ "they",
2407
+ "this",
2408
+ "those",
2409
+ "through",
2410
+ "under",
2411
+ "until",
2412
+ "very",
2413
+ "were",
2414
+ "what",
2415
+ "when",
2416
+ "where",
2417
+ "which",
2418
+ "while",
2419
+ "will",
2420
+ "with",
2421
+ "without",
2422
+ "would",
2423
+ "your",
2424
+ "yours"
2425
+ ]);
2426
+ function contentWords(text) {
2427
+ const words = text.toLowerCase().match(/[a-z][a-z0-9_]+/g) ?? [];
2428
+ return new Set(words.filter((w) => w.length > 3 && !STOPWORDS.has(w)));
2429
+ }
2430
+
2043
2431
  // src/radiant/core/patterns.ts
2044
2432
  async function interpretPatterns(input) {
2045
2433
  const prompt = buildInterpretationPrompt(input);
2046
2434
  const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
2047
- const parsed = parseInterpretation(raw, input.canonicalPatterns ?? []);
2435
+ const canonicalNames = [
2436
+ ...input.canonicalPatterns ?? [],
2437
+ ...input.declaredVocabulary?.allNames ?? []
2438
+ ];
2439
+ const parsed = parseInterpretation(raw, canonicalNames, input.declaredVocabulary);
2048
2440
  return {
2049
2441
  patterns: parsed.patterns,
2050
2442
  meaning: parsed.meaning,
@@ -2055,8 +2447,10 @@ async function interpretPatterns(input) {
2055
2447
  function buildInterpretationPrompt(input) {
2056
2448
  const signalSummary = formatSignalSummary(input.signals);
2057
2449
  const eventSample = formatEventSample(input.events, 30);
2058
- const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
2059
- ${input.canonicalPatterns.map((p) => `- ${p}`).join("\n")}` : "No patterns have been named yet. Everything you observe is new.";
2450
+ const canonicalList = formatDeclaredVocabulary(
2451
+ input.declaredVocabulary,
2452
+ input.canonicalPatterns ?? []
2453
+ );
2060
2454
  const compressedWorld = compressWorldmodel(input.worldmodelContent);
2061
2455
  const cl = compressLens(input.lens);
2062
2456
  const frame = input.lens.primary_frame;
@@ -2167,6 +2561,44 @@ Only recommend a move when the evidence actually calls for one.
2167
2561
  Do NOT use these phrases anywhere in your output:
2168
2562
  ${forbiddenList}`;
2169
2563
  }
2564
+ function formatDeclaredVocabulary(vocabulary, extraNames) {
2565
+ const aligned = vocabulary?.aligned ?? [];
2566
+ const drift = vocabulary?.drift ?? [];
2567
+ if (aligned.length === 0 && drift.length === 0 && extraNames.length === 0) {
2568
+ return 'No patterns have been named yet. Everything you observe is new \u2014 mark it type: "candidate".';
2569
+ }
2570
+ const parts = [];
2571
+ parts.push("## Declared vocabulary (use these names when you see matching evidence)");
2572
+ parts.push("");
2573
+ parts.push(
2574
+ 'The worldmodel declares the patterns below. If your observation matches one of these, use the EXACT snake_case name shown and mark type: "canonical" \u2014 do not invent a new name for something that already has one.'
2575
+ );
2576
+ parts.push("");
2577
+ if (aligned.length > 0) {
2578
+ parts.push("### Aligned behaviors (positive patterns)");
2579
+ for (const p of aligned) {
2580
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2581
+ }
2582
+ parts.push("");
2583
+ }
2584
+ if (drift.length > 0) {
2585
+ parts.push("### Drift behaviors (negative patterns)");
2586
+ for (const p of drift) {
2587
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2588
+ }
2589
+ parts.push("");
2590
+ }
2591
+ if (extraNames.length > 0) {
2592
+ parts.push(
2593
+ `Additional canonical names (from prior runs or caller): ${extraNames.join(", ")}`
2594
+ );
2595
+ parts.push("");
2596
+ }
2597
+ parts.push(
2598
+ 'If you observe something genuinely new that matches NO declared pattern, mark it type: "candidate" with a freshly-invented snake_case name.'
2599
+ );
2600
+ return parts.join("\n");
2601
+ }
2170
2602
  function formatSignalSummary(signals) {
2171
2603
  const lines = [];
2172
2604
  const domains = ["life", "cyber", "joint"];
@@ -2192,7 +2624,7 @@ function formatEventSample(events, maxEvents) {
2192
2624
  "${content}"`;
2193
2625
  }).join("\n");
2194
2626
  }
2195
- function parseInterpretation(raw, canonicalNames) {
2627
+ function parseInterpretation(raw, canonicalNames, vocabulary) {
2196
2628
  let meaning = "";
2197
2629
  let move = "";
2198
2630
  let patternsArray = [];
@@ -2222,14 +2654,23 @@ function parseInterpretation(raw, canonicalNames) {
2222
2654
  const patterns = [];
2223
2655
  for (const item of patternsArray) {
2224
2656
  if (!isPatternLike(item)) continue;
2225
- const nameStr = String(item.name ?? "unnamed");
2657
+ const rawName = String(item.name ?? "unnamed");
2658
+ const description = String(item.description ?? "");
2226
2659
  const ev = item.evidence;
2227
- const isCanonical = item.type === "canonical" || canonicalSet.has(nameStr.toLowerCase());
2660
+ let name = rawName;
2661
+ let isCanonical = item.type === "canonical" || canonicalSet.has(rawName.toLowerCase());
2662
+ if (!isCanonical && vocabulary) {
2663
+ const matched = matchDeclaredPattern(rawName, description, vocabulary);
2664
+ if (matched) {
2665
+ name = matched.name;
2666
+ isCanonical = true;
2667
+ }
2668
+ }
2228
2669
  patterns.push({
2229
- name: nameStr,
2670
+ name,
2230
2671
  type: isCanonical ? "canonical" : "candidate",
2231
- declaredAs: isCanonical ? nameStr : void 0,
2232
- description: String(item.description ?? ""),
2672
+ declaredAs: isCanonical ? name : void 0,
2673
+ description,
2233
2674
  evidence: {
2234
2675
  signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
2235
2676
  events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
@@ -4408,10 +4849,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4408
4849
  } catch {
4409
4850
  }
4410
4851
  }
4852
+ const linearKey = process.env.LINEAR_API_KEY;
4853
+ if (linearKey) {
4854
+ try {
4855
+ const linear = await fetchLinearActivity(linearKey, { windowDays });
4856
+ events.push(...linear.events);
4857
+ adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
4858
+ activeAdapters.push("linear");
4859
+ } catch {
4860
+ }
4861
+ }
4411
4862
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
4863
+ if (input.personalUser) {
4864
+ events = filterEventsByUser(events, input.personalUser);
4865
+ }
4412
4866
  const classified = classifyEvents(events);
4413
4867
  const signals = extractSignals(classified);
4414
4868
  const scores = computeScores(signals, input.worldmodelContent !== "");
4869
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
4415
4870
  const { patterns, meaning, move } = await interpretPatterns({
4416
4871
  signals,
4417
4872
  events: classified,
@@ -4419,6 +4874,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4419
4874
  lens,
4420
4875
  ai: input.ai,
4421
4876
  canonicalPatterns: input.canonicalPatterns,
4877
+ declaredVocabulary,
4422
4878
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
4423
4879
  });
4424
4880
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -4473,6 +4929,10 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4473
4929
  worldStack
4474
4930
  };
4475
4931
  }
4932
+ function filterEventsByUser(events, username) {
4933
+ const target = username.toLowerCase();
4934
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
4935
+ }
4476
4936
  function computeScores(signals, worldmodelLoaded) {
4477
4937
  const gate = DEFAULT_EVIDENCE_GATE;
4478
4938
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -4546,15 +5006,19 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
4546
5006
  detectOrgExtendsSpec,
4547
5007
  discoverWorlds,
4548
5008
  emergent,
5009
+ extractDeclaredVocabulary,
4549
5010
  extractSignals,
4550
5011
  fetchDiscordActivity,
4551
5012
  fetchGitHubActivity,
4552
5013
  fetchGitHubOrgActivity,
5014
+ fetchLinearActivity,
4553
5015
  fetchNotionActivity,
4554
5016
  fetchSlackActivity,
5017
+ filterEventsByUser,
4555
5018
  formatActiveWorlds,
4556
5019
  formatDiscordSignalsForPrompt,
4557
5020
  formatExocortexForPrompt,
5021
+ formatLinearSignalsForPrompt,
4558
5022
  formatNotionSignalsForPrompt,
4559
5023
  formatPriorReadsForPrompt,
4560
5024
  formatScope,
@@ -4570,6 +5034,7 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
4570
5034
  listLenses,
4571
5035
  loadExtendsConfig,
4572
5036
  loadPriorReads,
5037
+ matchDeclaredPattern,
4573
5038
  parseExtendsSpec,
4574
5039
  parseRemoteUrl,
4575
5040
  parseRepoScope,