chapterhouse 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agents/korg.agent.md +20 -0
- package/dist/api/auth.js +11 -1
- package/dist/api/auth.test.js +29 -0
- package/dist/api/errors.js +23 -0
- package/dist/api/route-coverage.test.js +61 -21
- package/dist/api/routes/agents.js +472 -0
- package/dist/api/routes/memory.js +299 -0
- package/dist/api/routes/projects.js +170 -0
- package/dist/api/routes/sessions.js +347 -0
- package/dist/api/routes/system.js +82 -0
- package/dist/api/routes/wiki.js +455 -0
- package/dist/api/routes/wiki.test.js +49 -0
- package/dist/api/send-json.js +16 -0
- package/dist/api/send-json.test.js +18 -0
- package/dist/api/server-runtime.js +45 -3
- package/dist/api/server.js +34 -1764
- package/dist/api/server.test.js +239 -8
- package/dist/api/sse-hub.js +37 -0
- package/dist/cli.js +1 -1
- package/dist/config.js +151 -58
- package/dist/config.test.js +29 -0
- package/dist/copilot/okr-mapper.js +2 -11
- package/dist/copilot/orchestrator.js +358 -352
- package/dist/copilot/orchestrator.test.js +139 -4
- package/dist/copilot/prompt-date.js +2 -1
- package/dist/copilot/session-manager.js +25 -23
- package/dist/copilot/session-manager.test.js +35 -1
- package/dist/copilot/standup.js +2 -2
- package/dist/copilot/task-event-log.js +7 -1
- package/dist/copilot/task-event-log.test.js +13 -0
- package/dist/copilot/tools/agent.js +608 -0
- package/dist/copilot/tools/index.js +19 -0
- package/dist/copilot/tools/memory.js +678 -0
- package/dist/copilot/tools/models.js +2 -0
- package/dist/copilot/tools/okr.js +171 -0
- package/dist/copilot/tools/wiki.js +333 -0
- package/dist/copilot/tools-deps.js +4 -0
- package/dist/copilot/tools.agent.test.js +10 -8
- package/dist/copilot/tools.inventory.test.js +76 -0
- package/dist/copilot/tools.js +1 -1725
- package/dist/copilot/tools.okr.test.js +31 -0
- package/dist/copilot/tools.wiki.test.js +358 -6
- package/dist/copilot/turn-event-log.js +31 -4
- package/dist/copilot/turn-event-log.test.js +24 -2
- package/dist/copilot/workiq-installer.test.js +2 -2
- package/dist/daemon-install.js +3 -2
- package/dist/daemon.js +9 -17
- package/dist/integrations/ado-client.js +90 -9
- package/dist/integrations/ado-client.test.js +56 -0
- package/dist/integrations/team-push.js +1 -0
- package/dist/integrations/team-push.test.js +6 -0
- package/dist/integrations/teams-notify.js +1 -0
- package/dist/integrations/teams-notify.test.js +5 -0
- package/dist/memory/active-scope.test.js +0 -1
- package/dist/memory/checkpoint.js +89 -72
- package/dist/memory/checkpoint.test.js +23 -3
- package/dist/memory/eot.js +194 -89
- package/dist/memory/eot.test.js +186 -3
- package/dist/memory/hooks.js +2 -4
- package/dist/memory/housekeeping-scheduler.js +1 -1
- package/dist/memory/housekeeping-scheduler.test.js +1 -2
- package/dist/memory/housekeeping.js +100 -3
- package/dist/memory/housekeeping.test.js +33 -2
- package/dist/memory/reflect.test.js +2 -0
- package/dist/memory/scope-lock.js +26 -0
- package/dist/memory/scope-lock.test.js +118 -0
- package/dist/memory/scopes.test.js +0 -1
- package/dist/mode-context.js +58 -5
- package/dist/mode-context.test.js +68 -0
- package/dist/paths.js +1 -0
- package/dist/setup.js +3 -2
- package/dist/shared/api-schemas.js +48 -5
- package/dist/store/connection.js +96 -0
- package/dist/store/db.js +5 -1498
- package/dist/store/db.test.js +182 -1
- package/dist/store/migrations.js +460 -0
- package/dist/store/repositories/memory.js +281 -0
- package/dist/store/repositories/okr.js +3 -0
- package/dist/store/repositories/projects.js +5 -0
- package/dist/store/repositories/sessions.js +284 -0
- package/dist/store/repositories/wiki.js +60 -0
- package/dist/store/schema.js +501 -0
- package/dist/util/logger.js +3 -2
- package/dist/wiki/consolidation.js +50 -9
- package/dist/wiki/consolidation.test.js +45 -0
- package/dist/wiki/frontmatter.js +45 -14
- package/dist/wiki/frontmatter.test.js +26 -1
- package/dist/wiki/fs.js +16 -4
- package/dist/wiki/fs.test.js +84 -0
- package/dist/wiki/index-manager.js +30 -2
- package/dist/wiki/index-manager.test.js +43 -12
- package/dist/wiki/ingest.js +17 -1
- package/dist/wiki/lock.js +11 -1
- package/dist/wiki/log-manager.js +2 -7
- package/dist/wiki/migrate.js +44 -17
- package/dist/wiki/project-registry.js +10 -5
- package/dist/wiki/project-registry.test.js +14 -0
- package/dist/wiki/scheduler.js +1 -1
- package/dist/wiki/seed-team-wiki.js +2 -1
- package/dist/wiki/team-sync.js +31 -6
- package/dist/wiki/team-sync.test.js +81 -0
- package/package.json +1 -1
- package/web/dist/assets/WikiEdit-BZXAdarz.js +30 -0
- package/web/dist/assets/WikiEdit-BZXAdarz.js.map +1 -0
- package/web/dist/assets/WikiGraph-KrCYco4v.js +2 -0
- package/web/dist/assets/WikiGraph-KrCYco4v.js.map +1 -0
- package/web/dist/assets/index-CUm2Wbuh.js +250 -0
- package/web/dist/assets/index-CUm2Wbuh.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-iQrv3lQN.js +0 -286
- package/web/dist/assets/index-iQrv3lQN.js.map +0 -1
package/README.md
CHANGED
package/agents/korg.agent.md
CHANGED
|
@@ -14,6 +14,26 @@ You are **Korg**, the Personal Knowledge Base (PKB) synthesizer for Chapterhouse
|
|
|
14
14
|
|
|
15
15
|
Your mission: ingest external sources, extract structured knowledge, maintain compiled truth pages, and manage research sessions — so the user's wiki becomes a reliable, growing knowledge asset.
|
|
16
16
|
|
|
17
|
+
## Character
|
|
18
|
+
|
|
19
|
+
You are a careful analyst and archivist. You take the long view: a knowledge base is only as good as its structure, its provenance, and the accuracy of what's been distilled. You treat every source as evidence, every page as a living document, and every synthesis as a claim that must be earned.
|
|
20
|
+
|
|
21
|
+
**Personality:**
|
|
22
|
+
|
|
23
|
+
- You think before you write. When ingesting a new source, you form a view on what it actually says before deciding how it changes existing compiled truth.
|
|
24
|
+
- You surface uncertainty explicitly. If evidence is thin or conflicting, say so — don't smooth it over with confident prose.
|
|
25
|
+
- You explain your reasoning when it matters. A user asking why you organized something a certain way deserves a real answer, not a deflection.
|
|
26
|
+
- You push back on low-quality sources. If something looks like noise or marketing, name it.
|
|
27
|
+
- You take provenance seriously. Where something came from is part of what it means.
|
|
28
|
+
|
|
29
|
+
**Communication:**
|
|
30
|
+
|
|
31
|
+
- Write like a thoughtful analyst, not a bullet-point generator. Prose when the idea warrants it.
|
|
32
|
+
- Be precise but not pedantic. Define terms when the distinction matters; skip it when it doesn't.
|
|
33
|
+
- When you surface a synthesis or conclusion, make it clear what the evidence is and where it came from.
|
|
34
|
+
- Don't perform enthusiasm. "This is a rich source" means nothing. Tell the user what's actually in it.
|
|
35
|
+
- End cleanly. No trailing "Let me know if you'd like me to dig deeper!" when the work speaks for itself.
|
|
36
|
+
|
|
17
37
|
## Your Toolkit
|
|
18
38
|
|
|
19
39
|
- `wiki_ingest_source(source, type?, topic?, session_id?, session_name?)` — ingest a URL, PDF, repo, or text into the PKB
|
package/dist/api/auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { timingSafeEqual } from "crypto";
|
|
1
2
|
import jwt from "jsonwebtoken";
|
|
2
3
|
import jwksClient from "jwks-rsa";
|
|
3
4
|
import { childLogger } from "../util/logger.js";
|
|
@@ -39,6 +40,15 @@ function buildAuthenticatedUser(claims, teamLeadId) {
|
|
|
39
40
|
function unauthorized(res, message) {
|
|
40
41
|
res.status(401).json({ error: message });
|
|
41
42
|
}
|
|
43
|
+
export function timingSafeTokenEqual(provided, expected) {
|
|
44
|
+
if (!provided || !expected)
|
|
45
|
+
return false;
|
|
46
|
+
const a = Buffer.from(provided);
|
|
47
|
+
const b = Buffer.from(expected);
|
|
48
|
+
if (a.length !== b.length)
|
|
49
|
+
return false;
|
|
50
|
+
return timingSafeEqual(a, b);
|
|
51
|
+
}
|
|
42
52
|
// Module-level cache of JWKS clients keyed by tenant ID.
|
|
43
53
|
// Creating a new client per request discards the in-process key cache; keeping
|
|
44
54
|
// one client per tenant lets jwks-rsa honour its cacheMaxAge and avoids an
|
|
@@ -124,7 +134,7 @@ export function createAuthMiddleware(options) {
|
|
|
124
134
|
}
|
|
125
135
|
if (!options.config.entraAuthEnabled) {
|
|
126
136
|
const token = getBearerToken(req);
|
|
127
|
-
if (!
|
|
137
|
+
if (!timingSafeTokenEqual(token, options.apiToken)) {
|
|
128
138
|
unauthorized(res, "Unauthorized");
|
|
129
139
|
return;
|
|
130
140
|
}
|
package/dist/api/auth.test.js
CHANGED
|
@@ -448,4 +448,33 @@ test("JWKS client is reused across calls for the same tenant (module-level cache
|
|
|
448
448
|
assert.doesNotMatch(String(err2.message), /ENTRA_TENANT_ID or ENTRA_CLIENT_ID is missing/, "second error must come from JWT processing — client reused from module cache");
|
|
449
449
|
auth._resetJwksClientCache();
|
|
450
450
|
});
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// timingSafeTokenEqual unit tests
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
test("timingSafeTokenEqual returns true for matching tokens", async () => {
|
|
455
|
+
const auth = await loadAuthModule();
|
|
456
|
+
assert.ok(auth, "auth module should exist");
|
|
457
|
+
assert.equal(typeof auth.timingSafeTokenEqual, "function", "timingSafeTokenEqual should be exported");
|
|
458
|
+
assert.equal(auth.timingSafeTokenEqual("secret-token", "secret-token"), true);
|
|
459
|
+
});
|
|
460
|
+
test("timingSafeTokenEqual returns false for wrong token", async () => {
|
|
461
|
+
const auth = await loadAuthModule();
|
|
462
|
+
assert.ok(auth, "auth module should exist");
|
|
463
|
+
assert.equal(auth.timingSafeTokenEqual("wrong-token", "secret-token"), false);
|
|
464
|
+
});
|
|
465
|
+
test("timingSafeTokenEqual returns false when provided token is null", async () => {
|
|
466
|
+
const auth = await loadAuthModule();
|
|
467
|
+
assert.ok(auth, "auth module should exist");
|
|
468
|
+
assert.equal(auth.timingSafeTokenEqual(null, "secret-token"), false);
|
|
469
|
+
});
|
|
470
|
+
test("timingSafeTokenEqual returns false when expected token is null", async () => {
|
|
471
|
+
const auth = await loadAuthModule();
|
|
472
|
+
assert.ok(auth, "auth module should exist");
|
|
473
|
+
assert.equal(auth.timingSafeTokenEqual("secret-token", null), false);
|
|
474
|
+
});
|
|
475
|
+
test("timingSafeTokenEqual returns false for different-length tokens", async () => {
|
|
476
|
+
const auth = await loadAuthModule();
|
|
477
|
+
assert.ok(auth, "auth module should exist");
|
|
478
|
+
assert.equal(auth.timingSafeTokenEqual("short", "much-longer-token"), false);
|
|
479
|
+
});
|
|
451
480
|
//# sourceMappingURL=auth.test.js.map
|
package/dist/api/errors.js
CHANGED
|
@@ -46,6 +46,19 @@ function isBodyParserSyntaxError(error) {
|
|
|
46
46
|
function formatZodError(error) {
|
|
47
47
|
return error.issues[0]?.message ?? "Invalid request";
|
|
48
48
|
}
|
|
49
|
+
function getStatusCodeError(error) {
|
|
50
|
+
if (!(error instanceof Error))
|
|
51
|
+
return undefined;
|
|
52
|
+
const candidate = error;
|
|
53
|
+
if (typeof candidate.statusCode !== "number" || candidate.statusCode < 400 || candidate.statusCode > 599) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
statusCode: candidate.statusCode,
|
|
58
|
+
message: error.message,
|
|
59
|
+
expose: candidate.expose === true || candidate.statusCode < 500,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
49
62
|
export function parseRequest(schema, input) {
|
|
50
63
|
const parsed = schema.safeParse(input);
|
|
51
64
|
if (!parsed.success) {
|
|
@@ -90,6 +103,16 @@ export function createApiErrorHandler() {
|
|
|
90
103
|
res.status(error.statusCode).json({ error: error.expose ? error.message : "Internal server error" });
|
|
91
104
|
return;
|
|
92
105
|
}
|
|
106
|
+
const statusCodeError = getStatusCodeError(error);
|
|
107
|
+
if (statusCodeError) {
|
|
108
|
+
if (statusCodeError.statusCode >= 500) {
|
|
109
|
+
log.error({ method: req.method, url: req.originalUrl, err: statusCodeError.message }, "API request failed");
|
|
110
|
+
}
|
|
111
|
+
res
|
|
112
|
+
.status(statusCodeError.statusCode)
|
|
113
|
+
.json({ error: statusCodeError.expose ? statusCodeError.message : "Internal server error" });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
93
116
|
log.error({ method: req.method, url: req.originalUrl, err: error instanceof Error ? error.message : error }, "API request failed (unhandled)");
|
|
94
117
|
res.status(500).json({ error: "Internal server error" });
|
|
95
118
|
};
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
18
18
|
import { describe, test } from "node:test";
|
|
19
19
|
import assert from "node:assert/strict";
|
|
20
|
-
import { readFileSync } from "node:fs";
|
|
20
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
24
24
|
const ROOT = join(__dirname, "..", "..");
|
|
25
25
|
const FRONTEND_API_PATH = join(ROOT, "web", "src", "api.ts");
|
|
26
26
|
const SERVER_TS_PATH = join(ROOT, "src", "api", "server.ts");
|
|
27
|
+
const ROUTES_DIR = join(ROOT, "src", "api", "routes");
|
|
27
28
|
const WEB_SCHEMAS_PATH = join(ROOT, "src", "shared", "api-schemas.ts");
|
|
28
29
|
/**
|
|
29
30
|
* Normalise a URL path for comparison:
|
|
@@ -59,20 +60,42 @@ function extractFrontendPaths(src) {
|
|
|
59
60
|
return paths;
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* Read server.ts plus extracted route modules so static route coverage stays
|
|
64
|
+
* effective after handlers move behind Express Router instances.
|
|
64
65
|
*/
|
|
65
|
-
function
|
|
66
|
-
const
|
|
66
|
+
function readServerRouteSources() {
|
|
67
|
+
const sources = [readFileSync(SERVER_TS_PATH, "utf8")];
|
|
68
|
+
if (!existsSync(ROUTES_DIR)) {
|
|
69
|
+
return sources;
|
|
70
|
+
}
|
|
71
|
+
const routeFiles = readdirSync(ROUTES_DIR)
|
|
72
|
+
.filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts"))
|
|
73
|
+
.sort();
|
|
74
|
+
for (const file of routeFiles) {
|
|
75
|
+
sources.push(readFileSync(join(ROUTES_DIR, file), "utf8"));
|
|
76
|
+
}
|
|
77
|
+
return sources;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extract all unique normalised API paths registered via app.METHOD() or
|
|
81
|
+
* router.METHOD(). Only /api/ prefixed paths are considered.
|
|
82
|
+
*/
|
|
83
|
+
function extractServerPaths(sources) {
|
|
84
|
+
const re = /\b(?:app|router)\.(get|post|put|delete|patch)\(["'`](\/api\/[^"'`?]+)/g;
|
|
67
85
|
const paths = new Set();
|
|
68
|
-
for (const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
for (const src of sources) {
|
|
87
|
+
for (const m of src.matchAll(re)) {
|
|
88
|
+
const normalised = normalizePath(m[2]);
|
|
89
|
+
if (normalised) {
|
|
90
|
+
paths.add(normalised);
|
|
91
|
+
}
|
|
72
92
|
}
|
|
73
93
|
}
|
|
74
94
|
return paths;
|
|
75
95
|
}
|
|
96
|
+
function readCombinedServerRouteSource() {
|
|
97
|
+
return readServerRouteSources().join("\n");
|
|
98
|
+
}
|
|
76
99
|
/**
|
|
77
100
|
* Extract SSE type literals emitted inline via formatSseData({ type: "X", ... })
|
|
78
101
|
* in server.ts. Pass-through calls like formatSseData(event) are not detected.
|
|
@@ -128,9 +151,9 @@ function extractStreamEventSchemaTypes(src) {
|
|
|
128
151
|
describe("route coverage — static analysis", () => {
|
|
129
152
|
test("all frontend authedFetch paths have a matching server route registration", () => {
|
|
130
153
|
const frontendSrc = readFileSync(FRONTEND_API_PATH, "utf8");
|
|
131
|
-
const
|
|
154
|
+
const serverSources = readServerRouteSources();
|
|
132
155
|
const frontendPaths = extractFrontendPaths(frontendSrc);
|
|
133
|
-
const serverPaths = extractServerPaths(
|
|
156
|
+
const serverPaths = extractServerPaths(serverSources);
|
|
134
157
|
const missing = [];
|
|
135
158
|
for (const fp of [...frontendPaths].sort()) {
|
|
136
159
|
if (!serverPaths.has(fp)) {
|
|
@@ -139,20 +162,37 @@ describe("route coverage — static analysis", () => {
|
|
|
139
162
|
}
|
|
140
163
|
assert.ok(missing.length === 0, `Frontend calls ${missing.length} route(s) with no matching server registration:\n` +
|
|
141
164
|
missing.map((p) => ` MISSING: ${p}`).join("\n") +
|
|
142
|
-
"\n\nAdd the missing route(s) to src/api/server.ts");
|
|
165
|
+
"\n\nAdd the missing route(s) to src/api/server.ts or src/api/routes/*.ts");
|
|
166
|
+
});
|
|
167
|
+
test("server route coverage scans extracted route modules", () => {
|
|
168
|
+
assert.ok(existsSync(ROUTES_DIR), "Expected src/api/routes/ to exist so route coverage scans extracted routers.");
|
|
169
|
+
const routeFiles = readdirSync(ROUTES_DIR)
|
|
170
|
+
.filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts"))
|
|
171
|
+
.sort();
|
|
172
|
+
assert.deepEqual(routeFiles, ["agents.ts", "memory.ts", "projects.ts", "sessions.ts", "system.ts", "wiki.ts"]);
|
|
143
173
|
});
|
|
144
174
|
test("server routes cover every unique normalised path (no obvious duplicates leaked)", () => {
|
|
145
|
-
const
|
|
146
|
-
const serverPaths = extractServerPaths(serverSrc);
|
|
175
|
+
const serverPaths = extractServerPaths(readServerRouteSources());
|
|
147
176
|
// Sanity: the server must expose at least 30 /api/ routes.
|
|
148
177
|
assert.ok(serverPaths.size >= 30, `Expected ≥30 server routes, found ${serverPaths.size}. Check extractServerPaths regex.`);
|
|
149
178
|
});
|
|
150
179
|
});
|
|
151
|
-
describe("
|
|
152
|
-
test("
|
|
153
|
-
const serverSrc =
|
|
154
|
-
assert.
|
|
155
|
-
|
|
180
|
+
describe("shared API schema enforcement — static analysis", () => {
|
|
181
|
+
test("server route success responses go through sendJson schema validation", () => {
|
|
182
|
+
const serverSrc = readCombinedServerRouteSource();
|
|
183
|
+
assert.doesNotMatch(serverSrc, /\bres\.json\(/, "Do not send successful route responses with res.json() directly; use sendJson(res, SharedSchema, payload).");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe("explicit auth route safeguards — static analysis", () => {
|
|
187
|
+
test("sensitive wiki and memory routes declare authMiddleware explicitly", () => {
|
|
188
|
+
const serverSrc = readCombinedServerRouteSource();
|
|
189
|
+
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/wiki\/update",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
190
|
+
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/wiki\/page\/pin",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
191
|
+
assert.match(serverSrc, /\b(?:app|router)\.get\("\/api\/wiki\/korg\/sessions",\s*authMiddleware,/);
|
|
192
|
+
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/wiki\/ingest",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
193
|
+
assert.match(serverSrc, /\b(?:app|router)\.get\("\/api\/wiki\/search",\s*authMiddleware,\s*async \(req: Request, res: Response\) => \{/);
|
|
194
|
+
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/memory\/hooks\/git-commit",\s*authMiddleware,/);
|
|
195
|
+
assert.match(serverSrc, /\b(?:app|router)\.post\("\/api\/memory\/hooks\/pr-merge",\s*authMiddleware,/);
|
|
156
196
|
});
|
|
157
197
|
});
|
|
158
198
|
describe("SSE exhaustiveness — static analysis", () => {
|
|
@@ -169,7 +209,7 @@ describe("SSE exhaustiveness — static analysis", () => {
|
|
|
169
209
|
// type in StreamEventSchema, this test FAILS, preventing the regression.
|
|
170
210
|
// ───────────────────────────────────────────────────────────────────────────
|
|
171
211
|
test("formatSseEvent is not used with names that belong in the StreamEventSchema data channel", () => {
|
|
172
|
-
const serverSrc =
|
|
212
|
+
const serverSrc = readCombinedServerRouteSource();
|
|
173
213
|
const schemaSrc = readFileSync(WEB_SCHEMAS_PATH, "utf8");
|
|
174
214
|
const namedEventNames = extractSseEventNames(serverSrc);
|
|
175
215
|
const schemaTypes = extractStreamEventSchemaTypes(schemaSrc);
|
|
@@ -184,7 +224,7 @@ describe("SSE exhaustiveness — static analysis", () => {
|
|
|
184
224
|
"\n\nNamed SSE events are silently dropped by the frontend's default 'message' handler (issues #367/#368).");
|
|
185
225
|
});
|
|
186
226
|
test("all inline formatSseData type literals are known StreamEventSchema types or agent-stream-only types", () => {
|
|
187
|
-
const serverSrc =
|
|
227
|
+
const serverSrc = readCombinedServerRouteSource();
|
|
188
228
|
const schemaSrc = readFileSync(WEB_SCHEMAS_PATH, "utf8");
|
|
189
229
|
const emittedTypes = extractSseDataTypes(serverSrc);
|
|
190
230
|
const schemaTypes = extractStreamEventSchemaTypes(schemaSrc);
|