chapterhouse 0.7.0 → 0.8.1

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.
Files changed (81) hide show
  1. package/agents/korg.agent.md +65 -0
  2. package/dist/api/korg.js +34 -0
  3. package/dist/api/korg.test.js +42 -0
  4. package/dist/api/server.js +238 -2
  5. package/dist/api/server.test.js +199 -0
  6. package/dist/config.js +28 -0
  7. package/dist/config.test.js +20 -0
  8. package/dist/copilot/agents.js +3 -4
  9. package/dist/copilot/agents.test.js +12 -1
  10. package/dist/copilot/orchestrator.js +12 -1
  11. package/dist/copilot/orchestrator.test.js +3 -7
  12. package/dist/copilot/system-message.js +12 -10
  13. package/dist/copilot/system-message.test.js +6 -1
  14. package/dist/copilot/tools.js +193 -375
  15. package/dist/copilot/tools.memory.test.js +32 -0
  16. package/dist/copilot/tools.wiki.test.js +80 -59
  17. package/dist/copilot/turn-event-log-env.test.js +11 -15
  18. package/dist/daemon.js +19 -0
  19. package/dist/memory/decisions.js +6 -5
  20. package/dist/memory/entities.js +20 -9
  21. package/dist/memory/eot.js +30 -8
  22. package/dist/memory/eot.test.js +220 -6
  23. package/dist/memory/hooks.js +151 -0
  24. package/dist/memory/hooks.test.js +325 -0
  25. package/dist/memory/hot-tier.js +37 -0
  26. package/dist/memory/hot-tier.test.js +30 -0
  27. package/dist/memory/housekeeping-scheduler.js +35 -0
  28. package/dist/memory/housekeeping-scheduler.test.js +50 -0
  29. package/dist/memory/inbox.js +10 -0
  30. package/dist/memory/index.js +3 -1
  31. package/dist/memory/migration.js +244 -0
  32. package/dist/memory/migration.test.js +108 -0
  33. package/dist/memory/reflect.js +273 -0
  34. package/dist/memory/reflect.test.js +254 -0
  35. package/dist/paths.js +31 -11
  36. package/dist/store/db.js +187 -4
  37. package/dist/store/db.test.js +66 -2
  38. package/dist/test/helpers/reset-singletons.js +8 -0
  39. package/dist/test/helpers/reset-singletons.test.js +37 -0
  40. package/dist/test/setup-env.js +9 -1
  41. package/dist/wiki/consolidation.js +641 -0
  42. package/dist/wiki/consolidation.test.js +143 -0
  43. package/dist/wiki/frontmatter.js +48 -0
  44. package/dist/wiki/frontmatter.test.js +42 -0
  45. package/dist/wiki/fs.js +22 -13
  46. package/dist/wiki/index-manager.js +305 -330
  47. package/dist/wiki/index-manager.test.js +265 -144
  48. package/dist/wiki/ingest.js +347 -0
  49. package/dist/wiki/ingest.test.js +111 -0
  50. package/dist/wiki/links.js +151 -0
  51. package/dist/wiki/links.test.js +176 -0
  52. package/dist/wiki/log-manager.js +8 -5
  53. package/dist/wiki/log-manager.test.js +4 -0
  54. package/dist/wiki/migrate-topics.test.js +16 -6
  55. package/dist/wiki/scheduler.js +118 -0
  56. package/dist/wiki/scheduler.test.js +64 -0
  57. package/dist/wiki/timeline.js +51 -0
  58. package/dist/wiki/timeline.test.js +65 -0
  59. package/dist/wiki/topic-structure.js +1 -1
  60. package/package.json +1 -1
  61. package/skills/pkb-ideas/SKILL.md +78 -0
  62. package/skills/pkb-ideas/_meta.json +4 -0
  63. package/skills/pkb-org/SKILL.md +82 -0
  64. package/skills/pkb-org/_meta.json +4 -0
  65. package/skills/pkb-people/SKILL.md +74 -0
  66. package/skills/pkb-people/_meta.json +4 -0
  67. package/skills/pkb-research/SKILL.md +83 -0
  68. package/skills/pkb-research/_meta.json +4 -0
  69. package/skills/pkb-source/SKILL.md +38 -0
  70. package/skills/pkb-source/_meta.json +4 -0
  71. package/skills/wiki-conventions/SKILL.md +5 -5
  72. package/web/dist/assets/{index-DuKYxMIR.css → index-5kz9aRU9.css} +1 -1
  73. package/web/dist/assets/{index-DytB69KC.js → index-BbX9RKf3.js} +91 -89
  74. package/web/dist/assets/index-BbX9RKf3.js.map +1 -0
  75. package/web/dist/index.html +2 -2
  76. package/dist/wiki/context.js +0 -138
  77. package/dist/wiki/fix.js +0 -335
  78. package/dist/wiki/fix.test.js +0 -350
  79. package/dist/wiki/lint.js +0 -451
  80. package/dist/wiki/lint.test.js +0 -329
  81. package/web/dist/assets/index-DytB69KC.js.map +0 -1
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
2
2
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import test from "node:test";
5
+ import { resetSingletons } from "../test/helpers/reset-singletons.js";
5
6
  const repoRoot = process.cwd();
