@neuroverseos/governance 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1670,6 +1670,262 @@ var init_notion = __esm({
1670
1670
  }
1671
1671
  });
1672
1672
 
1673
+ // src/radiant/adapters/linear.ts
1674
+ async function fetchLinearActivity(apiKey, options = {}) {
1675
+ const windowDays = options.windowDays ?? 14;
1676
+ const maxIssues = options.maxIssues ?? 200;
1677
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
1678
+ const sinceIso = since.toISOString();
1679
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1680
+ const teamFilter = options.teamIds && options.teamIds.length > 0 ? `team: { id: { in: [${options.teamIds.map((t) => JSON.stringify(t)).join(", ")}] } }` : "";
1681
+ const issuesQuery = `
1682
+ query RadiantIssues($since: DateTimeOrDuration!, $first: Int!) {
1683
+ issues(
1684
+ filter: {
1685
+ updatedAt: { gte: $since }
1686
+ ${teamFilter}
1687
+ }
1688
+ first: $first
1689
+ orderBy: updatedAt
1690
+ ) {
1691
+ nodes {
1692
+ id
1693
+ identifier
1694
+ title
1695
+ url
1696
+ createdAt
1697
+ updatedAt
1698
+ completedAt
1699
+ canceledAt
1700
+ state { name type }
1701
+ assignee { id name email }
1702
+ creator { id name }
1703
+ team { id name }
1704
+ project { id name }
1705
+ cycle { id number startsAt endsAt }
1706
+ comments(first: 20) {
1707
+ nodes { id body createdAt user { id name } }
1708
+ }
1709
+ }
1710
+ }
1711
+ }
1712
+ `;
1713
+ const cyclesQuery = `
1714
+ query RadiantCycles($since: DateTimeOrDuration!, $now: DateTimeOrDuration!) {
1715
+ cycles(
1716
+ filter: { endsAt: { gte: $since, lte: $now } }
1717
+ first: 20
1718
+ ) {
1719
+ nodes {
1720
+ id
1721
+ number
1722
+ startsAt
1723
+ endsAt
1724
+ issueCountHistory
1725
+ completedIssueCountHistory
1726
+ team { id name }
1727
+ }
1728
+ }
1729
+ }
1730
+ `;
1731
+ const [issuesResponse, cyclesResponse] = await Promise.all([
1732
+ fetchLinearGraphQL(apiKey, issuesQuery, {
1733
+ since: sinceIso,
1734
+ first: maxIssues
1735
+ }),
1736
+ fetchLinearGraphQL(apiKey, cyclesQuery, {
1737
+ since: sinceIso,
1738
+ now: nowIso
1739
+ })
1740
+ ]);
1741
+ const rawIssues = issuesResponse.issues?.nodes ?? [];
1742
+ const rawCycles = cyclesResponse.cycles?.nodes ?? [];
1743
+ const events = [];
1744
+ const assignees = /* @__PURE__ */ new Set();
1745
+ const projects = /* @__PURE__ */ new Map();
1746
+ let issuesCreated = 0;
1747
+ let issuesCompleted = 0;
1748
+ let issuesOpen = 0;
1749
+ let issuesStalled = 0;
1750
+ let commentsTotal = 0;
1751
+ const now = Date.now();
1752
+ const stallThresholdMs = 14 * 24 * 60 * 60 * 1e3;
1753
+ for (const issue of rawIssues) {
1754
+ const created = new Date(issue.createdAt);
1755
+ const updated = new Date(issue.updatedAt);
1756
+ const completed = issue.completedAt ? new Date(issue.completedAt) : null;
1757
+ const assigneeId = issue.assignee?.id ?? "unassigned";
1758
+ if (assigneeId !== "unassigned") assignees.add(assigneeId);
1759
+ if (issue.project) {
1760
+ projects.set(issue.project.name, (projects.get(issue.project.name) ?? 0) + 1);
1761
+ }
1762
+ const actor = {
1763
+ id: assigneeId,
1764
+ kind: "human",
1765
+ name: issue.assignee?.name ?? "unassigned"
1766
+ };
1767
+ if (created >= since) {
1768
+ issuesCreated++;
1769
+ events.push({
1770
+ id: `linear-created-${issue.id}`,
1771
+ timestamp: issue.createdAt,
1772
+ actor: {
1773
+ id: issue.creator?.id ?? "unknown",
1774
+ kind: "human",
1775
+ name: issue.creator?.name ?? "unknown"
1776
+ },
1777
+ kind: "issue_created",
1778
+ content: `[${issue.identifier}] ${issue.title}`,
1779
+ metadata: {
1780
+ issueId: issue.id,
1781
+ url: issue.url,
1782
+ team: issue.team?.name,
1783
+ project: issue.project?.name,
1784
+ state: issue.state?.name
1785
+ }
1786
+ });
1787
+ }
1788
+ if (completed && completed >= since) {
1789
+ issuesCompleted++;
1790
+ events.push({
1791
+ id: `linear-completed-${issue.id}`,
1792
+ timestamp: issue.completedAt,
1793
+ actor,
1794
+ kind: "issue_completed",
1795
+ content: `[${issue.identifier}] ${issue.title}`,
1796
+ metadata: {
1797
+ issueId: issue.id,
1798
+ url: issue.url,
1799
+ team: issue.team?.name,
1800
+ project: issue.project?.name,
1801
+ cycleDays: issue.cycle?.startsAt && issue.completedAt ? Math.round(
1802
+ (new Date(issue.completedAt).getTime() - new Date(issue.cycle.startsAt).getTime()) / (24 * 60 * 60 * 1e3)
1803
+ ) : null
1804
+ }
1805
+ });
1806
+ }
1807
+ if (!completed && !issue.canceledAt) {
1808
+ issuesOpen++;
1809
+ const isInProgress = issue.state?.type === "started";
1810
+ const idleMs = now - updated.getTime();
1811
+ if (isInProgress && idleMs > stallThresholdMs) issuesStalled++;
1812
+ }
1813
+ for (const comment of issue.comments?.nodes ?? []) {
1814
+ const commentedAt = new Date(comment.createdAt);
1815
+ if (commentedAt < since) continue;
1816
+ commentsTotal++;
1817
+ events.push({
1818
+ id: `linear-comment-${comment.id}`,
1819
+ timestamp: comment.createdAt,
1820
+ actor: {
1821
+ id: comment.user?.id ?? "unknown",
1822
+ kind: "human",
1823
+ name: comment.user?.name ?? "unknown"
1824
+ },
1825
+ kind: "issue_comment",
1826
+ content: comment.body.slice(0, 280),
1827
+ metadata: {
1828
+ issueId: issue.id,
1829
+ issueIdentifier: issue.identifier,
1830
+ url: issue.url
1831
+ }
1832
+ });
1833
+ }
1834
+ }
1835
+ let cycleCompletionRate = null;
1836
+ if (rawCycles.length > 0) {
1837
+ const rates = [];
1838
+ for (const cycle of rawCycles) {
1839
+ const committed = cycle.issueCountHistory?.at(0) ?? 0;
1840
+ const completed = cycle.completedIssueCountHistory?.at(-1) ?? 0;
1841
+ if (committed > 0) rates.push(completed / committed);
1842
+ }
1843
+ if (rates.length > 0) {
1844
+ cycleCompletionRate = Math.round(rates.reduce((a, b) => a + b, 0) / rates.length * 100) / 100;
1845
+ }
1846
+ }
1847
+ const topProjects = [...projects.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
1848
+ const signals = {
1849
+ issuesCreated,
1850
+ issuesCompleted,
1851
+ issuesOpen,
1852
+ issuesStalled,
1853
+ cycleCompletionRate,
1854
+ uniqueAssignees: assignees.size,
1855
+ commentsTotal,
1856
+ topProjects
1857
+ };
1858
+ events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
1859
+ return { events, signals };
1860
+ }
1861
+ function formatLinearSignalsForPrompt(signals) {
1862
+ if (signals.issuesCreated === 0 && signals.issuesCompleted === 0 && signals.issuesOpen === 0) {
1863
+ return "";
1864
+ }
1865
+ const lines = [
1866
+ "## Linear Activity (planned work vs. shipped outcome)",
1867
+ "",
1868
+ `${signals.issuesCreated} issues created, ${signals.issuesCompleted} completed in window.`,
1869
+ `${signals.issuesOpen} issues still open.`
1870
+ ];
1871
+ if (signals.issuesStalled > 0) {
1872
+ lines.push(
1873
+ `${signals.issuesStalled} in-progress issues haven't moved in 14+ days (stalled).`
1874
+ );
1875
+ }
1876
+ if (signals.cycleCompletionRate !== null) {
1877
+ const pct = Math.round(signals.cycleCompletionRate * 100);
1878
+ lines.push(`Cycles ended in window completed ${pct}% of what was committed.`);
1879
+ }
1880
+ if (signals.uniqueAssignees > 0) {
1881
+ lines.push(`${signals.uniqueAssignees} unique assignees active.`);
1882
+ }
1883
+ if (signals.commentsTotal > 0) {
1884
+ lines.push(`${signals.commentsTotal} comments across issues in window.`);
1885
+ }
1886
+ if (signals.topProjects.length > 0) {
1887
+ lines.push(`Most active projects: ${signals.topProjects.join(", ")}.`);
1888
+ }
1889
+ lines.push("");
1890
+ lines.push("Linear is where the team states what it will build.");
1891
+ lines.push("GitHub is where the team reveals what actually got built.");
1892
+ lines.push("Low completion rate + high creation rate = planning faster than shipping.");
1893
+ lines.push("High stalled count = commitments made but not honored.");
1894
+ lines.push("Compare Linear signals against GitHub to find the stated-vs-shipped gap.");
1895
+ return lines.join("\n");
1896
+ }
1897
+ async function fetchLinearGraphQL(apiKey, query, variables) {
1898
+ const res = await fetch("https://api.linear.app/graphql", {
1899
+ method: "POST",
1900
+ headers: {
1901
+ // Linear accepts the raw API key in Authorization with no "Bearer" prefix.
1902
+ Authorization: apiKey,
1903
+ "Content-Type": "application/json"
1904
+ },
1905
+ body: JSON.stringify({ query, variables })
1906
+ });
1907
+ if (!res.ok) {
1908
+ throw new Error(
1909
+ `Linear API error ${res.status}: ${(await res.text()).slice(0, 300)}`
1910
+ );
1911
+ }
1912
+ const json = await res.json();
1913
+ if (json.errors && json.errors.length > 0) {
1914
+ throw new Error(
1915
+ `Linear GraphQL errors: ${json.errors.map((e) => e.message).join("; ")}`
1916
+ );
1917
+ }
1918
+ if (!json.data) {
1919
+ throw new Error("Linear API returned no data");
1920
+ }
1921
+ return json.data;
1922
+ }
1923
+ var init_linear = __esm({
1924
+ "src/radiant/adapters/linear.ts"() {
1925
+ "use strict";
1926
+ }
1927
+ });
1928
+
1673
1929
  // src/radiant/core/git-remote.ts
1674
1930
  function resolveGitConfigPath(repoDir) {
1675
1931
  const dotGit = (0, import_path.join)(repoDir, ".git");
@@ -3799,6 +4055,144 @@ var init_signals = __esm({
3799
4055
  }
3800
4056
  });
3801
4057
 
4058
+ // src/radiant/core/vocabulary.ts
4059
+ function extractDeclaredVocabulary(worldmodelContent) {
4060
+ const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
4061
+ (b) => parseBehavior(b, "aligned")
4062
+ );
4063
+ const drift = extractSection(worldmodelContent, "Drift Behaviors").map(
4064
+ (b) => parseBehavior(b, "drift")
4065
+ );
4066
+ const allNames = [...aligned, ...drift].map((p) => p.name);
4067
+ return { aligned, drift, allNames };
4068
+ }
4069
+ function extractSection(content, header) {
4070
+ const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4071
+ const pattern = new RegExp(
4072
+ `##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
4073
+ "i"
4074
+ );
4075
+ const match = content.match(pattern);
4076
+ if (!match) return [];
4077
+ const body = match[1];
4078
+ const bullets = body.match(/^[-*]\s+.+$/gm);
4079
+ if (!bullets) return [];
4080
+ return bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()).filter((b) => b.length > 0 && !b.startsWith("<!--"));
4081
+ }
4082
+ function parseBehavior(bullet, kind) {
4083
+ const explicit = bullet.match(
4084
+ /^`?([a-z][a-z0-9_]*)`?\s+[—\u2014-]\s+(.+)$/i
4085
+ );
4086
+ if (explicit && isSnakeCase(explicit[1])) {
4087
+ return {
4088
+ name: explicit[1].toLowerCase(),
4089
+ prose: explicit[2].trim(),
4090
+ kind
4091
+ };
4092
+ }
4093
+ return { name: snakeCaseName(bullet), prose: bullet, kind };
4094
+ }
4095
+ function isSnakeCase(s) {
4096
+ return /^[a-z][a-z0-9_]*$/.test(s);
4097
+ }
4098
+ function snakeCaseName(s) {
4099
+ const base = s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
4100
+ if (base.length <= 60) return base;
4101
+ const truncated = base.slice(0, 60);
4102
+ const lastUnderscore = truncated.lastIndexOf("_");
4103
+ return lastUnderscore > 20 ? truncated.slice(0, lastUnderscore) : truncated;
4104
+ }
4105
+ function matchDeclaredPattern(candidateName, candidateDescription, vocabulary) {
4106
+ const candidateText = `${candidateName.replace(/_/g, " ")} ${candidateDescription}`;
4107
+ const candidateWords = contentWords(candidateText);
4108
+ if (candidateWords.size === 0) return null;
4109
+ let best = null;
4110
+ for (const pattern of [...vocabulary.aligned, ...vocabulary.drift]) {
4111
+ const proseWords = contentWords(pattern.prose);
4112
+ if (proseWords.size === 0) continue;
4113
+ let shared = 0;
4114
+ for (const w of proseWords) {
4115
+ if (candidateWords.has(w)) shared++;
4116
+ }
4117
+ const coverage = shared / proseWords.size;
4118
+ if (shared >= 2 && coverage >= 0.3) {
4119
+ if (!best || coverage > best.score) {
4120
+ best = { pattern, score: coverage };
4121
+ }
4122
+ }
4123
+ }
4124
+ return best?.pattern ?? null;
4125
+ }
4126
+ function contentWords(text) {
4127
+ const words = text.toLowerCase().match(/[a-z][a-z0-9_]+/g) ?? [];
4128
+ return new Set(words.filter((w) => w.length > 3 && !STOPWORDS.has(w)));
4129
+ }
4130
+ var STOPWORDS;
4131
+ var init_vocabulary = __esm({
4132
+ "src/radiant/core/vocabulary.ts"() {
4133
+ "use strict";
4134
+ STOPWORDS = /* @__PURE__ */ new Set([
4135
+ "about",
4136
+ "after",
4137
+ "against",
4138
+ "among",
4139
+ "around",
4140
+ "because",
4141
+ "been",
4142
+ "before",
4143
+ "being",
4144
+ "between",
4145
+ "both",
4146
+ "could",
4147
+ "does",
4148
+ "doing",
4149
+ "during",
4150
+ "each",
4151
+ "from",
4152
+ "further",
4153
+ "have",
4154
+ "having",
4155
+ "into",
4156
+ "itself",
4157
+ "most",
4158
+ "nor",
4159
+ "only",
4160
+ "other",
4161
+ "over",
4162
+ "same",
4163
+ "should",
4164
+ "some",
4165
+ "such",
4166
+ "than",
4167
+ "that",
4168
+ "their",
4169
+ "them",
4170
+ "then",
4171
+ "there",
4172
+ "these",
4173
+ "they",
4174
+ "this",
4175
+ "those",
4176
+ "through",
4177
+ "under",
4178
+ "until",
4179
+ "very",
4180
+ "were",
4181
+ "what",
4182
+ "when",
4183
+ "where",
4184
+ "which",
4185
+ "while",
4186
+ "will",
4187
+ "with",
4188
+ "without",
4189
+ "would",
4190
+ "your",
4191
+ "yours"
4192
+ ]);
4193
+ }
4194
+ });
4195
+
3802
4196
  // src/radiant/types.ts
3803
4197
  function isScored(s) {
3804
4198
  return typeof s === "number";
@@ -3850,7 +4244,11 @@ var init_math = __esm({
3850
4244
  async function interpretPatterns(input) {
3851
4245
  const prompt = buildInterpretationPrompt(input);
3852
4246
  const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
3853
- const parsed = parseInterpretation(raw, input.canonicalPatterns ?? []);
4247
+ const canonicalNames = [
4248
+ ...input.canonicalPatterns ?? [],
4249
+ ...input.declaredVocabulary?.allNames ?? []
4250
+ ];
4251
+ const parsed = parseInterpretation(raw, canonicalNames, input.declaredVocabulary);
3854
4252
  return {
3855
4253
  patterns: parsed.patterns,
3856
4254
  meaning: parsed.meaning,
@@ -3861,8 +4259,10 @@ async function interpretPatterns(input) {
3861
4259
  function buildInterpretationPrompt(input) {
3862
4260
  const signalSummary = formatSignalSummary(input.signals);
3863
4261
  const eventSample = formatEventSample(input.events, 30);
3864
- const canonicalList = (input.canonicalPatterns ?? []).length > 0 ? `Patterns the organization has already named (use these names if you see them):
3865
- ${input.canonicalPatterns.map((p) => `- ${p}`).join("\n")}` : "No patterns have been named yet. Everything you observe is new.";
4262
+ const canonicalList = formatDeclaredVocabulary(
4263
+ input.declaredVocabulary,
4264
+ input.canonicalPatterns ?? []
4265
+ );
3866
4266
  const compressedWorld = compressWorldmodel(input.worldmodelContent);
3867
4267
  const cl = compressLens(input.lens);
3868
4268
  const frame = input.lens.primary_frame;
@@ -3973,6 +4373,44 @@ Only recommend a move when the evidence actually calls for one.
3973
4373
  Do NOT use these phrases anywhere in your output:
3974
4374
  ${forbiddenList}`;
3975
4375
  }
4376
+ function formatDeclaredVocabulary(vocabulary, extraNames) {
4377
+ const aligned = vocabulary?.aligned ?? [];
4378
+ const drift = vocabulary?.drift ?? [];
4379
+ if (aligned.length === 0 && drift.length === 0 && extraNames.length === 0) {
4380
+ return 'No patterns have been named yet. Everything you observe is new \u2014 mark it type: "candidate".';
4381
+ }
4382
+ const parts = [];
4383
+ parts.push("## Declared vocabulary (use these names when you see matching evidence)");
4384
+ parts.push("");
4385
+ parts.push(
4386
+ '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.'
4387
+ );
4388
+ parts.push("");
4389
+ if (aligned.length > 0) {
4390
+ parts.push("### Aligned behaviors (positive patterns)");
4391
+ for (const p of aligned) {
4392
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
4393
+ }
4394
+ parts.push("");
4395
+ }
4396
+ if (drift.length > 0) {
4397
+ parts.push("### Drift behaviors (negative patterns)");
4398
+ for (const p of drift) {
4399
+ parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
4400
+ }
4401
+ parts.push("");
4402
+ }
4403
+ if (extraNames.length > 0) {
4404
+ parts.push(
4405
+ `Additional canonical names (from prior runs or caller): ${extraNames.join(", ")}`
4406
+ );
4407
+ parts.push("");
4408
+ }
4409
+ parts.push(
4410
+ 'If you observe something genuinely new that matches NO declared pattern, mark it type: "candidate" with a freshly-invented snake_case name.'
4411
+ );
4412
+ return parts.join("\n");
4413
+ }
3976
4414
  function formatSignalSummary(signals) {
3977
4415
  const lines = [];
3978
4416
  const domains = ["life", "cyber", "joint"];
@@ -3998,7 +4436,7 @@ function formatEventSample(events, maxEvents) {
3998
4436
  "${content}"`;
3999
4437
  }).join("\n");
4000
4438
  }
4001
- function parseInterpretation(raw, canonicalNames) {
4439
+ function parseInterpretation(raw, canonicalNames, vocabulary) {
4002
4440
  let meaning = "";
4003
4441
  let move = "";
4004
4442
  let patternsArray = [];
@@ -4028,14 +4466,23 @@ function parseInterpretation(raw, canonicalNames) {
4028
4466
  const patterns = [];
4029
4467
  for (const item of patternsArray) {
4030
4468
  if (!isPatternLike(item)) continue;
4031
- const nameStr = String(item.name ?? "unnamed");
4469
+ const rawName = String(item.name ?? "unnamed");
4470
+ const description = String(item.description ?? "");
4032
4471
  const ev = item.evidence;
4033
- const isCanonical = item.type === "canonical" || canonicalSet.has(nameStr.toLowerCase());
4472
+ let name = rawName;
4473
+ let isCanonical = item.type === "canonical" || canonicalSet.has(rawName.toLowerCase());
4474
+ if (!isCanonical && vocabulary) {
4475
+ const matched = matchDeclaredPattern(rawName, description, vocabulary);
4476
+ if (matched) {
4477
+ name = matched.name;
4478
+ isCanonical = true;
4479
+ }
4480
+ }
4034
4481
  patterns.push({
4035
- name: nameStr,
4482
+ name,
4036
4483
  type: isCanonical ? "canonical" : "candidate",
4037
- declaredAs: isCanonical ? nameStr : void 0,
4038
- description: String(item.description ?? ""),
4484
+ declaredAs: isCanonical ? name : void 0,
4485
+ description,
4039
4486
  evidence: {
4040
4487
  signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
4041
4488
  events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
@@ -4053,6 +4500,7 @@ var init_patterns = __esm({
4053
4500
  "src/radiant/core/patterns.ts"() {
4054
4501
  "use strict";
4055
4502
  init_compress();
4503
+ init_vocabulary();
4056
4504
  }
4057
4505
  });
4058
4506
 
@@ -4371,10 +4819,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4371
4819
  } catch {
4372
4820
  }
4373
4821
  }
4822
+ const linearKey = process.env.LINEAR_API_KEY;
4823
+ if (linearKey) {
4824
+ try {
4825
+ const linear = await fetchLinearActivity(linearKey, { windowDays });
4826
+ events.push(...linear.events);
4827
+ adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
4828
+ activeAdapters.push("linear");
4829
+ } catch {
4830
+ }
4831
+ }
4374
4832
  events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
4833
+ if (input.personalUser) {
4834
+ events = filterEventsByUser(events, input.personalUser);
4835
+ }
4375
4836
  const classified = classifyEvents(events);
4376
4837
  const signals = extractSignals(classified);
4377
4838
  const scores = computeScores(signals, input.worldmodelContent !== "");
4839
+ const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
4378
4840
  const { patterns, meaning, move } = await interpretPatterns({
4379
4841
  signals,
4380
4842
  events: classified,
@@ -4382,6 +4844,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4382
4844
  lens,
4383
4845
  ai: input.ai,
4384
4846
  canonicalPatterns: input.canonicalPatterns,
4847
+ declaredVocabulary,
4385
4848
  statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
4386
4849
  });
4387
4850
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
@@ -4436,6 +4899,10 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
4436
4899
  worldStack
4437
4900
  };
4438
4901
  }
4902
+ function filterEventsByUser(events, username) {
4903
+ const target = username.toLowerCase();
4904
+ return events.filter((e) => e.actor.id.toLowerCase() === target);
4905
+ }
4439
4906
  function computeScores(signals, worldmodelLoaded) {
4440
4907
  const gate = DEFAULT_EVIDENCE_GATE;
4441
4908
  const lifeSignals = signals.filter((s) => s.domain === "life");
@@ -4491,12 +4958,14 @@ var init_emergent = __esm({
4491
4958
  init_discord();
4492
4959
  init_slack();
4493
4960
  init_notion();
4961
+ init_linear();
4494
4962
  init_discovery();
4495
4963
  init_exocortex();
4496
4964
  init_palace();
4497
4965
  init_compress();
4498
4966
  init_governance();
4499
4967
  init_signals();
4968
+ init_vocabulary();
4500
4969
  init_math();
4501
4970
  init_patterns();
4502
4971
  init_renderer();
@@ -4818,7 +5287,9 @@ var init_server = __esm({
4818
5287
  // src/cli/radiant.ts
4819
5288
  var radiant_exports = {};
4820
5289
  __export(radiant_exports, {
4821
- main: () => main
5290
+ checkScopeConsent: () => checkScopeConsent,
5291
+ main: () => main,
5292
+ parseArgs: () => parseArgs
4822
5293
  });
4823
5294
  module.exports = __toCommonJS(radiant_exports);
4824
5295
  var import_fs7 = require("fs");
@@ -4831,6 +5302,7 @@ init_exocortex();
4831
5302
  init_lenses();
4832
5303
  init_discovery();
4833
5304
  var RED = "\x1B[31m";
5305
+ var GREEN = "\x1B[32m";
4834
5306
  var DIM = "\x1B[2m";
4835
5307
  var BOLD = "\x1B[1m";
4836
5308
  var YELLOW = "\x1B[33m";
@@ -4838,6 +5310,9 @@ var RESET = "\x1B[0m";
4838
5310
  var USAGE = `
4839
5311
  ${BOLD}neuroverse radiant${RESET} \u2014 behavioral intelligence for collaboration systems
4840
5312
 
5313
+ ${BOLD}Setup:${RESET}
5314
+ init Scaffold a Mind Palace in the current directory
5315
+
4841
5316
  ${BOLD}Stage A (voice layer):${RESET}
4842
5317
  think Send a query through the worldmodel + lens \u2192 AI-framed response
4843
5318
 
@@ -4848,13 +5323,27 @@ ${BOLD}Stage B (behavioral analysis, coming soon):${RESET}
4848
5323
  lenses List or describe available rendering lenses
4849
5324
 
4850
5325
  ${BOLD}Usage:${RESET}
5326
+ neuroverse radiant init (scaffolds ./mind-palace/)
5327
+ neuroverse radiant init ./my-palace (custom path)
4851
5328
  neuroverse radiant think --lens auki-builder --worlds ./worlds/ --query "What is our biggest risk?"
4852
5329
  neuroverse radiant think --lens auki-builder --worlds ./worlds/ < prompt.txt
4853
5330
  neuroverse radiant emergent aukiverse/posemesh --lens auki-builder --worlds ./worlds/
4854
5331
  neuroverse radiant emergent aukiverse/posemesh --lens auki-builder --worlds ./worlds/ --exocortex ~/exocortex/
5332
+ neuroverse radiant emergent aukilabs/posemesh --personal --user alice
5333
+ neuroverse radiant emergent aukilabs/ --entire-org --lens auki-builder --worlds ./worlds/
4855
5334
  neuroverse radiant lenses list
4856
5335
  neuroverse radiant lenses describe auki-builder
4857
5336
 
5337
+ ${BOLD}Read modes:${RESET}
5338
+ ${BOLD}Default (team):${RESET} reads all contributors in the given scope.
5339
+ ${BOLD}--personal --user <login>:${RESET} reads ONLY that user's activity.
5340
+ A local, private facilitator \u2014 no one else is observed. Works against
5341
+ any scope; an org scope with --personal is fine.
5342
+ ${BOLD}--entire-org (gated):${RESET} org-wide scope observes every contributor
5343
+ across every repo. This is a global-observer stance and is opt-in.
5344
+ \`radiant emergent <org>/\` without --entire-org will refuse and show
5345
+ you the three choices (single repo, --personal, or explicit opt-in).
5346
+
4858
5347
  ${BOLD}Auto-discovery:${RESET}
4859
5348
  You do not need to clone the target repo.
4860
5349
 
@@ -4873,6 +5362,7 @@ ${BOLD}Environment:${RESET}
4873
5362
  RADIANT_LENS Default lens id (overridden by --lens)
4874
5363
  RADIANT_MODEL AI model override (default: claude-sonnet-4-20250514)
4875
5364
  RADIANT_EXOCORTEX Default exocortex directory (overridden by --exocortex)
5365
+ RADIANT_USER Default personal-mode user (overridden by --user)
4876
5366
  `.trim();
4877
5367
  function parseArgs(argv) {
4878
5368
  const result = {
@@ -4884,8 +5374,12 @@ function parseArgs(argv) {
4884
5374
  exocortex: void 0,
4885
5375
  teamExocortex: void 0,
4886
5376
  view: void 0,
5377
+ user: void 0,
5378
+ personal: false,
5379
+ entireOrg: false,
4887
5380
  json: false,
4888
5381
  help: false,
5382
+ force: false,
4889
5383
  rest: []
4890
5384
  };
4891
5385
  let i = 0;
@@ -4917,9 +5411,22 @@ function parseArgs(argv) {
4917
5411
  case "--view":
4918
5412
  result.view = argv[++i];
4919
5413
  break;
5414
+ case "--user":
5415
+ result.user = argv[++i];
5416
+ break;
5417
+ case "--personal":
5418
+ result.personal = true;
5419
+ break;
5420
+ case "--entire-org":
5421
+ result.entireOrg = true;
5422
+ break;
4920
5423
  case "--json":
4921
5424
  result.json = true;
4922
5425
  break;
5426
+ case "--force":
5427
+ case "-f":
5428
+ result.force = true;
5429
+ break;
4923
5430
  case "--help":
4924
5431
  case "-h":
4925
5432
  result.help = true;
@@ -5077,6 +5584,15 @@ ${DIM}Model: ${model ?? "claude-sonnet-4-20250514 (default)"}${RESET}
5077
5584
  process.exit(2);
5078
5585
  }
5079
5586
  }
5587
+ function checkScopeConsent(input) {
5588
+ if (input.personal && !input.resolvedUser) {
5589
+ return { ok: false, reason: "personal_requires_user" };
5590
+ }
5591
+ if (input.scope.type === "org" && !input.entireOrg && !input.personal) {
5592
+ return { ok: false, reason: "org_requires_opt_in" };
5593
+ }
5594
+ return { ok: true };
5595
+ }
5080
5596
  async function cmdEmergent(args) {
5081
5597
  const scopeStr = args.rest[0];
5082
5598
  if (!scopeStr) {
@@ -5087,6 +5603,43 @@ async function cmdEmergent(args) {
5087
5603
  process.exit(1);
5088
5604
  }
5089
5605
  const scope = parseScope(scopeStr);
5606
+ const personalUser = args.user ?? process.env.RADIANT_USER;
5607
+ const consent = checkScopeConsent({
5608
+ scope,
5609
+ personal: args.personal,
5610
+ entireOrg: args.entireOrg,
5611
+ resolvedUser: personalUser
5612
+ });
5613
+ if (!consent.ok) {
5614
+ if (consent.reason === "personal_requires_user") {
5615
+ process.stderr.write(
5616
+ `${RED}Error:${RESET} --personal requires a GitHub username.
5617
+ ${DIM}Pass --user <login> or set RADIANT_USER. Radiant will read
5618
+ only that user's activity \u2014 no one else is observed.${RESET}
5619
+ `
5620
+ );
5621
+ } else {
5622
+ process.stderr.write(
5623
+ `${YELLOW}\u26A0${RESET} ${BOLD}"${scope.owner}" is an org-wide scope.${RESET}
5624
+
5625
+ ${DIM}This reads activity across ALL repos in the org \u2014 a global-observer
5626
+ pattern that some teams consider offside with decentralization and
5627
+ cognitive-liberty principles. It's opt-in for that reason.${RESET}
5628
+
5629
+ Three ways forward:
5630
+ ${GREEN}1.${RESET} Scope to a single repo (recommended default):
5631
+ radiant emergent ${scope.owner}/<repo>
5632
+
5633
+ ${GREEN}2.${RESET} Read only your own activity (personal mode):
5634
+ radiant emergent ${scope.owner}/ --personal --user <your-login>
5635
+
5636
+ ${GREEN}3.${RESET} Explicitly opt in to org-wide observation:
5637
+ radiant emergent ${scope.owner}/ --entire-org
5638
+ `
5639
+ );
5640
+ }
5641
+ process.exit(1);
5642
+ }
5090
5643
  const lensId = args.lens ?? process.env.RADIANT_LENS;
5091
5644
  if (!lensId) {
5092
5645
  process.stderr.write(
@@ -5135,8 +5688,11 @@ ${DIM}Set it to a GitHub PAT with repo read access.${RESET}
5135
5688
  const ctx = readExocortex(exocortexPath);
5136
5689
  exocortexStatus = summarizeExocortex(ctx);
5137
5690
  }
5691
+ const scopeLabel = scope.type === "org" ? scope.owner + " (entire org)" : scope.owner + "/" + scope.repo;
5692
+ const modeLabel = args.personal ? `personal \u2014 only ${personalUser}'s activity` : "team \u2014 all contributors";
5138
5693
  process.stderr.write(
5139
- `${DIM}Scope: ${scope.type === "org" ? scope.owner + " (entire org)" : scope.owner + "/" + scope.repo}${RESET}
5694
+ `${DIM}Scope: ${scopeLabel}${RESET}
5695
+ ${DIM}Mode: ${modeLabel}${RESET}
5140
5696
  ${DIM}View: ${view}${RESET}
5141
5697
  ${DIM}Lens: ${lensId}${RESET}
5142
5698
  ${DIM}Model: ${model ?? "claude-sonnet-4-20250514 (default)"}${RESET}
@@ -5152,7 +5708,8 @@ ${DIM}Fetching activity...${RESET}
5152
5708
  lensId,
5153
5709
  ai,
5154
5710
  windowDays: 14,
5155
- exocortexPath: exocortexPath || void 0
5711
+ exocortexPath: exocortexPath || void 0,
5712
+ personalUser: args.personal ? personalUser : void 0
5156
5713
  });
5157
5714
  if (!result.voiceClean) {
5158
5715
  process.stderr.write(
@@ -5254,6 +5811,204 @@ ${DIM}Use: lenses list | lenses describe <id>${RESET}
5254
5811
  );
5255
5812
  process.exit(1);
5256
5813
  }
5814
+ var MIND_PALACE_FILES = {
5815
+ "README.md": `# Mind Palace
5816
+
5817
+ This is your Mind Palace \u2014 structured external memory that gives Radiant
5818
+ (and any agent you wire into it) persistent context about who you are,
5819
+ what you're working on, and what "on track" means for you.
5820
+
5821
+ Radiant reads these files before every run and writes each read back into
5822
+ \`reads/\`. Over time, \`knowledge.md\` accumulates what's persisted and what
5823
+ hasn't \u2014 the feedback loop that turns raw activity into named behavior.
5824
+
5825
+ ## Files
5826
+
5827
+ - \`attention.md\` \u2014 what you're focused on **right now**
5828
+ - \`goals.md\` \u2014 what you're working toward
5829
+ - \`sprint.md\` \u2014 this week's focus
5830
+ - \`identity.md\` \u2014 who you are, what you value
5831
+ - \`worldmodels/\` \u2014 your thinking constitutions (drift + aligned behaviors)
5832
+ - \`reads/\` \u2014 dated Radiant reads (written by \`radiant emergent\`)
5833
+ - \`knowledge.md\` \u2014 accumulated pattern persistence across reads
5834
+
5835
+ ## How to use
5836
+
5837
+ 1. Fill in \`attention.md\`, \`goals.md\`, \`sprint.md\`, \`identity.md\` with
5838
+ your own words. A sentence each is enough to start \u2014 the files grow
5839
+ with you.
5840
+ 2. Edit \`worldmodels/starter.worldmodel.md\`: add a few aligned behaviors
5841
+ (what on-track looks like) and drift behaviors (what off-track looks
5842
+ like). The sharper these are, the sharper Radiant's reads.
5843
+ 3. Run \`neuroverse radiant emergent <owner/repo> --mind-palace .\` against
5844
+ the repo you want read. Radiant compares your stated intent (these
5845
+ files) to your observed activity (GitHub) and names the gap.
5846
+
5847
+ Edit freely. These files are yours.
5848
+ `,
5849
+ "attention.md": `# Attention
5850
+
5851
+ <!--
5852
+ What are you focused on RIGHT NOW? One paragraph. Updated as you shift.
5853
+ This is the file an AI agent reads at the start of a session to know
5854
+ what to help with today.
5855
+ -->
5856
+
5857
+ `,
5858
+ "goals.md": `# Goals
5859
+
5860
+ <!--
5861
+ What are you working toward? Bullet points welcome.
5862
+ Longer horizon than attention \u2014 months, not days.
5863
+ -->
5864
+
5865
+ -
5866
+ `,
5867
+ "sprint.md": `# Sprint
5868
+
5869
+ <!--
5870
+ This week's focus. What do you want to ship or finish?
5871
+ Keep it short \u2014 five bullets max.
5872
+ -->
5873
+
5874
+ -
5875
+ `,
5876
+ "identity.md": `# Identity
5877
+
5878
+ <!--
5879
+ Who are you, what do you value, how do you work?
5880
+ This is the context an agent needs to not treat you like a stranger
5881
+ every time. Write it in your own voice.
5882
+ -->
5883
+
5884
+ `,
5885
+ "knowledge.md": `# Knowledge
5886
+
5887
+ <!--
5888
+ Radiant writes accumulated pattern persistence here across reads.
5889
+ Leave this file empty on day one \u2014 it fills up as \`radiant emergent\`
5890
+ runs accumulate.
5891
+ -->
5892
+
5893
+ `,
5894
+ "reads/.gitkeep": "",
5895
+ "worldmodels/starter.worldmodel.md": `# Starter Worldmodel
5896
+
5897
+ <!--
5898
+ Your thinking constitution. Radiant reads this to understand what
5899
+ "aligned" and "drift" mean for your work.
5900
+
5901
+ The sharper the Aligned/Drift Behaviors, the sharper Radiant's reads.
5902
+ When Radiant detects something matching a drift behavior below, it
5903
+ labels it with THAT name \u2014 it doesn't invent new ones.
5904
+ -->
5905
+
5906
+ ## Mission
5907
+
5908
+ <!-- One sentence. What are you doing in the world? -->
5909
+
5910
+
5911
+ ## Invariants
5912
+
5913
+ <!--
5914
+ Non-negotiable rules. If a decision violates one, it's blocked.
5915
+ Keep this list short \u2014 3 to 6 items. Each should be a hard no.
5916
+ -->
5917
+
5918
+ -
5919
+
5920
+ ## Aligned Behaviors
5921
+
5922
+ <!--
5923
+ What "on track" looks like. One per line, phrased as a behavior.
5924
+ Radiant will use these as canonical pattern names when it sees
5925
+ matching evidence in your activity.
5926
+
5927
+ Example:
5928
+ - ships partial-but-working features rather than waiting for the full stack
5929
+ - writes decisions down before acting on them
5930
+ -->
5931
+
5932
+ -
5933
+
5934
+ ## Drift Behaviors
5935
+
5936
+ <!--
5937
+ What "off track" looks like. Same format as Aligned.
5938
+ When Radiant detects drift, it will label it with these names \u2014 not
5939
+ make up new ones.
5940
+
5941
+ Example:
5942
+ - shipping pace outruns strategic decision-making
5943
+ - architecture decisions surface without context about why
5944
+ -->
5945
+
5946
+ -
5947
+
5948
+ ## Signals
5949
+
5950
+ <!--
5951
+ Observable quantities you care about. Radiant scores activity
5952
+ against these \u2014 5 to 7 is the sweet spot.
5953
+
5954
+ Example:
5955
+ - shipping_velocity
5956
+ - decision_ownership
5957
+ - storytelling_cadence
5958
+ -->
5959
+
5960
+ -
5961
+
5962
+ ## Decision Priorities
5963
+
5964
+ <!--
5965
+ When tradeoffs appear, these resolve them. Format: "A > B".
5966
+
5967
+ Example:
5968
+ - correctness > speed
5969
+ - clarity > cleverness
5970
+ -->
5971
+
5972
+ -
5973
+ `
5974
+ };
5975
+ async function cmdInit(args) {
5976
+ const targetDir = (0, import_path7.resolve)(args.rest[0] ?? "./mind-palace");
5977
+ const existed = (0, import_fs7.existsSync)(targetDir);
5978
+ if (existed && !args.force) {
5979
+ const entries = (0, import_fs7.readdirSync)(targetDir);
5980
+ if (entries.length > 0) {
5981
+ process.stderr.write(
5982
+ `${RED}Error:${RESET} ${targetDir} already exists and is not empty.
5983
+ ${DIM}Use --force to write into it anyway (existing files will be overwritten).${RESET}
5984
+ `
5985
+ );
5986
+ process.exit(1);
5987
+ }
5988
+ }
5989
+ (0, import_fs7.mkdirSync)(targetDir, { recursive: true });
5990
+ (0, import_fs7.mkdirSync)((0, import_path7.join)(targetDir, "reads"), { recursive: true });
5991
+ (0, import_fs7.mkdirSync)((0, import_path7.join)(targetDir, "worldmodels"), { recursive: true });
5992
+ for (const [relPath, content] of Object.entries(MIND_PALACE_FILES)) {
5993
+ const fullPath = (0, import_path7.join)(targetDir, relPath);
5994
+ (0, import_fs7.mkdirSync)((0, import_path7.resolve)(fullPath, ".."), { recursive: true });
5995
+ (0, import_fs7.writeFileSync)(fullPath, content, "utf-8");
5996
+ }
5997
+ process.stdout.write(
5998
+ `${GREEN}\u2713${RESET} Mind Palace scaffolded at ${BOLD}${targetDir}${RESET}
5999
+
6000
+ ${DIM}Next steps:${RESET}
6001
+ 1. Edit ${targetDir}/attention.md \u2014 what you're focused on right now
6002
+ 2. Edit ${targetDir}/worldmodels/starter.worldmodel.md \u2014 add a few
6003
+ aligned and drift behaviors
6004
+ 3. Run: neuroverse radiant emergent <owner/repo> \\
6005
+ --worlds ${targetDir}/worldmodels \\
6006
+ --exocortex ${targetDir}
6007
+
6008
+ ${DIM}Files are yours. Edit freely.${RESET}
6009
+ `
6010
+ );
6011
+ }
5257
6012
  async function main(argv) {
5258
6013
  const args = parseArgs(argv);
5259
6014
  if (args.help || !args.subcommand) {
@@ -5261,6 +6016,8 @@ async function main(argv) {
5261
6016
  return;
5262
6017
  }
5263
6018
  switch (args.subcommand) {
6019
+ case "init":
6020
+ return cmdInit(args);
5264
6021
  case "think":
5265
6022
  return cmdThink(args);
5266
6023
  case "lenses":
@@ -5293,5 +6050,7 @@ async function main(argv) {
5293
6050
  }
5294
6051
  // Annotate the CommonJS export names for ESM import in node:
5295
6052
  0 && (module.exports = {
5296
- main
6053
+ checkScopeConsent,
6054
+ main,
6055
+ parseArgs
5297
6056
  });