openhermes 4.0.1 → 4.1.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/README.md CHANGED
@@ -55,7 +55,7 @@ These seven form a pipeline: **think → plan → build → test → ship → se
55
55
  | oh-issue | Break plans into vertical-slice issues |
56
56
  | oh-prd | Write structured PRDs |
57
57
  | oh-caveman | Ultra-compressed response mode |
58
- | oh-freeze | Freeze dependencies |
58
+ | oh-freeze | Restrict file edits to a specific directory |
59
59
  | oh-learn | Learn patterns from the codebase |
60
60
  | oh-guard | Safety confirmations for destructive operations |
61
61
  | oh-skills-link | Verify skills discovery |
@@ -111,14 +111,14 @@ openhermes-pkg/
111
111
  ├── AGENTS.md # Skill/command/agent inventory
112
112
  ├── CONTEXT.md # Shared language
113
113
  ├── ETHOS.md # Operating principles
114
- ├── bootstrap.mjs # Plugin loader — registers everything
115
- ├── index.mjs # Package entrypoint
114
+ ├── bootstrap.ts # Plugin loader — registers everything
115
+ ├── index.ts # Package entrypoint
116
116
  ├── harness/
117
117
  │ ├── agents/ # Agent manifests (OpenHermes)
118
118
  │ ├── codex/ # CONSTITUTION.md
119
119
  │ ├── commands/ # Slash command manifests (/oh-doctor)
120
- │ ├── instructions/ # RUNTIME.md, CONVENTIONS.md
121
- └── skills/ # 25 skill SKILL.md files
120
+ │ ├── instructions/ # RUNTIME.md
121
+ └── skills/ # 25 skill SKILL.md files
122
122
  └── test/