6
7
  const sandboxRoot = join(repoRoot, ".test-work", `memory-eot-${process.pid}`);
7
8
  const chapterhouseHome = join(sandboxRoot, ".chapterhouse");
@@ -18,24 +19,40 @@ async function loadModules(cacheBust = `${Date.now()}-${Math.random()}`) {
18
19
  const agentsModule = await import(new URL(`../copilot/agents.js?case=${cacheBust}`, import.meta.url).href);
19
20
  return { dbModule, memoryModule, eotModule, agentsModule };
20
21
  }
22
+ async function loadModulesWithWarnSpy(t, cacheBust) {
23
+ const warnings = [];
24
+ t.mock.module("../util/logger.js", {
25
+ namedExports: {
26
+ childLogger: () => ({
27
+ info: () => { },
28
+ warn: (...args) => {
29
+ warnings.push(args);
30
+ },
31
+ error: () => { },
32
+ }),
33
+ },
34
+ });
35
+ const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
36
+ const memoryModule = await import(new URL(`./index.js?case=${cacheBust}`, import.meta.url).href);
37
+ const eotModule = await import(new URL(`./eot.js?case=${cacheBust}`, import.meta.url).href);
38
+ return { dbModule, memoryModule, eotModule, warnings };
39
+ }
21
40
  function getFunction(module, name) {
22
41
  const value = module[name];
23
42
  assert.equal(typeof value, "function", `expected ${name} to be exported`);
24
43
  return value;
25
44
  }
