@neuroverseos/governance 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,12 +3,51 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@neuroverseos/governance)](https://www.npmjs.com/package/@neuroverseos/governance)
4
4
  [![license](https://img.shields.io/npm/l/@neuroverseos/governance)](LICENSE.md)
5
5
 
6
- **Control what AI can doand how it behaves across every app.**
6
+ **Behavioral governance for AI systems.** Give meaning to behavior meaning relative to the behavioral models that have been declared and governed.
7
7
 
8
- NeuroVerse is the governance layer for AI-powered devices. It gives users, developers, and organizations a single system to define permissions, behavioral personality, and role-based accessfor smart glasses, phones, agents, or any AI-enabled product.
8
+ Human intelligence and artificial intelligence are two different kinds of intelligence working together. All organizations, all systems, gather people around declared shared intent. NeuroverseOS tools help define those intentions into behaviors and those behaviors become a constitution carried out at runtime.
9
9
 
10
- **Built for multi-agent systems:** when many agents are active, they all evaluate against the same deterministic governance model.
11
- **Built for handoff:** governance is portable as world/plan artifacts, so teams can update policy quickly and hand it across agents, apps, and operators without rewriting core logic.
10
+ NeuroverseOS is the universe where human and AI meet to work together, defined by the behaviors the organization has agreed upon. **Radiant** is the behavioral intelligence layer built on top: it reads what happened, compares it against what was declared, and tells you where the work aligns and where it drifts.
11
+
12
+ Together they put you in a cocoon of behavior — not a cage, a cocoon. Something that holds your shape while you're becoming what you said you'd become.
13
+
14
+ ## Two products, one package
15
+
16
+ | Layer | What it does | Who uses it |
17
+ |---|---|---|
18
+ | **NeuroverseOS** | Governance engine — worldmodel compiler, guard engine, lens system, plan enforcement | Developers building governed AI apps |
19
+ | **Radiant** | Behavioral intelligence — reads activity, gives it meaning through the worldmodel, measures human-AI alignment | Teams and organizations (starting with Auki/ExoCortex) |
20
+
21
+ Both ship in `@neuroverseos/governance`. Install once, use either or both.
22
+
23
+ ## Radiant — behavioral intelligence for collaboration
24
+
25
+ Radiant gives meaning to behavior. It reads activity (GitHub commits, PRs, reviews), classifies each event by who did it (human, AI, or both together), extracts behavioral signals, identifies patterns through AI interpretation governed by the worldmodel, and produces a structured read:
26
+
27
+ ```bash
28
+ npx @neuroverseos/governance radiant emergent aukiverse/posemesh \
29
+ --lens auki-builder --worlds ./worlds/
30
+ ```
31
+
32
+ Output:
33
+
34
+ ```
35
+ EMERGENT — what patterns are visible in the team's work
36
+ MEANING — what it means against the worldmodel
37
+ MOVE — what to do about it (or "nothing's broken, keep shipping")
38
+ ALIGNMENT — L/C/N/R scores (human, AI, collaboration, composite)
39
+ GOVERNANCE — audit trail: which events triggered governance, on which side
40
+ DEPTH — what Radiant can see now vs what unlocks with more reads
41
+ ```
42
+
43
+ Three scores nobody else measures:
44
+ - **L** — is the human's work aligned with the declared model?
45
+ - **C** — is the AI's output aligned with the declared model?
46
+ - **N** — when human and AI work together, is shared meaning preserved through the worldmodel? This score only exists because the worldmodel exists.
47
+
48
+ For the full Radiant documentation, see [`src/radiant/examples/auki/README.md`](src/radiant/examples/auki/README.md).
49
+
50
+ ## Governance engine — deterministic rules for AI
12
51
 
13
52
  ```
14
53
  What AI can do → Rules (permissions)
@@ -55,7 +94,6 @@ NeuroVerse gives you composable primitives:
55
94
 
56
95
  These blocks let you build robots/agents that can traverse heterogeneous spaces while remaining policy-compliant, auditable, and deterministic.
57
96
 
58
- <<<<<<< codex/review-open-source-repo-for-ai-architecture
59
97
  ### Mental Model: Layered Rules (World → Law → Situation)
60
98
 
61
99
  If you're explaining this to developers or non-technical stakeholders, use this:
@@ -202,8 +240,6 @@ Use this section to show real runtime behavior and response time.
202
240
  4. **Level 4 — Spatial + Multi-Actor Governance**
203
241
  Add zone opt-in, handshake negotiation, and dynamic policy composition.
204
242
 
205
- =======
206
- >>>>>>> main
207
243
  ---
208
244
 
209
245
  ## The Product: Three Screens
@@ -1,6 +1,12 @@
1
1
  import {
2
2
  getLens
3
3
  } from "./chunk-VGFDMPVB.js";
4
+ import {
5
+ loadWorld
6
+ } from "./chunk-I4RTIMLX.js";
7
+ import {
8
+ evaluateGuard
9
+ } from "./chunk-ZAF6JH23.js";
4
10
 
5
11
  // src/radiant/core/prompt.ts
6
12
  function composeSystemPrompt(worldmodelContent, lens) {
@@ -139,17 +145,30 @@ function resolveLens(id) {
139
145
  }
140
146
 
141
147
  // src/radiant/core/scopes.ts
142
- function parseRepoScope(scope) {
148
+ function parseScope(scope) {
143
149
  const cleaned = scope.replace(/^https?:\/\//, "").replace(/^github\.com\//, "").replace(/\.git$/, "").replace(/\/$/, "");
144
- const parts = cleaned.split("/");
145
- if (parts.length < 2 || !parts[0] || !parts[1]) {
150
+ const parts = cleaned.split("/").filter(Boolean);
151
+ if (parts.length === 0 || !parts[0]) {
152
+ throw new Error(
153
+ `Cannot parse scope: "${scope}". Expected "owner/repo" or "owner".`
154
+ );
155
+ }
156
+ if (parts.length === 1) {
157
+ return { type: "org", owner: parts[0] };
158
+ }
159
+ return { type: "repo", owner: parts[0], repo: parts[1] };
160
+ }
161
+ function parseRepoScope(scope) {
162
+ const parsed = parseScope(scope);
163
+ if (parsed.type === "org") {
146
164
  throw new Error(
147
- `Cannot parse repo scope: "${scope}". Expected "owner/repo" or a GitHub URL.`
165
+ `Expected "owner/repo" but got org-level scope "${parsed.owner}". Use parseScope() for org-level.`
148
166
  );
149
167
  }
150
- return { owner: parts[0], repo: parts[1] };
168
+ return parsed;
151
169
  }
152
170
  function formatScope(scope) {
171
+ if (scope.type === "org") return `${scope.owner} (org)`;
153
172
  return `${scope.owner}/${scope.repo}`;
154
173
  }
155
174
 
@@ -333,12 +352,46 @@ async function fetchJSON(url, headers) {
333
352
  }
334
353
  return await res.json();
335
354
  }
355
+ async function fetchGitHubOrgActivity(scope, token, options = {}) {
356
+ const perPage = options.perPage ?? 100;
357
+ const headers = {
358
+ Authorization: `token ${token}`,
359
+ Accept: "application/vnd.github.v3+json",
360
+ "User-Agent": "neuroverseos-radiant"
361
+ };
362
+ const repos = await fetchJSON(
363
+ `https://api.github.com/orgs/${scope.owner}/repos?sort=pushed&direction=desc&per_page=${perPage}`,
364
+ headers
365
+ );
366
+ const windowDays = options.windowDays ?? 14;
367
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
368
+ const activeRepos = repos.filter(
369
+ (r) => new Date(r.pushed_at) >= since
370
+ );
371
+ const cappedRepos = activeRepos.slice(0, 10);
372
+ const allEvents = [];
373
+ const repoNames = [];
374
+ for (const repo of cappedRepos) {
375
+ const [owner, repoName] = repo.full_name.split("/");
376
+ try {
377
+ const repoScope = { type: "repo", owner, repo: repoName };
378
+ const events = await fetchGitHubActivity(repoScope, token, options);
379
+ allEvents.push(...events);
380
+ if (events.length > 0) repoNames.push(repo.full_name);
381
+ } catch {
382
+ }
383
+ }
384
+ allEvents.sort(
385
+ (a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)
386
+ );
387
+ return { events: allEvents, repos: repoNames };
388
+ }
336
389
  function createMockGitHubAdapter(fixedEvents) {
337
390
  return async () => fixedEvents;
338
391
  }
339
392
 
340
393
  // src/radiant/adapters/exocortex.ts
341
- import { readFileSync, existsSync } from "fs";
394
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
342
395
  import { join, resolve } from "path";
343
396
  function readExocortex(dirPath) {
344
397
  const dir = resolve(dirPath);
@@ -409,6 +462,46 @@ ${ctx.methods}`);
409
462
  }
410
463
  return sections.join("\n\n");
411
464
  }
465
+ function readTeamExocortices(teamDir) {
466
+ const dir = resolve(teamDir);
467
+ if (!existsSync(dir)) return [];
468
+ const entries = readdirSync(dir);
469
+ const results = [];
470
+ for (const entry of entries) {
471
+ const entryPath = join(dir, entry);
472
+ try {
473
+ const stat = statSync(entryPath);
474
+ if (stat.isDirectory()) {
475
+ const ctx = readExocortex(entryPath);
476
+ if (ctx.filesLoaded > 0) {
477
+ results.push({ name: entry, context: ctx });
478
+ }
479
+ }
480
+ } catch {
481
+ }
482
+ }
483
+ return results;
484
+ }
485
+ function formatTeamExocorticesForPrompt(team) {
486
+ if (team.length === 0) return "";
487
+ const sections = [
488
+ "## Team Intent (cross-exocortex read)",
489
+ "",
490
+ `Reading ${team.length} team members' exocortices. Compare each person's`,
491
+ "stated intent against the observed activity AND against each other.",
492
+ "Surface: duplicate focus, missing coverage, silent pivots,",
493
+ "and areas where no one is carrying the work.",
494
+ ""
495
+ ];
496
+ for (const { name, context } of team) {
497
+ sections.push(`### ${name}`);
498
+ if (context.attention) sections.push(`**Attention:** ${context.attention.split("\n")[0]}`);
499
+ if (context.goals) sections.push(`**Goals:** ${context.goals.split("\n").slice(0, 3).join("; ")}`);
500
+ if (context.sprint) sections.push(`**Sprint:** ${context.sprint.split("\n").slice(0, 3).join("; ")}`);
501
+ sections.push("");
502
+ }
503
+ return sections.join("\n");
504
+ }
412
505
  function summarizeExocortex(ctx) {
413
506
  if (ctx.filesLoaded === 0) return "no exocortex files found";
414
507
  const loaded = [];
@@ -421,6 +514,305 @@ function summarizeExocortex(ctx) {
421
514
  return `${loaded.join(", ")} (${ctx.filesLoaded} files)`;
422
515
  }
423
516
 
517
+ // src/radiant/memory/palace.ts
518
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
519
+ import { join as join2, resolve as resolve2 } from "path";
520
+ function writeRead(exocortexDir, frontmatter, text) {
521
+ const dir = resolve2(exocortexDir, "radiant", "reads");
522
+ mkdirSync(dir, { recursive: true });
523
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
524
+ const filename = `${date}.md`;
525
+ const filepath = join2(dir, filename);
526
+ const content = `${frontmatter}
527
+
528
+ ${text}
529
+ `;
530
+ writeFileSync(filepath, content, "utf-8");
531
+ return filepath;
532
+ }
533
+ function updateKnowledge(exocortexDir, persistence, options) {
534
+ const dir = resolve2(exocortexDir, "radiant");
535
+ mkdirSync(dir, { recursive: true });
536
+ const filepath = join2(dir, "knowledge.md");
537
+ const totalReads = options?.totalReads ?? 0;
538
+ const existingUntriggered = loadUntriggeredCounts(filepath);
539
+ const lines = [
540
+ "# Radiant \u2014 Accumulated Behavioral Knowledge",
541
+ "",
542
+ `Last updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
543
+ `Total reads: ${totalReads}`,
544
+ "",
545
+ "---",
546
+ "",
547
+ "## Evolution proposals",
548
+ ""
549
+ ];
550
+ const addCandidates = persistence.filter((p) => p.occurrences >= 3);
551
+ if (addCandidates.length > 0) {
552
+ lines.push("### Consider adding");
553
+ lines.push("");
554
+ lines.push("These candidate patterns keep recurring. They are not yet in the worldmodel.");
555
+ lines.push("If they represent real behavioral patterns worth tracking, add them.");
556
+ lines.push("If they were temporary, dismiss them.");
557
+ lines.push("");
558
+ for (const p of addCandidates) {
559
+ lines.push(
560
+ `- **${p.name}** \u2014 observed ${p.occurrences} times (${p.dates.join(", ")}). Add to auki-strategy.worldmodel.md \u2192 Evolution Layer \u2192 Drift Behaviors (if concerning) or Aligned Behaviors (if healthy).`
561
+ );
562
+ }
563
+ lines.push("");
564
+ }
565
+ if (options?.declaredItems && options.declaredItems.length > 0) {
566
+ const triggered = new Set(options.triggeredItems ?? []);
567
+ const removeCandidates = [];
568
+ for (const item of options.declaredItems) {
569
+ if (!triggered.has(item.name)) {
570
+ const prior = existingUntriggered.get(item.name) ?? 0;
571
+ const count = prior + 1;
572
+ existingUntriggered.set(item.name, count);
573
+ if (count >= 4) {
574
+ removeCandidates.push({ item, weeksSilent: count });
575
+ }
576
+ } else {
577
+ existingUntriggered.set(item.name, 0);
578
+ }
579
+ }
580
+ if (removeCandidates.length > 0) {
581
+ lines.push("### Consider removing");
582
+ lines.push("");
583
+ lines.push("These items are declared in the worldmodel but haven't triggered in multiple reads.");
584
+ lines.push("Either the team has internalized them (the rule is redundant) or they haven't been tested.");
585
+ lines.push("A lean worldmodel with 5 sharp invariants is stronger than a bloated one with 20.");
586
+ lines.push("");
587
+ for (const { item, weeksSilent } of removeCandidates) {
588
+ lines.push(
589
+ `- **${item.name}** (${item.type}) \u2014 hasn't triggered in ${weeksSilent} reads. Internalized or untested? Review whether it still earns its place.`
590
+ );
591
+ }
592
+ lines.push("");
593
+ }
594
+ }
595
+ if (options?.triggeredItems && options.triggeredItems.length > 0) {
596
+ lines.push("### Keep (recently active)");
597
+ lines.push("");
598
+ lines.push("These worldmodel items triggered governance in the most recent read. They're earning their place.");
599
+ lines.push("");
600
+ for (const name of options.triggeredItems) {
601
+ lines.push(`- **${name}** \u2014 triggered this read. Holding.`);
602
+ }
603
+ lines.push("");
604
+ }
605
+ lines.push("---");
606
+ lines.push("");
607
+ lines.push("## Pattern persistence (all observed)");
608
+ lines.push("");
609
+ for (const p of persistence) {
610
+ const status = p.occurrences >= 4 ? "**persistent**" : p.occurrences >= 2 ? "recurring" : "observed once";
611
+ lines.push(`### ${p.name}`);
612
+ lines.push(`- Status: ${status}`);
613
+ lines.push(`- Observed ${p.occurrences} time${p.occurrences > 1 ? "s" : ""}: ${p.dates.join(", ")}`);
614
+ lines.push("");
615
+ }
616
+ lines.push("---");
617
+ lines.push("");
618
+ lines.push("<!-- untriggered_counts (machine-readable, do not edit)");
619
+ for (const [name, count] of existingUntriggered.entries()) {
620
+ lines.push(`${name}=${count}`);
621
+ }
622
+ lines.push("-->");
623
+ writeFileSync(filepath, lines.join("\n"), "utf-8");
624
+ return filepath;
625
+ }
626
+ function loadUntriggeredCounts(filepath) {
627
+ const counts = /* @__PURE__ */ new Map();
628
+ if (!existsSync2(filepath)) return counts;
629
+ try {
630
+ const content = readFileSync2(filepath, "utf-8");
631
+ const match = content.match(
632
+ /<!-- untriggered_counts[\s\S]*?-->/
633
+ );
634
+ if (match) {
635
+ const lines = match[0].split("\n");
636
+ for (const line of lines) {
637
+ const eq = line.match(/^([^=]+)=(\d+)$/);
638
+ if (eq) {
639
+ counts.set(eq[1], parseInt(eq[2], 10));
640
+ }
641
+ }
642
+ }
643
+ } catch {
644
+ }
645
+ return counts;
646
+ }
647
+ function loadPriorReads(exocortexDir) {
648
+ const dir = resolve2(exocortexDir, "radiant", "reads");
649
+ if (!existsSync2(dir)) return [];
650
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
651
+ const reads = [];
652
+ for (const filename of files) {
653
+ const filepath = join2(dir, filename);
654
+ const content = readFileSync2(filepath, "utf-8");
655
+ const date = filename.replace(".md", "");
656
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
657
+ const frontmatter = fmMatch ? fmMatch[1] : "";
658
+ const patternNames = [];
659
+ const nameMatches = frontmatter.matchAll(/- name: ["']?([^"'\n]+)["']?/g);
660
+ for (const m of nameMatches) {
661
+ patternNames.push(m[1].trim());
662
+ }
663
+ reads.push({ date, filename, patternNames, frontmatter });
664
+ }
665
+ return reads;
666
+ }
667
+ function computePersistence(priorReads, currentPatternNames) {
668
+ const allPatterns = /* @__PURE__ */ new Map();
669
+ for (const read of priorReads) {
670
+ for (const name of read.patternNames) {
671
+ const dates = allPatterns.get(name) ?? [];
672
+ if (!dates.includes(read.date)) dates.push(read.date);
673
+ allPatterns.set(name, dates);
674
+ }
675
+ }
676
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
677
+ for (const name of currentPatternNames) {
678
+ const dates = allPatterns.get(name) ?? [];
679
+ if (!dates.includes(today)) dates.push(today);
680
+ allPatterns.set(name, dates);
681
+ }
682
+ return Array.from(allPatterns.entries()).map(([name, dates]) => ({
683
+ name,
684
+ occurrences: dates.length,
685
+ dates: dates.sort()
686
+ })).sort((a, b) => b.occurrences - a.occurrences);
687
+ }
688
+ function formatPriorReadsForPrompt(priorReads) {
689
+ if (priorReads.length === 0) return "";
690
+ const lines = [
691
+ "## Prior Radiant reads (history)",
692
+ "",
693
+ `Radiant has run ${priorReads.length} time${priorReads.length > 1 ? "s" : ""} before on this scope.`,
694
+ "If you see patterns that appeared in prior reads, note their persistence.",
695
+ "Patterns that recur across 3+ reads are strong candidates for declaration in the strategy file.",
696
+ ""
697
+ ];
698
+ for (const read of priorReads.slice(-4)) {
699
+ lines.push(`### Read from ${read.date}`);
700
+ if (read.patternNames.length > 0) {
701
+ lines.push(`Patterns observed: ${read.patternNames.join(", ")}`);
702
+ } else {
703
+ lines.push("No patterns extracted from frontmatter.");
704
+ }
705
+ lines.push("");
706
+ }
707
+ return lines.join("\n");
708
+ }
709
+
710
+ // src/radiant/core/governance.ts
711
+ async function auditGovernance(events, worldPath) {
712
+ let world;
713
+ try {
714
+ world = await loadWorld(worldPath);
715
+ } catch {
716
+ return emptyAudit(events.length, "Could not load compiled worldmodel for governance audit.");
717
+ }
718
+ const verdicts = [];
719
+ for (const ce of events) {
720
+ const intent = ce.event.content?.slice(0, 500) || ce.event.kind || "activity";
721
+ const scope = ce.event.metadata?.scope || void 0;
722
+ try {
723
+ const result = evaluateGuard(
724
+ {
725
+ intent,
726
+ scope,
727
+ actionCategory: mapKindToCategory(ce.event.kind)
728
+ },
729
+ world
730
+ );
731
+ verdicts.push({
732
+ eventId: ce.event.id,
733
+ domain: ce.domain,
734
+ status: result.status,
735
+ reason: result.reason,
736
+ ruleId: result.ruleId,
737
+ warning: result.warning
738
+ });
739
+ } catch {
740
+ verdicts.push({
741
+ eventId: ce.event.id,
742
+ domain: ce.domain,
743
+ status: "ALLOW",
744
+ reason: "guard evaluation skipped (error)"
745
+ });
746
+ }
747
+ }
748
+ const human = bucketVerdicts(verdicts.filter((v) => v.domain === "life"));
749
+ const cyber = bucketVerdicts(verdicts.filter((v) => v.domain === "cyber"));
750
+ const joint = bucketVerdicts(verdicts.filter((v) => v.domain === "joint"));
751
+ const summary = buildSummary(human, cyber, joint, events.length);
752
+ return {
753
+ totalEvents: events.length,
754
+ human,
755
+ cyber,
756
+ joint,
757
+ summary
758
+ };
759
+ }
760
+ function bucketVerdicts(verdicts) {
761
+ const nonAllow = verdicts.filter((v) => v.status !== "ALLOW");
762
+ return {
763
+ allow: verdicts.filter((v) => v.status === "ALLOW").length,
764
+ modify: verdicts.filter((v) => v.status === "MODIFY").length,
765
+ block: verdicts.filter((v) => v.status === "BLOCK").length,
766
+ details: nonAllow
767
+ };
768
+ }
769
+ function buildSummary(human, cyber, joint, total) {
770
+ const humanTriggered = human.modify + human.block;
771
+ const cyberTriggered = cyber.modify + cyber.block;
772
+ const jointTriggered = joint.modify + joint.block;
773
+ const totalTriggered = humanTriggered + cyberTriggered + jointTriggered;
774
+ if (totalTriggered === 0) {
775
+ return `${total} events evaluated. All passed. The cocoon held \u2014 no governance triggered.`;
776
+ }
777
+ const parts = [];
778
+ parts.push(`${total} events evaluated. ${totalTriggered} triggered governance.`);
779
+ if (humanTriggered > 0) {
780
+ parts.push(`Human side: ${humanTriggered} (${human.block} blocked, ${human.modify} modified).`);
781
+ }
782
+ if (cyberTriggered > 0) {
783
+ parts.push(`AI side: ${cyberTriggered} (${cyber.block} blocked, ${cyber.modify} modified).`);
784
+ }
785
+ if (jointTriggered > 0) {
786
+ parts.push(`Joint work: ${jointTriggered} (${joint.block} blocked, ${joint.modify} modified).`);
787
+ }
788
+ if (humanTriggered > 0 && cyberTriggered > 0) {
789
+ const ratio = humanTriggered / cyberTriggered;
790
+ if (ratio > 2) {
791
+ parts.push("Human side is testing the frame more than AI. Either the worldmodel needs calibrating for human workflows, or humans are genuinely drifting.");
792
+ } else if (ratio < 0.5) {
793
+ parts.push("AI side is testing the frame more than humans. Check whether the AI's output patterns match the declared invariants.");
794
+ } else {
795
+ parts.push("Roughly balanced between human and AI \u2014 both sides are testing the frame.");
796
+ }
797
+ }
798
+ return parts.join(" ");
799
+ }
800
+ function mapKindToCategory(kind) {
801
+ if (!kind) return "other";
802
+ if (kind.includes("commit") || kind.includes("merge")) return "write";
803
+ if (kind.includes("review") || kind.includes("comment")) return "read";
804
+ return "other";
805
+ }
806
+ function emptyAudit(total, reason) {
807
+ return {
808
+ totalEvents: total,
809
+ human: { allow: 0, modify: 0, block: 0, details: [] },
810
+ cyber: { allow: 0, modify: 0, block: 0, details: [] },
811
+ joint: { allow: 0, modify: 0, block: 0, details: [] },
812
+ summary: reason
813
+ };
814
+ }
815
+
424
816
  // src/radiant/core/domain.ts