123
123
  ```
124
124
 
@@ -1,8 +1,9 @@
1
1
  import path from "node:path"
2
2
  import fs from "node:fs"
3
3
  import { fileURLToPath } from "node:url"
4
- import { createLogger } from "./lib/logger.mjs"
5
- import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.mjs"
4
+ import type { Plugin } from "@opencode-ai/plugin"
5
+ import { createLogger } from "./lib/logger.ts"
6
+ import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.ts"
6
7
 
7
8
  const log = createLogger("bootstrap")
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -11,8 +12,8 @@ const OPENHERMES_AGENT = "OpenHermes"
11
12
 
12
13
  export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir }
13
14
 
14
- function parseFrontmatter(raw) {
15
- const frontmatter = {}
15
+ function parseFrontmatter(raw: string | undefined): Record<string, string> {
16
+ const frontmatter: Record<string, string> = {}
16
17
  if (!raw) return frontmatter
17
18
  for (const line of raw.split(/\r?\n/)) {
18
19
  const idx = line.indexOf(":")
@@ -24,16 +25,25 @@ function parseFrontmatter(raw) {
24
25
  return frontmatter
25
26
  }
26
27
 
27
- function readMarkdownDocument(filePath) {
28
+ interface MarkdownDocument {
29
+ frontmatter: Record<string, string>
30
+ body: string
31
+ }
32
+
33
+ function readMarkdownDocument(filePath: string): MarkdownDocument | null {
28
34
  if (!fs.existsSync(filePath)) return null
29
35
  const source = fs.readFileSync(filePath, "utf8")
30
- const match = source.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
36
+ const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/)
31
37
  const frontmatter = parseFrontmatter(match?.[1] ?? "")
32
38
  const body = (match ? match[2] : source).trim()
33
39
  return { frontmatter, body }
34
40
  }
35
41
 
36
- function readMarkdownDirectory(dir) {
42
+ interface DirEntry extends MarkdownDocument {
43
+ name: string
44
+ }
45
+
46
+ function readMarkdownDirectory(dir: string): DirEntry[] {
37
47
  if (!fs.existsSync(dir)) return []
38
48
  return fs.readdirSync(dir)
39
49
  .filter(name => name.endsWith(".md") && name.toLowerCase() !== "readme.md")
@@ -43,13 +53,21 @@ function readMarkdownDirectory(dir) {
43
53
  const document = readMarkdownDocument(filePath)
44
54
  return document ? { name: path.basename(name, ".md"), ...document } : null
45
55
  })
46
- .filter(Boolean)
56
+ .filter((e): e is DirEntry => e !== null)
57
+ }
58
+
59
+ interface CommandDef {
60
+ description: string
61
+ template: string
62
+ agent?: string
63
+ model?: string
64
+ subtask?: boolean
47
65
  }
48
66
 
49
- function commandDefinitions(dir) {
50
- const commands = {}
67
+ function commandDefinitions(dir: string): Record<string, CommandDef> {
68
+ const commands: Record<string, CommandDef> = {}
51
69
  for (const doc of readMarkdownDirectory(dir)) {
52
- const command = {
70
+ const command: CommandDef = {
53
71
  description: doc.frontmatter.description || `OpenHermes command ${doc.name}`,
54
72
  template: doc.body,
55
73
  }
@@ -61,8 +79,14 @@ function commandDefinitions(dir) {
61
79
  return commands
62
80
  }
63
81
 
64
- function agentDefinitions(dir) {
65
- const agents = {}
82
+ interface AgentDef {
83
+ description: string
84
+ mode: string
85
+ prompt: string
86
+ }
87
+
88
+ function agentDefinitions(dir: string): Record<string, AgentDef> {
89
+ const agents: Record<string, AgentDef> = {}
66
90
  for (const doc of readMarkdownDirectory(dir)) {
67
91
  const name = doc.name === "openhermes" ? OPENHERMES_AGENT : doc.name
68
92
  agents[name] = {
@@ -74,7 +98,7 @@ function agentDefinitions(dir) {
74
98
  return agents
75
99
  }
76
100
 
77
- function uniqueStrings(existing = [], additions = []) {
101
+ function uniqueStrings(existing: string[] = [], additions: string[] = []): string[] {
78
102
  const seen = new Set(existing.filter(Boolean))
79
103
  const merged = [...existing]
80
104
  for (const item of additions) {
@@ -85,11 +109,11 @@ function uniqueStrings(existing = [], additions = []) {
85
109
  return merged
86
110
  }
87
111
 
88
- function readText(filePath) {
112
+ function readText(filePath: string): string {
89
113
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : ""
90
114
  }
91
115
 
92
- function buildBootstrapContent(hDir) {
116
+ function buildBootstrapContent(hDir: string): string {
93
117
  const parts = [
94
118
  `<${BOOTSTRAP_MARKER}>`,
95
119
  `You are OpenHermes.`,
@@ -111,7 +135,15 @@ function buildBootstrapContent(hDir) {
111
135
  return parts.join("\n\n")
112
136
  }
113
137
 
114
- export const BootstrapPlugin = async () => {
138
+ interface OpenHermesConfig {
139
+ skills?: { paths?: string[] }
140
+ command?: Record<string, unknown>
141
+ agent?: Record<string, unknown>
142
+ instructions?: string[]
143
+ default_agent?: string
144
+ }
145
+
146
+ export const BootstrapPlugin: Plugin = async () => {
115
147
  const hDir = getHarnessDir()
116
148
  const skillsDir = path.join(hDir, "skills")
117
149
  const commandsDir = path.join(hDir, "commands")
@@ -119,7 +151,7 @@ export const BootstrapPlugin = async () => {
119
151
  const bootstrapContent = buildBootstrapContent(hDir)
120
152
 
121
153
  return {
122
- config: async (config) => {
154
+ config: async (config: OpenHermesConfig) => {
123
155
  config.skills = config.skills || {}
124
156
  config.skills.paths = uniqueStrings(config.skills.paths || [], [skillsDir])
125
157
 
@@ -149,16 +181,9 @@ export const BootstrapPlugin = async () => {
149
181
  }
150
182
 
151
183
  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
184
  },
160
185
 
161
- "experimental.chat.messages.transform": async (_input, output) => {
186
+ "experimental.chat.messages.transform": async (_input: unknown, output: { messages?: Array<{ info?: { role?: string }; parts?: Array<{ text?: string }> }> }) => {
162
187
  try {
163
188
  if (!output.messages?.length) return
164
189
  const firstUser = output.messages.find(m => m?.info?.role === "user")
@@ -166,8 +191,8 @@ export const BootstrapPlugin = async () => {
166
191
  if (firstUser.parts.some(p => p.text?.includes(BOOTSTRAP_MARKER))) return
167
192
  const ref = firstUser.parts[0]
168
193
  firstUser.parts.unshift({ ...ref, type: "text", text: bootstrapContent })
169
- } catch (err) {
170
- log.error("transform error:", err?.message)
194
+ } catch (err: unknown) {
195
+ log.error("transform error:", (err as Error)?.message)
171
196
  }
172
197
  },
173
198
  }
@@ -17,6 +17,7 @@ If a skill has no explicit route for an outcome, the fallback is always **surfac
17
17
  ## Canonical routing table
18
18
 
19
19
  ### Workflow skills
20
+ *Includes oh-doctor (command, not skill) for diagnostic routing.*
20
21
 
21
22
  | Skill | pass | fail | blocker |
22
23
  |-------|------|------|---------|
@@ -49,8 +50,8 @@ If a skill has no explicit route for an outcome, the fallback is always **surfac
49
50
  | **oh-triage** | → oh-issue or oh-handoff | → oh-expert (clarify) | surface |
50
51
  | **oh-retro** | → oh-planner (next cycle) | → oh-handoff (if blocked) | surface |
51
52
  | **oh-handoff** | → [end of session — intended terminal] | → [surface blocker] | surface |
52
- | **oh-skillcraft** | → oh-skills-link (verify discovery) | → oh-expert (diagnose) | surface |
53
- | **oh-skills-link** | → [report link status] | → oh-skillcraft (fix skill) | surface |
53
+ | **oh-skill-craft** | → oh-skills-link (verify discovery) | → oh-expert (diagnose) | surface |
54
+ | **oh-skills-link** | → [report link status] | → oh-skill-craft (fix skill) | surface |
54
55
  | **oh-skills-list** | → [done — read-only] | → [surface issue] | surface |
55
56
 
56
57
  ### Mode skills (no routing — mode switches)
@@ -42,6 +42,7 @@ Key skills:
42
42
  - `.opencode/plan.md` — produced by oh-planner, consumed by oh-builder and oh-manifest
43
43
  - `.opencode/work-log.md` — progress tracking across subagent delegations
44
44
  - `.opencode/todo.md` — task tracking for multi-step work
45
+ - `.opencode/instincts.jsonl` — behavioral patterns (trigger-action-confidence) extracted by oh-learn. On session start, read the highest-confidence entries (≥0.7) into context so past patterns inform current work. This is not durable state — it is an opt-in config that grows organically.
45
46
 
46
47
  **Bootstrap**: `harness/codex/CONSTITUTION.md`, this file, `CONTEXT.md`, and `ETHOS.md` are injected into the first user message so the agent starts with the same operating model every session.
47
48
 
@@ -50,5 +51,5 @@ Key skills:
50
51
  ## Conventions
51
52
 
52
53
  Security, coding style, testing, and orchestration standards:
53
- - See `CONVENTIONS.md` for the shared baseline.
54
+ - For coding conventions, see the Constitution.
54
55
  - Skills provide the detailed walkthroughs for specialized workflows.
@@ -23,15 +23,21 @@ The ALL-arounder builder. Merges prototyping, TDD, implementation from plan, and
23
23
  ### Mode A: Prototype (exploratory)
24
24
  When you need to answer a question before committing.
25
25
 
26
- 1. Determine what question the prototype answers (data model, state flow, UI direction)
27
- 2. Build minimal — just enough to answer the question
28
- 3. Let user play with it
29
- 4. Collect feedback
30
- 5. Decide: discard, iterate, or promote
31
-
32
- **Sub-modes:**
33
- - **Terminal** for state/business logic questions
34
- - **UI** — several radical design variations from one route
26
+ **Pick a branch based on the question being asked:**
27
+
28
+ - **"Does this logic / state model feel right?"** → **Terminal branch.** Build a tiny interactive terminal app that pushes the state machine through cases that are hard to reason about on paper.
29
+ - **"What should this look like?"** → **UI branch.** Generate several radically different visual variations, switchable via a URL param or floating control bar.
30
+
31
+ If the question is genuinely ambiguous, default to whichever branch better matches the surrounding code (backend module → terminal, page/component → UI) and state the assumption.
32
+
33
+ **Rules that apply to both branches:**
34
+
35
+ 1. **Throwaway from day one, clearly marked.** Name it so a casual reader sees it's a prototype.
36
+ 2. **One command to run.** Whatever the project's task runner supports — `pnpm <name>`, `bun <path>`, etc.
37
+ 3. **No persistence by default.** State lives in memory. If the question involves a database, hit a scratch DB with a clear "PROTOTYPE — wipe me" name.
38
+ 4. **Skip the polish.** No tests, no error handling beyond what makes it runnable. The point is to learn and then delete.
39
+ 5. **Surface the state.** After every action (terminal) or on every variant switch (UI), show the full relevant state so the user sees what changed.
40
+ 6. **Delete or absorb when done.** The answer is the only thing worth keeping. Capture it in a commit, ADR, or note — then delete the prototype code.
35
41
 
36
42
  ### Mode B: TDD (test-first implementation)
37
43
  When building production code from a plan or spec. Red-green-refactor with vertical tracer bullets.
@@ -34,7 +34,7 @@ If tests are missing or weak, flag what should be added. Do not add them here
34
34
 
35
35
  Spawn two sub-agents simultaneously:
36
36
 
37
- **Standards sub-agent:** Read the repo's documented standards (CONTEXT.md, AGENTS.md, eslint config, ADRs, STYLE.md, CONVENTIONS.md). Then read the diff. Report every place the diff violates a documented standard. Cite the standard source. Distinguish hard violations from judgement calls.
37
+ **Standards sub-agent:** Read the repo's documented standards (CONTEXT.md, AGENTS.md, eslint config, ADRs). Then read the diff. Report every place the diff violates a documented standard. Cite the standard source. Distinguish hard violations from judgement calls.
38
38
 
39
39
  **Spec sub-agent:** Read the spec source (plan.md, issue, PRD, or user's description). Then read the diff. Report: (a) requirements that are missing or partial, (b) scope creep (behavior not asked for), (c) requirements that look implemented but wrong. Quote the spec.
40
40
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oh-init
3
- description: "Initialize project for agent-assisted development: scaffold CONTEXT.md, AGENTS.md, docs/adr/, configure issue tracker and triage labels."
3
+ description: "Initialize project for OpenHermes takeover: scaffold .opencode/ runtime skeleton, wire AGENTS.md, configure domain docs, issue tracker, and triage labels."
4
4
  tier: 2
5
5
  triggers:
6
6
  - "init project"
@@ -8,15 +8,144 @@ triggers:
8
8
  - "initialize"
9
9
  - "onboard"
10
10
  - "scaffold"
11
+ - "takeover"
11
12
  ---
12
13
 
13
14
  # oh-init
14
15
 
15
- Per-repo setup for agent-assisted development. Run once per repo. Walks through configuration decisions one at a time.
16
+ Per-repo setup for OpenHermes-assisted development. Run once per repo. Wires the `.opencode/` runtime skeleton, connects `AGENTS.md` to the orchestrator, then walks through domain/issue configuration decisions one at a time.
17
+
18
+ Complements OpenCode's built-in `/init` command (which creates `AGENTS.md` with project build/test/architecture notes). Run oh-init after or instead — they serve different layers.
16
19
 
17
20
  ## Process
18
21
 
19
- ### 1. Issue Tracker
22
+ ### Phase 0: Check Existing State
23
+ Before writing anything, detect what already exists:
24
+
25
+ - ☐ `.opencode/` directory present?
26
+ - ☐ `.opencode/plan.md` exists?
27
+ - ☐ `.opencode/todo.md` exists?
28
+ - ☐ `.opencode/work-log.md` exists?
29
+ - ☐ `.opencode/instincts.jsonl` exists?
30
+ - ☐ `AGENTS.md` exists? (If yes, was it created by OpenCode `/init` or manually?)
31
+ - ☐ `opencode.json` / `opencode.jsonc` present?
32
+
33
+ Report findings. If everything exists, offer to skip or verify and exit.
34
+
35
+ ### Phase 1: .opencode/ Runtime Skeleton
36
+ Create `.opencode/` directory if missing. Scaffold shared state files:
37
+
38
+ **`.opencode/plan.md`** — working plan for the current session. Uses the same format as the global permanent plan directory (`%USERPROFILE%/.config/opencode/task/<project>-plan-<nnn>.md`). When a plan is completed, copy to the global directory with sequenced naming for permanent archive.
39
+
40
+ ```markdown
41
+ # PLAN: <project-name>
42
+
43
+ Plan ID: <project-name>-plan-<nnn>
44
+ Project: <project-name>
45
+ Status: active
46
+ Created: <local-date-time>
47
+ Updated: <local-date-time>
48
+ Project Path: <absolute-project-path>
49
+ Plan Path: .opencode/plan.md
50
+ Objective: <short objective>
51
+
52
+ ## Current State
53
+
54
+ ## Assumptions
55
+
56
+ ## Tasks
57
+
58
+ - [ ] Task 1
59
+ - [ ] Subtask 1.1
60
+
61
+ ## Active Task
62
+
63
+ ## Subagents
64
+
65
+ | Agent | Purpose | Status | Findings |
66
+ |---|---|---|---|
67
+
68
+ ## Completed
69
+
70
+ ## Blockers
71
+
72
+ - None
73
+
74
+ ## Validation
75
+
76
+ - [ ] Static checks
77
+ - [ ] Formatting checks
78
+ - [ ] Type checks
79
+ - [ ] Unit tests
80
+ - [ ] Integration checks
81
+ - [ ] Manual verification
82
+
83
+ ## Decisions
84
+
85
+ ## Notes
86
+ ```
87
+
88
+ **`.opencode/todo.md`** — task tracking for multi-step work (start empty).
89
+
90
+ **`.opencode/work-log.md`** — progress tracking across subagent delegations:
91
+ ```markdown
92
+ # Work Log
93
+
94
+ ## <date> — <description>
95
+ - Started: <time>
96
+ - Completed: <task>
97
+ - Next: <next task>
98
+ ```
99
+
100
+ **`.opencode/instincts.jsonl`** — behavioral pattern store for oh-learn (start as empty file). Will grow organically as the agent extracts patterns from sessions.
101
+
102
+ ### Phase 2: AGENTS.md Wiring
103
+
104
+ Check if AGENTS.md exists:
105
+
106
+ **If AGENTS.md does not exist:**
107
+ Create it with OpenHermes orchestrator header + prompts for project info:
108
+
109
+ ```markdown
110
+ # <project-name>
111
+
112
+ OpenHermes is the primary orchestrator. All routing, planning, and delegation flows through oh-* skills.
113
+
114
+ ## Project Context
115
+
116
+ - **Language**: <fill in>
117
+ - **Package manager**: <fill in>
118
+ - **Build command**: <fill in>
119
+ - **Test command**: <fill in>
120
+ - **Lint/type check**: <fill in>
121
+
122
+ ## Key Directives
123
+
124
+ - Plan first. Write to `.opencode/plan.md` before multi-file changes.
125
+ - Verify before claiming success. Read files, run commands, confirm output.
126
+ - Delegate substantive work to subagents — main context orchestrates.
127
+ - Use oh-* skills on demand. Load via OpenCode's skill tool when relevant.
128
+ - Shared state lives in `.opencode/` (plan.md, todo.md, work-log.md, instincts.jsonl).
129
+ ```
130
+
131
+ Then ask the user to fill in the Project Context fields. Offer to auto-detect from package manifests.
132
+
133
+ **If AGENTS.md exists** (e.g., created by OpenCode `/init`):
134
+ Append an `## OpenHermes Orchestrator` section to the end:
135
+
136
+ ```markdown
137
+ ## OpenHermes Orchestrator
138
+
139
+ OpenHermes is the primary orchestrator for this session.
140
+
141
+ - **Orchestrator**: OpenHermes — hub-and-spoke routing through oh-* skills
142
+ - **Plan**: `.opencode/plan.md` — always check before starting work
143
+ - **Shared state**: `.opencode/todo.md`, `.opencode/work-log.md`, `.opencode/instincts.jsonl`
144
+ - **Verify before claim**: read files, run commands, confirm output
145
+ - **Delegate**: subagents for implementation, main context orchestrates
146
+ ```
147
+
148
+ ### Phase 3: Issue Tracker
20
149
  Detect the git hosting platform:
