@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.
package/README.md CHANGED
@@ -77,7 +77,7 @@ DEPTH — what Radiant can see now vs what unlocks with more reads
77
77
 
78
78
  ### Memory + evolution
79
79
 
80
- Radiant writes each read to the ExoCortex as a dated Memory Palace file. Next run reads prior history, detects pattern persistence, and proposes worldmodel evolution — what to ADD (recurring candidate patterns) and what to REMOVE (invariants that haven't fired). A lean worldmodel with 5 sharp invariants is stronger than a bloated one with 20.
80
+ Radiant writes each read to the ExoCortex as a dated Mind Palace file. Next run reads prior history, detects pattern persistence, and proposes worldmodel evolution — what to ADD (recurring candidate patterns) and what to REMOVE (invariants that haven't fired). A lean worldmodel with 5 sharp invariants is stronger than a bloated one with 20.
81
81
 
82
82
  ### MCP server
83
83
 
@@ -859,6 +859,257 @@ async function fetchNotionAPI(url, headers, init) {
859
859
  return await res.json();
860
860
  }
861
861
 
862
+ // src/radiant/adapters/linear.ts
863
+ async function fetchLinearActivity(apiKey, options = {}) {
864
+ const windowDays = options.windowDays ?? 14;
865
+ const maxIssues = options.maxIssues ?? 200;
866
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
867
+ const sinceIso = since.toISOString();
868
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
869
+ const teamFilter = options.teamIds && options.teamIds.length > 0 ? `team: { id: { in: [${options.teamIds.map((t) => JSON.stringify(t)).join(", ")}] } }` : "";
870
+ const issuesQuery = `
871
+ query RadiantIssues($since: DateTimeOrDuration!, $first: Int!) {
872
+ issues(
873
+ filter: {
874
+ updatedAt: { gte: $since }
875
+ ${teamFilter}
876
+ }
877
+ first: $first
878
+ orderBy: updatedAt
879
+ ) {
880
+ nodes {
881
+ id
882
+ identifier
883
+ title
884
+ url
885
+ createdAt
886
+ updatedAt
887
+ completedAt
888
+ canceledAt
889
+ state { name type }
890
+ assignee { id name email }
891
+ creator { id name }
892
+ team { id name }
893
+ project { id name }
894
+ cycle { id number startsAt endsAt }
895
+ comments(first: 20) {
896
+ nodes { id body createdAt user { id name } }
897
+ }
898
+ }
899
+ }
900
+ }
901
+ `;
902
+ const cyclesQuery = `
903
+ query RadiantCycles($since: DateTimeOrDuration!, $now: DateTimeOrDuration!) {
904
+ cycles(
905
+ filter: { endsAt: { gte: $since, lte: $now } }
906
+ first: 20
907
+ ) {
908
+ nodes {
909
+ id
910
+ number
911
+ startsAt
912
+ endsAt
913
+ issueCountHistory
914
+ completedIssueCountHistory
915
+ team { id name }
916
+ }
917
+ }
918
+ }
919
+ `;
920
+ const [issuesResponse, cyclesResponse] = await Promise.all([
921
+ fetchLinearGraphQL(apiKey, issuesQuery, {
922
+ since: sinceIso,
923
+ first: maxIssues
924
+ }),
925
+ fetchLinearGraphQL(apiKey, cyclesQuery, {
926
+ since: sinceIso,
927
+ now: nowIso
928
+ })
929
+ ]);
930
+ const rawIssues = issuesResponse.issues?.nodes ?? [];
931
+ const rawCycles = cyclesResponse.cycles?.nodes ?? [];
932
+ const events = [];
933
+ const assignees = /* @__PURE__ */ new Set();
934
+ const projects = /* @__PURE__ */ new Map();
935
+ let issuesCreated = 0;
936
+ let issuesCompleted = 0;
937
+ let issuesOpen = 0;
938
+ let issuesStalled = 0;
939
+ let commentsTotal = 0;
940
+ const now = Date.now();
941
+ const stallThresholdMs = 14 * 24 * 60 * 60 * 1e3;
942
+ for (const issue of rawIssues) {
943
+ const created = new Date(issue.createdAt);
944
+ const updated = new Date(issue.updatedAt);
945
+ const completed = issue.completedAt ? new Date(issue.completedAt) : null;
946
+ const assigneeId = issue.assignee?.id ?? "unassigned";
947
+ if (assigneeId !== "unassigned") assignees.add(assigneeId);
948
+ if (issue.project) {
949
+ projects.set(issue.project.name, (projects.get(issue.project.name) ?? 0) + 1);
950
+ }
951
+ const actor = {
952
+ id: assigneeId,
953
+ kind: "human",
954
+ name: issue.assignee?.name ?? "unassigned"
955
+ };
956
+ if (created >= since) {
957
+ issuesCreated++;
958
+ events.push({
959
+ id: `linear-created-${issue.id}`,
960
+ timestamp: issue.createdAt,
961
+ actor: {
962
+ id: issue.creator?.id ?? "unknown",
963
+ kind: "human",
964
+ name: issue.creator?.name ?? "unknown"
965
+ },
966
+ kind: "issue_created",
967
+ content: `[${issue.identifier}] ${issue.title}`,
968
+ metadata: {
969
+ issueId: issue.id,
970
+ url: issue.url,
971
+ team: issue.team?.name,
972
+ project: issue.project?.name,
973
+ state: issue.state?.name
974
+ }
975
+ });
976
+ }
977
+ if (completed && completed >= since) {
978
+ issuesCompleted++;
979
+ events.push({
980
+ id: `linear-completed-${issue.id}`,
981
+ timestamp: issue.completedAt,
982
+ actor,
983
+ kind: "issue_completed",
984
+ content: `[${issue.identifier}] ${issue.title}`,
985
+ metadata: {
986
+ issueId: issue.id,
987
+ url: issue.url,
988
+ team: issue.team?.name,
989
+ project: issue.project?.name,
990
+ cycleDays: issue.cycle?.startsAt && issue.completedAt ? Math.round(
991
+ (new Date(issue.completedAt).getTime() - new Date(issue.cycle.startsAt).getTime()) / (24 * 60 * 60 * 1e3)
992
+ ) : null
993
+ }
994
+ });
995
+ }
996
+ if (!completed && !issue.canceledAt) {
997
+ issuesOpen++;
998
+ const isInProgress = issue.state?.type === "started";
999
+ const idleMs = now - updated.getTime();
1000
+ if (isInProgress && idleMs > stallThresholdMs) issuesStalled++;
1001
+ }
1002
+ for (const comment of issue.comments?.nodes ?? []) {
1003
+ const commentedAt = new Date(comment.createdAt);
1004
+ if (commentedAt < since) continue;
1005
+ commentsTotal++;
1006
+ events.push({
1007
+ id: `linear-comment-${comment.id}`,
1008
+ timestamp: comment.createdAt,
1009
+ actor: {
1010
+ id: comment.user?.id ?? "unknown",
1011
+ kind: "human",
1012
+ name: comment.user?.name ?? "unknown"
1013
+ },
1014
+ kind: "issue_comment",
1015
+ content: comment.body.slice(0, 280),
1016
+ metadata: {
1017
+ issueId: issue.id,
1018
+ issueIdentifier: issue.identifier,
1019
+ url: issue.url
1020
+ }
1021
+ });
1022
+ }
1023
+ }
1024
+ let cycleCompletionRate = null;
1025
+ if (rawCycles.length > 0) {
1026
+ const rates = [];
1027
+ for (const cycle of rawCycles) {
1028
+ const committed = cycle.issueCountHistory?.at(0) ?? 0;
1029
+ const completed = cycle.completedIssueCountHistory?.at(-1) ?? 0;
1030
+ if (committed > 0) rates.push(completed / committed);
1031
+ }
1032
+ if (rates.length > 0) {
1033
+ cycleCompletionRate = Math.round(rates.reduce((a, b) => a + b, 0) / rates.length * 100) / 100;
1034
+ }
1035
+ }
1036
+ const topProjects = [...projects.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
1037
+ const signals = {
1038
+ issuesCreated,
1039
+ issuesCompleted,
1040
+ issuesOpen,
1041
+ issuesStalled,
1042
+ cycleCompletionRate,
1043
+ uniqueAssignees: assignees.size,
1044
+ commentsTotal,
1045
+ topProjects
1046
+ };
1047
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
1048
+ return { events, signals };
1049
+ }
1050
+ function formatLinearSignalsForPrompt(signals) {
1051
+ if (signals.issuesCreated === 0 && signals.issuesCompleted === 0 && signals.issuesOpen === 0) {
1052
+ return "";
1053
+ }
1054
+ const lines = [
1055
+ "## Linear Activity (planned work vs. shipped outcome)",
1056
+ "",
1057
+ `${signals.issuesCreated} issues created, ${signals.issuesCompleted} completed in window.`,
1058
+ `${signals.issuesOpen} issues still open.`
1059
+ ];
1060
+ if (signals.issuesStalled > 0) {
1061
+ lines.push(
1062
+ `${signals.issuesStalled} in-progress issues haven't moved in 14+ days (stalled).`
1063
+ );
1064
+ }
1065
+ if (signals.cycleCompletionRate !== null) {
1066
+ const pct = Math.round(signals.cycleCompletionRate * 100);
1067
+ lines.push(`Cycles ended in window completed ${pct}% of what was committed.`);
1068
+ }
1069
+ if (signals.uniqueAssignees > 0) {
1070
+ lines.push(`${signals.uniqueAssignees} unique assignees active.`);
1071
+ }
1072
+ if (signals.commentsTotal > 0) {
1073
+ lines.push(`${signals.commentsTotal} comments across issues in window.`);
1074
+ }
1075
+ if (signals.topProjects.length > 0) {
1076
+ lines.push(`Most active projects: ${signals.topProjects.join(", ")}.`);
1077
+ }
1078
+ lines.push("");
1079
+ lines.push("Linear is where the team states what it will build.");
1080
+ lines.push("GitHub is where the team reveals what actually got built.");
1081
+ lines.push("Low completion rate + high creation rate = planning faster than shipping.");
1082
+ lines.push("High stalled count = commitments made but not honored.");
1083
+ lines.push("Compare Linear signals against GitHub to find the stated-vs-shipped gap.");
1084
+ return lines.join("\n");
1085
+ }
1086
+ async function fetchLinearGraphQL(apiKey, query, variables) {
1087
+ const res = await fetch("https://api.linear.app/graphql", {
1088
+ method: "POST",
1089
+ headers: {
1090
+ // Linear accepts the raw API key in Authorization with no "Bearer" prefix.
1091
+ Authorization: apiKey,
1092
+ "Content-Type": "application/json"
1093
+ },
1094
+ body: JSON.stringify({ query, variables })
1095
+ });
1096
+ if (!res.ok) {
1097
+ throw new Error(
1098
+ `Linear API error ${res.status}: ${(await res.text()).slice(0, 300)}`
1099
+ );
1100
+ }
1101
+ const json = await res.json();
1102
+ if (json.errors && json.errors.length > 0) {
1103
+ throw new Error(
1104
+ `Linear GraphQL errors: ${json.errors.map((e) => e.message).join("; ")}`
1105
+ );
1106
+ }
1107
+ if (!json.data) {
1108
+ throw new Error("Linear API returned no data");
1109
+ }
1110
+ return json.data;
1111
+ }
1112
+
862
1113
  // src/radiant/core/git-remote.ts
863
1114
  import { existsSync, readFileSync, statSync } from "fs";
864
1115
  import { join, resolve } from "path";
@@ -1836,6 +2087,138 @@ var DEFAULT_SIGNAL_EXTRACTORS = Object.freeze([
1836
2087
  DECISION_MOMENTUM_EXTRACTOR
1837
2088
  ]);
1838
2089
 
2090
+ // src/radiant/core/vocabulary.ts
2091
+ function extractDeclaredVocabulary(worldmodelContent) {
2092
+ const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
2093
+ (b) => parseBehavior(b, "aligned")
2094
+ );
2095
+ const drift = extractSection(worldmodelContent, "Drift Behaviors").map(
2096
+ (b) => parseBehavior(b, "drift")
2097
+ );
2098
+ const allNames = [...aligned, ...drift].map((p) => p.name);
2099
+ return { aligned, drift, allNames };
2100
+ }
2101
+ function extractSection(content, header) {
2102
+ const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2103
+ const pattern = new RegExp(
2104
+ `##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
2105
+ "i"
2106
+ );
2107
+ const match = content.match(pattern);
2108
+ if (!match) return [];
2109
+ const body = match[1];
2110
+ const bullets = body.match(/^[-*]\s+.+$/gm);
2111
+ if (!bullets) return [];
2112
+ return bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()).filter((b) => b.length > 0 && !b.startsWith("<!--"));
2113
+ }
2114
+ function parseBehavior(bullet, kind) {
2115
+ const explicit = bullet.match(
2116
+ /^`?([a-z][a-z0-9_]*)`?\s+[—\u2014-]\s+(.+)$/i
2117
+ );
2118
+ if (explicit && isSnakeCase(explicit[1])) {
2119
+ return {
2120
+ name: explicit[1].toLowerCase(),
2121
+ prose: explicit[2].trim(),
2122
+ kind
2123
+ };
2124
+ }
2125
+ return { name: snakeCaseName(bullet), prose: bullet, kind };
2126
+ }
2127
+ function isSnakeCase(s) {
2128
+ return /^[a-z][a-z0-9_]*$/.test(s);
2129
+ }
2130
+ function snakeCaseName(s) {
2131
+ const base = s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2132
+ if (base.length <= 60) return base;
2133
+ const truncated = base.slice(0, 60);
2134
+ const lastUnderscore = truncated.lastIndexOf("_");
2135
+ return lastUnderscore > 20 ? truncated.slice(0, lastUnderscore) : truncated;
2136
+ }
2137
+ function matchDeclaredPattern(candidateName, candidateDescription, vocabulary) {
2138
+ const candidateText = `${candidateName.replace(/_/g, " ")} ${candidateDescription}`;
2139
+ const candidateWords = contentWords(candidateText);
2140
+ if (candidateWords.size === 0) return null;
2141
+ let best = null;
2142
+ for (const pattern of [...vocabulary.aligned, ...vocabulary.drift]) {
2143
+ const proseWords = contentWords(pattern.prose);
2144
+ if (proseWords.size === 0) continue;
2145
+ let shared = 0;
2146
+ for (const w of proseWords) {
2147
+ if (candidateWords.has(w)) shared++;
2148
+ }
2149
+ const coverage = shared / proseWords.size;
2150
+ if (shared >= 2 && coverage >= 0.3) {
2151
+ if (!best || coverage > best.score) {
2152
+ best = { pattern, score: coverage };
2153
+ }
2154
+ }
2155
+ }
2156
+ return best?.pattern ?? null;
2157
+ }
2158
+ var STOPWORDS = /* @__PURE__ */ new Set([
2159
+ "about",
2160
+ "after",
2161
+ "against",
2162
+ "among",
2163
+ "around",
2164
+ "because",
2165
+ "been",
2166
+ "before",
2167
+ "being",
2168
+ "between",
2169
+ "both",
2170
+ "could",
2171
+ "does",
2172
+ "doing",
2173
+ "during",
2174
+ "each",
2175
+ "from",
2176
+ "further",
2177
+ "have",
2178
+ "having",
2179
+ "into",
2180
+ "itself",
2181
+ "most",
2182
+ "nor",
2183
+ "only",
2184
+ "other",
2185
+ "over",
2186
+ "same",
2187
+ "should",
2188
+ "some",
2189
+ "such",
2190
+ "than",
2191
+ "that",
2192
+ "their",
2193
+ "them",
2194
+ "then",
2195
+ "there",
2196
+ "these",
2197
+ "they",
2198
+ "this",
2199
+ "those",
2200
+ "through",
2201
+ "under",
2202
+ "until",
2203
+ "very",
2204
+ "were",
2205
+ "what",
2206
+ "when",
2207
+ "where",
2208
+ "which",
2209
+ "while",
2210
+ "will",
2211
+ "with",
2212
+ "without",
2213
+ "would",
2214
+ "your",
2215
+ "yours"
2216
+ ]);
2217
+ function contentWords(text) {
2218
+ const words = text.toLowerCase().match(/[a-z][a-z0-9_]+/g) ?? [];
2219
+ return new Set(words.filter((w) => w.length > 3 && !STOPWORDS.has(w)));
2220
+ }
2221
+
1839
2222
  // src/radiant/types.ts
1840
2223
  var DEFAULT_EVIDENCE_GATE = { k: 3, c: 0.5 };
1841
2224
  function isScored(s) {
@@ -1878,7 +2261,11 @@ function scoreComposite(a_L, a_C, a_N) {
1878
2261
  async function interpretPatterns(input) {
1879
2262
  const prompt = buildInterpretationPrompt(input);
1880
2263
  const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
1881
- const parsed = parseInterpretation(raw, input.canonicalPatterns ?? []);
2264
+ const canonicalNames = [
2265
+ ...input.canonicalPatterns ?? [],
2266
+ ...input.declaredVocabulary?.allNames ?? []
2267
+ ];
2268
+ const parsed = parseInterpretation(raw, canonicalNames, input.declaredVocabulary);
1882
2269
  return {
1883
2270
  patterns: parsed.patterns,
1884
2271
  meaning: parsed.meaning,
@@ -1889,8 +2276,10 @@ async function interpretPatterns(input) {
1889
2276
  function buildInterpretationPrompt(input) {
1890
2277
  const signalSummary = formatSignalSummary(input.signals);
1891
2278
  const eventSample = formatEventSample(input.events, 30);
1892
- const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
1893
- ${input.canonicalPatterns.map((p) => `- ${p}`).join("\n")}` : "No patterns have been named yet. Everything you observe is new.";
2279
+ const canonicalList = formatDeclaredVocabulary(
2280
+ input.declaredVocabulary,
2281
+ input.canonicalPatterns ?? []
2282
+ );
1894
2283
  const compressedWorld = compressWorldmodel(input.worldmodelContent);
1895
2284
  const cl = compressLens(input.lens);
1896
2285
  const frame = input.lens.primary_frame;
@@ -2001,6 +2390,44 @@ Only recommend a move when the evidence actually calls for one.
2001
2390
  Do NOT use these phrases anywhere in your output:
2002
2391
  ${forbiddenList}`;
2003
2392
  }
2393
+ function formatDeclaredVocabulary(vocabulary, extraNames) {
2394
+ const aligned = vocabulary?.aligned ?? [];
2395
+ const drift = vocabulary?.drift ?? [];
2396
+ if (aligned.length === 0 && drift.length === 0 && extraNames.length === 0) {
2397
+ return 'No patterns have been named yet. Everything you observe is new \u2014 mark it type: "candidate".';
2398
+ }
2399
+ const parts = [];
2400
+ parts.push("## Declared vocabulary (use these names when you see matching evidence)");
2401
+ parts.push("");
2402
+ parts.push(
2403
+ '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.'
2404
+ );
2405
+ parts.push("");
2406
+ if (aligned.length > 0) {
2407
+ parts.push("### Aligned behaviors (positive patterns)");
2408
+ for (const p of aligned) {
2409
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2410
+ }
2411
+ parts.push("");
2412
+ }
2413
+ if (drift.length > 0) {
2414
+ parts.push("### Drift behaviors (negative patterns)");
2415
+ for (const p of drift) {
2416
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2417
+ }
2418
+ parts.push("");
2419
+ }
2420
+ if (extraNames.length > 0) {
2421
+ parts.push(
2422
+ `Additional canonical names (from prior runs or caller): ${extraNames.join(", ")}`
2423
+ );
2424
+ parts.push("");
2425
+ }
2426
+ parts.push(
2427
+ 'If you observe something genuinely new that matches NO declared pattern, mark it type: "candidate" with a freshly-invented snake_case name.'
2428
+ );
2429
+ return parts.join("\n");
2430
+ }
2004
2431
  function formatSignalSummary(signals) {
2005
2432
  const lines = [];
2006
2433
  const domains = ["life", "cyber", "joint"];
@@ -2026,7 +2453,7 @@ function formatEventSample(events, maxEvents) {
2026
2453
  "${content}"`;
2027
2454
  }).join("\n");
2028
2455
  }
2029
- function parseInterpretation(raw, canonicalNames) {
2456
+ function parseInterpretation(raw, canonicalNames, vocabulary) {
2030
2457
  let meaning = "";
2031
2458
  let move = "";
2032
2459
  let patternsArray = [];
@@ -2056,14 +2483,23 @@ function parseInterpretation(raw, canonicalNames) {
2056
2483
  const patterns = [];
2057
2484
  for (const item of patternsArray) {
2058
2485
  if (!isPatternLike(item)) continue;
2059
- const nameStr = String(item.name ?? "unnamed");
2486
+ const rawName = String(item.name ?? "unnamed");
2487
+ const description = String(item.description ?? "");
2060
2488
  const ev = item.evidence;
2061
- const isCanonical = item.type === "canonical" || canonicalSet.has(nameStr.toLowerCase());
2489
+ let name = rawName;
2490
+ let isCanonical = item.type === "canonical" || canonicalSet.has(rawName.toLowerCase());
2491
+ if (!isCanonical && vocabulary) {
2492
+ const matched = matchDeclaredPattern(rawName, description, vocabulary);
2493
+ if (matched) {
2494
+ name = matched.name;
2495
+ isCanonical = true;
2496
+ }
2497
+ }
2062
2498
  patterns.push({
2063
- name: nameStr,
2499
+ name,
2064
2500
  type: isCanonical ? "canonical" : "candidate",
2065
- declaredAs: isCanonical ? nameStr : void 0,
2066
- description: String(item.description ?? ""),
2501
+ declaredAs: isCanonical ? name : void 0,
2502
+ description,
2067
2503
  evidence: {
2068
2504
  signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
2069
2505
  events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
@@ -2386,10 +2822,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2386
2822
  } catch {
2387
2823
  }
2388
2824
  }
2825
+ const linearKey = process.env.LINEAR_API_KEY;
2826
+ if (linearKey) {
2827
+ try {
2828
+ const linear = await fetchLinearActivity(linearKey, { windowDays });
2829
+ events.push(...linear.events);
2830
+ adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
2831
+ activeAdapters.push("linear");
2832
+ } catch {
2833
+ }
2834
+ }
2389
2835
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
2836
+ if (input.personalUser) {
2837
+ events = filterEventsByUser(events, input.personalUser);
2838
+ }
2390
2839
  const classified = classifyEvents(events);
2391
2840
  const signals = extractSignals(classified);
2392
2841
  const scores = computeScores(signals, input.worldmodelContent !== "");
2842
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
2393
2843
  const { patterns, meaning, move } = await interpretPatterns({
2394
2844
  signals,
2395
2845
  events: classified,
@@ -2397,6 +2847,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2397
2847
  lens,
2398
2848
  ai: input.ai,
2399
2849
  canonicalPatterns: input.canonicalPatterns,
2850
+ declaredVocabulary,
2400
2851
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
2401
2852
  });
2402
2853
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -2451,6 +2902,10 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2451
2902
  worldStack
2452
2903
  };
2453
2904
  }
2905
+ function filterEventsByUser(events, username) {
2906
+ const target = username.toLowerCase();
2907
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
2908
+ }
2454
2909
  function computeScores(signals, worldmodelLoaded) {
2455
2910
  const gate = DEFAULT_EVIDENCE_GATE;
2456
2911
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -2560,6 +3015,8 @@ export {
2560
3015
  formatSlackSignalsForPrompt,
2561
3016
  fetchNotionActivity,
2562
3017
  formatNotionSignalsForPrompt,
3018
+ fetchLinearActivity,
3019
+ formatLinearSignalsForPrompt,
2563
3020
  readOriginRemote,
2564
3021
  parseRemoteUrl,
2565
3022
  getRepoOrigin,
@@ -2586,6 +3043,8 @@ export {
2586
3043
  classifyEvents,
2587
3044
  extractSignals,
2588
3045
  DEFAULT_SIGNAL_EXTRACTORS,
3046
+ extractDeclaredVocabulary,
3047
+ matchDeclaredPattern,
2589
3048
  DEFAULT_EVIDENCE_GATE,
2590
3049
  isScored,
2591
3050
  isSentinel,
@@ -2598,6 +3057,7 @@ export {
2598
3057
  interpretPatterns,
2599
3058
  render,
2600
3059
  emergent,
3060
+ filterEventsByUser,
2601
3061
  createAnthropicAI,
2602
3062
  createMockAI
2603
3063
  };