openhermes 4.11.2 → 4.13.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.
Files changed (74) hide show
  1. package/CONTEXT.md +1 -1
  2. package/ETHOS.md +1 -1
  3. package/README.md +12 -18
  4. package/bootstrap.ts +73 -148
  5. package/docs/HOW-IT-WORKS.md +162 -0
  6. package/docs/adr/ADR-0001-rebuild-vs-increment.md +30 -0
  7. package/docs/adr/ADR-0002-routing-graph-vs-linear-chain.md +36 -0
  8. package/docs/adr/ADR-0003-per-directory-plan-storage.md +34 -0
  9. package/docs/adr/ADR-0004-composer-fragment-architecture.md +42 -0
  10. package/docs/adr/ADR-0005-hook-system-design.md +42 -0
  11. package/docs/adr/README.md +9 -0
  12. package/harness/codex/AUTOPILOT.md +30 -23
  13. package/harness/codex/CHARTER.md +3 -3
  14. package/harness/lib/composer/compose.test.ts +11 -0
  15. package/harness/lib/composer/fragments/02-delegation.md +2 -1
  16. package/harness/lib/composer/fragments/04-task-flow.md +42 -2
  17. package/harness/lib/composer/fragments/08-routing.md +1 -1
  18. package/harness/lib/composer/fragments/09-guardrails.md +17 -4
  19. package/harness/lib/composer/index.ts +1 -1
  20. package/harness/lib/guards/guard-config.ts +72 -0
  21. package/harness/lib/hooks/builtins/confidence-gate-hook.ts +2 -4
  22. package/harness/lib/hooks/builtins/delegation-depth-hook.ts +23 -4
  23. package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -0
  24. package/harness/lib/hooks/builtins/next-route-hook.ts +24 -0
  25. package/harness/lib/hooks/builtins/plan-check-hook.ts +2 -2
  26. package/harness/lib/hooks/builtins/route-tracking-hook.ts +79 -25
  27. package/harness/lib/hooks/hooks.test.ts +117 -205
  28. package/harness/lib/hooks/index.ts +38 -30
  29. package/harness/lib/hooks/registry.ts +309 -416
  30. package/harness/lib/hooks/types.ts +116 -71
  31. package/harness/lib/plans/plan-location.ts +134 -0
  32. package/harness/lib/routing/index.ts +21 -0
  33. package/harness/lib/routing/route-guidance.ts +147 -0
  34. package/harness/lib/routing/route-resolver.ts +58 -0
  35. package/harness/lib/routing/routing.test.ts +195 -0
  36. package/harness/lib/routing/skill-frontmatter.ts +125 -0
  37. package/harness/lib/routing/types.ts +52 -0
  38. package/harness/skills/oh-ascii/SKILL.md +1 -1
  39. package/harness/skills/oh-fusion/DEEP.md +56 -33
  40. package/harness/skills/oh-fusion/SKILL.md +30 -16
  41. package/harness/skills/oh-init/DEEP.md +2 -2
  42. package/harness/skills/oh-manifest/SKILL.md +1 -0
  43. package/harness/skills/oh-plan-review/DEEP.md +1 -1
  44. package/harness/skills/oh-planner/DEEP.md +3 -3
  45. package/harness/skills/oh-review/DEEP.md +2 -0
  46. package/harness/skills/oh-review/SKILL.md +1 -0
  47. package/package.json +56 -55
  48. package/harness/lib/background/background.test.ts +0 -197
  49. package/harness/lib/background/index.ts +0 -7
  50. package/harness/lib/background/interfaces.ts +0 -31
  51. package/harness/lib/background/manager.ts +0 -320
  52. package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
  53. package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
  54. package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
  55. package/harness/lib/memory/index.ts +0 -18
  56. package/harness/lib/memory/interfaces.ts +0 -53
  57. package/harness/lib/memory/memory-manager.ts +0 -205
  58. package/harness/lib/memory/memory.test.ts +0 -491
  59. package/harness/lib/memory/plan-store.ts +0 -366
  60. package/harness/lib/recovery/handler.ts +0 -243
  61. package/harness/lib/recovery/index.ts +0 -14
  62. package/harness/lib/recovery/interfaces.ts +0 -48
  63. package/harness/lib/recovery/patterns.ts +0 -149
  64. package/harness/lib/recovery/recovery.test.ts +0 -312
  65. package/harness/lib/sanity/anomaly-tracker.ts +0 -127
  66. package/harness/lib/sanity/checker.ts +0 -178
  67. package/harness/lib/sanity/index.ts +0 -13
  68. package/harness/lib/sanity/interfaces.ts +0 -24
  69. package/harness/lib/sanity/sanity.test.ts +0 -472
  70. package/harness/lib/sync/file-watcher.ts +0 -174
  71. package/harness/lib/sync/index.ts +0 -11
  72. package/harness/lib/sync/interfaces.ts +0 -27
  73. package/harness/lib/sync/plan-sync.ts +0 -536
  74. package/harness/lib/sync/sync.test.ts +0 -832