21
150
  - **GitHub** — `gh` CLI
22
151
  - **GitLab** — `glab` CLI
@@ -25,7 +154,7 @@ Detect the git hosting platform:
25
154
 
26
155
  Confirm with the user. Write the result to `docs/agents/issue-tracker.md`.
27
156
 
28
- ### 2. Triage Labels
157
+ ### Phase 4: Triage Labels
29
158
  The `triage` skill uses these label strings to move issues through a state machine:
30
159
  - `needs-triage` — maintainer needs to evaluate
31
160
  - `needs-info` — waiting on reporter
@@ -35,7 +164,7 @@ The `triage` skill uses these label strings to move issues through a state machi
35
164
 
36
165
  If the repo already has different label names, map them. Write to `docs/agents/triage-labels.md`.
37
166
 
38
- ### 3. Domain Docs
167
+ ### Phase 5: Domain Docs
39
168
  Configure how the project organizes domain language:
40
169
  - **Single-context** — one `CONTEXT.md` + `docs/adr/` at repo root
41
170
  - **Multi-context** — `CONTEXT-MAP.md` pointing to per-context files
@@ -44,7 +173,7 @@ Scaffold `CONTEXT.md` with project name, domain description, and placeholder glo
44
173
 
45
174
  Write to `docs/agents/domain.md`.
