openhermes 4.0.1 → 4.3.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 (50) hide show
  1. package/ETHOS.md +6 -3
  2. package/LICENSE +21 -21
  3. package/README.md +111 -81
  4. package/bootstrap.ts +405 -0
  5. package/harness/agents/openhermes.md +45 -55
  6. package/harness/codex/AUTOPILOT.md +126 -0
  7. package/harness/codex/CONSTITUTION.md +14 -11
  8. package/harness/codex/ROUTING.md +35 -69
  9. package/harness/commands/oh-log.md +18 -0
  10. package/harness/instructions/RUNTIME.md +27 -51
  11. package/harness/skills/oh-builder/SKILL.md +27 -16
  12. package/harness/skills/oh-caveman/SKILL.md +9 -0
  13. package/harness/skills/oh-expert/SKILL.md +6 -0
  14. package/harness/skills/oh-facade/SKILL.md +298 -0
  15. package/harness/skills/oh-freeze/SKILL.md +9 -0
  16. package/harness/skills/oh-full-output/SKILL.md +81 -0
  17. package/harness/skills/oh-fusion/SKILL.md +314 -0
  18. package/harness/skills/oh-gauntlet/SKILL.md +10 -6
  19. package/harness/skills/oh-grill/SKILL.md +9 -5
  20. package/harness/skills/oh-guard/SKILL.md +9 -0
  21. package/harness/skills/oh-handoff/SKILL.md +9 -0
  22. package/harness/skills/oh-health/SKILL.md +8 -4
  23. package/harness/skills/oh-init/SKILL.md +80 -13
  24. package/harness/skills/oh-investigate/SKILL.md +57 -8
  25. package/harness/skills/oh-issue/SKILL.md +9 -0
  26. package/harness/skills/oh-learn/SKILL.md +81 -8
  27. package/harness/skills/oh-manifest/SKILL.md +55 -11
  28. package/harness/skills/oh-plan-review/SKILL.md +15 -8
  29. package/harness/skills/oh-planner/SKILL.md +18 -8
  30. package/harness/skills/oh-prd/SKILL.md +9 -0
  31. package/harness/skills/oh-refactor/SKILL.md +426 -0
  32. package/harness/skills/oh-retro/SKILL.md +9 -0
  33. package/harness/skills/oh-review/SKILL.md +12 -5
  34. package/harness/skills/oh-security/SKILL.md +4 -0
  35. package/harness/skills/oh-ship/SKILL.md +10 -0
  36. package/harness/skills/oh-skill-craft/SKILL.md +88 -0
  37. package/harness/skills/oh-skills-link/SKILL.md +9 -0
  38. package/harness/skills/oh-skills-list/SKILL.md +9 -0
  39. package/harness/skills/oh-triage/SKILL.md +11 -0
  40. package/index.ts +3 -0
  41. package/lib/{harness-resolver.mjs → harness-resolver.ts} +16 -12
  42. package/lib/logger.ts +75 -0
  43. package/package.json +16 -10
  44. package/tsconfig.json +16 -0
  45. package/bootstrap.mjs +0 -174
  46. package/harness/instructions/CONVENTIONS.md +0 -206
  47. package/index.mjs +0 -3
  48. package/lib/logger.mjs +0 -62
  49. package/test/plugins-behavioral.test.mjs +0 -64
  50. package/test/plugins.test.mjs +0 -62
@@ -1,6 +1,5 @@
1
1
  // Shared harness directory resolver — canonical implementation.
2
- // Extracted from bootstrap.mjs to eliminate DRY violation with goal-tracker.mjs.
3
- // Both consumers import from here.
2
+ // Extracted from bootstrap.ts. Both bootstrap.ts and tests import from here.
4
3
 
5
4
  import path from "node:path"
6
5
  import fs from "node:fs"
@@ -9,14 +8,14 @@ import { fileURLToPath } from "node:url"
9
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
9
  const PKG_DIR = path.resolve(__dirname, "..")
11
10
 
