@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
@@ -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) : [],
@@ -3119,8 +3560,26 @@ function isExternalScope(scope) {
3119
3560
  ];
3120
3561
  return !internalPatterns.some((p) => p.test(scope));
3121
3562
  }
3122
- var MAX_INPUT_LENGTH = 1e5;
3123
3563
  function evaluateGuard(event, world, options = {}) {
3564
+ const verdict = evaluateGuardCore(event, world, options);
3565
+ return options.mode === "observe" ? toShadowVerdict(verdict) : verdict;
3566
+ }
3567
+ function toShadowVerdict(verdict) {
3568
+ if (verdict.status === "ALLOW") return verdict;
3569
+ return {
3570
+ ...verdict,
3571
+ status: "ALLOW",
3572
+ shadowStatus: verdict.status,
3573
+ shadowReason: verdict.reason,
3574
+ // Preserve the original reason as shadowReason and wipe the
3575
+ // top-level reason so callers that display `reason` for BLOCK/PAUSE
3576
+ // don't accidentally surface an enforcement message.
3577
+ reason: void 0,
3578
+ warning: verdict.reason ? `Observe mode: would have ${verdict.status.toLowerCase()} \u2014 ${verdict.reason}` : `Observe mode: would have ${verdict.status.toLowerCase()}`
3579
+ };
3580
+ }
3581
+ var MAX_INPUT_LENGTH = 1e5;
3582
+ function evaluateGuardCore(event, world, options = {}) {
3124
3583
  const startTime = performance.now();
3125
3584
  const level = options.level ?? "standard";
3126
3585
  const includeTrace = options.trace ?? false;
@@ -4010,6 +4469,7 @@ async function auditGovernance(events, worldPath) {
4010
4469
  return emptyAudit(events.length, "Could not load compiled worldmodel for governance audit.");
4011
4470
  }
4012
4471
  const verdicts = [];
4472
+ const crossings = [];
4013
4473
  for (const ce of events) {
4014
4474
  const intent = ce.event.content?.slice(0, 500) || ce.event.kind || "activity";
4015
4475
  const scope = ce.event.metadata?.scope || void 0;
@@ -4020,16 +4480,32 @@ async function auditGovernance(events, worldPath) {
4020
4480
  scope,
4021
4481
  actionCategory: mapKindToCategory(ce.event.kind)
4022
4482
  },
4023
- world
4483
+ world,
4484
+ { mode: "observe" }
4024
4485
  );
4486
+ const shadow = result.shadowStatus ?? "ALLOW";
4025
4487
  verdicts.push({
4026
4488
  eventId: ce.event.id,
4027
4489
  domain: ce.domain,
4028
- status: result.status,
4029
- reason: result.reason,
4490
+ status: shadow,
4491
+ reason: result.shadowReason,
4030
4492
  ruleId: result.ruleId,
4031
4493
  warning: result.warning
4032
4494
  });
4495
+ if (shadow !== "ALLOW") {
4496
+ crossings.push({
4497
+ eventId: ce.event.id,
4498
+ timestamp: ce.event.timestamp,
4499
+ kind: ce.event.kind,
4500
+ actorId: ce.event.actor.id,
4501
+ shadowStatus: shadow,
4502
+ shadowReason: result.shadowReason,
4503
+ ruleId: result.ruleId,
4504
+ excerpt: intent.length > 280 ? intent.slice(0, 279) + "\u2026" : intent,
4505
+ wouldHaveBlocked: true,
4506
+ verdict: result
4507
+ });
4508
+ }
4033
4509
  } catch {
4034
4510
  verdicts.push({
4035
4511
  eventId: ce.event.id,
@@ -4048,6 +4524,7 @@ async function auditGovernance(events, worldPath) {
4048
4524
  human,
4049
4525
  cyber,
4050
4526
  joint,
4527
+ crossings,
4051
4528
  summary
4052
4529
  };
4053
4530
  }
@@ -4103,6 +4580,7 @@ function emptyAudit(total, reason) {
4103
4580
  human: { allow: 0, modify: 0, block: 0, details: [] },
4104
4581
  cyber: { allow: 0, modify: 0, block: 0, details: [] },
4105
4582
  joint: { allow: 0, modify: 0, block: 0, details: [] },
4583
+ crossings: [],
4106
4584
  summary: reason
4107
4585
  };
4108
4586
  }
@@ -4408,10 +4886,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4408
4886
  } catch {
4409
4887
  }
4410
4888
  }
4889
+ const linearKey = process.env.LINEAR_API_KEY;
4890
+ if (linearKey) {
4891
+ try {
4892
+ const linear = await fetchLinearActivity(linearKey, { windowDays });
4893
+ events.push(...linear.events);
4894
+ adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
4895
+ activeAdapters.push("linear");
4896
+ } catch {
4897
+ }
4898
+ }
4411
4899
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
4900
+ if (input.personalUser) {
4901
+ events = filterEventsByUser(events, input.personalUser);
4902
+ }
4412
4903
  const classified = classifyEvents(events);
4413
4904
  const signals = extractSignals(classified);
4414
4905
  const scores = computeScores(signals, input.worldmodelContent !== "");
4906
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
4415
4907
  const { patterns, meaning, move } = await interpretPatterns({
4416
4908
  signals,
4417
4909
  events: classified,
@@ -4419,6 +4911,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4419
4911
  lens,
4420
4912
  ai: input.ai,
4421
4913
  canonicalPatterns: input.canonicalPatterns,
4914
+ declaredVocabulary,
4422
4915
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
4423
4916
  });
4424
4917
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -4470,9 +4963,14 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4470
4963
  scores,
4471
4964
  eventCount: events.length,
4472
4965
  activeAdapters,
4473
- worldStack
4966
+ worldStack,
4967
+ governance
4474
4968
  };
4475
4969
  }
4970
+ function filterEventsByUser(events, username) {
4971
+ const target = username.toLowerCase();
4972
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
4973
+ }
4476
4974
  function computeScores(signals, worldmodelLoaded) {
4477
4975
  const gate = DEFAULT_EVIDENCE_GATE;
4478
4976
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -4546,15 +5044,19 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
4546
5044
  detectOrgExtendsSpec,
4547
5045
  discoverWorlds,
4548
5046
  emergent,
5047
+ extractDeclaredVocabulary,
4549
5048
  extractSignals,
4550
5049
  fetchDiscordActivity,
4551
5050
  fetchGitHubActivity,
4552
5051
  fetchGitHubOrgActivity,
5052
+ fetchLinearActivity,
4553
5053
  fetchNotionActivity,
4554
5054
  fetchSlackActivity,
5055
+ filterEventsByUser,
4555
5056
  formatActiveWorlds,
4556
5057
  formatDiscordSignalsForPrompt,
4557
5058
  formatExocortexForPrompt,
5059
+ formatLinearSignalsForPrompt,
4558
5060
  formatNotionSignalsForPrompt,
4559
5061
  formatPriorReadsForPrompt,
4560
5062
  formatScope,
@@ -4570,6 +5072,7 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
4570
5072
  listLenses,
4571
5073
  loadExtendsConfig,
4572
5074
  loadPriorReads,
5075
+ matchDeclaredPattern,
4573
5076
  parseExtendsSpec,
4574
5077
  parseRemoteUrl,
4575
5078
  parseRepoScope,