46
175
 
47
- ### 4. Agent Skills Block
176
+ ### Phase 6: Agent Skills Block
48
177
  Add a `## Agent skills` section to `AGENTS.md` (or `CLAUDE.md` if it exists):
49
178
 
50
179
  ```markdown
@@ -60,14 +189,18 @@ Add a `## Agent skills` section to `AGENTS.md` (or `CLAUDE.md` if it exists):
60
189
  <summary>. See docs/agents/domain.md.
61
190
  ```
62
191
 
63
- ### 5. Decision Record
64
- Record: "oh-init completed for project \<name\> on \<date\>."
192
+ ### Phase 7: Decision Record
193
+ Record: "oh-init completed for project <name> on <date>."
65
194
 
66
195
  ## Anti-patterns
67
196
  - Running init without understanding the project domain
68
197
  - Scaffolding CONTEXT.md without populating any terms
69
198
  - Creating ADR directory but never writing ADRs
70
199
  - Creating both AGENTS.md and CLAUDE.md — edit the one that exists
200
+ - Overwriting an existing AGENTS.md created by OpenCode `/init` (append instead)
201
+ - Scaffolding `.opencode/` files that already exist (check first, skip duplicates)
202
+ - Empty instinct file never getting populated (run oh-learn extract periodically)
203
+ - Never archiving completed plans to the global task directory (completed plans rot in `.opencode/` instead of becoming permanent records)
71
204
 
72
205
  ## Routing
73
206
 
@@ -8,14 +8,52 @@ description: "Systematic bug diagnosis with root cause investigation"
8
8
  ## When to Use
9
9
  When a bug is reported, a test fails, or unexpected behavior occurs. Use this before attempting any fix.
10
10
 
11
- ## Workflow
12
- 1. **Reproduce** — get a reliable reproduction case (script, test, or steps)
13
- 2. **Minimise** strip away unrelated code until the minimal reproduction remains
14
- 3. **Hypothesise** — list possible root causes, rank by likelihood
15
- 4. **Instrument**add logging, assertions, or debug output to test hypothesis
16
- 5. **Fix** — implement the smallest correct change addressing root cause
17
- 6. **Regression test** verify fix doesn't break existing behavior
18
- 7. **Document** — log the root cause and fix in the handoff, issue, or docs that are actually in scope
11
+ ## Phase 0 — Build a feedback loop
12
+
13
+ **This is the actual skill. Everything else is mechanical.**
14
+
15
+ If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause bisection, hypothesis-testing, and instrumentation are just consuming that signal. If you don't have one, no amount of staring at code will save you.
16
+
17
+ Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
18
+
19
+ ### Ways to construct a feedback loop (try in this order)
20
+
21
+ 1. **Failing test** at whatever seam reaches the bug.
22
+ 2. **Curl / HTTP script** against a running dev server.
23
+ 3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
24
+ 4. **Headless browser script** — drive the UI, assert on DOM/console/network.
25
+ 5. **Replay a captured trace** — save a real payload/event log, replay it in isolation.
26
+ 6. **Throwaway harness** — minimal subset of the system exercising the bug code path with a single call.
27
+ 7. **Property / fuzz loop** — run 1000 random inputs, look for the failure mode.
28
+ 8. **Bisection harness** — automate "boot at state X, check, repeat" so you can `git bisect run` it.
29
+ 9. **Differential loop** — run same input through old-version vs new-version, diff outputs.
30
+ 10. **HITL script** — last resort. Drive a human with a structured loop.
31
+
32
+ ### Iterate on the loop itself
33
+
34
+ - Can I make it faster? (Cache setup, skip unrelated init, narrow the scope.)
35
+ - Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
36
+ - Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem.)
37
+
38
+ A 30-second flaky loop is barely better than no loop. A 2-second deterministic loop is a debugging superpower.
39
+
40
+ ### Non-deterministic bugs
41
+
42
+ The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows. A 50%-flake bug is debuggable; 1% is not.
43
+
44
+ ### When you genuinely cannot build a loop
45
+
46
+ Stop and say so explicitly. List what you tried. Do **not** proceed to hypothesise without a loop.
47
+
48
+ ## Workflow (consumes the loop)
49
+
50
+ 1. **Reproduce** — run the loop, confirm the bug appears. The loop must match the user's described failure, not a different nearby failure.
51
+ 2. **Minimise** — strip away unrelated code until the minimal reproduction remains.
52
+ 3. **Hypothesise** — generate 3–5 ranked falsifiable hypotheses before testing any. Each must state a prediction: "If X is the cause, then changing Y will make the bug disappear".
53
+ 4. **Instrument** — one probe per hypothesis. Change one variable at a time. Tag every debug log with a unique prefix (e.g. `[DEBUG-a4f2]`) for easy cleanup.
54
+ 5. **Fix** — write the regression test at a correct seam first. Watch it fail. Apply the smallest correct change. Watch it pass. Re-run the Phase 0 loop against the original scenario.
55
+ 6. **Regression test** — verify fix doesn't break existing behavior. If no correct seam exists for a regression test, that itself is a finding — flag the architecture gap.
56
+ 7. **Document** — log the root cause and fix in the handoff, issue, or relevant docs. State which hypothesis was correct so the next debugger learns.
19
57
 
20
58
  ## Iron Law
21
59
  No fixes without root cause. Surface-level fixes compound into technical debt.
@@ -25,6 +63,7 @@ No fixes without root cause. Surface-level fixes compound into technical debt.
25
63
  - Changing code without reproducing the bug first
26
64
  - "Shotgun" debugging — changing multiple things hoping one sticks
27
65
  - Not documenting root cause for future reference
66
+ - Proceeding to hypothesise without a feedback loop
28
67
 
29
68
  ## Routing
30
69
 
@@ -1,28 +1,92 @@
1
1
  ---
2
2
  name: oh-learn
3
- description: "Review, search, prune, and export session learnings"
3
+ description: "Extract, evolve, and promote session learnings as instincts. Review, search, prune, export."
4
4
  ---
5
5
 
6
6
  # oh-learn
7
7
 
8
+ Learning engine for the harness. Distills patterns from sessions into **instincts** (trigger-action pairs with confidence), clusters them into skill candidates, and graduates high-signal patterns from project to global scope.
9
+
10
+ ## Instinct Data Model
11
+
12
+ Every learning stored as one JSONL line in `.opencode/instincts.jsonl`:
13
+
14
+ ```json
15
+ { "trigger": "situation pattern", "action": "recommended response", "confidence": 0.5, "applications": 1, "successes": 1, "category": "coding", "source": "oh-learn:extract", "ts": "2026-05-15T12:00:00Z" }
16
+ ```
17
+
18
+ **Rules:**
19
+ - **Trigger** — specific, matchable situation. *Not* general advice.
20
+ - **Action** — executable response. *Not* a belief.
21
+ - **Confidence** — starts at 0.5, increments +0.05 per successful application, decays -0.02 per day without use.
22
+ - **Category** — one of: `coding`, `testing`, `security`, `git`, `planning`, `orchestration`, `debugging`, `ux`.
23
+
8
24
  ## When to Use
9
- To review what the agent has learned across sessions, search for specific patterns, prune stale knowledge, or export learnings for documentation.
10
25
 
11
- ## Workflow
12
- 1. **Review** — show recent learnings with context
13
- 2. **Search** — find learnings matching specific topics or patterns
14
- 3. **Prune** — remove stale, redundant, or superseded learnings
15
- 4. **Export** — format learnings for documentation or sharing
26
+ After completing a significant piece of work, at session handoff, or when you notice the same pattern repeat 2+ times in one session. Also on explicit user request.
27
+
28
+ ## Workflows
29
+
30
+ ### Extract
31
+ Mine the current session for reusable patterns.
32
+
33
+ 1. Scan recent conversation + code changes for repeated decision patterns
34
+ 2. For each distinct pattern write an instinct: trigger, action, confidence=0.5, category
35
+ 3. Read existing `.opencode/instincts.jsonl`, check for near-duplicate triggers
36
+ 4. If duplicate found: merge — `confidence = max(existing, 0.8 × new)`, increment applications
37
+ 5. If new: append line to file
38
+
39
+ **Good instinct:** trigger=`"tsc --noEmit shows 10+ errors after batch edit"`, action=`"Fix errors one at a time, re-running tsc after each, rather than batch-fixing"`, category=`"debugging"`
40
+
41
+ **Bad instinct:** `"Write clean code"` — too vague to trigger on.
42
+
43
+ ### Evolve
44
+ Cluster related instincts into skill/command/agent candidates.
45
+
46
+ 1. Read all instincts from `.opencode/instincts.jsonl`
47
+ 2. Group by `category`, then by trigger topic similarity
48
+ 3. **If cluster ≥ 5 instincts AND avg confidence ≥ 0.7** → generate `oh-skill-craft` spec for a new skill
49
+ 4. **If cluster 3-4 instincts with confidence ≥ 0.8** → suggest update to existing skill
50
+ 5. Output candidate summary with trigger list and extracted core pattern
51
+
52
+ ### Promote
53
+ Graduate high-confidence instincts from project to global scope.
54
+
55
+ 1. Scan `.opencode/instincts.jsonl` for instincts with `confidence >= 0.85 AND applications >= 10`
56
+ 2. Filter out project-specific patterns (reference paths, local APIs, domain terms)
57
+ 3. Append filtered candidates to `%USERPROFILE%\.config\opencode\instincts.jsonl` (global)
58
+ 4. Tag promoted instincts with `"promoted": true` in project file
59
+ 5. Report: "Promoted N instincts to global scope"
60
+
61
+ ### Review
62
+ Show instinct summary: total count, confidence distribution, category breakdown, recently promoted.
63
+
64
+ ### Search
65
+ Find instincts by topic, trigger fragment, category, or confidence range.
66
+
67
+ ### Prune
68
+ Remove instincts stale for 30+ days with confidence < 0.3, or superseded by a higher-confidence instinct covering the same trigger.
69
+
70
+ ### Export
71
+ Serialize instincts to portable JSON for sharing across projects or teams:
72
+
73
+ ```json
74
+ { "version": 1, "exported": "2026-05-15T12:00:00Z", "instincts": [...] }
75
+ ```
16
76
 
17
77
  ## Anti-patterns
78
+
18
79
  - Hoarding every observation (most things aren't learnings)
19
80
  - Never pruning (stale knowledge is worse than no knowledge)
20
81
  - Storing what, not why (context-less facts are forgettable)
82
+ - Over-promoting: not every pattern is globally useful
83
+ - Extracting without applying: instincts that never trigger are noise
84
+ - Ignoring confidence: treating all instincts as equally reliable
21
85
 
22
86
  ## Routing
23
87
 
24
88
  | Outcome | Route |
25
89
  |---------|-------|
26
- | pass | → [done — read-only report] |
90
+ | pass | → [done — report summary] |
27
91
  | fail | → [surface gaps to user] |
28
92
  | blocker | → surface to user |
@@ -15,10 +15,21 @@ triggers:
15
15
 
16
16
  # oh-manifest
17
17
 
18
- Full build orchestration loop. Runs planner → builder → verify → repeat until done or a blocker is surfaced. Uses gstack decision principles to auto-resolve intermediate questions. Only interrupts the user for genuine blockers.
18
+ Full build orchestration loop. Runs pre-flight checks → planner → builder → verify → repeat until done or a blocker is surfaced. Uses decision principles to auto-resolve intermediate questions. Only interrupts the user for genuine blockers.
19
19
 
20
20
  ## Pipeline
21
21
 
22
+ ### Phase 0: Pre-Flight
23
+
24
+ Before any work begins, ALL of these MUST pass:
25
+
26
+ - ☐ **Quality baseline** — existing tests pass (if any). Capture output for before/after comparison.
27
+ - ☐ **Rollback path** — clean `git stash` or a committed state you can return to.
28
+ - ☐ **Branch isolation** — confirm you are on a working branch, not main/master.
29
+ - ☐ **Scope documented** — plan or task description exists and is unambiguous.
30
+
31
+ If any check fails → **STOP**. Report which check failed and why. Do not proceed to Phase 1 until the blocker is resolved.
32
+
22
33
  ### Step 1: Plan
23
34
  - If `.opencode/plan.md` exists, load and verify it is current
24
35
  - If not, run `oh-planner` (Mode A, B, or C depending on context)
@@ -43,6 +54,32 @@ Full build orchestration loop. Runs planner → builder → verify → repeat un
43
54
  - Phase failed and cannot be fixed → BLOCKER (surface to user with context)
44
55
  - Phase passed but new work discovered → add to plan, continue loop
45
56
 
57
+ ## Loop Patterns
58
+
59
+ Select a pattern based on the nature of the work:
60
+
61
+ | Pattern | Use When | Behavior |
62
+ |---------|----------|----------|
63
+ | **sequential** | Normal feature work | One phase at a time, verify each before next |
64
+ | **continuous-pr** | Multi-step refactors | Each phase is its own PR — commit, push, PR per phase |
65
+ | **infinite** | Watch mode, CI repair | Continue until external stop signal or budget exhausted |
66
+ | **rfc-dag** | Complex dependency chains | Resolve phase ordering by DAG; parallelize independent branches |
67
+
68
+ Default is **sequential**. Switch patterns only when the work structure demands it.
69
+
70
+ ## Escalation Triggers
71
+
72
+ These conditions cause the loop to **pause** and surface to the user:
73
+
74
+ | Trigger | Condition | Action |
75
+ |---------|-----------|--------|
76
+ | **Stall** | 2 consecutive checkpoints with zero measurable progress | Pause. Report what was attempted, what blocked. |
77
+ | **Retry storm** | Same error message 3+ times in the loop | Stop retrying. Surface error with attempted fixes. |
78
+ | **Cost drift** | Cumulative changes exceed scope documented in pre-flight | Pause. Show diff between planned and actual scope. |
79
+ | **Quality regression** | Verify phase scores lower than pre-flight baseline | Pause. Report degraded metrics. Do not push through. |
80
+
81
+ These are not optional suggestions. When a trigger fires, the loop **must** pause and report.
82
+
46
83
  ## Decision Principles
47
84
 
48
85
  Auto-resolve these without asking the user:
@@ -69,11 +106,13 @@ When a blocker is encountered:
69
106
  4. **Wait for user decision** before continuing
70
107
 
71
108
  ## Anti-patterns
109
+ - Skipping pre-flight (every loop needs a baseline and a rollback plan)
72
110
  - Auto-deciding premises (fundamental assumptions need user input)
73
111
  - Pushing through blockers (surface immediately, don't try 5 workarounds silently)
74
112
  - Skipping verification (verify every phase, not just the final result)
75
113
  - Parallelizing dependent phases (respect the dependency order in plan.md)
76
114
  - Forgetting to update plan.md with completion status
115
+ - Ignoring escalation triggers (stall means pause, not try harder)
77
116
 
78
117
  ## Routing
79
118
 
@@ -80,9 +80,9 @@ Never auto-decide: premises (need human judgment) or cases where both the plan a
80
80
 
81
81
  ## Plan Artifact
82
82
 
83
- Output goes in `.opencode/plan.md` with this structure (matching the global AGENTS.md schema).
83
+ Output goes in `.opencode/plan.md` (per-project, overwritten each session) with this structure (matching the global AGENTS.md schema).
84
84
 
85
- **Then save a copy** to `%USERPROFILE%/.config/opencode/task/<project-name>-plan-<nnn>.md` per AGENTS.md persistent plan rules.
85
+ **Then save a copy** to `%USERPROFILE%/.config/opencode/task/<project-name>-plan-<nnn>.md` (global, incrementing, persistent) per AGENTS.md persistent plan rules.
86
86
 
87
87
  ```markdown
