@neuroverseos/governance 0.8.1 → 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.
@@ -48,35 +48,49 @@ __export(radiant_exports, {
48
48
  createAnthropicAI: () => createAnthropicAI,
49
49
  createMockAI: () => createMockAI,
50
50
  createMockGitHubAdapter: () => createMockGitHubAdapter,
51
+ detectOrgExtendsSpec: () => detectOrgExtendsSpec,
51
52
  discoverWorlds: () => discoverWorlds,
52
53
  emergent: () => emergent,
54
+ extractDeclaredVocabulary: () => extractDeclaredVocabulary,
53
55
  extractSignals: () => extractSignals,
54
56
  fetchDiscordActivity: () => fetchDiscordActivity,
55
57
  fetchGitHubActivity: () => fetchGitHubActivity,
56
58
  fetchGitHubOrgActivity: () => fetchGitHubOrgActivity,
59
+ fetchLinearActivity: () => fetchLinearActivity,
57
60
  fetchNotionActivity: () => fetchNotionActivity,
58
61
  fetchSlackActivity: () => fetchSlackActivity,
62
+ filterEventsByUser: () => filterEventsByUser,
59
63
  formatActiveWorlds: () => formatActiveWorlds,
60
64
  formatDiscordSignalsForPrompt: () => formatDiscordSignalsForPrompt,
61
65
  formatExocortexForPrompt: () => formatExocortexForPrompt,
66
+ formatLinearSignalsForPrompt: () => formatLinearSignalsForPrompt,
62
67
  formatNotionSignalsForPrompt: () => formatNotionSignalsForPrompt,
63
68
  formatPriorReadsForPrompt: () => formatPriorReadsForPrompt,
64
69
  formatScope: () => formatScope,
65
70
  formatSlackSignalsForPrompt: () => formatSlackSignalsForPrompt,
66
71
  formatTeamExocorticesForPrompt: () => formatTeamExocorticesForPrompt,
72
+ getCacheDir: () => getCacheDir,
67
73
  getLens: () => getLens,
74
+ getRepoOrigin: () => getRepoOrigin,
68
75
  interpretPatterns: () => interpretPatterns,
69
76
  isPresent: () => isPresent,
70
77
  isScored: () => isScored,
71
78
  isSentinel: () => isSentinel,
72
79
  listLenses: () => listLenses,
80
+ loadExtendsConfig: () => loadExtendsConfig,
73
81
  loadPriorReads: () => loadPriorReads,
82
+ matchDeclaredPattern: () => matchDeclaredPattern,
83
+ parseExtendsSpec: () => parseExtendsSpec,
84
+ parseRemoteUrl: () => parseRemoteUrl,
74
85
  parseRepoScope: () => parseRepoScope,
75
86
  parseScope: () => parseScope,
76
87
  presenceAverage: () => presenceAverage,
77
88
  readExocortex: () => readExocortex,
89
+ readOriginRemote: () => readOriginRemote,
78
90
  readTeamExocortices: () => readTeamExocortices,
79
91
  render: () => render,
92
+ resolveAllExtends: () => resolveAllExtends,
93
+ resolveExtendsSpec: () => resolveExtendsSpec,
80
94
  scoreComposite: () => scoreComposite,
81
95
  scoreCyber: () => scoreCyber,
82
96
  scoreLife: () => scoreLife,
@@ -575,17 +589,17 @@ var aukiBuilderLens = {
575
589
  var SOVEREIGN_CONDUIT_FRAME = {
576
590
  domains: [
577
591
  "stewardship",
578
- "sovereignty",
592
+ "agency",
579
593
  "integration"
580
594
  ],
581
595
  overlaps: [
582
596
  {
583
- domains: ["stewardship", "sovereignty"],
597
+ domains: ["stewardship", "agency"],
584
598
  emergent_state: "Trust",
585
599
  description: "I am safe to be myself. When the system protects AND preserves individual authority, trust emerges \u2014 the feeling that you can think freely because someone is watching the boundaries."
586
600
  },
587
601
  {
588
- domains: ["sovereignty", "integration"],
602
+ domains: ["agency", "integration"],
589
603
  emergent_state: "Possibility",
590
604
  description: "My thinking can expand. When individual authority is preserved AND AI extends cognitive capability, possibility opens \u2014 the feeling that you can reach further without losing yourself."
591
605
  },
@@ -616,7 +630,7 @@ var SOVEREIGN_CONDUIT_FRAME = {
616
630
  "harm detection",
617
631
  "constraint design"
618
632
  ],
619
- "sovereignty": [
633
+ "agency": [
620
634
  "independent thinking",
621
635
  "decision ownership",
622
636
  "self-trust",
@@ -640,7 +654,7 @@ var SOVEREIGN_CONDUIT_FRAME = {
640
654
  output_translation: {
641
655
  never_surface_in_output: [
642
656
  "Stewardship",
643
- "Sovereignty",
657
+ "Agency",
644
658
  "Integration"
645
659
  ],
646
660
  surface_freely: [
@@ -655,7 +669,7 @@ var SOVEREIGN_CONDUIT_FRAME = {
655
669
  external_expression: "the boundaries are clear and the system feels safe to operate inside"
656
670
  },
657
671
  {
658
- internal_reasoning: "Sovereignty is weakening",
672
+ internal_reasoning: "Agency is weakening",
659
673
  external_expression: "decision ownership is quietly shifting \u2014 the human is accepting AI output without engaging with it"
660
674
  },
661
675
  {
@@ -738,12 +752,12 @@ var SOVEREIGN_CONDUIT_VOICE = {
738
752
  close_with_strategic_frame: "preferred",
739
753
  punchline_move: "sparing",
740
754
  honesty_about_failure: "required",
741
- output_translation: `Reason internally through the three-domain frame (Stewardship, Sovereignty, Integration). Express externally through the skills inside each domain and the overlap feelings (Trust, Possibility, Responsibility). Do NOT surface the bucket names as labels. Readers understand "the boundaries feel safe" not "Stewardship is strong." Use everyday analogies \u2014 mom rules, friend's house rules, idea calculator. Name the emotion before the mechanism.`
755
+ output_translation: `Reason internally through the three-domain frame (Stewardship, Agency, Integration). Express externally through the skills inside each domain and the overlap feelings (Trust, Possibility, Responsibility). Do NOT surface the bucket names as labels. Readers understand "the boundaries feel safe" not "Stewardship is strong." Use everyday analogies \u2014 mom rules, friend's house rules, idea calculator. Name the emotion before the mechanism.`
742
756
  };
743
757
  var SOVEREIGN_CONDUIT_FORBIDDEN = Object.freeze([
744
758
  // Bucket names as labels
745
759
  "stewardship is",
746
- "sovereignty is",
760
+ "agency is",
747
761
  "integration is",
748
762
  // AI-assistant hedging
749
763
  "it may be beneficial to consider",
@@ -779,7 +793,7 @@ var SOVEREIGN_CONDUIT_PREFERRED = Object.freeze([
779
793
  "Here's what's actually happening: [plain explanation].",
780
794
  "The question to ask yourself: [question].",
781
795
  "The difference between [A] and [B] matters here: [why].",
782
- // Sovereignty checks
796
+ // Agency checks
783
797
  "Are you still the author of this decision, or did the AI make it for you?",
784
798
  "The AI extended your thinking here. That's working.",
785
799
  "The AI replaced your thinking here. That's the drift to watch.",
@@ -790,7 +804,7 @@ var SOVEREIGN_CONDUIT_PREFERRED = Object.freeze([
790
804
  ]);
791
805
  var SOVEREIGN_CONDUIT_STRATEGIC = Object.freeze([
792
806
  "Safety before expansion \u2014 always. No exception.",
793
- "Sovereignty before convenience \u2014 the right to think for yourself is not a feature to optimize away.",
807
+ "Agency before convenience \u2014 the right to think for yourself is not a feature to optimize away.",
794
808
  "Extension, not replacement \u2014 AI should make your thinking bigger, not do your thinking for you.",
795
809
  "Diversity over uniformity \u2014 different thinkers produce different ideas, and that's the engine of progress.",
796
810
  "The rules should be visible \u2014 like a good house, you know the rules before you walk in.",
@@ -800,9 +814,9 @@ var SOVEREIGN_CONDUIT_STRATEGIC = Object.freeze([
800
814
  ]);
801
815
  var SOVEREIGN_CONDUIT_EXEMPLARS = Object.freeze([
802
816
  {
803
- path: "neuroverseos-sovereign-conduit.worldmodel.md",
817
+ path: "src/radiant/examples/neuroverse-base/neuroverseos-sovereign-conduit.worldmodel.md",
804
818
  title: "The Sovereign Conduit Worldmodel",
805
- exhibits: ["stewardship", "sovereignty", "integration"],
819
+ exhibits: ["stewardship", "agency", "integration"],
806
820
  integration_quality: "full \u2014 all three domains defined, overlaps named, center identity declared",
807
821
  notes: 'The source worldmodel. The tagline "Humanity first. In constant learning. In shared teaching." is the voice compressed to its essence. Use this as the north star for tone calibration.'
808
822
  }
@@ -828,13 +842,13 @@ function sovereignConduitRewrite(pattern) {
828
842
  return {
829
843
  ...pattern,
830
844
  framing: "what this means for the people in the system",
831
- emphasis: "humanity + sovereignty + learning",
845
+ emphasis: "humanity + agency + learning",
832
846
  compress: false
833
847
  };
834
848
  }
835
849
  var sovereignConduitLens = {
836
850
  name: "sovereign-conduit",
837
- description: "The NeuroVerseOS base lens. Warm, accessible, teaching. Evaluates activity through Stewardship (safety), Sovereignty (authority over thinking), and Integration (AI as cognitive extension). Uses everyday analogies \u2014 mom rules, friend's house, idea calculator. Names emotions before mechanisms. If a non-technical person can't understand the output, the voice is wrong. Humanity first. In constant learning. In shared teaching.",
851
+ description: "The NeuroVerseOS base lens. Warm, accessible, teaching. Evaluates activity through Stewardship (safety), Agency (authority over thinking), and Integration (AI as cognitive extension). Uses everyday analogies \u2014 mom rules, friend's house, idea calculator. Names emotions before mechanisms. If a non-technical person can't understand the output, the voice is wrong. Humanity first. In constant learning. In shared teaching.",
838
852
  primary_frame: {
839
853
  domains: SOVEREIGN_CONDUIT_FRAME.domains,
840
854
  overlaps: SOVEREIGN_CONDUIT_FRAME.overlaps,
@@ -2031,11 +2045,398 @@ async function fetchNotionAPI(url, headers, init) {
2031
2045
  return await res.json();
2032
2046
  }
2033
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
+
2034
2431
  // src/radiant/core/patterns.ts
2035
2432
  async function interpretPatterns(input) {
2036
2433
  const prompt = buildInterpretationPrompt(input);
2037
2434
  const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
2038
- 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);
2039
2440
  return {
2040
2441
  patterns: parsed.patterns,
2041
2442
  meaning: parsed.meaning,
@@ -2046,8 +2447,10 @@ async function interpretPatterns(input) {
2046
2447
  function buildInterpretationPrompt(input) {
2047
2448
  const signalSummary = formatSignalSummary(input.signals);
2048
2449
  const eventSample = formatEventSample(input.events, 30);
2049
- const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
2050
- ${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
+ );
2051
2454
  const compressedWorld = compressWorldmodel(input.worldmodelContent);
2052
2455
  const cl = compressLens(input.lens);
2053
2456
  const frame = input.lens.primary_frame;
@@ -2158,6 +2561,44 @@ Only recommend a move when the evidence actually calls for one.
2158
2561
  Do NOT use these phrases anywhere in your output:
2159
2562
  ${forbiddenList}`;
2160
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
+ }
2161
2602
  function formatSignalSummary(signals) {
2162
2603
  const lines = [];
2163
2604
  const domains = ["life", "cyber", "joint"];
@@ -2183,7 +2624,7 @@ function formatEventSample(events, maxEvents) {
2183
2624
  "${content}"`;
2184
2625
  }).join("\n");
2185
2626
  }
2186
- function parseInterpretation(raw, canonicalNames) {
2627
+ function parseInterpretation(raw, canonicalNames, vocabulary) {
2187
2628
  let meaning = "";
2188
2629
  let move = "";
2189
2630
  let patternsArray = [];
@@ -2213,14 +2654,23 @@ function parseInterpretation(raw, canonicalNames) {
2213
2654
  const patterns = [];
2214
2655
  for (const item of patternsArray) {
2215
2656
  if (!isPatternLike(item)) continue;
2216
- const nameStr = String(item.name ?? "unnamed");
2657
+ const rawName = String(item.name ?? "unnamed");
2658
+ const description = String(item.description ?? "");
2217
2659
  const ev = item.evidence;
2218
- 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
+ }
2219
2669
  patterns.push({
2220
- name: nameStr,
2670
+ name,
2221
2671
  type: isCanonical ? "canonical" : "candidate",
2222
- declaredAs: isCanonical ? nameStr : void 0,
2223
- description: String(item.description ?? ""),
2672
+ declaredAs: isCanonical ? name : void 0,
2673
+ description,
2224
2674
  evidence: {
2225
2675
  signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
2226
2676
  events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
@@ -2467,70 +2917,351 @@ function serializeYAML(obj, indent = 0) {
2467
2917
  }
2468
2918
 
2469
2919
  // src/radiant/core/discovery.ts
2920
+ var import_fs4 = require("fs");
2921
+ var import_path4 = require("path");
2922
+ var import_os2 = require("os");
2923
+
2924
+ // src/radiant/core/extends.ts
2925
+ var import_fs3 = require("fs");
2926
+ var import_path3 = require("path");
2927
+ var import_os = require("os");
2928
+ var import_crypto = require("crypto");
2929
+ var import_child_process = require("child_process");
2930
+
2931
+ // src/radiant/core/git-remote.ts
2470
2932
  var import_fs2 = require("fs");
2471
2933
  var import_path2 = require("path");
2472
- var import_os = require("os");
2934
+ function resolveGitConfigPath(repoDir) {
2935
+ const dotGit = (0, import_path2.join)(repoDir, ".git");
2936
+ if (!(0, import_fs2.existsSync)(dotGit)) return null;
2937
+ try {
2938
+ const stat = (0, import_fs2.statSync)(dotGit);
2939
+ if (stat.isDirectory()) {
2940
+ return (0, import_path2.join)(dotGit, "config");
2941
+ }
2942
+ if (stat.isFile()) {
2943
+ const content = (0, import_fs2.readFileSync)(dotGit, "utf-8");
2944
+ const match = /^gitdir:\s*(.+)$/m.exec(content);
2945
+ if (!match) return null;
2946
+ const gitDir = (0, import_path2.resolve)(repoDir, match[1].trim());
2947
+ const configPath = (0, import_path2.join)(gitDir, "config");
2948
+ return (0, import_fs2.existsSync)(configPath) ? configPath : null;
2949
+ }
2950
+ } catch {
2951
+ return null;
2952
+ }
2953
+ return null;
2954
+ }
2955
+ function readOriginRemote(repoDir) {
2956
+ const configPath = resolveGitConfigPath(repoDir);
2957
+ if (!configPath) return null;
2958
+ try {
2959
+ const raw = (0, import_fs2.readFileSync)(configPath, "utf-8");
2960
+ const sectionRe = /\[remote "origin"\]\s*\n((?:(?!\[)[^\n]*\n?)*)/;
2961
+ const section = sectionRe.exec(raw);
2962
+ if (!section) return null;
2963
+ const urlRe = /^\s*url\s*=\s*(.+?)\s*$/m;
2964
+ const url = urlRe.exec(section[1]);
2965
+ return url ? url[1] : null;
2966
+ } catch {
2967
+ return null;
2968
+ }
2969
+ }
2970
+ function parseRemoteUrl(url) {
2971
+ const trimmed = url.trim();
2972
+ if (!trimmed) return null;
2973
+ const ssh = /^git@([^:]+):([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
2974
+ if (ssh) return { host: ssh[1], owner: ssh[2], repo: ssh[3] };
2975
+ const sshProto = /^ssh:\/\/git@([^/]+)\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
2976
+ if (sshProto) return { host: sshProto[1], owner: sshProto[2], repo: sshProto[3] };
2977
+ const https = /^https?:\/\/(?:[^@/]+@)?([^/]+)\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
2978
+ if (https) return { host: https[1], owner: https[2], repo: https[3] };
2979
+ return null;
2980
+ }
2981
+ function getRepoOrigin(repoDir) {
2982
+ const url = readOriginRemote(repoDir);
2983
+ if (!url) return null;
2984
+ return parseRemoteUrl(url);
2985
+ }
2986
+
2987
+ // src/radiant/core/extends.ts
2988
+ function loadExtendsConfig(repoDir) {
2989
+ const configPath = (0, import_path3.join)(repoDir, ".neuroverse", "config.json");
2990
+ if (!(0, import_fs3.existsSync)(configPath)) return null;
2991
+ try {
2992
+ const raw = (0, import_fs3.readFileSync)(configPath, "utf-8");
2993
+ const parsed = JSON.parse(raw);
2994
+ return parsed;
2995
+ } catch {
2996
+ return null;
2997
+ }
2998
+ }
2999
+ function parseExtendsSpec(raw) {
3000
+ const trimmed = raw.trim();
3001
+ if (!trimmed) return null;
3002
+ if (trimmed.startsWith("github:")) {
3003
+ const rest = trimmed.slice("github:".length);
3004
+ const match = /^([^/]+)\/([^@:]+)(?:@([^:]+))?(?::(.+))?$/.exec(rest);
3005
+ if (!match) return null;
3006
+ return {
3007
+ raw: trimmed,
3008
+ kind: "github",
3009
+ owner: match[1],
3010
+ repo: match[2],
3011
+ ref: match[3] ?? "HEAD",
3012
+ subpath: match[4] ?? ""
3013
+ };
3014
+ }
3015
+ if (trimmed.startsWith("./") || trimmed.startsWith("../") || (0, import_path3.isAbsolute)(trimmed)) {
3016
+ return { raw: trimmed, kind: "local", path: trimmed };
3017
+ }
3018
+ return null;
3019
+ }
3020
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
3021
+ function getCacheDir(spec, baseCacheDir) {
3022
+ const root = baseCacheDir ?? (0, import_path3.join)((0, import_os.homedir)(), ".neuroverse", "cache", "extends");
3023
+ const key = (0, import_crypto.createHash)("sha256").update(spec.raw).digest("hex").slice(0, 16);
3024
+ return (0, import_path3.join)(root, key);
3025
+ }
3026
+ function isCacheFresh(cacheDir, ttlMs) {
3027
+ const stampPath = (0, import_path3.join)(cacheDir, ".neuroverse-fetched");
3028
+ if (!(0, import_fs3.existsSync)(stampPath)) return false;
3029
+ try {
3030
+ const stamp = (0, import_fs3.statSync)(stampPath);
3031
+ return Date.now() - stamp.mtimeMs < ttlMs;
3032
+ } catch {
3033
+ return false;
3034
+ }
3035
+ }
3036
+ function markCacheFresh(cacheDir) {
3037
+ const stampPath = (0, import_path3.join)(cacheDir, ".neuroverse-fetched");
3038
+ try {
3039
+ (0, import_fs3.mkdirSync)(cacheDir, { recursive: true });
3040
+ (0, import_fs3.writeFileSync)(stampPath, (/* @__PURE__ */ new Date()).toISOString());
3041
+ } catch {
3042
+ }
3043
+ }
3044
+ var defaultGitFetcher = (spec, destDir) => {
3045
+ if (spec.kind !== "github") return;
3046
+ const url = `https://github.com/${spec.owner}/${spec.repo}.git`;
3047
+ const parent = (0, import_path3.resolve)(destDir, "..");
3048
+ (0, import_fs3.mkdirSync)(parent, { recursive: true });
3049
+ if ((0, import_fs3.existsSync)(destDir)) {
3050
+ (0, import_fs3.rmSync)(destDir, { recursive: true, force: true });
3051
+ }
3052
+ const args = ["clone", "--depth", "1", "--filter=blob:none"];
3053
+ if (spec.ref && spec.ref !== "HEAD") {
3054
+ args.push("--branch", spec.ref);
3055
+ }
3056
+ args.push(url, destDir);
3057
+ (0, import_child_process.execFileSync)("git", args, { stdio: "pipe" });
3058
+ };
3059
+ function resolveExtendsSpec(spec, repoDir, options) {
3060
+ if (spec.kind === "local") {
3061
+ const full = (0, import_path3.isAbsolute)(spec.path) ? spec.path : (0, import_path3.resolve)(repoDir, spec.path);
3062
+ if (!(0, import_fs3.existsSync)(full)) {
3063
+ return { spec, dir: null, warning: `local extends path not found: ${full}` };
3064
+ }
3065
+ return { spec, dir: full };
3066
+ }
3067
+ const cacheRoot = options?.cacheDir;
3068
+ const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
3069
+ const cacheDir = getCacheDir(spec, cacheRoot);
3070
+ const fresh = isCacheFresh(cacheDir, ttl);
3071
+ const needsFetch = options?.forceRefresh || !fresh || !(0, import_fs3.existsSync)(cacheDir);
3072
+ if (needsFetch && options?.noFetch) {
3073
+ if ((0, import_fs3.existsSync)(cacheDir) && (0, import_fs3.existsSync)((0, import_path3.join)(cacheDir, ".neuroverse-fetched"))) {
3074
+ return resolveSubpath(spec, cacheDir);
3075
+ }
3076
+ return options?.silentOnMissing ? { spec, dir: null } : { spec, dir: null, warning: `NEUROVERSE_NO_FETCH set and no cache for ${spec.raw}` };
3077
+ }
3078
+ if (needsFetch) {
3079
+ const fetcher = options?.fetcher ?? defaultGitFetcher;
3080
+ try {
3081
+ fetcher(spec, cacheDir);
3082
+ markCacheFresh(cacheDir);
3083
+ } catch (err) {
3084
+ if ((0, import_fs3.existsSync)(cacheDir) && (0, import_fs3.existsSync)((0, import_path3.join)(cacheDir, ".neuroverse-fetched"))) {
3085
+ const result = resolveSubpath(spec, cacheDir);
3086
+ return options?.silentOnMissing ? result : { ...result, warning: `fetch failed for ${spec.raw}, using stale cache: ${err.message}` };
3087
+ }
3088
+ return options?.silentOnMissing ? { spec, dir: null } : { spec, dir: null, warning: `fetch failed for ${spec.raw}: ${err.message}` };
3089
+ }
3090
+ }
3091
+ return resolveSubpath(spec, cacheDir);
3092
+ }
3093
+ function resolveSubpath(spec, cacheDir) {
3094
+ const target = spec.subpath ? (0, import_path3.join)(cacheDir, spec.subpath) : cacheDir;
3095
+ if (!(0, import_fs3.existsSync)(target)) {
3096
+ return { spec, dir: null, warning: `subpath not found in ${spec.raw}: ${spec.subpath}` };
3097
+ }
3098
+ if (!spec.subpath) {
3099
+ const candidates = [
3100
+ (0, import_path3.join)(target, "worlds"),
3101
+ (0, import_path3.join)(target, ".neuroverse", "worlds")
3102
+ ];
3103
+ for (const c of candidates) {
3104
+ if ((0, import_fs3.existsSync)(c)) return { spec, dir: c };
3105
+ }
3106
+ }
3107
+ return { spec, dir: target };
3108
+ }
3109
+ function detectOrgExtendsSpec(repoDir) {
3110
+ const origin = getRepoOrigin(repoDir);
3111
+ if (!origin) return null;
3112
+ if (origin.host !== "github.com") return null;
3113
+ if (origin.repo === "worlds") return null;
3114
+ return {
3115
+ raw: `github:${origin.owner}/worlds`,
3116
+ kind: "github",
3117
+ owner: origin.owner,
3118
+ repo: "worlds",
3119
+ ref: "HEAD",
3120
+ subpath: ""
3121
+ };
3122
+ }
3123
+ function resolveAllExtends(repoDir, options) {
3124
+ const config = options?.config !== void 0 ? options.config : loadExtendsConfig(repoDir);
3125
+ if (!config?.extends || config.extends.length === 0) return [];
3126
+ const results = [];
3127
+ for (const raw of config.extends) {
3128
+ const spec = parseExtendsSpec(raw);
3129
+ if (!spec) {
3130
+ results.push({
3131
+ spec: { raw, kind: "local" },
3132
+ dir: null,
3133
+ warning: `unparseable extends spec: ${raw}`
3134
+ });
3135
+ continue;
3136
+ }
3137
+ results.push(resolveExtendsSpec(spec, repoDir, options));
3138
+ }
3139
+ return results;
3140
+ }
3141
+
3142
+ // src/radiant/core/discovery.ts
2473
3143
  function discoverWorlds(options) {
2474
3144
  const worlds = [];
2475
- const userDir = options?.userWorldsDir ?? (0, import_path2.join)((0, import_os.homedir)(), ".neuroverse", "worlds");
2476
- if ((0, import_fs2.existsSync)(userDir)) {
3145
+ const warnings = [];
3146
+ const forceRefresh = process.env.NEUROVERSE_REFRESH === "1";
3147
+ const noFetch = process.env.NEUROVERSE_NO_FETCH === "1";
3148
+ const noOrg = options?.disableOrg || process.env.NEUROVERSE_NO_ORG === "1";
3149
+ const userDir = options?.userWorldsDir ?? (0, import_path4.join)((0, import_os2.homedir)(), ".neuroverse", "worlds");
3150
+ if ((0, import_fs4.existsSync)(userDir)) {
2477
3151
  worlds.push(...loadWorldsFromDir(userDir, "user"));
2478
3152
  }
3153
+ if (!noOrg && !options?.explicitWorldsDir) {
3154
+ const specs = [];
3155
+ if (options?.repoDir) {
3156
+ const fromGit = detectOrgExtendsSpec(options.repoDir);
3157
+ if (fromGit) specs.push(fromGit);
3158
+ }
3159
+ if (options?.scopeOwner) {
3160
+ const already = specs.some(
3161
+ (s) => s.owner?.toLowerCase() === options.scopeOwner.toLowerCase()
3162
+ );
3163
+ if (!already) {
3164
+ specs.push({
3165
+ raw: `github:${options.scopeOwner}/worlds`,
3166
+ kind: "github",
3167
+ owner: options.scopeOwner,
3168
+ repo: "worlds"
3169
+ });
3170
+ }
3171
+ }
3172
+ const baseDir = options?.repoDir ?? process.cwd();
3173
+ for (const spec of specs) {
3174
+ const result = resolveExtendsSpec(spec, baseDir, {
3175
+ cacheDir: options?.extendsCacheDir,
3176
+ fetcher: options?.extendsFetcher,
3177
+ ttlMs: options?.extendsTtlMs,
3178
+ forceRefresh,
3179
+ noFetch,
3180
+ silentOnMissing: true
3181
+ });
3182
+ worlds.push(...loadExtendsWorlds(result, "org"));
3183
+ }
3184
+ }
3185
+ if (options?.repoDir && !options.disableExtends && !options.explicitWorldsDir) {
3186
+ const results = resolveAllExtends(options.repoDir, {
3187
+ cacheDir: options.extendsCacheDir,
3188
+ fetcher: options.extendsFetcher,
3189
+ ttlMs: options.extendsTtlMs,
3190
+ forceRefresh,
3191
+ noFetch
3192
+ });
3193
+ for (const result of results) {
3194
+ worlds.push(...loadExtendsWorlds(result, "extends"));
3195
+ if (result.warning) warnings.push(result.warning);
3196
+ }
3197
+ }
2479
3198
  if (options?.explicitWorldsDir) {
2480
3199
  worlds.push(...loadWorldsFromDir(options.explicitWorldsDir, "repo"));
2481
3200
  } else if (options?.repoDir) {
2482
3201
  const repoPaths = [
2483
- (0, import_path2.join)(options.repoDir, "worlds"),
2484
- (0, import_path2.join)(options.repoDir, ".neuroverse", "worlds")
3202
+ (0, import_path4.join)(options.repoDir, "worlds"),
3203
+ (0, import_path4.join)(options.repoDir, ".neuroverse", "worlds")
2485
3204
  ];
2486
3205
  for (const p of repoPaths) {
2487
- if ((0, import_fs2.existsSync)(p)) {
3206
+ if ((0, import_fs4.existsSync)(p)) {
2488
3207
  worlds.push(...loadWorldsFromDir(p, "repo"));
2489
3208
  break;
2490
3209
  }
2491
3210
  }
2492
3211
  }
2493
- const combinedContent = worlds.map((w) => `<!-- world: ${w.name} (${w.source}) -->
2494
- ${w.content}`).join("\n\n---\n\n");
3212
+ const combinedContent = worlds.map((w) => {
3213
+ const tag = w.extendsFrom ? `<!-- world: ${w.name} (${w.source} ${w.extendsFrom}) -->` : `<!-- world: ${w.name} (${w.source}) -->`;
3214
+ return `${tag}
3215
+ ${w.content}`;
3216
+ }).join("\n\n---\n\n");
2495
3217
  const summary = worlds.length === 0 ? "no worlds discovered" : worlds.map((w) => `${w.name} (${w.source})`).join(", ");
2496
- return { worlds, combinedContent, summary };
3218
+ return { worlds, combinedContent, summary, warnings };
2497
3219
  }
2498
3220
  function formatActiveWorlds(stack) {
2499
3221
  if (stack.worlds.length === 0) return "No worlds loaded.";
2500
3222
  const lines = ["ACTIVE WORLDS", ""];
2501
3223
  for (const w of stack.worlds) {
2502
- const sourceLabel = w.source === "base" ? "universal" : w.source === "user" ? "personal" : "this repo";
3224
+ const sourceLabel = w.source === "base" ? "universal" : w.source === "user" ? "personal" : w.source === "org" ? `org (${w.extendsFrom ?? "auto"})` : w.source === "extends" ? `shared (${w.extendsFrom ?? "extends"})` : "this repo";
2503
3225
  lines.push(` ${w.name} (${sourceLabel})`);
2504
3226
  }
3227
+ if (stack.warnings.length > 0) {
3228
+ lines.push("", "WARNINGS");
3229
+ for (const w of stack.warnings) lines.push(` ${w}`);
3230
+ }
2505
3231
  return lines.join("\n");
2506
3232
  }
3233
+ function loadExtendsWorlds(result, source) {
3234
+ if (!result.dir) return [];
3235
+ const loaded = loadWorldsFromDir(result.dir, source);
3236
+ return loaded.map((w) => ({ ...w, extendsFrom: result.spec.raw }));
3237
+ }
2507
3238
  function loadWorldsFromDir(dirPath, source) {
2508
- const dir = (0, import_path2.resolve)(dirPath);
2509
- if (!(0, import_fs2.existsSync)(dir)) return [];
2510
- const stat = (0, import_fs2.statSync)(dir);
3239
+ const dir = (0, import_path4.resolve)(dirPath);
3240
+ if (!(0, import_fs4.existsSync)(dir)) return [];
3241
+ const stat = (0, import_fs4.statSync)(dir);
2511
3242
  if (stat.isFile() && dir.endsWith(".md")) {
2512
3243
  try {
2513
3244
  return [{
2514
- name: (0, import_path2.basename)(dir).replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
3245
+ name: (0, import_path4.basename)(dir).replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
2515
3246
  source,
2516
3247
  path: dir,
2517
- content: (0, import_fs2.readFileSync)(dir, "utf-8")
3248
+ content: (0, import_fs4.readFileSync)(dir, "utf-8")
2518
3249
  }];
2519
3250
  } catch {
2520
3251
  return [];
2521
3252
  }
2522
3253
  }
2523
3254
  if (!stat.isDirectory()) return [];
2524
- const files = (0, import_fs2.readdirSync)(dir).filter(
3255
+ const files = (0, import_fs4.readdirSync)(dir).filter(
2525
3256
  (f) => f.endsWith(".worldmodel.md") || f.endsWith(".nv-world.md")
2526
3257
  ).sort();
2527
3258
  return files.map((f) => {
2528
- const fullPath = (0, import_path2.join)(dir, f);
3259
+ const fullPath = (0, import_path4.join)(dir, f);
2529
3260
  return {
2530
3261
  name: f.replace(/\.worldmodel\.md$/, "").replace(/\.nv-world\.md$/, ""),
2531
3262
  source,
2532
3263
  path: fullPath,
2533
- content: (0, import_fs2.readFileSync)(fullPath, "utf-8")
3264
+ content: (0, import_fs4.readFileSync)(fullPath, "utf-8")
2534
3265
  };
2535
3266
  });
2536
3267
  }
@@ -3621,10 +4352,10 @@ function verdictToEvent(status, intent) {
3621
4352
  // src/loader/world-loader.ts
3622
4353
  async function loadWorldFromDirectory(dirPath) {
3623
4354
  const { readFile } = await import("fs/promises");
3624
- const { join: join4 } = await import("path");
4355
+ const { join: join6 } = await import("path");
3625
4356
  const { readdirSync: readdirSync4 } = await import("fs");
3626
4357
  async function readJson(filename) {
3627
- const filePath = join4(dirPath, filename);
4358
+ const filePath = join6(dirPath, filename);
3628
4359
  try {
3629
4360
  const content = await readFile(filePath, "utf-8");
3630
4361
  return JSON.parse(content);
@@ -3654,11 +4385,11 @@ async function loadWorldFromDirectory(dirPath) {
3654
4385
  const metadataJson = await readJson("metadata.json");
3655
4386
  const rules = [];
3656
4387
  try {
3657
- const rulesDir = join4(dirPath, "rules");
4388
+ const rulesDir = join6(dirPath, "rules");
3658
4389
  const ruleFiles = readdirSync4(rulesDir).filter((f) => f.endsWith(".json")).sort();
3659
4390
  for (const file of ruleFiles) {
3660
4391
  try {
3661
- const content = await readFile(join4(rulesDir, file), "utf-8");
4392
+ const content = await readFile(join6(rulesDir, file), "utf-8");
3662
4393
  rules.push(JSON.parse(content));
3663
4394
  } catch (err) {
3664
4395
  process.stderr.write(
@@ -3818,25 +4549,25 @@ function emptyAudit(total, reason) {
3818
4549
  }
3819
4550
 
3820
4551
  // src/radiant/memory/palace.ts
3821
- var import_fs3 = require("fs");
3822
- var import_path3 = require("path");
4552
+ var import_fs5 = require("fs");
4553
+ var import_path5 = require("path");
3823
4554
  function writeRead(exocortexDir, frontmatter, text) {
3824
- const dir = (0, import_path3.resolve)(exocortexDir, "radiant", "reads");
3825
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
4555
+ const dir = (0, import_path5.resolve)(exocortexDir, "radiant", "reads");
4556
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
3826
4557
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3827
4558
  const filename = `${date}.md`;
3828
- const filepath = (0, import_path3.join)(dir, filename);
4559
+ const filepath = (0, import_path5.join)(dir, filename);
3829
4560
  const content = `${frontmatter}
3830
4561
 
3831
4562
  ${text}
3832
4563
  `;
3833
- (0, import_fs3.writeFileSync)(filepath, content, "utf-8");
4564
+ (0, import_fs5.writeFileSync)(filepath, content, "utf-8");
3834
4565
  return filepath;
3835
4566
  }
3836
4567
  function updateKnowledge(exocortexDir, persistence, options) {
3837
- const dir = (0, import_path3.resolve)(exocortexDir, "radiant");
3838
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
3839
- const filepath = (0, import_path3.join)(dir, "knowledge.md");
4568
+ const dir = (0, import_path5.resolve)(exocortexDir, "radiant");
4569
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
4570
+ const filepath = (0, import_path5.join)(dir, "knowledge.md");
3840
4571
  const totalReads = options?.totalReads ?? 0;
3841
4572
  const existingUntriggered = loadUntriggeredCounts(filepath);
3842
4573
  const lines = [
@@ -3923,14 +4654,14 @@ function updateKnowledge(exocortexDir, persistence, options) {
3923
4654
  lines.push(`${name}=${count}`);
3924
4655
  }
3925
4656
  lines.push("-->");
3926
- (0, import_fs3.writeFileSync)(filepath, lines.join("\n"), "utf-8");
4657
+ (0, import_fs5.writeFileSync)(filepath, lines.join("\n"), "utf-8");
3927
4658
  return filepath;
3928
4659
  }
3929
4660
  function loadUntriggeredCounts(filepath) {
3930
4661
  const counts = /* @__PURE__ */ new Map();
3931
- if (!(0, import_fs3.existsSync)(filepath)) return counts;
4662
+ if (!(0, import_fs5.existsSync)(filepath)) return counts;
3932
4663
  try {
3933
- const content = (0, import_fs3.readFileSync)(filepath, "utf-8");
4664
+ const content = (0, import_fs5.readFileSync)(filepath, "utf-8");
3934
4665
  const match = content.match(
3935
4666
  /<!-- untriggered_counts[\s\S]*?-->/
3936
4667
  );
@@ -3948,13 +4679,13 @@ function loadUntriggeredCounts(filepath) {
3948
4679
  return counts;
3949
4680
  }
3950
4681
  function loadPriorReads(exocortexDir) {
3951
- const dir = (0, import_path3.resolve)(exocortexDir, "radiant", "reads");
3952
- if (!(0, import_fs3.existsSync)(dir)) return [];
3953
- const files = (0, import_fs3.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
4682
+ const dir = (0, import_path5.resolve)(exocortexDir, "radiant", "reads");
4683
+ if (!(0, import_fs5.existsSync)(dir)) return [];
4684
+ const files = (0, import_fs5.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
3954
4685
  const reads = [];
3955
4686
  for (const filename of files) {
3956
- const filepath = (0, import_path3.join)(dir, filename);
3957
- const content = (0, import_fs3.readFileSync)(filepath, "utf-8");
4687
+ const filepath = (0, import_path5.join)(dir, filename);
4688
+ const content = (0, import_fs5.readFileSync)(filepath, "utf-8");
3958
4689
  const date = filename.replace(".md", "");
3959
4690
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
3960
4691
  const frontmatter = fmMatch ? fmMatch[1] : "";
@@ -4118,10 +4849,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4118
4849
  } catch {
4119
4850
  }
4120
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
+ }
4121
4862
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
4863
+ if (input.personalUser) {
4864
+ events = filterEventsByUser(events, input.personalUser);
4865
+ }
4122
4866
  const classified = classifyEvents(events);
4123
4867
  const signals = extractSignals(classified);
4124
4868
  const scores = computeScores(signals, input.worldmodelContent !== "");
4869
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
4125
4870
  const { patterns, meaning, move } = await interpretPatterns({
4126
4871
  signals,
4127
4872
  events: classified,
@@ -4129,6 +4874,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4129
4874
  lens,
4130
4875
  ai: input.ai,
4131
4876
  canonicalPatterns: input.canonicalPatterns,
4877
+ declaredVocabulary,
4132
4878
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
4133
4879
  });
4134
4880
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -4183,6 +4929,10 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4183
4929
  worldStack
4184
4930
  };
4185
4931
  }
4932
+ function filterEventsByUser(events, username) {
4933
+ const target = username.toLowerCase();
4934
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
4935
+ }
4186
4936
  function computeScores(signals, worldmodelLoaded) {
4187
4937
  const gate = DEFAULT_EVIDENCE_GATE;
4188
4938
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -4253,35 +5003,49 @@ var RADIANT_PACKAGE_VERSION = "0.0.0";
4253
5003
  createAnthropicAI,
4254
5004
  createMockAI,
4255
5005
  createMockGitHubAdapter,
5006
+ detectOrgExtendsSpec,
4256
5007
  discoverWorlds,
4257
5008
  emergent,
5009
+ extractDeclaredVocabulary,
4258
5010
  extractSignals,
4259
5011
  fetchDiscordActivity,
4260
5012
  fetchGitHubActivity,
4261
5013
  fetchGitHubOrgActivity,
5014
+ fetchLinearActivity,
4262
5015
  fetchNotionActivity,
4263
5016
  fetchSlackActivity,
5017
+ filterEventsByUser,
4264
5018
  formatActiveWorlds,
4265
5019
  formatDiscordSignalsForPrompt,
4266
5020
  formatExocortexForPrompt,
5021
+ formatLinearSignalsForPrompt,
4267
5022
  formatNotionSignalsForPrompt,
4268
5023
  formatPriorReadsForPrompt,
4269
5024
  formatScope,
4270
5025
  formatSlackSignalsForPrompt,
4271
5026
  formatTeamExocorticesForPrompt,
5027
+ getCacheDir,
4272
5028
  getLens,
5029
+ getRepoOrigin,
4273
5030
  interpretPatterns,
4274
5031
  isPresent,
4275
5032
  isScored,
4276
5033
  isSentinel,
4277
5034
  listLenses,
5035
+ loadExtendsConfig,
4278
5036
  loadPriorReads,
5037
+ matchDeclaredPattern,
5038
+ parseExtendsSpec,
5039
+ parseRemoteUrl,
4279
5040
  parseRepoScope,
4280
5041
  parseScope,
4281
5042
  presenceAverage,
4282
5043
  readExocortex,
5044
+ readOriginRemote,
4283
5045
  readTeamExocortices,
4284
5046
  render,
5047
+ resolveAllExtends,
5048
+ resolveExtendsSpec,
4285
5049
  scoreComposite,
4286
5050
  scoreCyber,
4287
5051
  scoreLife,