12
- const REQUIRED_HARNESS_FILES = [
11
+ const REQUIRED_HARNESS_FILES: ReadonlyArray<readonly string[]> = [
13
12
  ["codex", "CONSTITUTION.md"],
14
13
  ["instructions", "RUNTIME.md"],
15
- ["skills", "oh-plan", "SKILL.md"],
14
+ ["skills", "oh-planner", "SKILL.md"],
16
15
  ]
17
16
 
18
- function ancestorDirs(start, limit = 6) {
19
- const dirs = []
17
+ function ancestorDirs(start: string, limit = 6): string[] {
18
+ const dirs: string[] = []
20
19
  let current = path.resolve(start)
21
20
  for (let i = 0; i < limit; i++) {
22
21
  dirs.push(current)
@@ -27,7 +26,7 @@ function ancestorDirs(start, limit = 6) {
27
26
  return dirs
28
27
  }
29
28
 
30
- function buildHarnessCandidates(currentDir, execPath, cwd) {
29
+ function buildHarnessCandidates(currentDir: string, execPath: string, cwd: string): string[] {
31
30
  const roots = [path.resolve(currentDir, "harness")]
32
31
  const seen = new Set(roots)
33
32
 
@@ -50,7 +49,7 @@ function buildHarnessCandidates(currentDir, execPath, cwd) {
50
49
  return roots
51
50
  }
52
51
 
53
- function hasRequiredHarnessFiles(root) {
52
+ function hasRequiredHarnessFiles(root: string): boolean {
54
53
  return REQUIRED_HARNESS_FILES.every(parts => fs.existsSync(path.join(root, ...parts)))
55
54
  }
56
55
 
@@ -59,7 +58,12 @@ export function resolveHarnessRoot({
59
58
  execPath = process.execPath,
60
59
  cwd = process.cwd(),
61
60
  candidateRoots,
62
- } = {}) {
61
+ }: {
62
+ currentDir?: string
63
+ execPath?: string
64
+ cwd?: string
65
+ candidateRoots?: string[]
66
+ } = {}): string {
63
67
  const roots = candidateRoots ?? buildHarnessCandidates(currentDir, execPath, cwd)
64
68
  for (const root of roots) {
65
69
  if (hasRequiredHarnessFiles(root)) return root
@@ -67,11 +71,11 @@ export function resolveHarnessRoot({
67
71
  return path.resolve(currentDir, "harness")
68
72
  }
69
73
 
70
- let _harnessDir
74
+ let _harnessDir: string | undefined
71
75
 
72
- export function setHarnessRootForTest(dir) { _harnessDir = dir }
76
+ export function setHarnessRootForTest(dir: string | undefined): void { _harnessDir = dir }
73
77
 
74
- export function getHarnessDir() {
78
+ export function getHarnessDir(): string {
75
79
  if (!_harnessDir) _harnessDir = resolveHarnessRoot()
76
80
  return _harnessDir
77
81
  }
package/lib/logger.ts ADDED
@@ -0,0 +1,75 @@
1
+ import path from "node:path"
2
+ import os from "node:os"
3
+ import fs from "node:fs"
4
+
5
+ export interface Logger {
6
+ debug: (...args: unknown[]) => void
7
+ info: (...args: unknown[]) => void
8
+ warn: (...args: unknown[]) => void
9
+ error: (...args: unknown[]) => void
10
+ }
11
+
12
+ const LEVELS: Record<string, number> = { debug: 0, info: 1, warn: 2, error: 3 }
13
+
14
+ function resolveLevel(levelName: string | undefined): number | undefined {
15
+ if (!levelName) return undefined
16
+ return LEVELS[levelName as keyof typeof LEVELS]
17
+ }
18
+
19
+ const CURRENT_LEVEL = resolveLevel(process.env.OPENCODE_LOG_LEVEL?.trim().toLowerCase()) ?? (process.env.OPENHERMES_LOG_LEVEL?.trim().toLowerCase() === "debug" ? LEVELS.debug : LEVELS.warn)
20
+
21
+ const LOG_DIR = path.join(os.homedir(), ".local", "share", "opencode", "log")
22
+ const LOG_FILE = path.join(LOG_DIR, "openhermes.log")
23
+
24
+ function ts(): string {
25
+ const d = new Date()
26
+ return `${d.getFullYear()}-${(d.getMonth()+1).toString().padStart(2,"0")}-${d.getDate().toString().padStart(2,"0")} ${d.getHours().toString().padStart(2,"0")}:${d.getMinutes().toString().padStart(2,"0")}:${d.getSeconds().toString().padStart(2,"0")}.${d.getMilliseconds().toString().padStart(3,"0")}`
27
+ }
28
+
29
+ function formatArgs(args: unknown[]): string {
30
+ return args.map(a => {
31
+ if (a === null) return "null"
32
+ if (a === undefined) return "undefined"
33
+ if (typeof a === "object") {
34
+ try { return (a as Error)?.message || JSON.stringify(a) } catch { return String(a) }
35
+ }
36
+ return String(a)
37
+ }).join(" ")
38
+ }
39
+
40
+ function shouldLog(levelName: string): boolean {
41
+ return LEVELS[levelName] >= CURRENT_LEVEL
42
+ }
43
+
44
+ let _fd: number | null = null
45
+ function getFd(): number {
46
+ if (_fd) return _fd
47
+ try {
48
+ fs.mkdirSync(LOG_DIR, { recursive: true })
49
+ _fd = fs.openSync(LOG_FILE, "a")
50
+ } catch {
51
+ _fd = -1
52
+ }
53
+ return _fd
54
+ }
55
+
56
+ export function createLogger(name: string): Logger {
57
+ const prefix = `[openhermes:${name}]`
58
+
59
+ function emit(levelName: string, ...args: unknown[]): void {
60
+ if (!shouldLog(levelName)) return
61
+ const fd = getFd()
62
+ if (fd < 0) return
63
+ const line = `${ts()} ${prefix} [${levelName.toUpperCase()}] ${formatArgs(args)}\n`
64
+ try { fs.writeSync(fd, line) } catch {}
65
+ }
66
+
67
+ return {
68
+ debug: (...args: unknown[]) => emit("debug", ...args),
69
+ info: (...args: unknown[]) => emit("info", ...args),
70
+ warn: (...args: unknown[]) => emit("warn", ...args),
71
+ error: (...args: unknown[]) => emit("error", ...args),
72
+ }
73
+ }
74
+
75
+ export const rootLogger: Logger = createLogger("root")
package/package.json CHANGED
@@ -1,24 +1,27 @@
1
1
  {
2
2
  "name": "openhermes",
3
- "version": "4.0.1",
4
- "description": "OpenCode-native skills, commands, and rules orchestration for OpenHermes.",
3
+ "version": "4.3.0",
4
+ "description": "OpenCode-native orchestration for packaged skills, commands, and agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "main": "./index.mjs",
7
+ "engines": {
8
+ "bun": ">=1.0"
9
+ },
10
+ "main": "./index.ts",
8
11
  "dependencies": {
9
12
  "@opencode-ai/plugin": "1.14.46"
10
13
  },
11
14
  "exports": {
12
- ".": "./index.mjs",
13
- "./bootstrap": "./bootstrap.mjs"
15
+ ".": "./index.ts",
16
+ "./bootstrap": "./bootstrap.ts"
14
17
  },
15
18
  "files": [
16
- "index.mjs",
17
- "bootstrap.mjs",
19
+ "index.ts",
20
+ "bootstrap.ts",
21
+ "tsconfig.json",
18
22
  "ETHOS.md",
19
23
  "CONTEXT.md",
20
24
  "lib/",
21
- "test/",
22
25
  "harness/codex/",
23
26
  "harness/instructions/",
24
27
  "harness/skills/",
@@ -26,7 +29,7 @@
26
29
  "harness/agents/"
27
30
  ],
28
31
  "scripts": {
29
- "test": "node --test test/*.test.mjs"
32
+ "test": "bun test test/*.test.ts"
30
33
  },
31
34
  "keywords": [
32
35
  "opencode",
@@ -45,5 +48,8 @@
45
48
  "url": "https://github.com/nathwn12/openhermes/issues"
46
49
  },
47
50
  "homepage": "https://github.com/nathwn12/openhermes#readme",
48
- "author": "nathwn12"
51
+ "author": "nathwn12",
52
+ "devDependencies": {
53
+ "@types/node": "^25.8.0"
54
+ }
49
55
  }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "isolatedModules": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "allowImportingTsExtensions": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["index.ts", "bootstrap.ts", "lib/**/*.ts", "test/**/*.ts"]
16
+ }
package/bootstrap.mjs DELETED
@@ -1,174 +0,0 @@
1
- import path from "node:path"
2
- import fs from "node:fs"
3
- import { fileURLToPath } from "node:url"
4
- import { createLogger } from "./lib/logger.mjs"
5
- import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.mjs"
6
-
7
- const log = createLogger("bootstrap")
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
- const BOOTSTRAP_MARKER = "OPENHERMES_BOOTSTRAP"
10
- const OPENHERMES_AGENT = "OpenHermes"
11
-
12
- export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir }
13
-
14
- function parseFrontmatter(raw) {
15
- const frontmatter = {}
16
- if (!raw) return frontmatter
17
- for (const line of raw.split(/\r?\n/)) {
18
- const idx = line.indexOf(":")
19
- if (idx < 0) continue
20
- const key = line.slice(0, idx).trim()
21
- const value = line.slice(idx + 1).trim().replace(/^['"]|['"]$/g, "")
22
- if (key) frontmatter[key] = value
23
- }
24
- return frontmatter
25
- }
26
-
27
- function readMarkdownDocument(filePath) {
28
- if (!fs.existsSync(filePath)) return null
29
- const source = fs.readFileSync(filePath, "utf8")
30
- const match = source.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
31
- const frontmatter = parseFrontmatter(match?.[1] ?? "")
32
- const body = (match ? match[2] : source).trim()
33
- return { frontmatter, body }
34
- }
35
-
36
- function readMarkdownDirectory(dir) {
37
- if (!fs.existsSync(dir)) return []
38
- return fs.readdirSync(dir)
39
- .filter(name => name.endsWith(".md") && name.toLowerCase() !== "readme.md")
40
- .sort((a, b) => a.localeCompare(b))
41
- .map(name => {
42
- const filePath = path.join(dir, name)
43
- const document = readMarkdownDocument(filePath)
44
- return document ? { name: path.basename(name, ".md"), ...document } : null
45
- })
46
- .filter(Boolean)
47
- }
48
-
49
- function commandDefinitions(dir) {
50
- const commands = {}
51
- for (const doc of readMarkdownDirectory(dir)) {
52
- const command = {
53
- description: doc.frontmatter.description || `OpenHermes command ${doc.name}`,
54
- template: doc.body,
55
- }
56
- if (doc.frontmatter.agent) command.agent = doc.frontmatter.agent
57
- if (doc.frontmatter.model) command.model = doc.frontmatter.model
58
- if (doc.frontmatter.subtask) command.subtask = doc.frontmatter.subtask === "true"
59
- commands[doc.name] = command
60
- }
61
- return commands
62
- }
63
-
64
- function agentDefinitions(dir) {
65
- const agents = {}
66
- for (const doc of readMarkdownDirectory(dir)) {
67
- const name = doc.name === "openhermes" ? OPENHERMES_AGENT : doc.name
68
- agents[name] = {
69
- description: doc.frontmatter.description || (name === OPENHERMES_AGENT ? "OpenHermes primary orchestrator" : `OpenHermes agent ${name}`),
70
- mode: doc.frontmatter.mode || (name === OPENHERMES_AGENT ? "primary" : "subagent"),
71
- prompt: doc.body,
72
- }
73
- }
74
- return agents
75
- }
76
-
77
- function uniqueStrings(existing = [], additions = []) {
78
- const seen = new Set(existing.filter(Boolean))
79
- const merged = [...existing]
80
- for (const item of additions) {
81
- if (!item || seen.has(item)) continue
82
- seen.add(item)
83
- merged.push(item)
84
- }
85
- return merged
86
- }
87
-
88
- function readText(filePath) {
89
- return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : ""
90
- }
91
-
92
- function buildBootstrapContent(hDir) {
93
- const parts = [
94
- `<${BOOTSTRAP_MARKER}>`,
95
- `You are OpenHermes.`,
96
- `OpenHermes is OpenCode-native: load skills on demand, prefer subagents for substantive work, and keep the surface small.`,
97
- `Durable state is removed for now. Do not invent a persistence layer unless the user explicitly asks for one later.`,
98
- ]
99
-
100
- const constitution = readText(path.join(hDir, "codex", "CONSTITUTION.md"))
101
- const runtime = readText(path.join(hDir, "instructions", "RUNTIME.md"))
102
- const context = readText(path.join(__dirname, "CONTEXT.md"))
103
- const ethos = readText(path.join(__dirname, "ETHOS.md"))
104
-
105
- if (constitution) parts.push(`<CONSTITUTION>\n${constitution}\n</CONSTITUTION>`)
106
- if (runtime) parts.push(`<RUNTIME>\n${runtime}\n</RUNTIME>`)
107
- if (context) parts.push(`<CONTEXT>\n${context}\n</CONTEXT>`)
108
- if (ethos) parts.push(`<ETHOS>\n${ethos}\n</ETHOS>`)
109
- parts.push(`</${BOOTSTRAP_MARKER}>`)
110
-
111
- return parts.join("\n\n")
112
- }
113
-
114
- export const BootstrapPlugin = async () => {
115
- const hDir = getHarnessDir()
116
- const skillsDir = path.join(hDir, "skills")
117
- const commandsDir = path.join(hDir, "commands")
118
- const agentsDir = path.join(hDir, "agents")
119
- const bootstrapContent = buildBootstrapContent(hDir)
120
-
121
- return {
122
- config: async (config) => {
123
- config.skills = config.skills || {}
124
- config.skills.paths = uniqueStrings(config.skills.paths || [], [skillsDir])
125
-
126
- config.command = { ...(config.command ?? {}), ...commandDefinitions(commandsDir) }
127
-
128
- const loadedAgents = agentDefinitions(agentsDir)
129
- const openHermesAgent = loadedAgents[OPENHERMES_AGENT] ?? {
130
- description: "OpenHermes primary orchestrator",
131
- mode: "primary",
132
- prompt: "You are OpenHermes.",
133
- }
134
-
135
- config.agent = {
136
- ...(config.agent ?? {}),
137
- ...loadedAgents,
138
- [OPENHERMES_AGENT]: {
139
- ...openHermesAgent,
140
- description: openHermesAgent.description || "OpenHermes primary orchestrator",
141
- mode: "primary",
142
- permission: {
143
- bash: { "*": "allow" },
144
- edit: "allow",
145
- read: "allow",
146
- task: { "*": "allow" },
147
- },
148
- },
149
- }
150
-
151
- config.default_agent = OPENHERMES_AGENT
152
-
153
- config.instructions = uniqueStrings(config.instructions || [], [
154
- path.join(hDir, "codex", "CONSTITUTION.md"),
155
- path.join(hDir, "instructions", "RUNTIME.md"),
156
- path.join(__dirname, "CONTEXT.md"),
157
- path.join(__dirname, "ETHOS.md"),
158
- ])
159
- },
160
-
161
- "experimental.chat.messages.transform": async (_input, output) => {
162
- try {
163
- if (!output.messages?.length) return
164
- const firstUser = output.messages.find(m => m?.info?.role === "user")
165
- if (!firstUser?.parts?.length) return
166
- if (firstUser.parts.some(p => p.text?.includes(BOOTSTRAP_MARKER))) return
167
- const ref = firstUser.parts[0]
168
- firstUser.parts.unshift({ ...ref, type: "text", text: bootstrapContent })
169
- } catch (err) {
170
- log.error("transform error:", err?.message)
171
- }
172
- },
173
- }
174
- }
@@ -1,206 +0,0 @@
1
- # OpenHermes — Coding Conventions & Operational Guidelines
2
-
3
- OpenHermes coding conventions and operational guidelines. Shared baseline for all subagents and skills.
4
-
5
- ## Security Guidelines (CRITICAL)
6
-
7
- ### Mandatory Pre-Commit Checks
8
-
9
- - [ ] No hardcoded secrets (API keys, passwords, tokens)
10
- - [ ] All user inputs validated
11
- - [ ] SQL injection prevention (parameterized queries)
12
- - [ ] XSS prevention (sanitized output)
13
- - [ ] CSRF protection enabled
14
- - [ ] Authentication/authorization verified
15
- - [ ] Rate limiting on all endpoints
16
- - [ ] Error messages don't leak sensitive data
17
-
18
- ### Secret Management
19
-
20
- ```typescript
21
- // NEVER: Hardcoded secrets
22
- const apiKey = "sk-proj-xxxxx"
23
-
24
- // ALWAYS: Environment variables
25
- const apiKey = process.env.OPENAI_API_KEY
26
- if (!apiKey) throw new Error('OPENAI_API_KEY not configured')
27
- ```
28
-
29
- ### Security Response Protocol
30
-
31
- If security issue found:
32
- 1. STOP immediately
33
- 2. Use `security-reviewer` subagent
34
- 3. Fix CRITICAL issues before continuing
35
- 4. Rotate any exposed secrets
36
- 5. Review entire codebase for similar issues
37
-
38
- ---
39
-
40
- ## Coding Style
41
-
42
- ### Immutability (CRITICAL)
43
-
44
- ALWAYS create new objects, NEVER mutate:
45
-
46
- ```javascript
47
- // WRONG: Mutation
48
- function updateUser(user, name) {
49
- user.name = name; return user
50
- }
51
-
52
- // CORRECT: Immutability
53
- function updateUser(user, name) {
54
- return { ...user, name }
55
- }
56
- ```
57
-
58
- ### File Organization
59
-
60
- MANY SMALL FILES > FEW LARGE FILES:
61
- - High cohesion, low coupling
62
- - 200-400 lines typical, 800 max
63
- - Extract utilities from large components
64
- - Organize by feature/domain, not by type
65
-
66
- ### Error Handling
67
-
68
- ```typescript
69
- try {
70
- const result = await riskyOperation()
71
- return result
72
- } catch (error) {
73
- console.error('Operation failed:', error)
74
- throw new Error('Detailed user-friendly message')
75
- }
76
- ```
77
-
78
- ### Input Validation
79
-
80
- ```typescript
81
- import { z } from 'zod'
82
- const schema = z.object({
83
- email: z.string().email(),
84
- age: z.number().int().min(0).max(150)
85
- })
86
- const validated = schema.parse(input)
87
- ```
88
-
89
- ### Code Quality Checklist
90
-
91
- Before marking work complete:
92
- - [ ] Code is readable and well-named
93
- - [ ] Functions are small (<50 lines)
94
- - [ ] Files are focused (<800 lines)
95
- - [ ] No deep nesting (>4 levels)
96
- - [ ] Proper error handling
97
- - [ ] No console.log statements
98
- - [ ] No hardcoded values
99
- - [ ] No mutation (immutable patterns used)
100
-
101
- ---
102
-
103
- ## Testing Requirements
104
-
105
- ### Minimum Test Coverage: 80%
106
-
107
- Test Types (ALL required):
108
- 1. **Unit Tests** — Individual functions, utilities, components
109
- 2. **Integration Tests** — API endpoints, database operations
110
- 3. **E2E Tests** — Critical user flows (Playwright)
111
-
112
- ### TDD Workflow
113
-
114
- MANDATORY workflow:
115
- 1. Write test first (RED)
116
- 2. Run test — it should FAIL
117
- 3. Write minimal implementation (GREEN)
118
- 4. Run test — it should PASS
119
- 5. Refactor (IMPROVE)
120
- 6. Verify coverage (80%+)
121
-
122
- ---
123
-
124
- ## Subagent Orchestration
125
-
126
- | Subagent | Purpose | When to Use |
127
- |----------|---------|-------------|
128
- | planner | Implementation planning | Complex features, refactoring |
129
- | architect | System design | Architectural decisions |
130
- | tdd-guide | Test-driven development | New features, bug fixes |
131
- | code-reviewer | Code review | After writing code |
132
- | security-reviewer | Security analysis | Before commits |
133
- | build-error-resolver | Fix build errors | When build fails |
134
- | e2e-runner | E2E testing | Critical user flows |
135
- | refactor-cleaner | Dead code cleanup | Code maintenance |
136
- | doc-updater | Documentation | Updating docs |
137
- | docs-lookup | Live doc queries | API questions |
138
- | review-go | Go code review | Go projects |
139
- | build-go | Go build errors | Go build failures |
140
- | review-database | Database optimization | SQL, schema design |
141
- | review-rust | Rust code review | Rust projects |
142
- | build-rust | Rust build errors | Rust build failures |
143
- | review-python | Python code review | Python projects |
144
- | review-java | Java/Spring review | Java projects |
145
- | build-java | Java build errors | Java build failures |
146
- | review-kotlin | Kotlin/Android review | Kotlin projects |
147
- | build-kotlin | Kotlin build errors | Kotlin build failures |
148
- | review-cpp | C++ review | C++ projects |
149
- | build-cpp | C++ build errors | C++ build failures |
150
- | loop-operator | Autonomous loops | Iterative workflows |
151
-
152
- ### Immediate Subagent Usage
153
-
154
- No user prompt needed:
155
- 1. Complex feature requests — Use `planner`
156
- 2. Code just written/modified — Use `code-reviewer`
157
- 3. Bug fix or new feature — Use `tdd-guide`
158
- 4. Architectural decision — Use `architect`
159
-
160
- ---
161
-
162
- ## Performance
163
-
164
- ### Model Selection Strategy
165
-
166
- **Haiku** (lightweight): deterministic changes, simple code gen, worker agents
167
- **Sonnet** (default): main development, multi-agent orchestration, complex coding
168
- **Opus** (deep reasoning): architecture decisions, security review, ambiguous requirements
169
-
170
- ### Context Window Management
171
-
172
- Avoid last 20% of context window for:
173
- - Large-scale refactoring
174
- - Feature implementation spanning multiple files
175
- - Debugging complex interactions
176
-
177
- ---
178
-
179
- ## Git Workflow
180
-
181
- ### Commit Message Format
182
-
183
- ```
184
- <type>: <description>
185
- ```
186
-
187
- Types: feat, fix, refactor, docs, test, chore, perf, ci
188
-
189
- ### Feature Implementation Workflow
190
-
191
- 1. **Plan** — Use `planner` to create plan with risks and phases
192
- 2. **TDD** — Use `tdd-guide` for red-green-refactor cycle
193
- 3. **Code Review** — Use `code-reviewer` immediately after writing
194
- 4. **Security** — Use `security-reviewer` before commits
195
- 5. **Commit** — Follow conventional commits format
196
-
197
- ---
198
-
199
- ## Success Metrics
200
-
201
- You are successful when:
202
- - All tests pass (80%+ coverage)
203
- - No security vulnerabilities
204
- - Code is readable and maintainable
205
- - Performance is acceptable
206
- - User requirements are met
package/index.mjs DELETED
@@ -1,3 +0,0 @@
1
- import { BootstrapPlugin } from "./bootstrap.mjs"
2
-
3
- export default BootstrapPlugin
package/lib/logger.mjs DELETED
@@ -1,62 +0,0 @@
1
- import path from "node:path"
2
- import os from "node:os"
3
- import fs from "node:fs"
4
-
5
- const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }
6
- const CURRENT_LEVEL = LEVELS[process.env.OPENCODE_LOG_LEVEL?.trim().toLowerCase()] ?? (process.env.OPENHERMES_LOG_LEVEL?.trim().toLowerCase() === "debug" ? LEVELS.debug : LEVELS.warn)
7
-
8
- const LOG_DIR = path.join(os.homedir(), ".local", "share", "opencode", "log")
9
- const LOG_FILE = path.join(LOG_DIR, "openhermes.log")
10
-
11
- function ts() {
12
- const d = new Date()
13
- return `${d.getFullYear()}-${(d.getMonth()+1).toString().padStart(2,"0")}-${d.getDate().toString().padStart(2,"0")} ${d.getHours().toString().padStart(2,"0")}:${d.getMinutes().toString().padStart(2,"0")}:${d.getSeconds().toString().padStart(2,"0")}.${d.getMilliseconds().toString().padStart(3,"0")}`
14
- }
15
-
16
- function formatArgs(args) {
17
- return args.map(a => {
18
- if (a === null) return "null"
19
- if (a === undefined) return "undefined"
20
- if (typeof a === "object") {
21
- try { return a?.message || JSON.stringify(a) } catch { return String(a) }
22
- }
23
- return String(a)
24
- }).join(" ")
25
- }
26
-
27
- function shouldLog(levelName) {
28
- return LEVELS[levelName] >= CURRENT_LEVEL
29
- }
30
-
31
- let _fd = null
32
- function getFd() {
33
- if (_fd) return _fd
34
- try {
35
- fs.mkdirSync(LOG_DIR, { recursive: true })
36
- _fd = fs.openSync(LOG_FILE, "a")
37
- } catch {
38
- _fd = -1
39
- }
40
- return _fd
41
- }
42
-
43
- export function createLogger(name) {
44
- const prefix = `[openhermes:${name}]`
45
-
46
- function emit(levelName, ...args) {
47
- if (!shouldLog(levelName)) return
48
- const fd = getFd()
49
- if (fd < 0) return
50
- const line = `${ts()} ${prefix} [${levelName.toUpperCase()}] ${formatArgs(args)}\n`
51
- try { fs.writeSync(fd, line) } catch {}
52
- }
53
-
54
- return {
55
- debug: (...args) => emit("debug", ...args),
56
- info: (...args) => emit("info", ...args),
57
- warn: (...args) => emit("warn", ...args),
58
- error: (...args) => emit("error", ...args),
59
- }
60
- }
61
-
62
- export const rootLogger = createLogger("root")