425
817
  function isLifeSide(k) {
426
818
  return k === "human" || k === "unknown";
@@ -690,6 +1082,14 @@ ${jargonTable}
690
1082
 
691
1083
  For example: don't say "update the worldmodel." Say "add a line to your strategy file."
692
1084
 
1085
+ ## When the same invariant keeps firing
1086
+
1087
+ If the prior read history or the current evidence shows the same worldmodel invariant being triggered repeatedly (by the same side \u2014 human or AI), name it in MEANING and ask the real question:
1088
+
1089
+ "This invariant has been tested N times across M reads. Always on the [human/AI] side. Either the team needs alignment on WHY this rule exists \u2014 or the team is telling you something the worldmodel hasn't absorbed yet."
1090
+
1091
+ Don't just say "invariant held." Say what it means that people keep pushing against the same wall.
1092
+
693
1093
  ## Health is a valid read
694
1094
 
695
1095
  If the activity is healthy and aligned with the worldmodel, SAY SO. Don't fabricate problems. Over-prescription is a voice failure. Legitimate outputs include:
@@ -718,7 +1118,7 @@ Only recommend a move when the evidence actually calls for one.
718
1118
  }
719
1119
  ],
720
1120
  "meaning": "3-5 sentences. Weave the patterns into ONE strategic thesis. Compress. The reader should finish this paragraph and understand the one thing that matters most in this read. Plain English \u2014 no system jargon.",
