@neuroverseos/governance 0.9.0 → 0.11.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.
Files changed (99) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/autoresearch.cjs +19 -1
  3. package/dist/adapters/autoresearch.d.cts +1 -1
  4. package/dist/adapters/autoresearch.d.ts +1 -1
  5. package/dist/adapters/autoresearch.js +2 -2
  6. package/dist/adapters/deep-agents.cjs +19 -1
  7. package/dist/adapters/deep-agents.d.cts +2 -2
  8. package/dist/adapters/deep-agents.d.ts +2 -2
  9. package/dist/adapters/deep-agents.js +2 -2
  10. package/dist/adapters/express.cjs +19 -1
  11. package/dist/adapters/express.d.cts +1 -1
  12. package/dist/adapters/express.d.ts +1 -1
  13. package/dist/adapters/express.js +2 -2
  14. package/dist/adapters/github.cjs +19 -1
  15. package/dist/adapters/github.d.cts +2 -2
  16. package/dist/adapters/github.d.ts +2 -2
  17. package/dist/adapters/github.js +2 -2
  18. package/dist/adapters/index.cjs +19 -1
  19. package/dist/adapters/index.d.cts +2 -2
  20. package/dist/adapters/index.d.ts +2 -2
  21. package/dist/adapters/index.js +8 -8
  22. package/dist/adapters/langchain.cjs +19 -1
  23. package/dist/adapters/langchain.d.cts +2 -2
  24. package/dist/adapters/langchain.d.ts +2 -2
  25. package/dist/adapters/langchain.js +2 -2
  26. package/dist/adapters/mentraos.cjs +19 -1
  27. package/dist/adapters/mentraos.d.cts +2 -2
  28. package/dist/adapters/mentraos.d.ts +2 -2
  29. package/dist/adapters/mentraos.js +2 -2
  30. package/dist/adapters/openai.cjs +19 -1
  31. package/dist/adapters/openai.d.cts +2 -2
  32. package/dist/adapters/openai.d.ts +2 -2
  33. package/dist/adapters/openai.js +2 -2
  34. package/dist/adapters/openclaw.cjs +19 -1
  35. package/dist/adapters/openclaw.d.cts +2 -2
  36. package/dist/adapters/openclaw.d.ts +2 -2
  37. package/dist/adapters/openclaw.js +2 -2
  38. package/dist/admin/index.cjs +19 -1
  39. package/dist/admin/index.js +1 -1
  40. package/dist/audit-CRJOB4CP.js +93 -0
  41. package/dist/audit-behavior-C62FdRAC.d.cts +100 -0
  42. package/dist/audit-behavior-DFy7LeYv.d.ts +100 -0
  43. package/dist/{behavioral-SPWPGYXL.js → behavioral-4TKMHZQZ.js} +2 -2
  44. package/dist/{chunk-OQU65525.js → chunk-24YW7BHC.js} +1 -1
  45. package/dist/{chunk-3ZWU7C43.js → chunk-2KTPIE57.js} +494 -14
  46. package/dist/{chunk-TJ5L2UTE.js → chunk-5K3LATTM.js} +1 -1
  47. package/dist/{chunk-HDNDL6D5.js → chunk-5LDBYOSJ.js} +1 -1
  48. package/dist/{chunk-FDPPZLSQ.js → chunk-5ZWKM7MO.js} +1 -1
  49. package/dist/{chunk-B3IIPTY3.js → chunk-6MB6TMAG.js} +1 -1
  50. package/dist/{chunk-IOVXB6QN.js → chunk-GXTAHCND.js} +1 -1
  51. package/dist/{chunk-FKQCPRKI.js → chunk-MAOIHKFO.js} +1 -1
  52. package/dist/{chunk-ZAF6JH23.js → chunk-MBOW6YXN.js} +19 -1
  53. package/dist/{chunk-A2UZTLRV.js → chunk-MLXKSX3L.js} +1 -1
  54. package/dist/{chunk-7FL3U7Z5.js → chunk-MWGEXHOD.js} +1 -1
  55. package/dist/{chunk-6CV4XG3J.js → chunk-QFDFAWZ6.js} +1 -1
  56. package/dist/{chunk-2VAWP6FI.js → chunk-RAS62JXV.js} +1 -1
  57. package/dist/{chunk-OTZU76DH.js → chunk-XAF3CYCW.js} +1 -1
  58. package/dist/{chunk-T6GMRZWC.js → chunk-XTYQCTDD.js} +1 -1
  59. package/dist/{chunk-TIXVEPS2.js → chunk-YN7OI5ZV.js} +1 -1
  60. package/dist/cli/neuroverse.cjs +999 -111
  61. package/dist/cli/neuroverse.js +16 -12
  62. package/dist/cli/plan.cjs +18 -0
  63. package/dist/cli/radiant.cjs +814 -17
  64. package/dist/cli/radiant.d.cts +44 -1
  65. package/dist/cli/radiant.d.ts +44 -1
  66. package/dist/cli/radiant.js +295 -7
  67. package/dist/cli/run.cjs +18 -0
  68. package/dist/cli/run.js +4 -4
  69. package/dist/{decision-flow-IJPNMVQK.js → decision-flow-5VI5YG6A.js} +2 -2
  70. package/dist/{demo-6W3YXLAX.js → demo-GYX6CYHC.js} +2 -2
  71. package/dist/engine/guard-engine.cjs +19 -1
  72. package/dist/engine/guard-engine.d.cts +21 -1
  73. package/dist/engine/guard-engine.d.ts +21 -1
  74. package/dist/engine/guard-engine.js +1 -1
  75. package/dist/{equity-penalties-CCO3GVHS.js → equity-penalties-NOM46NEO.js} +2 -2
  76. package/dist/{guard-IHJEKHL2.js → guard-PQ3SYV4Y.js} +3 -3
  77. package/dist/{guard-contract-ddiIPlOg.d.cts → guard-contract-Oznf-Kgq.d.cts} +32 -0
  78. package/dist/{guard-contract-q6HJAq3Q.d.ts → guard-contract-w_i_6gh-.d.ts} +32 -0
  79. package/dist/{impact-WIAM66IH.js → impact-LDJLTVRU.js} +3 -3
  80. package/dist/index.cjs +62 -1
  81. package/dist/index.d.cts +4 -3
  82. package/dist/index.d.ts +4 -3
  83. package/dist/index.js +49 -8
  84. package/dist/{mcp-server-CKYBHXWK.js → mcp-server-W3MWSKD7.js} +2 -2
  85. package/dist/{playground-3TTBN7XD.js → playground-SSZRNUAF.js} +1 -1
  86. package/dist/radiant/index.cjs +517 -14
  87. package/dist/radiant/index.d.cts +180 -10
  88. package/dist/radiant/index.d.ts +180 -10
  89. package/dist/radiant/index.js +12 -2
  90. package/dist/{redteam-W644UMWN.js → redteam-KCULS7EW.js} +1 -1
  91. package/dist/{server-JKUBUK5H.js → server-EGRGGSM2.js} +2 -2
  92. package/dist/{session-FMAROEIE.js → session-PZLTL22G.js} +2 -2
  93. package/dist/{shared-PpalGKxc.d.cts → shared-BC8mOpt0.d.cts} +1 -1
  94. package/dist/{shared-DAzdfWtU.d.ts → shared-CP63gNNW.d.ts} +1 -1
  95. package/dist/{test-XDB2DH3L.js → test-LIHGWHBA.js} +1 -1
  96. package/dist/{trace-2YDNAXMK.js → trace-DC3D7XPD.js} +2 -2
  97. package/examples/radiant-weekly-workflow.yml +4 -1
  98. package/package.json +1 -1
  99. /package/dist/{doctor-XEMLO6UA.js → doctor-SIWQGTAO.js} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-I4RTIMLX.js";
