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.
- package/ETHOS.md +6 -3
- package/LICENSE +21 -21
- package/README.md +111 -81
- package/bootstrap.ts +405 -0
- package/harness/agents/openhermes.md +45 -55
- package/harness/codex/AUTOPILOT.md +126 -0
- package/harness/codex/CONSTITUTION.md +14 -11
- package/harness/codex/ROUTING.md +35 -69
- package/harness/commands/oh-log.md +18 -0
- package/harness/instructions/RUNTIME.md +27 -51
- package/harness/skills/oh-builder/SKILL.md +27 -16
- package/harness/skills/oh-caveman/SKILL.md +9 -0
- package/harness/skills/oh-expert/SKILL.md +6 -0
- package/harness/skills/oh-facade/SKILL.md +298 -0
- package/harness/skills/oh-freeze/SKILL.md +9 -0
- package/harness/skills/oh-full-output/SKILL.md +81 -0
- package/harness/skills/oh-fusion/SKILL.md +314 -0
- package/harness/skills/oh-gauntlet/SKILL.md +10 -6
- package/harness/skills/oh-grill/SKILL.md +9 -5
- package/harness/skills/oh-guard/SKILL.md +9 -0
- package/harness/skills/oh-handoff/SKILL.md +9 -0
- package/harness/skills/oh-health/SKILL.md +8 -4
- package/harness/skills/oh-init/SKILL.md +80 -13
- package/harness/skills/oh-investigate/SKILL.md +57 -8
- package/harness/skills/oh-issue/SKILL.md +9 -0
- package/harness/skills/oh-learn/SKILL.md +81 -8
- package/harness/skills/oh-manifest/SKILL.md +55 -11
- package/harness/skills/oh-plan-review/SKILL.md +15 -8
- package/harness/skills/oh-planner/SKILL.md +18 -8
- package/harness/skills/oh-prd/SKILL.md +9 -0
- package/harness/skills/oh-refactor/SKILL.md +426 -0
- package/harness/skills/oh-retro/SKILL.md +9 -0
- package/harness/skills/oh-review/SKILL.md +12 -5
- package/harness/skills/oh-security/SKILL.md +4 -0
- package/harness/skills/oh-ship/SKILL.md +10 -0
- package/harness/skills/oh-skill-craft/SKILL.md +88 -0
- package/harness/skills/oh-skills-link/SKILL.md +9 -0
- package/harness/skills/oh-skills-list/SKILL.md +9 -0
- package/harness/skills/oh-triage/SKILL.md +11 -0
- package/index.ts +3 -0
- package/lib/{harness-resolver.mjs → harness-resolver.ts} +16 -12
- package/lib/logger.ts +75 -0
- package/package.json +16 -10
- package/tsconfig.json +16 -0
- package/bootstrap.mjs +0 -174
- package/harness/instructions/CONVENTIONS.md +0 -206
- package/index.mjs +0 -3
- package/lib/logger.mjs +0 -62
- package/test/plugins-behavioral.test.mjs +0 -64
- package/test/plugins.test.mjs +0 -62
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Shared harness directory resolver — canonical implementation.
|
|
2
|
-
// Extracted from bootstrap.
|
|
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-
|
|
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
|
|
4
|
-
"description": "OpenCode-native skills, commands, and
|
|
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
|
-
"
|
|
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.
|
|
13
|
-
"./bootstrap": "./bootstrap.
|
|
15
|
+
".": "./index.ts",
|
|
16
|
+
"./bootstrap": "./bootstrap.ts"
|
|
14
17
|
},
|
|
15
18
|
"files": [
|
|
16
|
-
"index.
|
|
17
|
-
"bootstrap.
|
|
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": "
|
|
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
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")
|