721
- "move": "1-3 direct imperatives, OR explicit 'nothing to act on' if the read is healthy. Do not fabricate urgency. Examples: 'Force cross-module ownership this sprint.' / 'Nothing's broken. Keep shipping.' / 'If you want future reads to track this pattern by name, add a line to your strategy file.'"
1121
+ "move": "1-3 direct imperatives, OR explicit 'nothing to act on' if the read is healthy. Do not fabricate urgency. When a candidate pattern has high confidence (>0.6), tell the reader EXACTLY where to declare it: 'If you want Radiant to track [pattern_name] over time, add it to auki-strategy.worldmodel.md under Evolution Layer \u2192 Drift Behaviors (or Aligned Behaviors if it is positive). If you don't, Radiant will rediscover it from scratch next run.' Be specific about the file and section \u2014 don't make them guess."
722
1122
  }
723
1123
  \`\`\`
724
1124
 
@@ -874,6 +1274,27 @@ Lens: ${input.lens.name}`
874
1274
  ` Composite: ${formatScore(input.scores.R)}`
875
1275
  ].join("\n");
876
1276
  sections.push(alignBlock);
1277
+ if (input.governance && input.governance.totalEvents > 0) {
1278
+ const gov = input.governance;
1279
+ const govLines = ["GOVERNANCE", "", ` ${gov.summary}`];
1280
+ const showSide = (label, side) => {
1281
+ if (side.allow + side.modify + side.block === 0) return;
1282
+ govLines.push("");
1283
+ govLines.push(` ${label}:`);
1284
+ govLines.push(` ${side.allow} ALLOW \xB7 ${side.modify} MODIFY \xB7 ${side.block} BLOCK`);
1285
+ for (const d of side.details.slice(0, 3)) {
1286
+ const reason = d.reason ? ` \u2192 ${d.reason}` : "";
1287
+ govLines.push(` ${d.status}: ${d.eventId}${reason}`);
1288
+ }
1289
+ if (side.details.length > 3) {
1290
+ govLines.push(` ... and ${side.details.length - 3} more`);
1291
+ }
1292
+ };
1293
+ showSide("Human side", gov.human);
1294
+ showSide("AI side", gov.cyber);
1295
+ showSide("Human\u2013AI joint", gov.joint);
1296
+ sections.push(govLines.join("\n"));
1297
+ }
877
1298
  sections.push(renderDepth(input.priorReadCount ?? 0, input.windowDays));