88
88
  # PLAN: <project-name>
@@ -45,7 +45,7 @@ Collect all files documenting how code should be written:
45
45
  - AGENTS.md, CLAUDE.md, CONTRIBUTING.md
46
46
  - CONTEXT.md, ADRs
47
47
  - eslint/biome/prettier config (note tool-enforced ones — don't re-check)
48
- - Any STYLE.md, STANDARDS.md, STYLEGUIDE.md
48
+
49
49
 
50
50
  ### 4. Spawn Both Sub-Agents (parallel)
51
51
 
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { BootstrapPlugin } from "./bootstrap.ts"
2
+
3
+ export default BootstrapPlugin
@@ -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<[string, string, string]> = [
13
12
  ["codex", "CONSTITUTION.md"],
14
13
  ["instructions", "RUNTIME.md"],
15
14
  ["skills", "oh-plan", "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
  }
@@ -2,34 +2,41 @@ import path from "node:path"
2
2
  import os from "node:os"
3
3
  import fs from "node:fs"
4
4
 
5
- const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }
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 }
6
13
  const CURRENT_LEVEL = LEVELS[process.env.OPENCODE_LOG_LEVEL?.trim().toLowerCase()] ?? (process.env.OPENHERMES_LOG_LEVEL?.trim().toLowerCase() === "debug" ? LEVELS.debug : LEVELS.warn)
