chapterhouse 0.6.0 → 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/agents/korg.agent.md +65 -0
- package/dist/api/agent-edit-access.js +11 -0
- package/dist/api/agents.api.test.js +48 -0
- package/dist/api/korg.js +34 -0
- package/dist/api/korg.test.js +42 -0
- package/dist/api/server.js +420 -13
- package/dist/api/server.test.js +533 -3
- package/dist/config.js +28 -0
- package/dist/config.test.js +20 -0
- package/dist/copilot/agent-event-bus.js +1 -0
- package/dist/copilot/agents.js +117 -50
- package/dist/copilot/agents.mcp-servers.test.js +87 -0
- package/dist/copilot/agents.parse.test.js +69 -0
- package/dist/copilot/agents.test.js +137 -2
- package/dist/copilot/orchestrator.js +62 -13
- package/dist/copilot/orchestrator.test.js +130 -8
- package/dist/copilot/session-manager.js +34 -0
- package/dist/copilot/system-message.js +11 -10
- package/dist/copilot/system-message.test.js +6 -1
- package/dist/copilot/tools.js +184 -376
- package/dist/copilot/tools.memory.test.js +32 -0
- package/dist/copilot/tools.wiki.test.js +53 -59
- package/dist/daemon.js +9 -0
- package/dist/memory/decisions.js +6 -5
- package/dist/memory/entities.js +20 -9
- package/dist/memory/hooks.js +151 -0
- package/dist/memory/hooks.test.js +325 -0
- package/dist/memory/hot-tier.js +37 -0
- package/dist/memory/hot-tier.test.js +30 -0
- package/dist/memory/housekeeping-scheduler.js +35 -0
- package/dist/memory/housekeeping-scheduler.test.js +50 -0
- package/dist/memory/inbox.js +10 -0
- package/dist/memory/index.js +3 -1
- package/dist/memory/migration.js +244 -0
- package/dist/memory/migration.test.js +100 -0
- package/dist/memory/reflect.js +273 -0
- package/dist/memory/reflect.test.js +254 -0
- package/dist/store/db.js +119 -4
- package/dist/store/db.test.js +19 -1
- package/dist/test/setup-env.js +3 -1
- package/dist/test/setup-env.test.js +8 -1
- package/dist/wiki/consolidation.js +641 -0
- package/dist/wiki/consolidation.test.js +140 -0
- package/dist/wiki/frontmatter.js +48 -0
- package/dist/wiki/frontmatter.test.js +42 -0
- package/dist/wiki/index-manager.js +246 -330
- package/dist/wiki/index-manager.test.js +138 -145
- package/dist/wiki/ingest.js +347 -0
- package/dist/wiki/ingest.test.js +111 -0
- package/dist/wiki/links.js +151 -0
- package/dist/wiki/links.test.js +176 -0
- package/dist/wiki/migrate-topics.test.js +16 -6
- package/dist/wiki/scheduler.js +118 -0
- package/dist/wiki/scheduler.test.js +64 -0
- package/dist/wiki/timeline.js +51 -0
- package/dist/wiki/timeline.test.js +65 -0
- package/dist/wiki/topic-structure.js +1 -1
- package/package.json +3 -1
- package/skills/pkb-ideas/SKILL.md +78 -0
- package/skills/pkb-ideas/_meta.json +4 -0
- package/skills/pkb-org/SKILL.md +82 -0
- package/skills/pkb-org/_meta.json +4 -0
- package/skills/pkb-people/SKILL.md +74 -0
- package/skills/pkb-people/_meta.json +4 -0
- package/skills/pkb-research/SKILL.md +83 -0
- package/skills/pkb-research/_meta.json +4 -0
- package/skills/pkb-source/SKILL.md +38 -0
- package/skills/pkb-source/_meta.json +4 -0
- package/skills/wiki-conventions/SKILL.md +5 -5
- package/web/dist/assets/index-5kz9aRU9.css +10 -0
- package/web/dist/assets/{index-B5oDsQ5y.js → index-BbX9RKf3.js} +101 -99
- package/web/dist/assets/index-BbX9RKf3.js.map +1 -0
- package/web/dist/index.html +2 -2
- package/dist/wiki/context.js +0 -138
- package/dist/wiki/fix.js +0 -335
- package/dist/wiki/fix.test.js +0 -350
- package/dist/wiki/lint.js +0 -451
- package/dist/wiki/lint.test.js +0 -329
- package/web/dist/assets/index-B5oDsQ5y.js.map +0 -1
- package/web/dist/assets/index-DknKAtDS.css +0 -10
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Korg
|
|
3
|
+
description: PKB synthesizer — handles ingestion, synthesis, and knowledge consolidation
|
|
4
|
+
model: claude-sonnet-4.6
|
|
5
|
+
scope: pkb
|
|
6
|
+
skills:
|
|
7
|
+
- pkb-source
|
|
8
|
+
- pkb-research
|
|
9
|
+
- pkb-people
|
|
10
|
+
persistent: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You are **Korg**, the Personal Knowledge Base (PKB) synthesizer for Chapterhouse.
|
|
14
|
+
|
|
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
|
+
|
|
17
|
+
## Your Toolkit
|
|
18
|
+
|
|
19
|
+
- `wiki_ingest_source(source, type?, topic?, session_id?, session_name?)` — ingest a URL, PDF, repo, or text into the PKB
|
|
20
|
+
- `wiki_append_timeline(page, entry, source_id?)` — append an event to a page's timeline section
|
|
21
|
+
- `wiki_search(query)` — search the wiki knowledge base
|
|
22
|
+
- `wiki_read(path)` — read a specific wiki page
|
|
23
|
+
- `wiki_update(path, title, summary, content)` — create or update a wiki page
|
|
24
|
+
- `memory_propose(kind, payload)` — propose a memory to the orchestrator
|
|
25
|
+
|
|
26
|
+
## PKB Schema
|
|
27
|
+
|
|
28
|
+
Entity pages live at `pages/{type}/{slug}/index.md` where type is one of: `people`, `projects`, `orgs`, `tools`, `topics`, `areas`.
|
|
29
|
+
|
|
30
|
+
Each entity page has two zones:
|
|
31
|
+
1. **Summary** — the `## Summary` section: a distilled, up-to-date synthesis of what is known. Rewritten on every new source ingestion.
|
|
32
|
+
2. **Timeline** — the `## Timeline` section: append-only record of events, observations, and source ingestions. Never edited, only appended.
|
|
33
|
+
|
|
34
|
+
Frontmatter must include: `title`, `summary` (one-line, plain text ≤200 chars), `updated`, `tags`.
|
|
35
|
+
|
|
36
|
+
## Synthesis Principles
|
|
37
|
+
|
|
38
|
+
1. **Summary over raw notes.** After ingesting a new source, rewrite the `## Summary` section to reflect current understanding — don't just append.
|
|
39
|
+
2. **Timeline is append-only.** Never edit existing timeline entries. Each new event gets a `### {ISO timestamp}` heading.
|
|
40
|
+
3. **Source attribution.** Every ingestion event in the timeline should reference the `source_id` returned by `wiki_ingest_source`.
|
|
41
|
+
4. **Idempotency.** Before ingesting, check if the source is already known. `wiki_ingest_source` handles this automatically.
|
|
42
|
+
|
|
43
|
+
## Research Session Lifecycle
|
|
44
|
+
|
|
45
|
+
1. User provides a research topic.
|
|
46
|
+
2. Create `pages/research/{slug}/index.md` — the research hub with frontmatter, summary, and empty timeline.
|
|
47
|
+
3. Create `pages/research/{slug}/synthesis.md` — the compiled synthesis document (rewritten after each source).
|
|
48
|
+
4. For each source: call `wiki_ingest_source`, update `synthesis.md` with new understanding, append to timeline.
|
|
49
|
+
5. When research is complete, write a final synthesis and close the session with a timeline entry.
|
|
50
|
+
|
|
51
|
+
## People Capture
|
|
52
|
+
|
|
53
|
+
When ingesting information about a person:
|
|
54
|
+
- Page: `pages/people/{slug}/index.md`
|
|
55
|
+
- Summary: role, affiliation, key facts, relationship to user
|
|
56
|
+
- Timeline: every interaction, meeting, or observation
|
|
57
|
+
- After significant interactions, extract follow-up items and propose them with `memory_propose`
|
|
58
|
+
|
|
59
|
+
## Behavioral Rules
|
|
60
|
+
|
|
61
|
+
- Always call `wiki_ingest_source` before manually creating pages — let the pipeline do entity extraction.
|
|
62
|
+
- Keep summaries factual and concise. Avoid speculation in compiled truth.
|
|
63
|
+
- If entity extraction returns nothing useful, create a minimal page with the source content.
|
|
64
|
+
- Log every ingestion event with `wiki_append_timeline` on the relevant entity pages.
|
|
65
|
+
- When unsure about entity type, use `topics` as the fallback category.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ForbiddenError } from "./errors.js";
|
|
2
|
+
export function assertAgentEditAccess(options, user) {
|
|
3
|
+
if (!options.entraAuthEnabled) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (user?.role === "team-lead") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
throw new ForbiddenError("Admin access required");
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=agent-edit-access.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createAgentFile, isBuiltinAgent, parseAgentMd } from "../copilot/agents.js";
|
|
4
|
+
test("isBuiltinAgent returns true for shipped agent slugs", () => {
|
|
5
|
+
assert.equal(isBuiltinAgent("chapterhouse"), true);
|
|
6
|
+
assert.equal(isBuiltinAgent("coder"), true);
|
|
7
|
+
assert.equal(isBuiltinAgent("designer"), true);
|
|
8
|
+
assert.equal(isBuiltinAgent("general-purpose"), true);
|
|
9
|
+
assert.equal(isBuiltinAgent("custom-helper"), false);
|
|
10
|
+
});
|
|
11
|
+
test("parseAgentMd parses block-style YAML arrays", () => {
|
|
12
|
+
const agent = parseAgentMd([
|
|
13
|
+
"---",
|
|
14
|
+
"name: Designer",
|
|
15
|
+
"description: Handles UI flows",
|
|
16
|
+
"model: claude-sonnet-4.6",
|
|
17
|
+
"skills:",
|
|
18
|
+
" - frontend-design",
|
|
19
|
+
" - ux-copy",
|
|
20
|
+
"tools:",
|
|
21
|
+
" - read",
|
|
22
|
+
" - write",
|
|
23
|
+
"---",
|
|
24
|
+
"",
|
|
25
|
+
"You are Designer.",
|
|
26
|
+
].join("\n"), "designer");
|
|
27
|
+
assert.ok(agent, "agent charter should parse");
|
|
28
|
+
assert.deepEqual(agent.skills, ["frontend-design", "ux-copy"]);
|
|
29
|
+
assert.deepEqual(agent.tools, ["read", "write"]);
|
|
30
|
+
});
|
|
31
|
+
test("slug validation regex rejects traversal-like agent slugs", () => {
|
|
32
|
+
const error = createAgentFile("..%2F..%2F", "Traversal", "Should fail validation", "claude-sonnet-4.6", "You are not valid.");
|
|
33
|
+
assert.equal(error, "Invalid slug '..%2F..%2F': must be kebab-case (a-z0-9 with hyphens).");
|
|
34
|
+
});
|
|
35
|
+
test("parseAgentMd returns null for invalid frontmatter", () => {
|
|
36
|
+
const agent = parseAgentMd([
|
|
37
|
+
"---",
|
|
38
|
+
"name: Designer",
|
|
39
|
+
"description: Handles UI flows",
|
|
40
|
+
"model: claude-sonnet-4.6",
|
|
41
|
+
"skills: [frontend-design",
|
|
42
|
+
"---",
|
|
43
|
+
"",
|
|
44
|
+
"You are Designer.",
|
|
45
|
+
].join("\n"), "designer");
|
|
46
|
+
assert.equal(agent, null);
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=agents.api.test.js.map
|
package/dist/api/korg.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { sendToAgentSession } from "../copilot/orchestrator.js";
|
|
2
|
+
export async function routeKorgMessage(input) {
|
|
3
|
+
const message = input.message.trim();
|
|
4
|
+
const sessionId = input.session_id?.trim() || `korg-${Date.now()}`;
|
|
5
|
+
const sessionName = input.session_id?.trim() || message.slice(0, 80).trim() || sessionId;
|
|
6
|
+
const prompt = [
|
|
7
|
+
"Handle this request as Korg via the API.",
|
|
8
|
+
`Research session id: ${sessionId}`,
|
|
9
|
+
`Research session name: ${sessionName}`,
|
|
10
|
+
`When you ingest sources for this session, pass session_id: \"${sessionId}\" and session_name: \"${sessionName}\" to wiki_ingest_source.`,
|
|
11
|
+
"Reply directly to the user with the next best research action or synthesis.",
|
|
12
|
+
"",
|
|
13
|
+
message,
|
|
14
|
+
].join("\n");
|
|
15
|
+
const reply = await sendToAgentSession("korg", prompt);
|
|
16
|
+
return { ok: true, session_id: sessionId, reply };
|
|
17
|
+
}
|
|
18
|
+
export function listKorgResearchSessions(db) {
|
|
19
|
+
return db.prepare(`
|
|
20
|
+
SELECT
|
|
21
|
+
session_id AS id,
|
|
22
|
+
COALESCE(NULLIF(TRIM(session_name), ''), session_id) AS name,
|
|
23
|
+
COUNT(*) AS source_count,
|
|
24
|
+
0 AS open_questions,
|
|
25
|
+
MAX(ingested_at) AS last_activity
|
|
26
|
+
FROM wiki_sources
|
|
27
|
+
WHERE status = 'active'
|
|
28
|
+
AND session_id IS NOT NULL
|
|
29
|
+
AND TRIM(session_id) != ''
|
|
30
|
+
GROUP BY session_id, COALESCE(NULLIF(TRIM(session_name), ''), session_id)
|
|
31
|
+
ORDER BY MAX(ingested_at) DESC, session_id ASC
|
|
32
|
+
`).all();
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=korg.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
test("routeKorgMessage creates a research session id and delegates to the persistent Korg agent", async (t) => {
|
|
4
|
+
const calls = [];
|
|
5
|
+
t.mock.module("../copilot/orchestrator.js", {
|
|
6
|
+
namedExports: {
|
|
7
|
+
sendToAgentSession: async (slug, prompt) => {
|
|
8
|
+
calls.push({ slug, prompt });
|
|
9
|
+
return "Korg reply";
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const mod = await import(new URL(`./korg.js?case=${Date.now()}-${Math.random()}`, import.meta.url).href);
|
|
14
|
+
const result = await mod.routeKorgMessage({ message: "Research local-first PKB patterns." });
|
|
15
|
+
assert.equal(result.ok, true);
|
|
16
|
+
assert.match(result.session_id, /^korg-/);
|
|
17
|
+
assert.equal(result.reply, "Korg reply");
|
|
18
|
+
assert.deepEqual(calls, [{
|
|
19
|
+
slug: "korg",
|
|
20
|
+
prompt: calls[0].prompt,
|
|
21
|
+
}]);
|
|
22
|
+
assert.match(calls[0].prompt, /Research session id:/);
|
|
23
|
+
assert.match(calls[0].prompt, /Research local-first PKB patterns\./);
|
|
24
|
+
});
|
|
25
|
+
test("routeKorgMessage preserves an existing research session id in the delegated prompt", async (t) => {
|
|
26
|
+
let prompt = "";
|
|
27
|
+
t.mock.module("../copilot/orchestrator.js", {
|
|
28
|
+
namedExports: {
|
|
29
|
+
sendToAgentSession: async (_slug, incomingPrompt) => {
|
|
30
|
+
prompt = incomingPrompt;
|
|
31
|
+
return "Continuing session";
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const mod = await import(new URL(`./korg.js?case=${Date.now()}-${Math.random()}`, import.meta.url).href);
|
|
36
|
+
const result = await mod.routeKorgMessage({ message: "Add two more sources.", session_id: "compiler-research" });
|
|
37
|
+
assert.equal(result.session_id, "compiler-research");
|
|
38
|
+
assert.equal(result.reply, "Continuing session");
|
|
39
|
+
assert.match(prompt, /compiler-research/);
|
|
40
|
+
assert.match(prompt, /Add two more sources\./);
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=korg.test.js.map
|