878
1299
  return sections.join("\n\n");
879
1300
  }
@@ -1043,14 +1464,31 @@ async function emergent(input) {
1043
1464
  const windowDays = input.windowDays ?? 14;
1044
1465
  let statedIntent;
1045
1466
  let exocortexContext;
1467
+ let priorReadContext = "";
1046
1468
  if (input.exocortexPath) {
1047
1469
  exocortexContext = readExocortex(input.exocortexPath);
1048
1470
  const formatted = formatExocortexForPrompt(exocortexContext);
1049
1471
  if (formatted) statedIntent = formatted;
1472
+ const priorReads = loadPriorReads(input.exocortexPath);
1473
+ if (priorReads.length > 0) {
1474
+ priorReadContext = formatPriorReadsForPrompt(priorReads);
1475
+ }
1476
+ }
1477
+ let events;
1478
+ let orgRepos;
1479
+ if (input.scope.type === "org") {
1480
+ const orgResult = await fetchGitHubOrgActivity(
1481
+ input.scope,
1482
+ input.githubToken,
1483
+ { windowDays }
1484
+ );
1485
+ events = orgResult.events;
1486
+ orgRepos = orgResult.repos;
1487
+ } else {
1488
+ events = await fetchGitHubActivity(input.scope, input.githubToken, {
1489
+ windowDays
1490
+ });
1050
1491
  }
1051
- const events = await fetchGitHubActivity(input.scope, input.githubToken, {
1052
- windowDays
1053
- });
1054
1492
  const classified = classifyEvents(events);
1055
1493
  const signals = extractSignals(classified);
1056
1494
  const scores = computeScores(signals, input.worldmodelContent !== "");
@@ -1061,11 +1499,18 @@ async function emergent(input) {
1061
1499
  lens,
1062
1500
  ai: input.ai,
1063
1501
  canonicalPatterns: input.canonicalPatterns,
1064
- statedIntent
1502
+ statedIntent: statedIntent ? statedIntent + (priorReadContext ? "\n\n" + priorReadContext : "") : priorReadContext || void 0
1065
1503
  });