@@ -13,28 +13,29 @@ import {
13
13
  confidenceGateHook,
14
14
  delegationDepthHook,
15
15
  resetDepthTracker,
16
- errorRecoveryHook,
17
- memorySyncHook,
18
16
  routeTrackingHook,
19
17
  resetRouteTracker,
20
18
  getHopHistory,
21
- sanityCheckHook,
19
+ dynamicRouteHook,
22
20
  } from "./index.ts";
23
- import { AnomalyTracker } from "../sanity/anomaly-tracker.ts";
24
21
  import type {
25
22
  HookContext,
23
+ HookContextPatch,
26
24
  HookMetadata,
27
25
  PreToolUseHook,
28
26
  PostToolUseHook,
29
27
  RouteHook,
30
28
  SessionHook,
31
29
  } from "./types.ts";
30
+ import fs from "node:fs";
31
+ import os from "node:os";
32
+ import path from "node:path";
32
33
 
33
34
  // ---------------------------------------------------------------------------
34
35
  // Helpers
35
36
  // ---------------------------------------------------------------------------
36
37
 
37
- function makeContext(overrides?: Partial<HookContext>): HookContext {
38
+ function makeContext(overrides?: HookContextPatch): HookContext {
38
39
  return {
39
40
  sessionId: "test-session",
40
41
  agent: "oh-builder",
@@ -51,7 +52,7 @@ function makePreToolHook(
51
52
  ctx: HookContext,
52
53
  ) => Promise<{
53
54
  result: HookResult;
54
- modifiedContext?: Partial<HookContext>;
55
+ modifiedContext?: HookContextPatch;
55
56
  }>,
56
57
  ): PreToolUseHook {
57
58
  return {
@@ -76,7 +77,6 @@ function makePostToolHook(
76
77
  ) => Promise<{
77
78
  result: HookResult;
78
79
  modifiedOutput?: string;
79
- injectRecovery?: string;
80
80
  }>,
81
81
  ): PostToolUseHook {
82
82
  return {
@@ -136,6 +136,14 @@ function makeSessionHook(
136
136
  // ---------------------------------------------------------------------------
137
137
 
138
138
  describe("HookRegistry", () => {
139
+ const tmpDirs: string[] = [];
140
+
141
+ after(() => {
142
+ for (const dir of tmpDirs) {
143
+ fs.rmSync(dir, { recursive: true, force: true });
144
+ }
145
+ });
146
+
139
147
  beforeEach(() => {
140
148
  HookRegistry.resetInstance();
141
149
  resetDepthTracker();
@@ -251,7 +259,7 @@ describe("HookRegistry", () => {
251
259
  assert.equal(sorted[2].metadata.name, "late-hook");
252
260
  });
253
261
 
254
- it("handles simple linear dependencies", () => {
262
+ it("sorts by priority within same phase, ignoring dependencies", () => {
255
263
  const reg = HookRegistry.getInstance();
256
264
  const a = makePreToolHook("a", {
257
265
  phase: HookPhase.EARLY,
@@ -260,89 +268,27 @@ describe("HookRegistry", () => {
260
268
  });
261
269
  const b = makePreToolHook("b", {
262
270
  phase: HookPhase.EARLY,
263
- priority: 50,
271
+ priority: 70,
264
272
  dependencies: ["a"],
265
273
  });
266
- const c = makePreToolHook("c", {
267
- phase: HookPhase.EARLY,
268
- priority: 50,
269
- dependencies: ["b"],
270
- });
271
-
272
- const sorted = reg.topologicalSort([c, a, b]);
273
- assert.equal(sorted[0].metadata.name, "a");
274
- assert.equal(sorted[1].metadata.name, "b");
275
- assert.equal(sorted[2].metadata.name, "c");
276
- });
277
-
278
- it("handles diamond dependencies (A→B→D and A→C→D)", () => {
279
- const reg = HookRegistry.getInstance();
280
- const a = makePreToolHook("a", {
281
- phase: HookPhase.EARLY,
282
- priority: 50,
283
- dependencies: [],
284
- });
285
- const b = makePreToolHook("b", {
286
- phase: HookPhase.EARLY,
287
- priority: 50,
288
- dependencies: ["a"],
289
- });
290
- const c = makePreToolHook("c", {
291
- phase: HookPhase.EARLY,
292
- priority: 50,
293
- dependencies: ["a"],
294
- });
295
- const d = makePreToolHook("d", {
296
- phase: HookPhase.EARLY,
297
- priority: 50,
298
- dependencies: ["b", "c"],
299
- });
300
274
 
301
- const sorted = reg.topologicalSort([d, c, b, a]);
302
- // A must come first, D must come last
303
- assert.equal(sorted[0].metadata.name, "a");
304
- assert.equal(sorted[3].metadata.name, "d");
305
- // B and C can be in any order but must be before D and after A
306
- const bIdx = sorted.findIndex((h) => h.metadata.name === "b");
307
- const cIdx = sorted.findIndex((h) => h.metadata.name === "c");
308
- assert.ok(bIdx > 0);
309
- assert.ok(cIdx > 0);
310
- assert.ok(bIdx < 3);
311
- assert.ok(cIdx < 3);
312
- });
313
-
314
- it("throws on circular dependency (A→B→C→A)", () => {
315
- const reg = HookRegistry.getInstance();
316
- const a = makePreToolHook("a", {
317
- phase: HookPhase.EARLY,
318
- dependencies: ["c"],
319
- });
320
- const b = makePreToolHook("b", {
321
- phase: HookPhase.EARLY,
322
- dependencies: ["a"],
323
- });
324
- const c = makePreToolHook("c", {
325
- phase: HookPhase.EARLY,
326
- dependencies: ["b"],
327
- });
328
-
329
- assert.throws(
330
- () => reg.topologicalSort([a, b, c]),
331
- /Circular dependency detected/,
332
- );
275
+ // Higher priority first (70 > 50), regardless of dependency declaration
276
+ const sorted = reg.topologicalSort([a, b]);
277
+ assert.equal(sorted[0].metadata.name, "b");
278
+ assert.equal(sorted[1].metadata.name, "a");
333
279
  });
334
280
 
335
- it("self-dependency throws", () => {
281
+ it("preserves original order for equal phase and priority", () => {
336
282
  const reg = HookRegistry.getInstance();
337
- const a = makePreToolHook("a", {
338
- phase: HookPhase.EARLY,
339
- dependencies: ["a"],
340
- });
283
+ const c = makePreToolHook("c", { phase: HookPhase.EARLY, priority: 50 });
284
+ const a = makePreToolHook("a", { phase: HookPhase.EARLY, priority: 50 });
285
+ const b = makePreToolHook("b", { phase: HookPhase.EARLY, priority: 50 });
341
286
 
342
- assert.throws(
343
- () => reg.topologicalSort([a]),
344
- /Circular dependency detected/,
345
- );
287
+ // Stable sort: same phase + priority means original order is preserved
288
+ const sorted = reg.topologicalSort([c, a, b]);
289
+ assert.equal(sorted[0].metadata.name, "c");
290
+ assert.equal(sorted[1].metadata.name, "a");
291
+ assert.equal(sorted[2].metadata.name, "b");
346
292
  });
347
293
 
348
294
  it("cross-phase dependencies are ignored (not within same phase)", () => {
@@ -381,6 +327,9 @@ describe("HookRegistry", () => {
381
327
 
382
328
  const result = await reg.executePreTool(makeContext());
383
329
  assert.equal(result.result, HookResult.CONTINUE);
330
+ assert.equal(result.modifiedContext?.sessionId, "test-session");
331
+ assert.equal(result.modifiedContext?.agent, "oh-builder");
332
+ assert.equal(result.modifiedContext?.directory, "/tmp/test-project");
384
333
  assert.equal(result.modifiedContext?._track, "ran");
385
334
  });
386
335
 
@@ -462,12 +411,12 @@ describe("HookRegistry", () => {
462
411
  assert.equal(result.modifiedOutput, "[[[HELLO]]]");
463
412
  });
464
413
 
465
- it("injects recovery action", async () => {
414
+ it("injects recovery action (stub)", async () => {
415
+ // Recovery field removed in cleanup — test kept as placeholder
466
416
  const reg = HookRegistry.getInstance();
467
417
  reg.registerPostTool(
468
418
  makePostToolHook("recovery-test", {}, async () => ({
469
419
  result: HookResult.INJECT,
470
- injectRecovery: "retry with backoff",
471
420
  })),
472
421
  );
473
422
 
@@ -475,7 +424,83 @@ describe("HookRegistry", () => {
475
424
  makeContext(),
476
425
  "output",
477
426
  );
478
- assert.equal(result.recovery, "retry with backoff");
427
+ assert.equal(result.result, HookResult.INJECT);
428
+ });
429
+
430
+ it("appends structured route guidance from output evidence", async () => {
431
+ const reg = HookRegistry.getInstance();
432
+ reg.registerPostTool(dynamicRouteHook);
433
+
434
+ const skillsDir = fs.mkdtempSync(path.join(os.tmpdir(), "oh-routing-hook-"));
435
+ tmpDirs.push(skillsDir);
436
+ const skillDir = path.join(skillsDir, "oh-review");
437
+ fs.mkdirSync(skillDir, { recursive: true });
438
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
439
+ name: oh-review
440
+ route:
441
+ pass:
442
+ - oh-gauntlet
443
+ - oh-ship
444
+ fail: oh-builder
445
+ blocker: surface
446
+ ---\n`);
447
+
448
+ const result = await reg.executePostTool(
449
+ makeContext({ agent: "oh-review", _routingSkillsDir: skillsDir }),
450
+ 'Review complete\nROUTE_EVIDENCE: {"outcome":"pass","target":"oh-ship"}',
451
+ );
452
+
453
+ assert.equal(result.result, HookResult.INJECT);
454
+ assert.ok(result.modifiedOutput?.includes("Review complete"));
455
+ assert.ok(result.modifiedOutput?.includes("ROUTE_GUIDANCE:"));
456
+
457
+ const guidanceLine = result.modifiedOutput
458
+ ?.split(/\r?\n/)
459
+ .find((line) => line.startsWith("ROUTE_GUIDANCE:"));
460
+ assert.ok(guidanceLine);
461
+ assert.deepEqual(JSON.parse(guidanceLine!.slice("ROUTE_GUIDANCE:".length).trim()), {
462
+ outcome: "pass",
463
+ candidates: ["oh-gauntlet", "oh-ship"],
464
+ selected: "oh-ship",
465
+ reason: 'Selected "oh-ship" from output evidence.',
466
+ });
467
+ });
468
+
469
+ it("ignores malformed structured route evidence safely", async () => {
470
+ const reg = HookRegistry.getInstance();
471
+ reg.registerPostTool(dynamicRouteHook);
472
+
473
+ const skillsDir = fs.mkdtempSync(path.join(os.tmpdir(), "oh-routing-hook-"));
474
+ tmpDirs.push(skillsDir);
475
+ const skillDir = path.join(skillsDir, "oh-review");
476
+ fs.mkdirSync(skillDir, { recursive: true });
477
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
478
+ name: oh-review
479
+ route:
480
+ pass:
481
+ - oh-gauntlet
482
+ - oh-ship
483
+ fail: oh-builder
484
+ blocker: surface
485
+ ---\n`);
486
+
487
+ const output = 'Review complete\nROUTE_EVIDENCE: {"outcome":"pass","verification":"maybe"}';
488
+ const result = await reg.executePostTool(
489
+ makeContext({ agent: "oh-review", _routingSkillsDir: skillsDir }),
490
+ output,
491
+ );
492
+
493
+ assert.equal(result.result, HookResult.CONTINUE);
494
+ assert.equal(result.modifiedOutput, output);
495
+ });
496
+
497
+ it("leaves output unchanged when no route evidence is present", async () => {
498
+ const reg = HookRegistry.getInstance();
499
+ reg.registerPostTool(dynamicRouteHook);
500
+
501
+ const result = await reg.executePostTool(makeContext({ agent: "oh-review" }), "plain output");
502
+ assert.equal(result.result, HookResult.CONTINUE);
503
+ assert.equal(result.modifiedOutput, "plain output");
479
504
  });
480
505
  });
481
506
 
@@ -586,18 +611,6 @@ describe("HookRegistry", () => {
586
611
  assert.equal(delegationDepthHook.metadata.phase, HookPhase.NORMAL);
587
612
  });
588
613
 
589
- it("errorRecoveryHook has correct metadata", () => {
590
- assert.equal(errorRecoveryHook.metadata.name, "error-recovery");
591
- assert.equal(errorRecoveryHook.metadata.priority, 50);
592
- assert.equal(errorRecoveryHook.metadata.phase, HookPhase.LATE);
593
- });
594
-
595
- it("memorySyncHook has correct metadata", () => {
596
- assert.equal(memorySyncHook.metadata.name, "memory-sync");
597
- assert.equal(memorySyncHook.metadata.priority, 40);
598
- assert.equal(memorySyncHook.metadata.phase, HookPhase.LATE);
599
- });
600
-
601
614
  it("shellDetectHook returns shell context", async () => {
602
615
  const result = await shellDetectHook.execute(makeContext());
603
616
  assert.equal(result.result, HookResult.CONTINUE);
@@ -624,24 +637,6 @@ describe("HookRegistry", () => {
624
637
  assert.equal(result.modifiedContext?._depthExceeded, true);
625
638
  });
626
639
 
627
- it("errorRecoveryHook returns CONTINUE for normal output", async () => {
628
- const result = await errorRecoveryHook.execute(
629
- makeContext(),
630
- "Everything completed successfully.",
631
- );
632
- assert.equal(result.result, HookResult.CONTINUE);
633
- });
634
-
635
- it("errorRecoveryHook detects error output", async () => {
636
- const result = await errorRecoveryHook.execute(
637
- makeContext(),
638
- "Error: Failed to connect to server",
639
- );
640
- assert.equal(result.result, HookResult.INJECT);
641
- assert.ok(result.injectRecovery);
642
- assert.ok(result.injectRecovery!.includes("Error Recovery"));
643
- });
644
-
645
640
  it("confidenceGateHook passes through without confidence info", async () => {
646
641
  const result = await confidenceGateHook.execute(
647
642
  makeContext(),
@@ -666,12 +661,9 @@ describe("HookRegistry", () => {
666
661
  reg.registerPreTool(shellDetectHook);
667
662
  reg.registerPreTool(delegationDepthHook);
668
663
  reg.registerRoute(confidenceGateHook);
669
- reg.registerPostTool(errorRecoveryHook);
670
- reg.registerPostTool(memorySyncHook);
671
664
 
672
665
  assert.equal(reg.getPreToolHooks().length, 3);
673
666
  assert.equal(reg.getRouteHooks().length, 1);
674
- assert.equal(reg.getPostToolHooks().length, 2);
675
667
  });
676
668
 
677
669
  it("routeTrackingHook has correct metadata", () => {
@@ -692,9 +684,6 @@ describe("HookRegistry", () => {
692
684
  // Route hook
693
685
  await confidenceGateHook.execute(ctx, "oh-builder");
694
686
 
695
- // Post-tool hooks
696
- await errorRecoveryHook.execute(ctx, "normal output");
697
- await memorySyncHook.execute(ctx, "some output");
698
687
  // If we got here without throwing, success
699
688
  assert.ok(true);
700
689
  });
@@ -724,17 +713,9 @@ describe("HookRegistry", () => {
724
713
  const result = await routeTrackingHook.execute(ctx, "oh-builder");
725
714
  assert.equal(result.result, HookResult.STOP);
726
715
  assert.ok(ctx._optiRoute);
727
- assert.ok(
728
- (ctx._optiRoute as Record<string, unknown>).reason as string,
729
- );
730
- assert.ok(
731
- ((ctx._optiRoute as Record<string, unknown>).reason as string).includes(
732
- "oh-builder",
733
- ),
734
- );
735
- assert.ok(
736
- ((ctx._optiRoute as Record<string, unknown>).chain as unknown[]).length === 5,
737
- );
716
+ assert.ok(ctx._optiRoute.reason);
717
+ assert.ok(ctx._optiRoute.reason.includes("oh-builder"));
718
+ assert.ok(ctx._optiRoute.chain.length === 5);
738
719
  });
739
720
 
740
721
  it("stops on 8th unproductive hop (default max 8)", async () => {
@@ -757,11 +738,7 @@ describe("HookRegistry", () => {
757
738
  const result = await routeTrackingHook.execute(ctx, "oh-builder");
758
739
  assert.equal(result.result, HookResult.STOP);
759
740
  assert.ok(ctx._optiRoute);
760
- assert.ok(
761
- ((ctx._optiRoute as Record<string, unknown>).reason as string).includes(
762
- "unproductive",
763
- ),
764
- );
741
+ assert.ok(ctx._optiRoute.reason.includes("unproductive"));
765
742
  });
766
743
 
767
744
  it("productive hop resets unproductive counter", async () => {
@@ -875,11 +852,7 @@ describe("HookRegistry", () => {
875
852
  const result = await routeTrackingHook.execute(ctx, "oh-gauntlet");
876
853
  assert.equal(result.result, HookResult.STOP);
877
854
  assert.ok(ctx._optiRoute);
878
- assert.ok(
879
- ((ctx._optiRoute as Record<string, unknown>).reason as string).includes(
880
- "unproductive",
881
- ),
882
- );
855
+ assert.ok(ctx._optiRoute.reason.includes("unproductive"));
883
856
  });
884
857
  });
885
858
  });
@@ -952,65 +925,4 @@ describe("HookRegistry", () => {
952
925
  });
953
926
  });
954
927
 
955
- // ---------------------------------------------------------------------------
956
- // sanityCheckHook
957
- // ---------------------------------------------------------------------------
958
-
959
- describe("sanityCheckHook", () => {
960
- beforeEach(() => {
961
- AnomalyTracker.getInstance().resetAll();
962
- });
963
-
964
- it("passes clean output through unchanged", async () => {
965
- const ctx = makeContext();
966
- const result = await sanityCheckHook.execute(
967
- ctx,
968
- "Everything is working fine. The system completed the task successfully.",
969
- );
970
- assert.equal(result.result, HookResult.CONTINUE);
971
- assert.equal(result.modifiedOutput, undefined);
972
- });
973
-
974
- it("detects repetitive output", async () => {
975
- const ctx = makeContext();
976
- const line =
977
- "Sphinx of black quartz, judge my vow! The five boxing wizards jump quickly. 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ";
978
- const repetitiveOutput = Array.from({ length: 20 }, () => line).join("\n");
979
- const result = await sanityCheckHook.execute(ctx, repetitiveOutput);
980
- // First anomaly — not yet escalated
981
- assert.equal(result.result, HookResult.CONTINUE);
982
- });
983
-
984
- it("detects box-drawing character flooding", async () => {
985
- const ctx = makeContext();
986
- const boxArt = "─│┌┐└┘├┤┬┴┼".repeat(50);
987
- const result = await sanityCheckHook.execute(ctx, boxArt);
988
- assert.equal(result.result, HookResult.CONTINUE);
989
- });
990
-
991
- it("detects placeholder patterns", async () => {
992
- const ctx = makeContext();
993
- // 50× [PLACEHOLDER] = 650 chars, 11 unique → triggers low_diversity check
994
- const placeholderText = "[PLACEHOLDER]".repeat(50);
995
- const result = await sanityCheckHook.execute(ctx, placeholderText);
996
- assert.equal(result.result, HookResult.CONTINUE);
997
- });
998
-
999
- it("tracks anomalies across calls", async () => {
1000
- const ctx = makeContext({ sessionId: "anomaly-escalation-test" });
1001
- const line =
1002
- "Sphinx of black quartz, judge my vow! The five boxing wizards jump quickly. 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1003
- const repetitiveOutput = Array.from({ length: 20 }, () => line).join("\n");
1004
-
1005
- // First call: anomaly detected but below threshold → CONTINUE
1006
- const firstResult = await sanityCheckHook.execute(ctx, repetitiveOutput);
1007
- assert.equal(firstResult.result, HookResult.CONTINUE);
1008
-
1009
- // Second call: threshold reached → INJECT with recovery
1010
- const secondResult = await sanityCheckHook.execute(ctx, repetitiveOutput);
1011
- assert.equal(secondResult.result, HookResult.INJECT);
1012
- assert.ok(secondResult.injectRecovery);
1013
- assert.equal(secondResult.modifiedOutput, repetitiveOutput);
1014
- });
1015
- });
1016
928
  });
@@ -1,30 +1,38 @@
1
- // ---------------------------------------------------------------------------
2
- // Hook System — barrel export
3
- // ---------------------------------------------------------------------------
4
-
5
- export {
6
- HookPhase,
7
- HookResult,
8
- } from "./types.ts";
9
- export type {
10
- HookContext,
11
- HookMetadata,
12
- PreToolUseHook,
13
- PostToolUseHook,
14
- RouteHook,
15
- SessionHook,
16
- AnyHook,
17
- } from "./types.ts";
18
-
19
- export { HookRegistry } from "./registry.ts";
20
-
21
- // Built-in hooks
22
- export { planCheckHook } from "./builtins/plan-check-hook.ts";
23
- export { shellDetectHook } from "./builtins/shell-detect-hook.ts";
24
- export { confidenceGateHook } from "./builtins/confidence-gate-hook.ts";
25
- export { delegationDepthHook, resetDepthTracker } from "./builtins/delegation-depth-hook.ts";
26
- export { errorRecoveryHook } from "./builtins/error-recovery-hook.ts";
27
- export { memorySyncHook } from "./builtins/memory-sync-hook.ts";
28
- export { sanityCheckHook } from "./builtins/sanity-check-hook.ts";
29
- export { routeTrackingHook, resetRouteTracker, getHopHistory } from "./builtins/route-tracking-hook.ts";
30
- export type { HopRecord, RouteTrackingConfig } from "./builtins/route-tracking-hook.ts";
1
+ // ---------------------------------------------------------------------------
2
+ // Hook System — barrel export
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export {
6
+ HookPhase,
7
+ HookResult,
8
+ } from "./types.ts";
9
+ export type {
10
+ HookContextBase,
11
+ HookContextExtras,
12
+ HookContext,
13
+ HookContextPatch,
14
+ HookMetadata,
15
+ PreToolUseHook,
16
+ PostToolUseHook,
17
+ RouteHook,
18
+ SessionHook,
19
+ AnyHook,
20
+ } from "./types.ts";
21
+
22
+ export { HookRegistry } from "./registry.ts";
23
+
24
+ // Built-in hooks
25
+ export { planCheckHook } from "./builtins/plan-check-hook.ts";
26
+ export { shellDetectHook } from "./builtins/shell-detect-hook.ts";
27
+ export { confidenceGateHook } from "./builtins/confidence-gate-hook.ts";
28
+ export { delegationDepthHook, resetDepthTracker } from "./builtins/delegation-depth-hook.ts";
29
+ export { dynamicRouteHook } from "./builtins/dynamic-route-hook.ts";
30
+ export { nextRouteHook } from "./builtins/next-route-hook.ts";
31
+ export { routeTrackingHook, resetRouteTracker, getHopHistory } from "./builtins/route-tracking-hook.ts";
32
+ export type { HopRecord, RouteTrackingConfig } from "./builtins/route-tracking-hook.ts";
33
+
34
+ // Guard configuration
35
+ export type { GuardConfig, GuardProgression, GuardLevel } from "../guards/guard-config.ts";
36
+ export { DEFAULT_GUARD_CONFIG, checkGuardProgression, mergeGuardConfig } from "../guards/guard-config.ts";
37
+
38
+