7
14
 
8
15
  const LOG_DIR = path.join(os.homedir(), ".local", "share", "opencode", "log")
9
16
  const LOG_FILE = path.join(LOG_DIR, "openhermes.log")
10
17
 
11
- function ts() {
18
+ function ts(): string {
12
19
  const d = new Date()
13
20
  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
21
  }
15
22
 
16
- function formatArgs(args) {
23
+ function formatArgs(args: unknown[]): string {
17
24
  return args.map(a => {
18
25
  if (a === null) return "null"
19
26
  if (a === undefined) return "undefined"
20
27
  if (typeof a === "object") {
21
- try { return a?.message || JSON.stringify(a) } catch { return String(a) }
28
+ try { return (a as Error)?.message || JSON.stringify(a) } catch { return String(a) }
22
29
  }
23
30
  return String(a)
24
31
  }).join(" ")
25
32
  }
26
33
 
27
- function shouldLog(levelName) {
34
+ function shouldLog(levelName: string): boolean {
28
35
  return LEVELS[levelName] >= CURRENT_LEVEL
29
36
  }
30
37
 
31
- let _fd = null
32
- function getFd() {
38
+ let _fd: number | null = null
39
+ function getFd(): number {
33
40
  if (_fd) return _fd
34
41
  try {
35
42
  fs.mkdirSync(LOG_DIR, { recursive: true })
@@ -40,10 +47,10 @@ function getFd() {
40
47
  return _fd
41
48
  }
42
49
 
43
- export function createLogger(name) {
50
+ export function createLogger(name: string): Logger {
44
51
  const prefix = `[openhermes:${name}]`
45
52
 
46
- function emit(levelName, ...args) {
53
+ function emit(levelName: string, ...args: unknown[]): void {
47
54
  if (!shouldLog(levelName)) return
48
55
  const fd = getFd()
49
56
  if (fd < 0) return
@@ -52,11 +59,11 @@ export function createLogger(name) {
52
59
  }
53
60
 
54
61
  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),
62
+ debug: (...args: unknown[]) => emit("debug", ...args),
63
+ info: (...args: unknown[]) => emit("info", ...args),
64
+ warn: (...args: unknown[]) => emit("warn", ...args),
65
+ error: (...args: unknown[]) => emit("error", ...args),
59
66
  }
60
67
  }
61
68
 
62
- export const rootLogger = createLogger("root")
69
+ 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",
3
+ "version": "4.1.0",
4
4
  "description": "OpenCode-native skills, commands, and rules orchestration for OpenHermes.",
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",
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
+ }
@@ -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
@@ -1,64 +0,0 @@
1
- import { describe, it, before } from "node:test"
2
- import assert from "node:assert/strict"
3
- import path from "node:path"
4
- import { fileURLToPath } from "node:url"
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
-
8
- describe("BootstrapPlugin behavior", () => {
9
- let mod
10
-
11
- before(async () => {
12
- mod = await import("../bootstrap.mjs")
13
- })
14
-
15
- it("registers package-local skills, commands, agents, and instructions", async () => {
16
- const plugin = await mod.BootstrapPlugin({ directory: __dirname })
17
- const config = { skills: { paths: [] }, command: {}, agent: {}, instructions: [] }
18
-
19
- await plugin.config(config)
20
-
21
- assert.ok(config.skills.paths.some(p => p.endsWith(path.join("harness", "skills"))))
22
- assert.ok(config.command["oh-doctor"])
23
- assert.ok(config.agent.OpenHermes)
24
- assert.equal(config.default_agent, "OpenHermes")
25
- assert.ok(config.instructions.some(p => p.endsWith(path.join("harness", "codex", "CONSTITUTION.md"))))
26
- assert.ok(config.instructions.some(p => p.endsWith(path.join("harness", "instructions", "RUNTIME.md"))))
27
- })
28
-
29
- it("loads markdown manifests into command and agent config", async () => {
30
- const plugin = await mod.BootstrapPlugin({ directory: __dirname })
31
- const config = { skills: { paths: [] }, command: {}, agent: {}, instructions: [] }
32
-
33
- await plugin.config(config)
34
-
35
- assert.match(config.command["oh-doctor"].template, /Inspect the current OpenHermes\/OpenCode setup/)
36
- assert.equal(config.command["oh-doctor"].agent, "OpenHermes")
37
- assert.match(config.agent.OpenHermes.prompt, /You are OpenHermes, the primary orchestrator/)
38
- assert.equal(config.agent.OpenHermes.mode, "primary")
39
- })
40
-
41
- it("injects bootstrap text only once", async () => {
42
- const plugin = await mod.BootstrapPlugin({ directory: __dirname })
43
- const output = {
44
- messages: [
45
- {
46
- info: { role: "user" },
47
- parts: [
48
- { type: "text", text: "actual user request" },
49
- ],
50
- },
51
- ],
52
- }
53
-
54
- await plugin["experimental.chat.messages.transform"]({}, output)
55
- await plugin["experimental.chat.messages.transform"]({}, output)
56
-
57
- assert.match(output.messages[0].parts[0].text, /OPENHERMES_BOOTSTRAP/)
58
- assert.match(output.messages[0].parts[1].text, /actual user request/)
59
- assert.equal(
60
- output.messages[0].parts.filter(part => typeof part.text === "string" && part.text.includes("OPENHERMES_BOOTSTRAP")).length,
61
- 1,
62
- )
63
- })
64
- })
@@ -1,62 +0,0 @@
1
- import { describe, it, before } from "node:test"
2
- import assert from "node:assert/strict"
3
- import fs from "node:fs"
4
- import os from "node:os"
5
- import path from "node:path"
6
-
7
- describe("plugin exports", () => {
8
- it("index.mjs default exports plugin", async () => {
9
- const pkg = await import("../index.mjs")
10
- assert.ok(typeof pkg.default === "function")
11
- })
12
-
13
- it("bootstrap.mjs exports BootstrapPlugin", async () => {
14
- const mod = await import("../bootstrap.mjs")
15
- assert.ok(typeof mod.BootstrapPlugin === "function")
16
- })
17
- })
18
-
19
- describe("bootstrap helpers", () => {
20
- let mod
21
-
22
- before(async () => {
23
- mod = await import("../bootstrap.mjs")
24
- })
25
-
26
- it("re-exports harness resolver helpers", async () => {
27
- const { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir } = mod
28
- assert.ok(typeof resolveHarnessRoot === "function")
29
- assert.ok(typeof setHarnessRootForTest === "function")
30
- assert.ok(typeof getHarnessDir === "function")
31
- })
32
-
33
- it("resolveHarnessRoot picks complete harness root", async () => {
34
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openhermes-harness-"))
35
- const badRoot = path.join(tmpRoot, "bad")
36
- const goodRoot = path.join(tmpRoot, "good")
37
-
38
- fs.mkdirSync(path.join(badRoot, "codex"), { recursive: true })
39
- fs.writeFileSync(path.join(badRoot, "codex", "CONSTITUTION.md"), "# incomplete\n")
40
-
41
- const requiredFiles = [
42
- ["codex", "CONSTITUTION.md"],
43
- ["instructions", "RUNTIME.md"],
44
- ["skills", "oh-plan", "SKILL.md"],
45
- ]
46
-
47
- for (const parts of requiredFiles) {
48
- const filePath = path.join(goodRoot, ...parts)
49
- fs.mkdirSync(path.dirname(filePath), { recursive: true })
50
- fs.writeFileSync(filePath, "ok\n")
51
- }
52
-
53
- const resolved = mod.resolveHarnessRoot({ candidateRoots: [badRoot, goodRoot] })
54
- assert.equal(resolved, goodRoot)
55
- })
56
-
57
- it("setHarnessRootForTest overrides harness resolution", async () => {
58
- mod.setHarnessRootForTest("/custom/harness")
59
- assert.equal(mod.getHarnessDir(), "/custom/harness")
60
- mod.setHarnessRootForTest(undefined)
61
- })
62
- })