openhermes 4.12.1 → 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 (73) hide show
  1. package/CONTEXT.md +6 -6
  2. package/ETHOS.md +2 -2
  3. package/README.md +11 -17
  4. package/bootstrap.ts +118 -126
  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 +35 -40
  13. package/harness/codex/CHARTER.md +3 -3
  14. package/harness/lib/composer/compose.test.ts +29 -29
  15. package/harness/lib/composer/fragments/02-delegation.md +5 -5
  16. package/harness/lib/composer/fragments/04-task-flow.md +13 -13
  17. package/harness/lib/composer/fragments/08-routing.md +1 -1
  18. package/harness/lib/composer/fragments/09-guardrails.md +25 -25
  19. package/harness/lib/composer/index.ts +1 -1
  20. package/harness/lib/guards/guard-config.ts +72 -72
  21. package/harness/lib/hooks/builtins/confidence-gate-hook.ts +9 -9
  22. package/harness/lib/hooks/builtins/delegation-depth-hook.ts +1 -1
  23. package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -99
  24. package/harness/lib/hooks/builtins/next-route-hook.ts +24 -24
  25. package/harness/lib/hooks/builtins/plan-check-hook.ts +5 -5
  26. package/harness/lib/hooks/builtins/route-tracking-hook.ts +1 -1
  27. package/harness/lib/hooks/hooks.test.ts +160 -324
  28. package/harness/lib/hooks/index.ts +38 -42
  29. package/harness/lib/hooks/registry.ts +309 -416
  30. package/harness/lib/hooks/types.ts +116 -119
  31. package/harness/lib/plans/plan-location.ts +134 -134
  32. package/harness/lib/routing/index.ts +21 -21
  33. package/harness/lib/routing/route-guidance.ts +147 -147
  34. package/harness/lib/routing/route-resolver.ts +58 -58
  35. package/harness/lib/routing/routing.test.ts +195 -195
  36. package/harness/lib/routing/skill-frontmatter.ts +125 -125
  37. package/harness/lib/routing/types.ts +52 -52
  38. package/harness/skills/oh-ascii/SKILL.md +1 -1
  39. package/harness/skills/oh-fusion/DEEP.md +109 -109
  40. package/harness/skills/oh-fusion/SKILL.md +47 -47
  41. package/harness/skills/oh-init/DEEP.md +2 -2
  42. package/harness/skills/oh-plan-review/DEEP.md +1 -1
  43. package/harness/skills/oh-planner/DEEP.md +3 -3
  44. package/harness/skills/oh-review/DEEP.md +5 -5
  45. package/package.json +56 -53
  46. package/harness/lib/background/background.test.ts +0 -216
  47. package/harness/lib/background/index.ts +0 -7
  48. package/harness/lib/background/interfaces.ts +0 -31
  49. package/harness/lib/background/manager.ts +0 -320
  50. package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
  51. package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
  52. package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
  53. package/harness/lib/hooks/builtins/subagent-failure-hook.ts +0 -93
  54. package/harness/lib/memory/index.ts +0 -18
  55. package/harness/lib/memory/interfaces.ts +0 -53
  56. package/harness/lib/memory/memory-manager.ts +0 -205
  57. package/harness/lib/memory/memory.test.ts +0 -485
  58. package/harness/lib/memory/plan-store.ts +0 -346
  59. package/harness/lib/recovery/handler.ts +0 -243
  60. package/harness/lib/recovery/index.ts +0 -14
  61. package/harness/lib/recovery/interfaces.ts +0 -48
  62. package/harness/lib/recovery/patterns.ts +0 -149
  63. package/harness/lib/recovery/recovery.test.ts +0 -312
  64. package/harness/lib/sanity/anomaly-tracker.ts +0 -127
  65. package/harness/lib/sanity/checker.ts +0 -189
  66. package/harness/lib/sanity/index.ts +0 -13
  67. package/harness/lib/sanity/interfaces.ts +0 -24
  68. package/harness/lib/sanity/sanity.test.ts +0 -472
  69. package/harness/lib/sync/file-watcher.ts +0 -175
  70. package/harness/lib/sync/index.ts +0 -11
  71. package/harness/lib/sync/interfaces.ts +0 -27
  72. package/harness/lib/sync/plan-sync.ts +0 -533
  73. package/harness/lib/sync/sync.test.ts +0 -858
