@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.
- package/README.md +1 -1
- package/dist/{chunk-3ZWU7C43.js → chunk-BZYQHJDM.js} +469 -9
- package/dist/cli/neuroverse.cjs +777 -20
- package/dist/cli/radiant.cjs +772 -13
- package/dist/cli/radiant.d.cts +44 -1
- package/dist/cli/radiant.d.ts +44 -1
- package/dist/cli/radiant.js +294 -6
- package/dist/radiant/index.cjs +474 -9
- package/dist/radiant/index.d.cts +160 -10
- package/dist/radiant/index.d.ts +160 -10
- package/dist/radiant/index.js +11 -1
- package/dist/{server-JKUBUK5H.js → server-EI5JCIBU.js} +1 -1
- package/examples/radiant-weekly-workflow.yml +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ DEPTH — what Radiant can see now vs what unlocks with more reads
|
|
|
77
77
|
|
|
78
78
|
### Memory + evolution
|
|
79
79
|
|
|
80
|
-
Radiant writes each read to the ExoCortex as a dated
|
|
80
|
+
Radiant writes each read to the ExoCortex as a dated Mind Palace file. Next run reads prior history, detects pattern persistence, and proposes worldmodel evolution — what to ADD (recurring candidate patterns) and what to REMOVE (invariants that haven't fired). A lean worldmodel with 5 sharp invariants is stronger than a bloated one with 20.
|
|
81
81
|
|
|
82
82
|
### MCP server
|
|
83
83
|
|
|
@@ -859,6 +859,257 @@ async function fetchNotionAPI(url, headers, init) {
|
|
|
859
859
|
return await res.json();
|
|
860
860
|
}
|
|
861
861
|
|
|
862
|
+
// src/radiant/adapters/linear.ts
|
|
863
|
+
async function fetchLinearActivity(apiKey, options = {}) {
|
|
864
|
+
const windowDays = options.windowDays ?? 14;
|
|
865
|
+
const maxIssues = options.maxIssues ?? 200;
|
|
866
|
+
const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1e3);
|
|
867
|
+
const sinceIso = since.toISOString();
|
|
868
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
869
|
+
const teamFilter = options.teamIds && options.teamIds.length > 0 ? `team: { id: { in: [${options.teamIds.map((t) => JSON.stringify(t)).join(", ")}] } }` : "";
|
|
870
|
+
const issuesQuery = `
|
|
871
|
+
query RadiantIssues($since: DateTimeOrDuration!, $first: Int!) {
|
|
872
|
+
issues(
|
|
873
|
+
filter: {
|
|
874
|
+
updatedAt: { gte: $since }
|
|
875
|
+
${teamFilter}
|
|
876
|
+
}
|
|
877
|
+
first: $first
|
|
878
|
+
orderBy: updatedAt
|
|
879
|
+
) {
|
|
880
|
+
nodes {
|
|
881
|
+
id
|
|
882
|
+
identifier
|
|
883
|
+
title
|
|
884
|
+
url
|
|
885
|
+
createdAt
|
|
886
|
+
updatedAt
|
|
887
|
+
completedAt
|
|
888
|
+
canceledAt
|
|
889
|
+
state { name type }
|
|
890
|
+
assignee { id name email }
|
|
891
|
+
creator { id name }
|
|
892
|
+
team { id name }
|
|
893
|
+
project { id name }
|
|
894
|
+
cycle { id number startsAt endsAt }
|
|
895
|
+
comments(first: 20) {
|
|
896
|
+
nodes { id body createdAt user { id name } }
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
`;
|
|
902
|
+
const cyclesQuery = `
|
|
903
|
+
query RadiantCycles($since: DateTimeOrDuration!, $now: DateTimeOrDuration!) {
|
|
904
|
+
cycles(
|
|
905
|
+
filter: { endsAt: { gte: $since, lte: $now } }
|
|
906
|
+
first: 20
|
|
907
|
+
) {
|
|
908
|
+
nodes {
|
|
909
|
+
id
|
|
910
|
+
number
|
|
911
|
+
startsAt
|
|
912
|
+
endsAt
|
|
913
|
+
issueCountHistory
|
|
914
|
+
completedIssueCountHistory
|
|
915
|
+
team { id name }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
`;
|
|
920
|
+
const [issuesResponse, cyclesResponse] = await Promise.all([
|
|
921
|
+
fetchLinearGraphQL(apiKey, issuesQuery, {
|
|
922
|
+
since: sinceIso,
|
|
923
|
+
first: maxIssues
|
|
924
|
+
}),
|
|
925
|
+
fetchLinearGraphQL(apiKey, cyclesQuery, {
|
|
926
|
+
since: sinceIso,
|
|
927
|
+
now: nowIso
|
|
928
|
+
})
|
|
929
|
+
]);
|
|
930
|
+
const rawIssues = issuesResponse.issues?.nodes ?? [];
|
|
931
|
+
const rawCycles = cyclesResponse.cycles?.nodes ?? [];
|
|
932
|
+
const events = [];
|
|
933
|
+
const assignees = /* @__PURE__ */ new Set();
|
|
934
|
+
const projects = /* @__PURE__ */ new Map();
|
|
935
|
+
let issuesCreated = 0;
|
|
936
|
+
let issuesCompleted = 0;
|
|
937
|
+
let issuesOpen = 0;
|
|
938
|
+
let issuesStalled = 0;
|
|
939
|
+
let commentsTotal = 0;
|
|
940
|
+
const now = Date.now();
|
|
941
|
+
const stallThresholdMs = 14 * 24 * 60 * 60 * 1e3;
|
|
942
|
+
for (const issue of rawIssues) {
|
|
943
|
+
const created = new Date(issue.createdAt);
|
|
944
|
+
const updated = new Date(issue.updatedAt);
|
|
945
|
+
const completed = issue.completedAt ? new Date(issue.completedAt) : null;
|
|
946
|
+
const assigneeId = issue.assignee?.id ?? "unassigned";
|
|
947
|
+
if (assigneeId !== "unassigned") assignees.add(assigneeId);
|
|
948
|
+
if (issue.project) {
|
|
949
|
+
projects.set(issue.project.name, (projects.get(issue.project.name) ?? 0) + 1);
|
|
950
|
+
}
|
|
951
|
+
const actor = {
|
|
952
|
+
id: assigneeId,
|
|
953
|
+
kind: "human",
|
|
954
|
+
name: issue.assignee?.name ?? "unassigned"
|
|
955
|
+
};
|
|
956
|
+
if (created >= since) {
|
|
957
|
+
issuesCreated++;
|
|
958
|
+
events.push({
|
|
959
|
+
id: `linear-created-${issue.id}`,
|
|
960
|
+
timestamp: issue.createdAt,
|
|
961
|
+
actor: {
|
|
962
|
+
id: issue.creator?.id ?? "unknown",
|
|
963
|
+
kind: "human",
|
|
964
|
+
name: issue.creator?.name ?? "unknown"
|
|
965
|
+
},
|
|
966
|
+
kind: "issue_created",
|
|
967
|
+
content: `[${issue.identifier}] ${issue.title}`,
|
|
968
|
+
metadata: {
|
|
969
|
+
issueId: issue.id,
|
|
970
|
+
url: issue.url,
|
|
971
|
+
team: issue.team?.name,
|
|
972
|
+
project: issue.project?.name,
|
|
973
|
+
state: issue.state?.name
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
if (completed && completed >= since) {
|
|
978
|
+
issuesCompleted++;
|
|
979
|
+
events.push({
|
|
980
|
+
id: `linear-completed-${issue.id}`,
|
|
981
|
+
timestamp: issue.completedAt,
|
|
982
|
+
actor,
|
|
983
|
+
kind: "issue_completed",
|
|
984
|
+
content: `[${issue.identifier}] ${issue.title}`,
|
|
985
|
+
metadata: {
|
|
986
|
+
issueId: issue.id,
|
|
987
|
+
url: issue.url,
|
|
988
|
+
team: issue.team?.name,
|
|
989
|
+
project: issue.project?.name,
|
|
990
|
+
cycleDays: issue.cycle?.startsAt && issue.completedAt ? Math.round(
|
|
991
|
+
(new Date(issue.completedAt).getTime() - new Date(issue.cycle.startsAt).getTime()) / (24 * 60 * 60 * 1e3)
|
|
992
|
+
) : null
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
if (!completed && !issue.canceledAt) {
|
|
997
|
+
issuesOpen++;
|
|
998
|
+
const isInProgress = issue.state?.type === "started";
|
|
999
|
+
const idleMs = now - updated.getTime();
|
|
1000
|
+
if (isInProgress && idleMs > stallThresholdMs) issuesStalled++;
|
|
1001
|
+
}
|
|
1002
|
+
for (const comment of issue.comments?.nodes ?? []) {
|
|
1003
|
+
const commentedAt = new Date(comment.createdAt);
|
|
1004
|
+
if (commentedAt < since) continue;
|
|
1005
|
+
commentsTotal++;
|
|
1006
|
+
events.push({
|
|
1007
|
+
id: `linear-comment-${comment.id}`,
|
|
1008
|
+
timestamp: comment.createdAt,
|
|
1009
|
+
actor: {
|
|
1010
|
+
id: comment.user?.id ?? "unknown",
|
|
1011
|
+
kind: "human",
|
|
1012
|
+
name: comment.user?.name ?? "unknown"
|
|
1013
|
+
},
|
|
1014
|
+
kind: "issue_comment",
|
|
1015
|
+
content: comment.body.slice(0, 280),
|
|
1016
|
+
metadata: {
|
|
1017
|
+
issueId: issue.id,
|
|
1018
|
+
issueIdentifier: issue.identifier,
|
|
1019
|
+
url: issue.url
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
let cycleCompletionRate = null;
|
|
1025
|
+
if (rawCycles.length > 0) {
|
|
1026
|
+
const rates = [];
|
|
1027
|
+
for (const cycle of rawCycles) {
|
|
1028
|
+
const committed = cycle.issueCountHistory?.at(0) ?? 0;
|
|
1029
|
+
const completed = cycle.completedIssueCountHistory?.at(-1) ?? 0;
|
|
1030
|
+
if (committed > 0) rates.push(completed / committed);
|
|
1031
|
+
}
|
|
1032
|
+
if (rates.length > 0) {
|
|
1033
|
+
cycleCompletionRate = Math.round(rates.reduce((a, b) => a + b, 0) / rates.length * 100) / 100;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const topProjects = [...projects.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name]) => name);
|
|
1037
|
+
const signals = {
|
|
1038
|
+
issuesCreated,
|
|
1039
|
+
issuesCompleted,
|
|
1040
|
+
issuesOpen,
|
|
1041
|
+
issuesStalled,
|
|
1042
|
+
cycleCompletionRate,
|
|
1043
|
+
uniqueAssignees: assignees.size,
|
|
1044
|
+
commentsTotal,
|
|
1045
|
+
topProjects
|
|
1046
|
+
};
|
|
1047
|
+
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
1048
|
+
return { events, signals };
|
|
1049
|
+
}
|
|
1050
|
+
function formatLinearSignalsForPrompt(signals) {
|
|
1051
|
+
if (signals.issuesCreated === 0 && signals.issuesCompleted === 0 && signals.issuesOpen === 0) {
|
|
1052
|
+
return "";
|
|
1053
|
+
}
|
|
1054
|
+
const lines = [
|
|
1055
|
+
"## Linear Activity (planned work vs. shipped outcome)",
|
|
1056
|
+
"",
|
|
1057
|
+
`${signals.issuesCreated} issues created, ${signals.issuesCompleted} completed in window.`,
|
|
1058
|
+
`${signals.issuesOpen} issues still open.`
|
|
1059
|
+
];
|
|
1060
|
+
if (signals.issuesStalled > 0) {
|
|
1061
|
+
lines.push(
|
|
1062
|
+
`${signals.issuesStalled} in-progress issues haven't moved in 14+ days (stalled).`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (signals.cycleCompletionRate !== null) {
|
|
1066
|
+
const pct = Math.round(signals.cycleCompletionRate * 100);
|
|
1067
|
+
lines.push(`Cycles ended in window completed ${pct}% of what was committed.`);
|
|
1068
|
+
}
|
|
1069
|
+
if (signals.uniqueAssignees > 0) {
|
|
1070
|
+
lines.push(`${signals.uniqueAssignees} unique assignees active.`);
|
|
1071
|
+
}
|
|
1072
|
+
if (signals.commentsTotal > 0) {
|
|
1073
|
+
lines.push(`${signals.commentsTotal} comments across issues in window.`);
|
|
1074
|
+
}
|
|
1075
|
+
if (signals.topProjects.length > 0) {
|
|
1076
|
+
lines.push(`Most active projects: ${signals.topProjects.join(", ")}.`);
|
|
1077
|
+
}
|
|
1078
|
+
lines.push("");
|
|
1079
|
+
lines.push("Linear is where the team states what it will build.");
|
|
1080
|
+
lines.push("GitHub is where the team reveals what actually got built.");
|
|
1081
|
+
lines.push("Low completion rate + high creation rate = planning faster than shipping.");
|
|
1082
|
+
lines.push("High stalled count = commitments made but not honored.");
|
|
1083
|
+
lines.push("Compare Linear signals against GitHub to find the stated-vs-shipped gap.");
|
|
1084
|
+
return lines.join("\n");
|
|
1085
|
+
}
|
|
1086
|
+
async function fetchLinearGraphQL(apiKey, query, variables) {
|
|
1087
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
1088
|
+
method: "POST",
|
|
1089
|
+
headers: {
|
|
1090
|
+
// Linear accepts the raw API key in Authorization with no "Bearer" prefix.
|
|
1091
|
+
Authorization: apiKey,
|
|
1092
|
+
"Content-Type": "application/json"
|
|
1093
|
+
},
|
|
1094
|
+
body: JSON.stringify({ query, variables })
|
|
1095
|
+
});
|
|
1096
|
+
if (!res.ok) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`Linear API error ${res.status}: ${(await res.text()).slice(0, 300)}`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const json = await res.json();
|
|
1102
|
+
if (json.errors && json.errors.length > 0) {
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`Linear GraphQL errors: ${json.errors.map((e) => e.message).join("; ")}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
if (!json.data) {
|
|
1108
|
+
throw new Error("Linear API returned no data");
|
|
1109
|
+
}
|
|
1110
|
+
return json.data;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
862
1113
|
// src/radiant/core/git-remote.ts
|
|
863
1114
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
864
1115
|
import { join, resolve } from "path";
|
|
@@ -1836,6 +2087,138 @@ var DEFAULT_SIGNAL_EXTRACTORS = Object.freeze([
|
|
|
1836
2087
|
DECISION_MOMENTUM_EXTRACTOR
|
|
1837
2088
|
]);
|
|
1838
2089
|
|
|
2090
|
+
// src/radiant/core/vocabulary.ts
|
|
2091
|
+
function extractDeclaredVocabulary(worldmodelContent) {
|
|
2092
|
+
const aligned = extractSection(worldmodelContent, "Aligned Behaviors").map(
|
|
2093
|
+
(b) => parseBehavior(b, "aligned")
|
|
2094
|
+
);
|
|
2095
|
+
const drift = extractSection(worldmodelContent, "Drift Behaviors").map(
|
|
2096
|
+
(b) => parseBehavior(b, "drift")
|
|
2097
|
+
);
|
|
2098
|
+
const allNames = [...aligned, ...drift].map((p) => p.name);
|
|
2099
|
+
return { aligned, drift, allNames };
|
|
2100
|
+
}
|
|
2101
|
+
function extractSection(content, header) {
|
|
2102
|
+
const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2103
|
+
const pattern = new RegExp(
|
|
2104
|
+
`##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
|
|
2105
|
+
"i"
|
|
2106
|
+
);
|
|
2107
|
+
const match = content.match(pattern);
|
|
2108
|
+
if (!match) return [];
|
|
2109
|
+
const body = match[1];
|
|
2110
|
+
const bullets = body.match(/^[-*]\s+.+$/gm);
|
|
2111
|
+
if (!bullets) return [];
|
|
2112
|
+
return bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()).filter((b) => b.length > 0 && !b.startsWith("<!--"));
|
|
2113
|
+
}
|
|
2114
|
+
function parseBehavior(bullet, kind) {
|
|
2115
|
+
const explicit = bullet.match(
|
|
2116
|
+
/^`?([a-z][a-z0-9_]*)`?\s+[—\u2014-]\s+(.+)$/i
|
|
2117
|
+
);
|
|
2118
|
+
if (explicit && isSnakeCase(explicit[1])) {
|
|
2119
|
+
return {
|
|
2120
|
+
name: explicit[1].toLowerCase(),
|
|
2121
|
+
prose: explicit[2].trim(),
|
|
2122
|
+
kind
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
return { name: snakeCaseName(bullet), prose: bullet, kind };
|
|
2126
|
+
}
|
|
2127
|
+
function isSnakeCase(s) {
|
|
2128
|
+
return /^[a-z][a-z0-9_]*$/.test(s);
|
|
2129
|
+
}
|
|
2130
|
+
function snakeCaseName(s) {
|
|
2131
|
+
const base = s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2132
|
+
if (base.length <= 60) return base;
|
|
2133
|
+
const truncated = base.slice(0, 60);
|
|
2134
|
+
const lastUnderscore = truncated.lastIndexOf("_");
|
|
2135
|
+
return lastUnderscore > 20 ? truncated.slice(0, lastUnderscore) : truncated;
|
|
2136
|
+
}
|
|
2137
|
+
function matchDeclaredPattern(candidateName, candidateDescription, vocabulary) {
|
|
2138
|
+
const candidateText = `${candidateName.replace(/_/g, " ")} ${candidateDescription}`;
|
|
2139
|
+
const candidateWords = contentWords(candidateText);
|
|
2140
|
+
if (candidateWords.size === 0) return null;
|
|
2141
|
+
let best = null;
|
|
2142
|
+
for (const pattern of [...vocabulary.aligned, ...vocabulary.drift]) {
|
|
2143
|
+
const proseWords = contentWords(pattern.prose);
|
|
2144
|
+
if (proseWords.size === 0) continue;
|
|
2145
|
+
let shared = 0;
|
|
2146
|
+
for (const w of proseWords) {
|
|
2147
|
+
if (candidateWords.has(w)) shared++;
|
|
2148
|
+
}
|
|
2149
|
+
const coverage = shared / proseWords.size;
|
|
2150
|
+
if (shared >= 2 && coverage >= 0.3) {
|
|
2151
|
+
if (!best || coverage > best.score) {
|
|
2152
|
+
best = { pattern, score: coverage };
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return best?.pattern ?? null;
|
|
2157
|
+
}
|
|
2158
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
2159
|
+
"about",
|
|
2160
|
+
"after",
|
|
2161
|
+
"against",
|
|
2162
|
+
"among",
|
|
2163
|
+
"around",
|
|
2164
|
+
"because",
|
|
2165
|
+
"been",
|
|
2166
|
+
"before",
|
|
2167
|
+
"being",
|
|
2168
|
+
"between",
|
|
2169
|
+
"both",
|
|
2170
|
+
"could",
|
|
2171
|
+
"does",
|
|
2172
|
+
"doing",
|
|
2173
|
+
"during",
|
|
2174
|
+
"each",
|
|
2175
|
+
"from",
|
|
2176
|
+
"further",
|
|
2177
|
+
"have",
|
|
2178
|
+
"having",
|
|
2179
|
+
"into",
|
|
2180
|
+
"itself",
|
|
2181
|
+
"most",
|
|
2182
|
+
"nor",
|
|
2183
|
+
"only",
|
|
2184
|
+
"other",
|
|
2185
|
+
"over",
|
|
2186
|
+
"same",
|
|
2187
|
+
"should",
|
|
2188
|
+
"some",
|
|
2189
|
+
"such",
|
|
2190
|
+
"than",
|
|
2191
|
+
"that",
|
|
2192
|
+
"their",
|
|
2193
|
+
"them",
|
|
2194
|
+
"then",
|
|
2195
|
+
"there",
|
|
2196
|
+
"these",
|
|
2197
|
+
"they",
|
|
2198
|
+
"this",
|
|
2199
|
+
"those",
|
|
2200
|
+
"through",
|
|
2201
|
+
"under",
|
|
2202
|
+
"until",
|
|
2203
|
+
"very",
|
|
2204
|
+
"were",
|
|
2205
|
+
"what",
|
|
2206
|
+
"when",
|
|
2207
|
+
"where",
|
|
2208
|
+
"which",
|
|
2209
|
+
"while",
|
|
2210
|
+
"will",
|
|
2211
|
+
"with",
|
|
2212
|
+
"without",
|
|
2213
|
+
"would",
|
|
2214
|
+
"your",
|
|
2215
|
+
"yours"
|
|
2216
|
+
]);
|
|
2217
|
+
function contentWords(text) {
|
|
2218
|
+
const words = text.toLowerCase().match(/[a-z][a-z0-9_]+/g) ?? [];
|
|
2219
|
+
return new Set(words.filter((w) => w.length > 3 && !STOPWORDS.has(w)));
|
|
2220
|
+
}
|
|
2221
|
+
|
|
1839
2222
|
// src/radiant/types.ts
|
|
1840
2223
|
var DEFAULT_EVIDENCE_GATE = { k: 3, c: 0.5 };
|
|
1841
2224
|
function isScored(s) {
|
|
@@ -1878,7 +2261,11 @@ function scoreComposite(a_L, a_C, a_N) {
|
|
|
1878
2261
|
async function interpretPatterns(input) {
|
|
1879
2262
|
const prompt = buildInterpretationPrompt(input);
|
|
1880
2263
|
const raw = await input.ai.complete(prompt, "Analyze the activity and produce the read.");
|
|
1881
|
-
const
|
|
2264
|
+
const canonicalNames = [
|
|
2265
|
+
...input.canonicalPatterns ?? [],
|
|
2266
|
+
...input.declaredVocabulary?.allNames ?? []
|
|
2267
|
+
];
|
|
2268
|
+
const parsed = parseInterpretation(raw, canonicalNames, input.declaredVocabulary);
|
|
1882
2269
|
return {
|
|
1883
2270
|
patterns: parsed.patterns,
|
|
1884
2271
|
meaning: parsed.meaning,
|
|
@@ -1889,8 +2276,10 @@ async function interpretPatterns(input) {
|
|
|
1889
2276
|
function buildInterpretationPrompt(input) {
|
|
1890
2277
|
const signalSummary = formatSignalSummary(input.signals);
|
|
1891
2278
|
const eventSample = formatEventSample(input.events, 30);
|
|
1892
|
-
const canonicalList = (
|
|
1893
|
-
|
|
2279
|
+
const canonicalList = formatDeclaredVocabulary(
|
|
2280
|
+
input.declaredVocabulary,
|
|
2281
|
+
input.canonicalPatterns ?? []
|
|
2282
|
+
);
|
|
1894
2283
|
const compressedWorld = compressWorldmodel(input.worldmodelContent);
|
|
1895
2284
|
const cl = compressLens(input.lens);
|
|
1896
2285
|
const frame = input.lens.primary_frame;
|
|
@@ -2001,6 +2390,44 @@ Only recommend a move when the evidence actually calls for one.
|
|
|
2001
2390
|
Do NOT use these phrases anywhere in your output:
|
|
2002
2391
|
${forbiddenList}`;
|
|
2003
2392
|
}
|
|
2393
|
+
function formatDeclaredVocabulary(vocabulary, extraNames) {
|
|
2394
|
+
const aligned = vocabulary?.aligned ?? [];
|
|
2395
|
+
const drift = vocabulary?.drift ?? [];
|
|
2396
|
+
if (aligned.length === 0 && drift.length === 0 && extraNames.length === 0) {
|
|
2397
|
+
return 'No patterns have been named yet. Everything you observe is new \u2014 mark it type: "candidate".';
|
|
2398
|
+
}
|
|
2399
|
+
const parts = [];
|
|
2400
|
+
parts.push("## Declared vocabulary (use these names when you see matching evidence)");
|
|
2401
|
+
parts.push("");
|
|
2402
|
+
parts.push(
|
|
2403
|
+
'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.'
|
|
2404
|
+
);
|
|
2405
|
+
parts.push("");
|
|
2406
|
+
if (aligned.length > 0) {
|
|
2407
|
+
parts.push("### Aligned behaviors (positive patterns)");
|
|
2408
|
+
for (const p of aligned) {
|
|
2409
|
+
parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
|
|
2410
|
+
}
|
|
2411
|
+
parts.push("");
|
|
2412
|
+
}
|
|
2413
|
+
if (drift.length > 0) {
|
|
2414
|
+
parts.push("### Drift behaviors (negative patterns)");
|
|
2415
|
+
for (const p of drift) {
|
|
2416
|
+
parts.push(`- \`${p.name}\` \u2014 ${p.prose}`);
|
|
2417
|
+
}
|
|
2418
|
+
parts.push("");
|
|
2419
|
+
}
|
|
2420
|
+
if (extraNames.length > 0) {
|
|
2421
|
+
parts.push(
|
|
2422
|
+
`Additional canonical names (from prior runs or caller): ${extraNames.join(", ")}`
|
|
2423
|
+
);
|
|
2424
|
+
parts.push("");
|
|
2425
|
+
}
|
|
2426
|
+
parts.push(
|
|
2427
|
+
'If you observe something genuinely new that matches NO declared pattern, mark it type: "candidate" with a freshly-invented snake_case name.'
|
|
2428
|
+
);
|
|
2429
|
+
return parts.join("\n");
|
|
2430
|
+
}
|
|
2004
2431
|
function formatSignalSummary(signals) {
|
|
2005
2432
|
const lines = [];
|
|
2006
2433
|
const domains = ["life", "cyber", "joint"];
|
|
@@ -2026,7 +2453,7 @@ function formatEventSample(events, maxEvents) {
|
|
|
2026
2453
|
"${content}"`;
|
|
2027
2454
|
}).join("\n");
|
|
2028
2455
|
}
|
|
2029
|
-
function parseInterpretation(raw, canonicalNames) {
|
|
2456
|
+
function parseInterpretation(raw, canonicalNames, vocabulary) {
|
|
2030
2457
|
let meaning = "";
|
|
2031
2458
|
let move = "";
|
|
2032
2459
|
let patternsArray = [];
|
|
@@ -2056,14 +2483,23 @@ function parseInterpretation(raw, canonicalNames) {
|
|
|
2056
2483
|
const patterns = [];
|
|
2057
2484
|
for (const item of patternsArray) {
|
|
2058
2485
|
if (!isPatternLike(item)) continue;
|
|
2059
|
-
const
|
|
2486
|
+
const rawName = String(item.name ?? "unnamed");
|
|
2487
|
+
const description = String(item.description ?? "");
|
|
2060
2488
|
const ev = item.evidence;
|
|
2061
|
-
|
|
2489
|
+
let name = rawName;
|
|
2490
|
+
let isCanonical = item.type === "canonical" || canonicalSet.has(rawName.toLowerCase());
|
|
2491
|
+
if (!isCanonical && vocabulary) {
|
|
2492
|
+
const matched = matchDeclaredPattern(rawName, description, vocabulary);
|
|
2493
|
+
if (matched) {
|
|
2494
|
+
name = matched.name;
|
|
2495
|
+
isCanonical = true;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2062
2498
|
patterns.push({
|
|
2063
|
-
name
|
|
2499
|
+
name,
|
|
2064
2500
|
type: isCanonical ? "canonical" : "candidate",
|
|
2065
|
-
declaredAs: isCanonical ?
|
|
2066
|
-
description
|
|
2501
|
+
declaredAs: isCanonical ? name : void 0,
|
|
2502
|
+
description,
|
|
2067
2503
|
evidence: {
|
|
2068
2504
|
signals: Array.isArray(ev?.signals) ? ev.signals.map(String) : [],
|
|
2069
2505
|
events: Array.isArray(ev?.events) ? ev.events.map(String) : [],
|
|
@@ -2386,10 +2822,24 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
|
|
|
2386
2822
|
} catch {
|
|
2387
2823
|
}
|
|
2388
2824
|
}
|
|
2825
|
+
const linearKey = process.env.LINEAR_API_KEY;
|
|
2826
|
+
if (linearKey) {
|
|
2827
|
+
try {
|
|
2828
|
+
const linear = await fetchLinearActivity(linearKey, { windowDays });
|
|
2829
|
+
events.push(...linear.events);
|
|
2830
|
+
adapterSignals += "\n\n" + formatLinearSignalsForPrompt(linear.signals);
|
|
2831
|
+
activeAdapters.push("linear");
|
|
2832
|
+
} catch {
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2389
2835
|
events.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
2836
|
+
if (input.personalUser) {
|
|
2837
|
+
events = filterEventsByUser(events, input.personalUser);
|
|
2838
|
+
}
|
|
2390
2839
|
const classified = classifyEvents(events);
|
|
2391
2840
|
const signals = extractSignals(classified);
|
|
2392
2841
|
const scores = computeScores(signals, input.worldmodelContent !== "");
|
|
2842
|
+
const declaredVocabulary = extractDeclaredVocabulary(worldmodelContent);
|
|
2393
2843
|
const { patterns, meaning, move } = await interpretPatterns({
|
|
2394
2844
|
signals,
|
|
2395
2845
|
events: classified,
|
|
@@ -2397,6 +2847,7 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
|
|
|
2397
2847
|
lens,
|
|
2398
2848
|
ai: input.ai,
|
|
2399
2849
|
canonicalPatterns: input.canonicalPatterns,
|
|
2850
|
+
declaredVocabulary,
|
|
2400
2851
|
statedIntent: [statedIntent, adapterSignals, priorReadContext].filter(Boolean).join("\n\n") || void 0
|
|
2401
2852
|
});
|
|
2402
2853
|
const rewrittenPatterns = patterns.map((p) => lens.rewrite(p));
|
|
@@ -2451,6 +2902,10 @@ Compare stated intent against actual GitHub activity. Gaps = drift.`;
|
|
|
2451
2902
|
worldStack
|
|
2452
2903
|
};
|
|
2453
2904
|
}
|
|
2905
|
+
function filterEventsByUser(events, username) {
|
|
2906
|
+
const target = username.toLowerCase();
|
|
2907
|
+
return events.filter((e) => e.actor.id.toLowerCase() === target);
|
|
2908
|
+
}
|
|
2454
2909
|
function computeScores(signals, worldmodelLoaded) {
|
|
2455
2910
|
const gate = DEFAULT_EVIDENCE_GATE;
|
|
2456
2911
|
const lifeSignals = signals.filter((s) => s.domain === "life");
|
|
@@ -2560,6 +3015,8 @@ export {
|
|
|
2560
3015
|
formatSlackSignalsForPrompt,
|
|
2561
3016
|
fetchNotionActivity,
|
|
2562
3017
|
formatNotionSignalsForPrompt,
|
|
3018
|
+
fetchLinearActivity,
|
|
3019
|
+
formatLinearSignalsForPrompt,
|
|
2563
3020
|
readOriginRemote,
|
|
2564
3021
|
parseRemoteUrl,
|
|
2565
3022
|
getRepoOrigin,
|
|
@@ -2586,6 +3043,8 @@ export {
|
|
|
2586
3043
|
classifyEvents,
|
|
2587
3044
|
extractSignals,
|
|
2588
3045
|
DEFAULT_SIGNAL_EXTRACTORS,
|
|
3046
|
+
extractDeclaredVocabulary,
|
|
3047
|
+
matchDeclaredPattern,
|
|
2589
3048
|
DEFAULT_EVIDENCE_GATE,
|
|
2590
3049
|
isScored,
|
|
2591
3050
|
isSentinel,
|
|
@@ -2598,6 +3057,7 @@ export {
|
|
|
2598
3057
|
interpretPatterns,
|
|
2599
3058
|
render,
|
|
2600
3059
|
emergent,
|
|
3060
|
+
filterEventsByUser,
|
|
2601
3061
|
createAnthropicAI,
|
|
2602
3062
|
createMockAI
|
|
2603
3063
|
};
|