7
7
  import {
8
8
  evaluateGuard
9
- } from "./chunk-ZAF6JH23.js";
9
+ } from "./chunk-MBOW6YXN.js";
10
10
 
11
11
  // src/radiant/core/compress.ts
12
12
  function compressWorldmodel(content) {
@@ -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";
@@ -1579,6 +1830,7 @@ async function auditGovernance(events, worldPath) {
1579
1830
  return emptyAudit(events.length, "Could not load compiled worldmodel for governance audit.");
1580
1831
  }
1581
1832
  const verdicts = [];
1833
+ const crossings = [];
1582
1834
  for (const ce of events) {
1583
1835
  const intent = ce.event.content?.slice(0, 500) || ce.event.kind || "activity";
1584
1836
  const scope = ce.event.metadata?.scope || void 0;
@@ -1589,16 +1841,32 @@ async function auditGovernance(events, worldPath) {
1589
1841
  scope,
1590
1842
  actionCategory: mapKindToCategory(ce.event.kind)
1591
1843
  },
1592
- world
1844
+ world,
1845
+ { mode: "observe" }
1593
1846
  );
1847
+ const shadow = result.shadowStatus ?? "ALLOW";
1594
1848
  verdicts.push({
1595
1849
  eventId: ce.event.id,
1596
1850
  domain: ce.domain,
1597
- status: result.status,
1598
- reason: result.reason,
1851
+ status: shadow,
1852
+ reason: result.shadowReason,
1599
1853
  ruleId: result.ruleId,
1600
1854
  warning: result.warning
1601
1855
  });
1856
+ if (shadow !== "ALLOW") {
1857
+ crossings.push({
1858
+ eventId: ce.event.id,
1859
+ timestamp: ce.event.timestamp,
1860
+ kind: ce.event.kind,
1861
+ actorId: ce.event.actor.id,
1862
+ shadowStatus: shadow,
1863
+ shadowReason: result.shadowReason,
1864
+ ruleId: result.ruleId,
1865
+ excerpt: intent.length > 280 ? intent.slice(0, 279) + "\u2026" : intent,
1866
+ wouldHaveBlocked: true,
1867
+ verdict: result
1868
+ });
1869
+ }
1602
1870
  } catch {
1603
1871
  verdicts.push({
1604
1872
  eventId: ce.event.id,
@@ -1617,6 +1885,7 @@ async function auditGovernance(events, worldPath) {
1617
1885
  human,
1618
1886
  cyber,
1619
1887
  joint,
1888
+ crossings,
1620
1889
  summary
1621
1890
  };
1622
1891
  }
@@ -1672,6 +1941,7 @@ function emptyAudit(total, reason) {
1672
1941
  human: { allow: 0, modify: 0, block: 0, details: [] },
1673
1942
  cyber: { allow: 0, modify: 0, block: 0, details: [] },
1674
1943
  joint: { allow: 0, modify: 0, block: 0, details: [] },
1944
+ crossings: [],
1675
1945
  summary: reason
1676
1946
  };
1677
1947
  }
@@ -1836,6 +2106,138 @@ var DEFAULT_SIGNAL_EXTRACTORS = Object.freeze([
1836
2106
  DECISION_MOMENTUM_EXTRACTOR
1837
2107
  ]);
1838
2108
 
2109
+ // src/radiant/core/vocabulary.ts
2110
+ function extractDeclaredVocabulary(worldmodelContent) {
2111
+ const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
2112
+ (b) => parseBehavior(b, "aligned")
2113
+ );
2114
+ const drift = extractSection(worldmodelContent, "Drift Behaviors").map(
2115
+ (b) => parseBehavior(b, "drift")
2116
+ );
2117
+ const allNames = [...aligned, ...drift].map((p) => p.name);
2118
+ return { aligned, drift, allNames };
2119
+ }
2120
+ function extractSection(content, header) {
2121
+ const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2122
+ const pattern = new RegExp(
2123
+ `##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
2124
+ "i"
2125
+ );
2126
+ const match = content.match(pattern);
2127
+ if (!match) return [];
2128
+ const body = match[1];
2129
+ const bullets = body.match(/^[-*]\s+.+$/gm);
2130
+ if (!bullets) return [];
2131
+ return bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()).filter((b) => b.length > 0 && !b.startsWith("<!--"));
2132
+ }
2133
+ function parseBehavior(bullet, kind) {
2134
+ const explicit = bullet.match(
2135
+ /^`?([a-z][a-z0-9_]*)`?\s+[—\u2014-]\s+(.+)$/i
2136
+ );
2137
+ if (explicit && isSnakeCase(explicit[1])) {
2138
+ return {
2139
+ name: explicit[1].toLowerCase(),
2140
+ prose: explicit[2].trim(),
2141
+ kind
2142
+ };
2143
+ }
2144
+ return { name: snakeCaseName(bullet), prose: bullet, kind };
2145
+ }
2146
+ function isSnakeCase(s) {
2147
+ return /^[a-z][a-z0-9_]*$/.test(s);
2148
+ }
2149
+ function snakeCaseName(s) {
2150
+ const base = s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2151
+ if (base.length <= 60) return base;
2152
+ const truncated = base.slice(0, 60);
2153
+ const lastUnderscore = truncated.lastIndexOf("_");
2154
+ return lastUnderscore > 20 ? truncated.slice(0, lastUnderscore) : truncated;
2155
+ }
2156
+ function matchDeclaredPattern(candidateName, candidateDescription, vocabulary) {
2157
+ const candidateText = `${candidateName.replace(/_/g, " ")} ${candidateDescription}`;
2158
+ const candidateWords = contentWords(candidateText);
2159
+ if (candidateWords.size === 0) return null;
2160
+ let best = null;
2161
+ for (const pattern of [...vocabulary.aligned, ...vocabulary.drift]) {
2162
+ const proseWords = contentWords(pattern.prose);
2163
+ if (proseWords.size === 0) continue;
2164
+ let shared = 0;
2165
+ for (const w of proseWords) {
2166
+ if (candidateWords.has(w)) shared++;
2167
+ }
2168
+ const coverage = shared / proseWords.size;
2169
+ if (shared >= 2 && coverage >= 0.3) {
2170
+ if (!best || coverage > best.score) {
2171
+ best = { pattern, score: coverage };
2172
+ }
2173
+ }
2174
+ }
2175
+ return best?.pattern ?? null;
2176
+ }
2177
+ var STOPWORDS = /* @__PURE__ */ new Set([
2178
+ "about",
2179
+ "after",
2180
+ "against",
2181
+ "among",
2182
+ "around",
2183
+ "because",
2184
+ "been",
2185
+ "before",
2186
+ "being",
2187
+ "between",
2188
+ "both",
2189
+ "could",
2190
+ "does",
2191
+ "doing",
2192
+ "during",
2193
+ "each",
2194
+ "from",
2195
+ "further",
2196
+ "have",
2197
+ "having",
2198
+ "into",
2199
+ "itself",
2200
+ "most",
2201
+ "nor",
2202
+ "only",
2203
+ "other",
2204
+ "over",
2205
+ "same",
2206
+ "should",
2207
+ "some",
2208
+ "such",
2209
+ "than",
2210
+ "that",
2211
+ "their",
2212
+ "them",
2213
+ "then",
2214
+ "there",
2215
+ "these",
2216
+ "they",
2217
+ "this",
2218
+ "those",
2219
+ "through",
2220
+ "under",
2221
+ "until",
2222
+ "very",
2223
+ "were",
2224
+ "what",
2225
+ "when",
2226
+ "where",
2227
+ "which",
2228
+ "while",
2229
+ "will",
2230
+ "with",
2231
+ "without",
2232
+ "would",
2233
+ "your",
2234
+ "yours"
2235
+ ]);
2236
+ function contentWords(text) {
2237
+ const words = text.toLowerCase().match(/[a-z][a-z0-9_]+/g) ?? [];
2238
+ return new Set(words.filter((w) => w.length > 3 && !STOPWORDS.has(w)));
2239
+ }
2240
+
1839
2241
  // src/radiant/types.ts
1840
2242
  var DEFAULT_EVIDENCE_GATE = { k: 3, c: 0.5 };
1841
2243
  function isScored(s) {
@@ -1878,7 +2280,11 @@ function scoreComposite(a_L, a_C, a_N) {
1878
2280
  async function interpretPatterns(input) {
1879
2281
  const prompt = buildInterpretationPrompt(input);
1880
2282
  const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
1881
- const parsed = parseInterpretation(raw, input.canonicalPatterns ?? []);
2283
+ const canonicalNames = [
2284
+ ...input.canonicalPatterns ?? [],
2285
+ ...input.declaredVocabulary?.allNames ?? []
2286
+ ];
2287
+ const parsed = parseInterpretation(raw, canonicalNames, input.declaredVocabulary);
1882
2288
  return {
1883
2289
  patterns: parsed.patterns,
1884
2290
  meaning: parsed.meaning,
@@ -1889,8 +2295,10 @@ async function interpretPatterns(input) {
1889
2295
  function buildInterpretationPrompt(input) {
1890
2296
  const signalSummary = formatSignalSummary(input.signals);
1891
2297
  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.";
2298
+ const canonicalList = formatDeclaredVocabulary(
2299
+ input.declaredVocabulary,
2300
+ input.canonicalPatterns ?? []
2301
+ );
1894
2302
  const compressedWorld = compressWorldmodel(input.worldmodelContent);
1895
2303
  const cl = compressLens(input.lens);
1896
2304
  const frame = input.lens.primary_frame;
@@ -2001,6 +2409,44 @@ Only recommend a move when the evidence actually calls for one.
2001
2409
  Do NOT use these phrases anywhere in your output:
2002
2410
  ${forbiddenList}`;
2003
2411
  }
2412
+ function formatDeclaredVocabulary(vocabulary, extraNames) {
2413
+ const aligned = vocabulary?.aligned ?? [];
2414
+ const drift = vocabulary?.drift ?? [];
2415
+ if (aligned.length === 0 && drift.length === 0 && extraNames.length === 0) {
2416
+ return 'No patterns have been named yet. Everything you observe is new \u2014 mark it type: "candidate".';
2417
+ }
2418
+ const parts = [];
2419
+ parts.push("## Declared vocabulary (use these names when you see matching evidence)");
2420
+ parts.push("");
2421
+ parts.push(
2422
+ '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.'
2423
+ );
2424
+ parts.push("");
2425
+ if (aligned.length > 0) {
2426
+ parts.push("### Aligned behaviors (positive patterns)");
2427
+ for (const p of aligned) {
2428
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2429
+ }
2430
+ parts.push("");
2431
+ }
2432
+ if (drift.length > 0) {
2433
+ parts.push("### Drift behaviors (negative patterns)");
2434
+ for (const p of drift) {
2435
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
2436
+ }
2437
+ parts.push("");
2438
+ }
2439
+ if (extraNames.length > 0) {
2440
+ parts.push(
2441
+ `Additional canonical names (from prior runs or caller): ${extraNames.join(", ")}`
2442
+ );
2443
+ parts.push("");
2444
+ }
2445
+ parts.push(
2446
+ 'If you observe something genuinely new that matches NO declared pattern, mark it type: "candidate" with a freshly-invented snake_case name.'
2447
+ );
2448
+ return parts.join("\n");
2449
+ }
2004
2450
  function formatSignalSummary(signals) {
2005
2451
  const lines = [];
2006
2452
  const domains = ["life", "cyber", "joint"];
@@ -2026,7 +2472,7 @@ function formatEventSample(events, maxEvents) {
2026
2472
  "${content}"`;
2027
2473
  }).join("\n");
2028
2474
  }
2029
- function parseInterpretation(raw, canonicalNames) {
2475
+ function parseInterpretation(raw, canonicalNames, vocabulary) {
2030
2476
  let meaning = "";
2031
2477
  let move = "";
2032
2478
  let patternsArray = [];
@@ -2056,14 +2502,23 @@ function parseInterpretation(raw, canonicalNames) {
2056
2502
  const patterns = [];
2057
2503
  for (const item of patternsArray) {
2058
2504
  if (!isPatternLike(item)) continue;
2059
- const nameStr = String(item.name ?? "unnamed");
2505
+ const rawName = String(item.name ?? "unnamed");
2506
+ const description = String(item.description ?? "");
2060
2507
  const ev = item.evidence;
2061
- const isCanonical = item.type === "canonical" || canonicalSet.has(nameStr.toLowerCase());
2508
+ let name = rawName;
2509
+ let isCanonical = item.type === "canonical" || canonicalSet.has(rawName.toLowerCase());
2510
+ if (!isCanonical && vocabulary) {
2511
+ const matched = matchDeclaredPattern(rawName, description, vocabulary);
2512
+ if (matched) {
2513
+ name = matched.name;
2514
+ isCanonical = true;
2515
+ }
2516
+ }
2062
2517
  patterns.push({
2063
- name: nameStr,
2518
+ name,
2064
2519
  type: isCanonical ? "canonical" : "candidate",
2065
- declaredAs: isCanonical ? nameStr : void 0,
2066
- description: String(item.description ?? ""),
2520
+ declaredAs: isCanonical ? name : void 0,
2521
+ description,
2067
2522
  evidence: {
2068
2523
  signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
2069
2524
  events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
@@ -2386,10 +2841,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2386
2841
  } catch {
2387
2842
  }
2388
2843
  }
2844
+ const linearKey = process.env.LINEAR_API_KEY;
2845
+ if (linearKey) {
2846
+ try {
2847
+ const linear = await fetchLinearActivity(linearKey, { windowDays });
2848
+ events.push(...linear.events);
2849
+ adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
2850
+ activeAdapters.push("linear");
2851
+ } catch {
2852
+ }
2853
+ }
2389
2854
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
2855
+ if (input.personalUser) {
2856
+ events = filterEventsByUser(events, input.personalUser);
2857
+ }
2390
2858
  const classified = classifyEvents(events);
2391
2859
  const signals = extractSignals(classified);
2392
2860
  const scores = computeScores(signals, input.worldmodelContent !== "");
2861
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
2393
2862
  const { patterns, meaning, move } = await interpretPatterns({
2394
2863
  signals,
2395
2864
  events: classified,
@@ -2397,6 +2866,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2397
2866
  lens,
2398
2867
  ai: input.ai,
2399
2868
  canonicalPatterns: input.canonicalPatterns,
2869
+ declaredVocabulary,
2400
2870
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
2401
2871
  });
2402
2872
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -2448,9 +2918,14 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
2448
2918
  scores,
2449
2919
  eventCount: events.length,
2450
2920
  activeAdapters,
2451
- worldStack
2921
+ worldStack,
2922
+ governance
2452
2923
  };
2453
2924
  }
2925
+ function filterEventsByUser(events, username) {
2926
+ const target = username.toLowerCase();
2927
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
2928
+ }
2454
2929
  function computeScores(signals, worldmodelLoaded) {
2455
2930
  const gate = DEFAULT_EVIDENCE_GATE;
2456
2931
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -2560,6 +3035,8 @@ export {
2560
3035
  formatSlackSignalsForPrompt,
2561
3036
  fetchNotionActivity,
2562
3037
  formatNotionSignalsForPrompt,
3038
+ fetchLinearActivity,
3039
+ formatLinearSignalsForPrompt,
2563
3040
  readOriginRemote,
2564
3041
  parseRemoteUrl,
2565
3042
  getRepoOrigin,
@@ -2586,6 +3063,8 @@ export {
2586
3063
  classifyEvents,
2587
3064
  extractSignals,
2588
3065
  DEFAULT_SIGNAL_EXTRACTORS,
3066
+ extractDeclaredVocabulary,
3067
+ matchDeclaredPattern,
2589
3068
  DEFAULT_EVIDENCE_GATE,
2590
3069
  isScored,
2591
3070
  isSentinel,
@@ -2598,6 +3077,7 @@ export {
2598
3077
  interpretPatterns,
2599
3078
  render,
2600
3079
  emergent,
3080
+ filterEventsByUser,
2601
3081
  createAnthropicAI,
2602
3082
  createMockAI
2603
3083
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-I4RTIMLX.js";
4
4
  import {
5
5
  evaluateGuard
6
- } from "./chunk-ZAF6JH23.js";
6
+ } from "./chunk-MBOW6YXN.js";
7
7
 
8
8
  // src/adapters/autoresearch.ts
9
9
  var AutoresearchGovernor = class {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-I4RTIMLX.js";
4
4
  import {
5
5
  evaluateGuard
6
- } from "./chunk-ZAF6JH23.js";
6
+ } from "./chunk-MBOW6YXN.js";
7
7
 
8
8
  // src/adapters/express.ts
9
9
  function methodToCategory(method) {
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-I4RTIMLX.js";
8
8
  import {
9
9
  evaluateGuard
10
- } from "./chunk-ZAF6JH23.js";
10
+ } from "./chunk-MBOW6YXN.js";
11
11
  import {
12
12
  advancePlan,
13
13
  evaluatePlan,
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-I4RTIMLX.js";
11
11
  import {
12
12
  evaluateGuard
13
- } from "./chunk-ZAF6JH23.js";
13
+ } from "./chunk-MBOW6YXN.js";
14
14
 
15
15
  // src/adapters/openai.ts
16
16
  var GovernanceBlockedError2 = class extends GovernanceBlockedError {
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-I4RTIMLX.js";
13
13
  import {
14
14
  evaluateGuard
15
- } from "./chunk-ZAF6JH23.js";
15
+ } from "./chunk-MBOW6YXN.js";
16
16
 
17
17
  // src/adapters/mentraos.ts
18
18
  var DEFAULT_USER_RULES = {
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-I4RTIMLX.js";
10
10
  import {
11
11
  evaluateGuard
12
- } from "./chunk-ZAF6JH23.js";
12
+ } from "./chunk-MBOW6YXN.js";
13
13
 
14
14
  // src/adapters/langchain.ts
15
15
  var GovernanceBlockedError2 = class extends GovernanceBlockedError {