@@ -1,119 +1,116 @@
1
- // ---------------------------------------------------------------------------
2
- // Hook System — type definitions
3
- // ---------------------------------------------------------------------------
4
-
5
- export enum HookPhase {
6
- EARLY = 0,
7
- NORMAL = 1,
8
- LATE = 2,
9
- }
10
-
11
- export interface HookContextBase {
12
- sessionId: string;
13
- agent: string;
14
- directory: string;
15
- sessions: Map<string, unknown>;
16
- }
17
-
18
- export interface HookContextExtras {
19
- _planCheck?: "missing" | "found";
20
- _planFilePath?: string;
21
- _planCheckInstruction?: string;
22
-
23
- _shellPlatform?: string;
24
- _shellType?: string;
25
- _shellPreamble?: string;
26
-
27
- _delegationDepth?: number;
28
- _depthExceeded?: boolean;
29
- _depthError?: string;
30
-
31
- _confidenceLevel?: "HIGH" | "MEDIUM" | "LOW" | string;
32
- _confidenceExchanges?: number;
33
-
34
- // Guard configuration (centralized — replaces _routeTrackingConfig and _maxDelegationDepth)
35
- _guardConfig?: import("../guards/guard-config.ts").GuardConfig;
36
- _guardProgression?: import("../guards/guard-config.ts").GuardProgression;
37
-
38
- // Subagent failure tracking
39
- _subagentFailures?: number;
40
- _subagentFailureThreshold?: number;
41
- _optiRoute?: {
42
- reason: string;
43
- chain: Array<{
44
- skill: string;
45
- timestamp: number;
46
- producedArtifact: boolean;
47
- }>;
48
- skillCounts: Record<string, number>;
49
- unproductiveCount: number;
50
- maxSkillRepeats: number;
51
- maxUnproductiveHops: number;
52
- };
53
-
54
- _memorySyncCount?: number;
55
- _recoveryAttempt?: number;
56
- _routingSkillsDir?: string;
57
- _nextRoute?: import("../routing/index.ts").RuntimeRouteDecision;
58
-
59
- [key: string]: unknown;
60
- }
61
-
62
- export type HookContext = HookContextBase & HookContextExtras;
63
-
64
- export type HookContextPatch = Partial<HookContextBase> &
65
- Partial<HookContextExtras>;
66
-
67
- export interface HookMetadata {
68
- name: string;
69
- priority: number; // 0-100, higher = earlier within phase
70
- phase: HookPhase;
71
- dependencies: string[]; // hook names this depends on
72
- errorHandling: "propagate" | "isolate" | "retry";
73
- }
74
-
75
- export enum HookResult {
76
- CONTINUE = "continue",
77
- STOP = "stop",
78
- INJECT = "inject",
79
- }
80
-
81
- export interface PreToolUseHook {
82
- metadata: HookMetadata;
83
- execute(
84
- context: HookContext,
85
- ): Promise<{ result: HookResult; modifiedContext?: HookContextPatch }>;
86
- }
87
-
88
- export interface PostToolUseHook {
89
- metadata: HookMetadata;
90
- execute(
91
- context: HookContext,
92
- output: string,
93
- ): Promise<{
94
- result: HookResult;
95
- modifiedOutput?: string;
96
- injectRecovery?: string;
97
- }>;
98
- }
99
-
100
- export interface RouteHook {
101
- metadata: HookMetadata;
102
- execute(
103
- context: HookContext,
104
- route: string,
105
- ): Promise<{ result: HookResult; modifiedRoute?: string }>;
106
- }
107
-
108
- export interface SessionHook {
109
- metadata: HookMetadata;
110
- onSessionStart(context: HookContext): Promise<void>;
111
- onSessionEnd(context: HookContext): Promise<void>;
112
- }
113
-
114
- // Union type for any hook in the registry
115
- export type AnyHook =
116
- | PreToolUseHook
117
- | PostToolUseHook
118
- | RouteHook
119
- | SessionHook;
1
+ // ---------------------------------------------------------------------------
2
+ // Hook System — type definitions
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export enum HookPhase {
6
+ EARLY = 0,
7
+ NORMAL = 1,
8
+ LATE = 2,
9
+ }
10
+
11
+ export interface HookContextBase {
12
+ sessionId: string;
13
+ agent: string;
14
+ directory: string;
15
+ sessions: Map<string, unknown>;
16
+ }
17
+
18
+ export interface HookContextExtras {
19
+ _planCheck?: "missing" | "found";
20
+ _planFilePath?: string;
21
+ _planCheckInstruction?: string;
22
+
23
+ _shellPlatform?: string;
24
+ _shellType?: string;
25
+ _shellPreamble?: string;
26
+
27
+ _delegationDepth?: number;
28
+ _depthExceeded?: boolean;
29
+ _depthError?: string;
30
+
31
+ _confidenceLevel?: "HIGH" | "MEDIUM" | "LOW" | string;
32
+ _confidenceExchanges?: number;
33
+
34
+ // Guard configuration (centralized — replaces _routeTrackingConfig and _maxDelegationDepth)
35
+ _guardConfig?: import("../guards/guard-config.ts").GuardConfig;
36
+ _guardProgression?: import("../guards/guard-config.ts").GuardProgression;
37
+
38
+ // Subagent failure tracking
39
+ _subagentFailures?: number;
40
+ _subagentFailureThreshold?: number;
41
+ _optiRoute?: {
42
+ reason: string;
43
+ chain: Array<{
44
+ skill: string;
45
+ timestamp: number;
46
+ producedArtifact: boolean;
47
+ }>;
48
+ skillCounts: Record<string, number>;
49
+ unproductiveCount: number;
50
+ maxSkillRepeats: number;
51
+ maxUnproductiveHops: number;
52
+ };
53
+
54
+ _routingSkillsDir?: string;
55
+ _nextRoute?: import("../routing/index.ts").RuntimeRouteDecision;
56
+
57
+ [key: string]: unknown;
58
+ }
59
+
60
+ export type HookContext = HookContextBase & HookContextExtras;
61
+
62
+ export type HookContextPatch = Partial<HookContextBase> &
63
+ Partial<HookContextExtras>;
64
+
65
+ export interface HookMetadata {
66
+ name: string;
67
+ priority: number; // 0-100, higher = earlier within phase
68
+ phase: HookPhase;
69
+ dependencies: string[]; // hook names this depends on
70
+ errorHandling: "propagate" | "isolate" | "retry";
71
+ }
72
+
73
+ export enum HookResult {
74
+ CONTINUE = "continue",
75
+ STOP = "stop",
76
+ INJECT = "inject",
77
+ }
78
+
79
+ export interface PreToolUseHook {
80
+ metadata: HookMetadata;
81
+ execute(
82
+ context: HookContext,
83
+ ): Promise<{ result: HookResult; modifiedContext?: HookContextPatch }>;
84
+ }
85
+
86
+ export interface PostToolUseHook {
87
+ metadata: HookMetadata;
88
+ execute(
89
+ context: HookContext,
90
+ output: string,
91
+ ): Promise<{
92
+ result: HookResult;
93
+ modifiedOutput?: string;
94
+ }>;
95
+ }
96
+
97
+ export interface RouteHook {
98
+ metadata: HookMetadata;
99
+ execute(
100
+ context: HookContext,
101
+ route: string,
102
+ ): Promise<{ result: HookResult; modifiedRoute?: string }>;
103
+ }
104
+
105
+ export interface SessionHook {
106
+ metadata: HookMetadata;
107
+ onSessionStart(context: HookContext): Promise<void>;
108
+ onSessionEnd(context: HookContext): Promise<void>;
109
+ }
110
+
111
+ // Union type for any hook in the registry
112
+ export type AnyHook =
113
+ | PreToolUseHook
114
+ | PostToolUseHook
115
+ | RouteHook
116
+ | SessionHook;
@@ -1,134 +1,134 @@
1
- import fs from "node:fs"
2
- import os from "node:os"
3
- import path from "node:path"
4
-
5
- let _planStorageOverride: string | undefined
6
-
7
- export interface PlanAccess {
8
- path: string
9
- status: string | null
10
- objective: string | null
11
- summary: string | null
12
- }
13
-
14
- export function setPlanStorageDirForTest(dir: string | undefined): void {
15
- _planStorageOverride = dir
16
- }
17
-
18
- export function planStorageDir(): string {
19
- return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "openhermes", "plans")
20
- }
21
-
22
- function getProjectName(projectDir: string): string {
23
- return path.basename(projectDir)
24
- }
25
-
26
- function ensureDir(dir: string): void {
27
- try {
28
- if (!fs.existsSync(dir)) {
29
- fs.mkdirSync(dir, { recursive: true })
30
- }
31
- } catch (err) {
32
- const msg = err instanceof Error ? err.message : String(err)
33
- console.error(`[openhermes] Failed to create directory ${dir}: ${msg}`)
34
- }
35
- }
36
-
37
- function readPlanAccess(filePath: string): PlanAccess | null {
38
- if (!fs.existsSync(filePath)) return null
39
- const source = fs.readFileSync(filePath, "utf8")
40
- const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim() ?? null
41
- const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim() ?? null
42
- if (!status && !objective) return null
43
- const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
44
- return {
45
- path: filePath,
46
- status,
47
- objective,
48
- summary: `Active plan: ${parts.join(" | ")}`,
49
- }
50
- }
51
-
52
- export function resolvePlanAccess(projectDir: string): PlanAccess | null {
53
- const latest = findLatestPlanFile(projectDir)
54
- if (!latest) return null
55
- return readPlanAccess(latest)
56
- }
57
-
58
- export function findLatestPlanFile(projectDir: string): string | null {
59
- const projectName = getProjectName(projectDir)
60
- const storage = planStorageDir()
61
- const projectDirPath = path.join(storage, projectName)
62
- if (!fs.existsSync(projectDirPath)) return null
63
- let latest: string | null = null
64
- let highest = -1
65
- try {
66
- for (const entry of fs.readdirSync(projectDirPath)) {
67
- const m = entry.match(/^plan-(\d{3})\.md$/)
68
- if (m) {
69
- const n = parseInt(m[1], 10)
70
- if (n > highest) {
71
- highest = n
72
- latest = path.join(projectDirPath, entry)
73
- }
74
- }
75
- }
76
- } catch {
77
- return null
78
- }
79
- return latest
80
- }
81
-
82
- export function ensurePlanFile(projectDir: string): string {
83
- const access = resolvePlanAccess(projectDir)
84
- if (access?.status === "active" || access?.status === "in-progress") {
85
- return access.path
86
- }
87
-
88
- const projectName = getProjectName(projectDir)
89
- const storage = planStorageDir()
90
- const projectDirPath = path.join(storage, projectName)
91
- ensureDir(projectDirPath)
92
-
93
- const latest = access?.path ?? findLatestPlanFile(projectDir)
94
- let nextSeq = 1
95
- if (latest) {
96
- const m = path.basename(latest).match(/^plan-(\d{3})\.md$/)
97
- if (m) nextSeq = parseInt(m[1], 10) + 1
98
- }
99
-
100
- const seq = String(nextSeq).padStart(3, "0")
101
- const planId = `${projectName}/plan-${seq}.md`
102
- const planPath = path.join(projectDirPath, `plan-${seq}.md`)
103
- const now = new Date().toISOString().replace("T", " ").slice(0, 16)
104
-
105
- const content = [
106
- `# PLAN: ${projectName}`,
107
- "",
108
- `Plan ID: ${planId}`,
109
- `Project: ${projectName}`,
110
- `Status: active`,
111
- `Created: ${now}`,
112
- `Updated: ${now}`,
113
- `Project Path: ${projectDir}`,
114
- `Plan Path: ${planPath}`,
115
- `Objective: (pending classification)`,
116
- "",
117
- "## Tasks",
118
- "",
119
- "- [ ] (discoverable — pending classification)",
120
- "",
121
- ].join("\n")
122
-
123
- try {
124
- fs.writeFileSync(planPath, content, "utf8")
125
- } catch (err) {
126
- const msg = err instanceof Error ? err.message : String(err)
127
- console.error(`[openhermes] Failed to write plan file ${planPath}: ${msg}`)
128
- }
129
- return planPath
130
- }
131
-
132
- export function readPlanSummary(projectDir: string): string | null {
133
- return resolvePlanAccess(projectDir)?.summary ?? null
134
- }
1
+ import fs from "node:fs"
2
+ import os from "node:os"
3
+ import path from "node:path"
4
+
5
+ let _planStorageOverride: string | undefined
6
+
7
+ export interface PlanAccess {
8
+ path: string
9
+ status: string | null
10
+ objective: string | null
11
+ summary: string | null
12
+ }
13
+
14
+ export function setPlanStorageDirForTest(dir: string | undefined): void {
15
+ _planStorageOverride = dir
16
+ }
17
+
18
+ export function planStorageDir(): string {
19
+ return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "openhermes", "plans")
20
+ }
21
+
22
+ function getProjectName(projectDir: string): string {
23
+ return path.basename(projectDir)
24
+ }
25
+
26
+ function ensureDir(dir: string): void {
27
+ try {
28
+ if (!fs.existsSync(dir)) {
29
+ fs.mkdirSync(dir, { recursive: true })
30
+ }
31
+ } catch (err) {
32
+ const msg = err instanceof Error ? err.message : String(err)
33
+ console.error(`[openhermes] Failed to create directory ${dir}: ${msg}`)
34
+ }
35
+ }
36
+
37
+ function readPlanAccess(filePath: string): PlanAccess | null {
38
+ if (!fs.existsSync(filePath)) return null
39
+ const source = fs.readFileSync(filePath, "utf8")
40
+ const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim() ?? null
41
+ const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim() ?? null
42
+ if (!status && !objective) return null
43
+ const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
44
+ return {
45
+ path: filePath,
46
+ status,
47
+ objective,
48
+ summary: `Active plan: ${parts.join(" | ")}`,
49
+ }
50
+ }
51
+
52
+ export function resolvePlanAccess(projectDir: string): PlanAccess | null {
53
+ const latest = findLatestPlanFile(projectDir)
54
+ if (!latest) return null
55
+ return readPlanAccess(latest)
56
+ }
57
+
58
+ export function findLatestPlanFile(projectDir: string): string | null {
59
+ const projectName = getProjectName(projectDir)
60
+ const storage = planStorageDir()
61
+ const projectDirPath = path.join(storage, projectName)
62
+ if (!fs.existsSync(projectDirPath)) return null
63
+ let latest: string | null = null
64
+ let highest = -1
65
+ try {
66
+ for (const entry of fs.readdirSync(projectDirPath)) {
67
+ const m = entry.match(/^plan-(\d{3})\.md$/)
68
+ if (m) {
69
+ const n = parseInt(m[1], 10)
70
+ if (n > highest) {
71
+ highest = n
72
+ latest = path.join(projectDirPath, entry)
73
+ }
74
+ }
75
+ }
76
+ } catch {
77
+ return null
78
+ }
79
+ return latest
80
+ }
81
+
82
+ export function ensurePlanFile(projectDir: string): string {
83
+ const access = resolvePlanAccess(projectDir)
84
+ if (access?.status === "active" || access?.status === "in-progress") {
85
+ return access.path
86
+ }
87
+
88
+ const projectName = getProjectName(projectDir)
89
+ const storage = planStorageDir()
90
+ const projectDirPath = path.join(storage, projectName)
91
+ ensureDir(projectDirPath)
92
+
93
+ const latest = access?.path ?? findLatestPlanFile(projectDir)
94
+ let nextSeq = 1
95
+ if (latest) {
96
+ const m = path.basename(latest).match(/^plan-(\d{3})\.md$/)
97
+ if (m) nextSeq = parseInt(m[1], 10) + 1
98
+ }
99
+
100
+ const seq = String(nextSeq).padStart(3, "0")
101
+ const planId = `${projectName}/plan-${seq}.md`
102
+ const planPath = path.join(projectDirPath, `plan-${seq}.md`)
103
+ const now = new Date().toISOString().replace("T", " ").slice(0, 16)
104
+
105
+ const content = [
106
+ `# PLAN: ${projectName}`,
107
+ "",
108
+ `Plan ID: ${planId}`,
109
+ `Project: ${projectName}`,
110
+ `Status: active`,
111
+ `Created: ${now}`,
112
+ `Updated: ${now}`,
113
+ `Project Path: ${projectDir}`,
114
+ `Plan Path: ${planPath}`,
115
+ `Objective: (pending classification)`,
116
+ "",
117
+ "## Tasks",
118
+ "",
119
+ "- [ ] (discoverable — pending classification)",
120
+ "",
121
+ ].join("\n")
122
+
123
+ try {
124
+ fs.writeFileSync(planPath, content, "utf8")
125
+ } catch (err) {
126
+ const msg = err instanceof Error ? err.message : String(err)
127
+ console.error(`[openhermes] Failed to write plan file ${planPath}: ${msg}`)
128
+ }
129
+ return planPath
130
+ }
131
+
132
+ export function readPlanSummary(projectDir: string): string | null {
133
+ return resolvePlanAccess(projectDir)?.summary ?? null
134
+ }
@@ -1,21 +1,21 @@
1
- export { extractFrontmatter, parseSkillFrontmatter, readSkillFrontmatter, emptySkillRoutes } from "./skill-frontmatter.ts";
2
- export { resolveRoute } from "./route-resolver.ts";
3
- export {
4
- clearRuntimeRouteDecision,
5
- consumeRouteGuidance,
6
- extractRouteGuidance,
7
- extractRuntimeRouteDecision,
8
- getRuntimeRouteDecision,
9
- NEXT_ROUTE_PREFIX,
10
- rememberRuntimeRouteDecision,
11
- ROUTE_GUIDANCE_PREFIX,
12
- } from "./route-guidance.ts";
13
- export { ROUTE_OUTCOMES } from "./types.ts";
14
- export type {
15
- RouteEvidence,
16
- RouteOutcome,
17
- RouteResolution,
18
- RuntimeRouteDecision,
19
- SkillRouteMap,
20
- SkillRoutingFrontmatter,
21
- } from "./types.ts";
1
+ export { extractFrontmatter, parseSkillFrontmatter, readSkillFrontmatter, emptySkillRoutes } from "./skill-frontmatter.ts";
2
+ export { resolveRoute } from "./route-resolver.ts";
3
+ export {
4
+ clearRuntimeRouteDecision,
5
+ consumeRouteGuidance,
6
+ extractRouteGuidance,
7
+ extractRuntimeRouteDecision,
8
+ getRuntimeRouteDecision,
9
+ NEXT_ROUTE_PREFIX,
10
+ rememberRuntimeRouteDecision,
11
+ ROUTE_GUIDANCE_PREFIX,
12
+ } from "./route-guidance.ts";
13
+ export { ROUTE_OUTCOMES } from "./types.ts";
14
+ export type {
15
+ RouteEvidence,
16
+ RouteOutcome,
17
+ RouteResolution,
18
+ RuntimeRouteDecision,
19
+ SkillRouteMap,
20
+ SkillRoutingFrontmatter,
21
+ } from "./types.ts";