1066
1504
  const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
1067
1505
  const allDescriptions = rewrittenPatterns.map((p) => p.description).join("\n");
1068
1506
  const voiceViolations = checkForbiddenPhrases(lens, allDescriptions);
1507
+ let governance;
1508
+ if (input.worldPath) {
1509
+ try {
1510
+ governance = await auditGovernance(classified, input.worldPath);
1511
+ } catch {
1512
+ }
1513
+ }
1069
1514
  const rendered = render({
1070
1515
  scope: input.scope,
1071
1516
  windowDays,
@@ -1075,8 +1520,27 @@ async function emergent(input) {
1075
1520
  scores,
1076
1521
  lens,
1077
1522
  meaning: meaning || void 0,
1078
- move: move || void 0
1523
+ move: move || void 0,
1524
+ governance
1079
1525
  });
1526
+ if (input.exocortexPath) {
1527
+ try {
1528
+ const readPath = writeRead(input.exocortexPath, rendered.frontmatter, rendered.text);
1529
+ const priorReads = loadPriorReads(input.exocortexPath);
1530
+ const currentPatternNames = rewrittenPatterns.map((p) => p.name);
1531
+ const persistence = computePersistence(priorReads, currentPatternNames);
1532
+ const triggeredItems = governance ? [
1533
+ ...governance.human.details.map((d) => d.ruleId).filter(Boolean),
1534
+ ...governance.cyber.details.map((d) => d.ruleId).filter(Boolean),
1535
+ ...governance.joint.details.map((d) => d.ruleId).filter(Boolean)
1536
+ ] : [];
1537
+ updateKnowledge(input.exocortexPath, persistence, {
1538
+ triggeredItems,
1539
+ totalReads: priorReads.length + 1
1540
+ });
1541
+ } catch {
1542
+ }
1543
+ }
1080
1544
  return {
1081
1545
  text: rendered.text,
1082
1546
  frontmatter: rendered.frontmatter,
@@ -1180,13 +1644,23 @@ export {
1180
1644
  composeSystemPrompt,
1181
1645
  checkForbiddenPhrases,
1182
1646
  think,
1647
+ parseScope,
1183
1648
  parseRepoScope,
1184
1649
  formatScope,
1185
1650
  fetchGitHubActivity,
1651
+ fetchGitHubOrgActivity,
1186
1652
  createMockGitHubAdapter,
1187
1653
  readExocortex,
1188
1654
  formatExocortexForPrompt,
1655
+ readTeamExocortices,
1656
+ formatTeamExocorticesForPrompt,
1189
1657
  summarizeExocortex,
1658
+ writeRead,
1659
+ updateKnowledge,
1660
+ loadPriorReads,
1661
+ computePersistence,
1662
+ formatPriorReadsForPrompt,
1663
+ auditGovernance,
1190
1664
  classifyActorDomain,
1191
1665
  classifyEvents,
1192
1666
  extractSignals,