26
- test.beforeEach(async () => {
45
+ test.beforeEach(() => {
27
46
  process.env.CHAPTERHOUSE_HOME = sandboxRoot;
28
47
  delete process.env.CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED;
29
48
  delete process.env.CHAPTERHOUSE_MEMORY_AUTO_ACCEPT;
30
- const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
31
- dbModule.closeDb();
32
49
  resetSandbox();
50
+ resetSingletons();
33
51
  });
34
- test.after(async () => {
52
+ test.after(() => {
35
53
  delete process.env.CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED;
36
54
  delete process.env.CHAPTERHOUSE_MEMORY_AUTO_ACCEPT;
37
- const dbModule = await import(new URL("../store/db.js", import.meta.url).href);
38
- dbModule.closeDb();
55
+ resetSingletons();
39
56
  rmSync(sandboxRoot, { recursive: true, force: true });
40
57
  });
41
58
  test("runEndOfTaskMemoryHook does nothing when CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED=0", async () => {
@@ -239,6 +256,98 @@ test("runEndOfTaskMemoryHook rejects invalid action_item proposals with a clear
239
256
  assert.match(inbox.resolution_reason ?? "", /title/i);
240
257
  assert.ok(inbox.resolved_at);
241
258
  });
259
+ async function runAcceptedObservationHookScenario(t, cacheBust, taskId, payload) {
260
+ const warnings = [];
261
+ t.mock.module("../util/logger.js", {
262
+ namedExports: {
263
+ childLogger: () => ({
264
+ info: () => { },
265
+ warn: (obj, msg) => warnings.push({ obj, msg }),
266
+ error: () => { },
267
+ }),
268
+ },
269
+ });
270
+ const { dbModule, memoryModule, eotModule } = await loadModules(cacheBust);
271
+ const db = dbModule.getDb();
272
+ const getScope = getFunction(memoryModule, "getScope");
273
+ const listObservations = getFunction(memoryModule, "listObservations");
274
+ const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
275
+ const chapterhouse = getScope("chapterhouse");
276
+ assert.ok(chapterhouse);
277
+ const before = listObservations({ scope_id: chapterhouse.id });
278
+ const inserted = db.prepare(`
279
+ INSERT INTO mem_inbox (scope_id, kind, payload, source_agent, source_task_id, status)
280
+ VALUES (?, 'memory_proposal', ?, 'coder', ?, 'pending')
281
+ `).run(chapterhouse.id, JSON.stringify({
282
+ kind: "observation",
283
+ payload,
284
+ confidence: 0.9,
285
+ }), taskId);
286
+ await runEndOfTaskMemoryHook({
287
+ taskId,
288
+ finalResult: "Completed and reviewed an observation proposal.",
289
+ copilotClient: {},
290
+ callLLM: async () => JSON.stringify({
291
+ decisions: [{
292
+ proposal_id: Number(inserted.lastInsertRowid),
293
+ decision: "accept",
294
+ reason: "Durable finding.",
295
+ }],
296
+ implicit_memories: [],
297
+ }),
298
+ });
299
+ const inbox = db.prepare(`
300
+ SELECT status, resolution_reason
301
+ FROM mem_inbox
302
+ WHERE id = ?
303
+ `).get(Number(inserted.lastInsertRowid));
304
+ return {
305
+ beforeCount: before.length,
306
+ after: listObservations({ scope_id: chapterhouse.id }),
307
+ warnings,
308
+ inbox,
309
+ };
310
+ }
311
+ test("runEndOfTaskMemoryHook skips accepted observation proposals with null content and warns", async (t) => {
312
+ const result = await runAcceptedObservationHookScenario(t, "observation-null-content", "task-eot-observation-null-content", { content: null });
313
+ assert.equal(result.after.length, result.beforeCount);
314
+ assert.equal(result.after.some((row) => row.content.trim().length === 0), false);
315
+ assert.equal(result.inbox.status, "accepted");
316
+ assert.equal(result.inbox.resolution_reason, "Durable finding.");
317
+ assert.ok(result.warnings.some((entry) => /empty content/i.test(entry.msg)));
318
+ });
319
+ test("runEndOfTaskMemoryHook skips accepted observation proposals with undefined content and warns", async (t) => {
320
+ const result = await runAcceptedObservationHookScenario(t, "observation-undefined-content", "task-eot-observation-undefined-content", {});
321
+ assert.equal(result.after.length, result.beforeCount);
322
+ assert.equal(result.after.some((row) => row.content.trim().length === 0), false);
323
+ assert.equal(result.inbox.status, "accepted");
324
+ assert.equal(result.inbox.resolution_reason, "Durable finding.");
325
+ assert.ok(result.warnings.some((entry) => /empty content/i.test(entry.msg)));
326
+ });
327
+ test("runEndOfTaskMemoryHook skips accepted observation proposals with empty string content and warns", async (t) => {
328
+ const result = await runAcceptedObservationHookScenario(t, "observation-empty-string-content", "task-eot-observation-empty-string-content", { content: "" });
329
+ assert.equal(result.after.length, result.beforeCount);
330
+ assert.equal(result.after.some((row) => row.content.trim().length === 0), false);
331
+ assert.equal(result.inbox.status, "accepted");
332
+ assert.equal(result.inbox.resolution_reason, "Durable finding.");
333
+ assert.ok(result.warnings.some((entry) => /empty content/i.test(entry.msg)));
334
+ });
335
+ test("runEndOfTaskMemoryHook skips accepted observation proposals with whitespace-only content and warns", async (t) => {
336
+ const result = await runAcceptedObservationHookScenario(t, "observation-whitespace-content", "task-eot-observation-whitespace-content", { content: " " });
337
+ assert.equal(result.after.length, result.beforeCount);
338
+ assert.equal(result.after.some((row) => row.content.trim().length === 0), false);
339
+ assert.equal(result.inbox.status, "accepted");
340
+ assert.equal(result.inbox.resolution_reason, "Durable finding.");
341
+ assert.ok(result.warnings.some((entry) => /empty content/i.test(entry.msg)));
342
+ });
343
+ test("runEndOfTaskMemoryHook inserts accepted observation proposals with valid content", async (t) => {
344
+ const result = await runAcceptedObservationHookScenario(t, "observation-valid-content", "task-eot-observation-valid-content", { content: " Durable finding from the task. " });
345
+ assert.equal(result.after.length, result.beforeCount + 1);
346
+ assert.ok(result.after.some((row) => row.content === "Durable finding from the task."));
347
+ assert.equal(result.inbox.status, "accepted");
348
+ assert.equal(result.inbox.resolution_reason, "Durable finding.");
349
+ assert.equal(result.warnings.length, 0);
350
+ });
242
351
  test("runEndOfTaskMemoryHook rejects action_item proposals with ambiguous entity references", async () => {
243
352
  const { dbModule, memoryModule, eotModule } = await loadModules("action-item-ambiguous-entity");
244
353
  const db = dbModule.getDb();
@@ -549,4 +658,109 @@ test("runEndOfTaskMemoryHook accepts implicit entity memories with entity_kind",
549
658
  && entity.kind === "host"
550
659
  && entity.summary === "NAS host used by Bellonda."), true);
551
660
  });
661
+ test("runEndOfTaskMemoryHook skips implicit observation memories with null content and warns", async (t) => {
662
+ const { memoryModule, eotModule, warnings } = await loadModulesWithWarnSpy(t, "implicit-null-content");
663
+ const getScope = getFunction(memoryModule, "getScope");
664
+ const listObservations = getFunction(memoryModule, "listObservations");
665
+ const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
666
+ const chapterhouse = getScope("chapterhouse");
667
+ assert.ok(chapterhouse, "chapterhouse scope should be seeded");
668
+ const initialCount = listObservations({ scope_id: chapterhouse.id }).length;
669
+ const summary = await runEndOfTaskMemoryHook({
670
+ taskId: "task-eot-implicit-null",
671
+ finalResult: "The reviewer attempted to persist an invalid implicit memory.",
672
+ copilotClient: {},
673
+ callLLM: async () => JSON.stringify({
674
+ decisions: [],
675
+ implicit_memories: [{
676
+ kind: "observation",
677
+ scope_slug: "chapterhouse",
678
+ payload: {
679
+ content: null,
680
+ },
681
+ }],
682
+ }),
683
+ });
684
+ assert.equal(listObservations({ scope_id: chapterhouse.id }).length, initialCount);
685
+ assert.equal(summary.implicit_extracted, 0);
686
+ assert.equal(warnings.length, 1);
687
+ });
688
+ test("runEndOfTaskMemoryHook skips implicit observation memories with undefined content and warns", async (t) => {
689
+ const { memoryModule, eotModule, warnings } = await loadModulesWithWarnSpy(t, "implicit-undefined-content");
690
+ const getScope = getFunction(memoryModule, "getScope");
691
+ const listObservations = getFunction(memoryModule, "listObservations");
692
+ const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
693
+ const chapterhouse = getScope("chapterhouse");
694
+ assert.ok(chapterhouse, "chapterhouse scope should be seeded");
695
+ const initialCount = listObservations({ scope_id: chapterhouse.id }).length;
696
+ const summary = await runEndOfTaskMemoryHook({
697
+ taskId: "task-eot-implicit-undefined",
698
+ finalResult: "The reviewer attempted to persist an invalid implicit memory.",
699
+ copilotClient: {},
700
+ callLLM: async () => JSON.stringify({
701
+ decisions: [],
702
+ implicit_memories: [{
703
+ kind: "observation",
704
+ scope_slug: "chapterhouse",
705
+ payload: {},
706
+ }],
707
+ }),
708
+ });
709
+ assert.equal(listObservations({ scope_id: chapterhouse.id }).length, initialCount);
710
+ assert.equal(summary.implicit_extracted, 0);
711
+ assert.equal(warnings.length, 1);
712
+ });
713
+ test("runEndOfTaskMemoryHook skips implicit observation memories with empty content and warns", async (t) => {
714
+ const { memoryModule, eotModule, warnings } = await loadModulesWithWarnSpy(t, "implicit-empty-content");
715
+ const getScope = getFunction(memoryModule, "getScope");
716
+ const listObservations = getFunction(memoryModule, "listObservations");
717
+ const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
718
+ const chapterhouse = getScope("chapterhouse");
719
+ assert.ok(chapterhouse, "chapterhouse scope should be seeded");
720
+ const initialCount = listObservations({ scope_id: chapterhouse.id }).length;
721
+ const summary = await runEndOfTaskMemoryHook({
722
+ taskId: "task-eot-implicit-empty",
723
+ finalResult: "The reviewer attempted to persist an invalid implicit memory.",
724
+ copilotClient: {},
725
+ callLLM: async () => JSON.stringify({
726
+ decisions: [],
727
+ implicit_memories: [{
728
+ kind: "observation",
729
+ scope_slug: "chapterhouse",
730
+ payload: {
731
+ content: " ",
732
+ },
733
+ }],
734
+ }),
735
+ });
736
+ assert.equal(listObservations({ scope_id: chapterhouse.id }).length, initialCount);
737
+ assert.equal(summary.implicit_extracted, 0);
738
+ assert.equal(warnings.length, 1);
739
+ });
740
+ test("runEndOfTaskMemoryHook persists implicit observation memories with valid content", async (t) => {
741
+ const { memoryModule, eotModule, warnings } = await loadModulesWithWarnSpy(t, "implicit-valid-content");
742
+ const getScope = getFunction(memoryModule, "getScope");
743
+ const listObservations = getFunction(memoryModule, "listObservations");
744
+ const runEndOfTaskMemoryHook = getFunction(eotModule, "runEndOfTaskMemoryHook");
745
+ const chapterhouse = getScope("chapterhouse");
746
+ assert.ok(chapterhouse, "chapterhouse scope should be seeded");
747
+ const summary = await runEndOfTaskMemoryHook({
748
+ taskId: "task-eot-implicit-valid",
749
+ finalResult: "The reviewer discovered a valid durable memory.",
750
+ copilotClient: {},
751
+ callLLM: async () => JSON.stringify({
752
+ decisions: [],
753
+ implicit_memories: [{
754
+ kind: "observation",
755
+ scope_slug: "chapterhouse",
756
+ payload: {
757
+ content: "A valid implicit memory should still be stored.",
758
+ },
759
+ }],
760
+ }),
761
+ });
762
+ assert.equal(listObservations({ scope_id: chapterhouse.id }).some((row) => row.content === "A valid implicit memory should still be stored."), true);
763
+ assert.equal(summary.implicit_extracted, 1);
764
+ assert.equal(warnings.length, 0);
765
+ });
552
766
  //# sourceMappingURL=eot.test.js.map
@@ -0,0 +1,151 @@
1
+ import { childLogger } from "../util/logger.js";
2
+ import { getActiveScope } from "./active-scope.js";
3
+ import { recordObservation } from "./observations.js";
4
+ import { getScope } from "./scopes.js";
5
+ const log = childLogger("memory.hooks");
6
+ // ---------------------------------------------------------------------------
7
+ // Env knob
8
+ // ---------------------------------------------------------------------------
9
+ function isHooksEnabled() {
10
+ const raw = process.env.CHAPTERHOUSE_MEMORY_HOOKS_ENABLED?.trim();
11
+ if (raw === "false" || raw === "0")
12
+ return false;
13
+ return true;
14
+ }
15
+ // ---------------------------------------------------------------------------
16
+ // Dispatcher
17
+ // ---------------------------------------------------------------------------
18
+ export class MemoryHookDispatcher {
19
+ handlers = new Map();
20
+ register(event, handler) {
21
+ const bucket = this.handlers.get(event) ?? [];
22
+ bucket.push(handler);
23
+ this.handlers.set(event, bucket);
24
+ }
25
+ async dispatch(event, payload) {
26
+ if (!isHooksEnabled()) {
27
+ log.debug({ event }, "memory.hooks.skip.disabled");
28
+ return;
29
+ }
30
+ const bucket = this.handlers.get(event) ?? [];
31
+ if (bucket.length === 0) {
32
+ log.debug({ event }, "memory.hooks.no_handlers");
33
+ return;
34
+ }
35
+ log.info({ event, handlerCount: bucket.length }, "memory.hooks.dispatch");
36
+ for (const handler of bucket) {
37
+ try {
38
+ await handler(payload);
39
+ }
40
+ catch (err) {
41
+ log.error({ err, event }, "memory.hooks.handler_error");
42
+ }
43
+ }
44
+ }
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Singleton dispatcher — shared across the process
48
+ // ---------------------------------------------------------------------------
49
+ export const hookDispatcher = new MemoryHookDispatcher();
50
+ // ---------------------------------------------------------------------------
51
+ // Built-in handler: git:commit → observation in active scope
52
+ // ---------------------------------------------------------------------------
53
+ function buildGitCommitContent(payload) {
54
+ const lines = [`Git commit: ${payload.message}`];
55
+ if (payload.stat?.trim()) {
56
+ lines.push("", "Changed files:", payload.stat.trim());
57
+ }
58
+ return lines.join("\n");
59
+ }
60
+ function resolveGlobalOrActiveScope() {
61
+ const active = getActiveScope();
62
+ if (active)
63
+ return active;
64
+ return getScope("global") ?? null;
65
+ }
66
+ export async function handleGitCommitHook(payload) {
67
+ if (!isHooksEnabled()) {
68
+ log.debug("memory.hooks.git_commit.disabled");
69
+ return { observation_id: "disabled" };
70
+ }
71
+ const scope = resolveGlobalOrActiveScope();
72
+ if (!scope) {
73
+ throw new Error("No active or global memory scope found. Create a scope before using memory hooks.");
74
+ }
75
+ const content = buildGitCommitContent(payload);
76
+ const observation = recordObservation({
77
+ scope_id: scope.id,
78
+ content,
79
+ source: "git:commit",
80
+ tier: "warm",
81
+ confidence: 1.0,
82
+ });
83
+ log.info({ scope: scope.slug, observation_id: observation.id }, "memory.hooks.git_commit.recorded");
84
+ return { observation_id: String(observation.id) };
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Built-in handler: pr:merge → observation
88
+ // ---------------------------------------------------------------------------
89
+ function buildPrMergeContent(payload) {
90
+ const lines = [`PR #${payload.number} merged: ${payload.title}`];
91
+ if (payload.body?.trim()) {
92
+ lines.push("", payload.body.trim());
93
+ }
94
+ if (payload.files_changed && payload.files_changed.length > 0) {
95
+ lines.push("", "Files changed:");
96
+ for (const file of payload.files_changed) {
97
+ lines.push(` ${file}`);
98
+ }
99
+ }
100
+ return lines.join("\n");
101
+ }
102
+ export async function handlePrMergeHook(payload) {
103
+ if (!isHooksEnabled()) {
104
+ log.debug("memory.hooks.pr_merge.disabled");
105
+ return { observation_id: "disabled" };
106
+ }
107
+ const scope = resolveGlobalOrActiveScope();
108
+ if (!scope) {
109
+ throw new Error("No active or global memory scope found. Create a scope before using memory hooks.");
110
+ }
111
+ const content = buildPrMergeContent(payload);
112
+ const observation = recordObservation({
113
+ scope_id: scope.id,
114
+ content,
115
+ source: "pr:merge",
116
+ tier: "warm",
117
+ confidence: 1.0,
118
+ });
119
+ log.info({ scope: scope.slug, observation_id: observation.id, pr: payload.number }, "memory.hooks.pr_merge.recorded");
120
+ return { observation_id: String(observation.id) };
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Built-in handler: scope:created → observation in global scope
124
+ // ---------------------------------------------------------------------------
125
+ hookDispatcher.register("scope:created", async (payload) => {
126
+ const globalScope = getScope("global");
127
+ if (!globalScope) {
128
+ log.debug("memory.hooks.scope_created.no_global_scope");
129
+ return;
130
+ }
131
+ const content = `Scope '${payload.slug}' created: ${payload.description || payload.title}`;
132
+ const observation = recordObservation({
133
+ scope_id: globalScope.id,
134
+ content,
135
+ source: "scope:created",
136
+ tier: "warm",
137
+ confidence: 1.0,
138
+ });
139
+ log.info({ scope: payload.slug, observation_id: observation.id }, "memory.hooks.scope_created.recorded");
140
+ });
141
+ // ---------------------------------------------------------------------------
142
+ // Built-in handler: memory:decision → structured log event
143
+ // ---------------------------------------------------------------------------
144
+ hookDispatcher.register("memory:decision", async (payload) => {
145
+ log.info({
146
+ decision_id: payload.id,
147
+ scope_id: payload.scope_id,
148
+ title: payload.title,
149
+ }, "memory.hooks.decision.recorded");
150
+ });
151
+ //# sourceMappingURL=hooks.js.map