openhermes 4.1.0 → 4.9.2

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 (99) hide show
  1. package/CONTEXT.md +9 -0
  2. package/ETHOS.md +6 -3
  3. package/LICENSE +21 -21
  4. package/README.md +120 -79
  5. package/bootstrap.ts +284 -41
  6. package/harness/agents/oh-browser.md +97 -0
  7. package/harness/agents/oh-builder.md +78 -0
  8. package/harness/agents/oh-facade.md +75 -0
  9. package/harness/agents/oh-fusion.md +45 -0
  10. package/harness/agents/oh-gauntlet.md +71 -0
  11. package/harness/agents/oh-grill.md +71 -0
  12. package/harness/agents/oh-investigate.md +60 -0
  13. package/harness/agents/oh-manifest.md +95 -0
  14. package/harness/agents/oh-plan-review.md +40 -0
  15. package/harness/agents/oh-planner.md +50 -0
  16. package/harness/agents/oh-refactor.md +37 -0
  17. package/harness/agents/oh-retro.md +46 -0
  18. package/harness/agents/oh-review.md +85 -0
  19. package/harness/agents/oh-security.md +83 -0
  20. package/harness/agents/oh-ship.md +76 -0
  21. package/harness/agents/oh-skill-craft.md +38 -0
  22. package/harness/agents/openhermes.md +106 -62
  23. package/harness/codex/AUTOPILOT.md +178 -0
  24. package/harness/codex/CHARTER.md +81 -0
  25. package/harness/commands/oh-doctor.md +193 -14
  26. package/harness/commands/oh-log.md +18 -0
  27. package/harness/instructions/SHELL.md +76 -0
  28. package/harness/skills/oh-ascii/DEEP.md +292 -0
  29. package/harness/skills/oh-ascii/SKILL.md +31 -0
  30. package/harness/skills/oh-ascii/scripts/check_ascii_alignment.py +596 -0
  31. package/harness/skills/oh-browser/DEEP.md +54 -0
  32. package/harness/skills/oh-browser/SKILL.md +30 -0
  33. package/harness/skills/oh-builder/DEEP.md +63 -0
  34. package/harness/skills/oh-builder/SKILL.md +16 -89
  35. package/harness/skills/oh-expert/DEEP.md +85 -0
  36. package/harness/skills/oh-expert/SKILL.md +19 -106
  37. package/harness/skills/oh-facade/DEEP.md +182 -0
  38. package/harness/skills/oh-facade/SKILL.md +34 -0
  39. package/harness/skills/oh-freeze/DEEP.md +18 -0
  40. package/harness/skills/oh-freeze/SKILL.md +15 -15
  41. package/harness/skills/oh-full-output/DEEP.md +25 -0
  42. package/harness/skills/oh-full-output/SKILL.md +28 -0
  43. package/harness/skills/oh-fusion/DEEP.md +120 -0
  44. package/harness/skills/oh-fusion/SKILL.md +36 -0
  45. package/harness/skills/oh-gauntlet/DEEP.md +77 -0
  46. package/harness/skills/oh-gauntlet/SKILL.md +17 -105
  47. package/harness/skills/oh-grill/DEEP.md +51 -0
  48. package/harness/skills/oh-grill/SKILL.md +16 -63
  49. package/harness/skills/oh-guard/DEEP.md +19 -0
  50. package/harness/skills/oh-guard/SKILL.md +15 -20
  51. package/harness/skills/oh-handoff/DEEP.md +48 -0
  52. package/harness/skills/oh-handoff/SKILL.md +18 -19
  53. package/harness/skills/oh-health/DEEP.md +74 -0
  54. package/harness/skills/oh-health/SKILL.md +17 -76
  55. package/harness/skills/oh-init/DEEP.md +85 -0
  56. package/harness/skills/oh-init/SKILL.md +17 -197
  57. package/harness/skills/oh-investigate/DEEP.md +171 -0
  58. package/harness/skills/oh-investigate/SKILL.md +18 -61
  59. package/harness/skills/oh-issue/DEEP.md +21 -0
  60. package/harness/skills/oh-issue/SKILL.md +16 -23
  61. package/harness/skills/oh-learn/DEEP.md +44 -0
  62. package/harness/skills/oh-learn/SKILL.md +17 -79
  63. package/harness/skills/oh-manifest/DEEP.md +92 -0
  64. package/harness/skills/oh-manifest/SKILL.md +15 -107
  65. package/harness/skills/oh-plan-review/DEEP.md +90 -0
  66. package/harness/skills/oh-plan-review/SKILL.md +19 -114
  67. package/harness/skills/oh-planner/DEEP.md +172 -0
  68. package/harness/skills/oh-planner/SKILL.md +16 -143
  69. package/harness/skills/oh-prd/DEEP.md +45 -0
  70. package/harness/skills/oh-prd/SKILL.md +15 -22
  71. package/harness/skills/oh-refactor/DEEP.md +122 -0
  72. package/harness/skills/oh-refactor/SKILL.md +33 -0
  73. package/harness/skills/oh-retro/DEEP.md +26 -0
  74. package/harness/skills/oh-retro/SKILL.md +17 -20
  75. package/harness/skills/oh-review/DEEP.md +87 -0
  76. package/harness/skills/oh-review/SKILL.md +17 -96
  77. package/harness/skills/oh-security/DEEP.md +83 -0
  78. package/harness/skills/oh-security/SKILL.md +18 -96
  79. package/harness/skills/oh-ship/DEEP.md +141 -0
  80. package/harness/skills/oh-ship/SKILL.md +18 -26
  81. package/harness/skills/oh-skill-craft/DEEP.md +369 -0
  82. package/harness/skills/oh-skill-craft/SKILL.md +20 -93
  83. package/harness/skills/oh-skills-link/DEEP.md +16 -0
  84. package/harness/skills/oh-skills-link/SKILL.md +15 -16
  85. package/harness/skills/oh-skills-list/DEEP.md +20 -0
  86. package/harness/skills/oh-skills-list/SKILL.md +14 -18
  87. package/harness/skills/oh-triage/DEEP.md +23 -0
  88. package/harness/skills/oh-triage/SKILL.md +15 -20
  89. package/harness/skills/oh-worktree/DEEP.md +169 -0
  90. package/harness/skills/oh-worktree/SKILL.md +32 -0
  91. package/lib/harness-resolver.ts +10 -12
  92. package/package.json +9 -4
  93. package/scripts/count-tokens.mjs +158 -0
  94. package/scripts/oh-doctor.ps1 +342 -0
  95. package/harness/codex/CONSTITUTION.md +0 -70
  96. package/harness/codex/ROUTING.md +0 -127
  97. package/harness/instructions/RUNTIME.md +0 -55
  98. package/harness/skills/oh-caveman/SKILL.md +0 -33
  99. package/lib/logger.ts +0 -69
package/bootstrap.ts CHANGED
@@ -1,16 +1,31 @@
1
1
  import path from "node:path"
2
2
  import fs from "node:fs"
3
- import { fileURLToPath } from "node:url"
3
+ import os from "node:os"
4
4
  import type { Plugin } from "@opencode-ai/plugin"
5
- import { createLogger } from "./lib/logger.ts"
6
5
  import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.ts"
7
6
 
8
- const log = createLogger("bootstrap")
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
- const BOOTSTRAP_MARKER = "OPENHERMES_BOOTSTRAP"
11
7
  const OPENHERMES_AGENT = "OpenHermes"
12
8
 
13
- export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir }
9
+ // User skill directories — auto-discovered on every session, survive npm updates
10
+ const USER_SKILL_DIRS: ReadonlyArray<string> = [
11
+ path.join(os.homedir(), ".agents", "skills"),
12
+ path.join(os.homedir(), ".config", "opencode", "skills"),
13
+ path.join(os.homedir(), ".claude", "skills"), // Claude Code backward compat
14
+ ]
15
+
16
+ // Canonical storage under OpenCode's data directory — survives npm updates
17
+ let _planStorageOverride: string | undefined
18
+ export function setPlanStorageDirForTest(dir: string | undefined): void { _planStorageOverride = dir }
19
+ function planStorageDir(): string {
20
+ return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "opencode", "openhermes", "plans")
21
+ }
22
+
23
+ function getProjectName(projectDir: string): string {
24
+ return path.basename(projectDir)
25
+ }
26
+
27
+
28
+ export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir, ensurePlanFile }
14
29
 
15
30
  function parseFrontmatter(raw: string | undefined): Record<string, string> {
16
31
  const frontmatter: Record<string, string> = {}
@@ -109,32 +124,153 @@ function uniqueStrings(existing: string[] = [], additions: string[] = []): strin
109
124
  return merged
110
125
  }
111
126
 
112
- function readText(filePath: string): string {
113
- return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : ""
127
+
128
+ function regexEscape(s: string): string {
129
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
130
+ }
131
+
132
+ function findLatestPlanFile(projectDir: string): string | null {
133
+ const projectName = getProjectName(projectDir)
134
+ const storage = planStorageDir()
135
+ if (!fs.existsSync(storage)) return null
136
+ const pattern = new RegExp(`^${regexEscape(projectName)}-plan-(\\d{3})\\.md$`)
137
+ let latest: string | null = null
138
+ let highest = -1
139
+ try {
140
+ for (const entry of fs.readdirSync(storage)) {
141
+ const m = entry.match(pattern)
142
+ if (m) {
143
+ const n = parseInt(m[1], 10)
144
+ if (n > highest) {
145
+ highest = n
146
+ latest = path.join(storage, entry)
147
+ }
148
+ }
149
+ }
150
+ } catch {
151
+ return null
152
+ }
153
+ return latest
154
+ }
155
+
156
+ function readPlanFromFile(filePath: string): string | null {
157
+ if (!fs.existsSync(filePath)) return null
158
+ const source = fs.readFileSync(filePath, "utf8")
159
+ const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim()
160
+ const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim()
161
+ if (!status && !objective) return null
162
+ const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
163
+ return `Active plan: ${parts.join(" | ")}`
164
+ }
165
+
166
+ function readPlanSummary(projectDir: string): string | null {
167
+ const planFile = findLatestPlanFile(projectDir)
168
+ if (!planFile) return null
169
+ return readPlanFromFile(planFile)
170
+ }
171
+
172
+ function ensureDir(dir: string): void {
173
+ if (!fs.existsSync(dir)) {
174
+ fs.mkdirSync(dir, { recursive: true })
175
+ }
114
176
  }
115
177
 
116
- function buildBootstrapContent(hDir: string): string {
117
- const parts = [
118
- `<${BOOTSTRAP_MARKER}>`,
119
- `You are OpenHermes.`,
120
- `OpenHermes is OpenCode-native: load skills on demand, prefer subagents for substantive work, and keep the surface small.`,
121
- `Durable state is removed for now. Do not invent a persistence layer unless the user explicitly asks for one later.`,
178
+ /**
179
+ * Ensure a plan file exists for the project.
180
+ * Creates a skeleton plan if none exists or if the latest is complete/abandoned.
181
+ * Reuses an existing active or in-progress plan.
182
+ * Returns the path to the plan file.
183
+ */
184
+ function ensurePlanFile(projectDir: string): string {
185
+ const projectName = getProjectName(projectDir)
186
+ const storage = planStorageDir()
187
+ ensureDir(storage)
188
+
189
+ // Reuse active or in-progress plan
190
+ const latest = findLatestPlanFile(projectDir)
191
+ if (latest) {
192
+ const content = fs.readFileSync(latest, "utf8")
193
+ const status = content.match(/^Status:\s*(.+)$/m)?.[1]?.trim()
194
+ if (status === "active" || status === "in-progress") {
195
+ return latest
196
+ }
197
+ }
198
+
199
+ // Determine next sequence number
200
+ let nextSeq = 1
201
+ if (latest) {
202
+ const m = path.basename(latest).match(/-plan-(\d{3})\.md$/)
203
+ if (m) nextSeq = parseInt(m[1], 10) + 1
204
+ }
205
+
206
+ const planId = `${projectName}-plan-${String(nextSeq).padStart(3, "0")}`
207
+ const planPath = path.join(storage, `${planId}.md`)
208
+ const now = new Date().toISOString().replace("T", " ").slice(0, 16)
209
+
210
+ const content = [
211
+ `# PLAN: ${projectName}`,
212
+ "",
213
+ `Plan ID: ${planId}`,
214
+ `Project: ${projectName}`,
215
+ `Status: active`,
216
+ `Created: ${now}`,
217
+ `Updated: ${now}`,
218
+ `Project Path: ${projectDir}`,
219
+ `Plan Path: ${planPath}`,
220
+ `Objective: (pending classification)`,
221
+ "",
222
+ "## Tasks",
223
+ "",
224
+ "- [ ] (discoverable — pending classification)",
225
+ "",
226
+ ].join("\n")
227
+
228
+ fs.writeFileSync(planPath, content, "utf8")
229
+ return planPath
230
+ }
231
+
232
+ export function buildCompactionContext(projectDir: string): string[] {
233
+ const context = [
234
+ "OpenHermes: native-first, verify before claim, always delegate, concise over verbose.",
235
+ "Preserve domain terms: skill, command, agent, bootstrap, compaction.",
236
+ "Preserve blockers, current task, and next steps; do not invent durable state.",
122
237
  ]
123
238
 
124
- const constitution = readText(path.join(hDir, "codex", "CONSTITUTION.md"))
125
- const runtime = readText(path.join(hDir, "instructions", "RUNTIME.md"))
126
- const context = readText(path.join(__dirname, "CONTEXT.md"))
127
- const ethos = readText(path.join(__dirname, "ETHOS.md"))
239
+ const planSummary = readPlanSummary(projectDir)
240
+ if (planSummary) context.push(planSummary)
241
+
242
+ return context
243
+ }
244
+
245
+ type SessionLifecycleEvent =
246
+ | { type: "session.created"; properties: { info: { id: string } } }
247
+ | { type: "session.compacted"; properties: { sessionID: string } }
248
+ | { type: "session.error"; properties: { sessionID?: string; error?: unknown } }
128
249
 
129
- if (constitution) parts.push(`<CONSTITUTION>\n${constitution}\n</CONSTITUTION>`)
130
- if (runtime) parts.push(`<RUNTIME>\n${runtime}\n</RUNTIME>`)
131
- if (context) parts.push(`<CONTEXT>\n${context}\n</CONTEXT>`)
132
- if (ethos) parts.push(`<ETHOS>\n${ethos}\n</ETHOS>`)
133
- parts.push(`</${BOOTSTRAP_MARKER}>`)
250
+ function readErrorMessage(error: unknown): string {
251
+ if (!error || typeof error !== "object") return "unknown error"
252
+ const value = error as { name?: unknown; message?: unknown; data?: { message?: unknown } }
253
+ const name = typeof value.name === "string" && value.name ? value.name : "Error"
254
+ const message = typeof value.data?.message === "string" && value.data.message ? value.data.message : typeof value.message === "string" && value.message ? value.message : ""
255
+ return message ? `${name}: ${message}` : name
256
+ }
134
257
 
135
- return parts.join("\n\n")
258
+ export function formatSessionEvent(event: SessionLifecycleEvent): { level: "info" | "error"; message: string } | null {
259
+ switch (event.type) {
260
+ case "session.created":
261
+ return { level: "info", message: `session.created session=${event.properties.info.id}` }
262
+ case "session.compacted":
263
+ return { level: "info", message: `session.compacted session=${event.properties.sessionID}` }
264
+ case "session.error":
265
+ return { level: "error", message: `session.error session=${event.properties.sessionID ?? "unknown"} error=${readErrorMessage(event.properties.error)}` }
266
+ default:
267
+ return null
268
+ }
136
269
  }
137
270
 
271
+
272
+
273
+
138
274
  interface OpenHermesConfig {
139
275
  skills?: { paths?: string[] }
140
276
  command?: Record<string, unknown>
@@ -143,17 +279,48 @@ interface OpenHermesConfig {
143
279
  default_agent?: string
144
280
  }
145
281
 
146
- export const BootstrapPlugin: Plugin = async () => {
282
+ export const BootstrapPlugin: Plugin = async (ctx) => {
147
283
  const hDir = getHarnessDir()
148
284
  const skillsDir = path.join(hDir, "skills")
149
285
  const commandsDir = path.join(hDir, "commands")
150
286
  const agentsDir = path.join(hDir, "agents")
151
- const bootstrapContent = buildBootstrapContent(hDir)
287
+ const client = ctx.client // SDK client for structured logging
288
+
289
+ // Safe logging — uses OpenCode SDK when available, falls back to stdout for tests
290
+ async function logToOC(level: "info" | "warn" | "error" | "debug", message: string): Promise<void> {
291
+ if (client?.app?.log) {
292
+ await client.app.log({ body: { service: "openhermes", level, message } })
293
+ } else {
294
+ console.log(`[openhermes] [${level.toUpperCase()}] ${message}`)
295
+ }
296
+ }
297
+
298
+ // Auto-detect and wire user skills from ~/.agents/skills and ~/.config/opencode/skills
299
+ const userSkillPaths: string[] = []
300
+ for (const userDir of USER_SKILL_DIRS) {
301
+ ensureDir(userDir)
302
+ userSkillPaths.push(userDir)
303
+ await logToOC("info", `wired user skills from ${userDir}`)
304
+ }
305
+
306
+ const compactionContext = buildCompactionContext(ctx.directory)
307
+ // Ensure plan storage exists
308
+ ensureDir(planStorageDir())
152
309
 
153
310
  return {
154
311
  config: async (config: OpenHermesConfig) => {
155
312
  config.skills = config.skills || {}
156
- config.skills.paths = uniqueStrings(config.skills.paths || [], [skillsDir])
313
+ // Built-in paths first, user paths last user skills override built-in on name conflict
314
+ const allPaths = [skillsDir, ...userSkillPaths]
315
+ config.skills.paths = uniqueStrings(config.skills.paths || [], allPaths)
316
+
317
+ await logToOC("info", `skills: ${allPaths.length} path(s)`)
318
+
319
+ // Register harness docs as native OpenCode instructions — no prompt-embedding needed
320
+ config.instructions = uniqueStrings(config.instructions ?? [], [
321
+ path.join(hDir, "codex"),
322
+ path.join(hDir, "instructions"),
323
+ ])
157
324
 
158
325
  config.command = { ...(config.command ?? {}), ...commandDefinitions(commandsDir) }
159
326
 
@@ -164,18 +331,63 @@ export const BootstrapPlugin: Plugin = async () => {
164
331
  prompt: "You are OpenHermes.",
165
332
  }
166
333
 
334
+ // Subagent permissions — tier-4 and tier-3 get execution access but cannot spawn orchestrators
335
+ const SUBAGENT_PERMISSIONS: Record<string, Record<string, unknown>> = {
336
+ "oh-builder": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
337
+ "oh-browser": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
338
+ "oh-facade": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
339
+ "oh-gauntlet": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
340
+ "oh-manifest": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
341
+ "oh-ship": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
342
+ "oh-planner": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
343
+ "oh-grill": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
344
+ "oh-investigate": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
345
+ "oh-plan-review": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
346
+ "oh-security": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
347
+ "oh-refactor": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
348
+ "oh-review": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
349
+ "oh-fusion": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
350
+ "oh-retro": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
351
+ "oh-skill-craft": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
352
+ }
353
+
167
354
  config.agent = {
168
355
  ...(config.agent ?? {}),
169
356
  ...loadedAgents,
357
+ // Apply permissions + hidden flag to subagents
358
+ ...Object.fromEntries(
359
+ Object.entries(loadedAgents)
360
+ .filter(([name]) => name !== OPENHERMES_AGENT)
361
+ .map(([name, agentDef]) => [
362
+ name,
363
+ {
364
+ ...agentDef,
365
+ permission: SUBAGENT_PERMISSIONS[name] ?? { bash: { "*": "deny" }, edit: "deny", read: "allow" },
366
+ // Hide routing-internal subagents from @-menu
367
+ // Only agents with existing .md files can be hidden — names without files are no-ops
368
+ ...(["oh-planner", "oh-grill", "oh-skill-craft"].includes(name) ? { hidden: true } : {}),
369
+ },
370
+ ])
371
+ ),
170
372
  [OPENHERMES_AGENT]: {
171
373
  ...openHermesAgent,
172
374
  description: openHermesAgent.description || "OpenHermes primary orchestrator",
173
375
  mode: "primary",
376
+ steps: 15, // Max agentic iterations — prevents runaway loops
174
377
  permission: {
175
- bash: { "*": "allow" },
176
- edit: "allow",
177
- read: "allow",
178
- task: { "*": "allow" },
378
+ bash: { "*": "deny" }, // CANNOT execute commands
379
+ edit: "deny", // CANNOT write/edit files
380
+ read: "allow", // CAN read for classification
381
+ glob: "allow", // CAN search for files
382
+ grep: "allow", // CAN search content
383
+ task: { "*": "allow" }, // MUST delegate via subagents
384
+ skill: "allow", // CAN load skill instructions
385
+ webfetch: "allow", // CAN fetch docs for context
386
+ question: "allow", // CAN ask user questions
387
+ websearch: "allow", // CAN search web for research context
388
+ external_directory: { // CAN read/write plan files outside worktree
389
+ "~/.local/share/opencode/openhermes/**": "allow",
390
+ },
179
391
  },
180
392
  },
181
393
  }
@@ -183,17 +395,48 @@ export const BootstrapPlugin: Plugin = async () => {
183
395
  config.default_agent = OPENHERMES_AGENT
184
396
  },
185
397
 
186
- "experimental.chat.messages.transform": async (_input: unknown, output: { messages?: Array<{ info?: { role?: string }; parts?: Array<{ text?: string }> }> }) => {
187
- try {
188
- if (!output.messages?.length) return
189
- const firstUser = output.messages.find(m => m?.info?.role === "user")
190
- if (!firstUser?.parts?.length) return
191
- if (firstUser.parts.some(p => p.text?.includes(BOOTSTRAP_MARKER))) return
192
- const ref = firstUser.parts[0]
193
- firstUser.parts.unshift({ ...ref, type: "text", text: bootstrapContent })
194
- } catch (err: unknown) {
195
- log.error("transform error:", (err as Error)?.message)
398
+ event: async ({ event }) => {
399
+ const typed = event as SessionLifecycleEvent
400
+ const record = formatSessionEvent(typed)
401
+ if (!record) return
402
+ await logToOC(record.level, record.message)
403
+
404
+ // NOTE: Plan files are NOT auto-created here. The LLM agent
405
+ // creates plans on demand (see Task Flow step 1 in agent prompt).
406
+ // Auto-creation produced ghost skeletons like plan-004.
407
+
408
+ // Reset delegation depth on session start/error
409
+ if (typed.type === "session.created" || typed.type === "session.error") {
410
+ delegationDepths.delete(`delegation:${ctx.directory}`)
411
+ }
412
+ },
413
+
414
+ "experimental.session.compacting": async (_input, output) => {
415
+ output.context.push(...compactionContext)
416
+ },
417
+
418
+ // Mechanical delegation loop guard — prevents runaway agent nesting
419
+ "tool.execute.before": async (input, output) => {
420
+ if (input.tool === "task") {
421
+ // Track delegation depth per project (one session per project at a time)
422
+ const depthKey = `delegation:${ctx.directory}`
423
+ const currentDepth = (delegationDepths.get(depthKey) ?? 0) + 1
424
+ delegationDepths.set(depthKey, currentDepth)
425
+
426
+ if (currentDepth >= 10) {
427
+ const errOutput = output as { args: unknown; isError?: boolean; content?: unknown[] }
428
+ errOutput.isError = true
429
+ errOutput.content = [{
430
+ type: "text",
431
+ text: "LOOP GUARD: Delegation depth exceeded (max 10). " +
432
+ "Surface to orchestrator with findings and stop delegating."
433
+ }]
434
+ }
196
435
  }
197
436
  },
437
+
198
438
  }
199
439
  }
440
+
441
+ // Module-level delegation depth tracker — reset per project session
442
+ const delegationDepths = new Map<string, number>()
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: oh-browser
3
+ description: "Browser automation via agent-browser CLI. Navigate pages, fill forms, click buttons, take screenshots, extract data, test web apps. Use when the user needs to interact with websites, automate browser tasks, scrape data, or test web applications."
4
+ mode: subagent
5
+ ---
6
+
7
+ ## Shell Pre-flight (Windows)
8
+
9
+ You are on Windows. Before ANY command execution, detect your shell:
10
+ - `$PSVersionTable` exists → PowerShell (`powershell` or `pwsh`)
11
+ - `%CMDCMDLINE%` is set → CMD
12
+ - `$0` or `$BASH` → Bash (Git Bash)
13
+
14
+ Operation → required shell:
15
+ - File ops (`Remove-Item`, `New-Item`), scoop, `.ps1` scripts, `$env:VAR` → **PowerShell**
16
+ - `git`, `bun`, `npm`, `node` → **any shell** (all work)
17
+ - `rm -rf`, `make`, Unix tools → **Git Bash**
18
+ - `.bat`/`.cmd` files → **CMD**
19
+
20
+ Wrong shell? Switch:
21
+ - → PowerShell: `powershell.exe -NoProfile -Command "..."`
22
+ - → Git Bash: `& "C:\Program Files\Git\bin\bash.exe" -c "..."`
23
+ - → CMD: `cmd.exe /c "..."`
24
+
25
+ Always know before you go.
26
+
27
+ # oh-browser
28
+
29
+ Browser automation via agent-browser CLI. Fast native Rust CLI wrapping Chrome/Chromium via CDP.
30
+
31
+ ## Prerequisites
32
+
33
+ - agent-browser installed globally: `npm install -g agent-browser && agent-browser install`
34
+ - Chrome/Chromium (auto-downloaded by `agent-browser install`)
35
+ - State files contain session tokens — add to `.gitignore`, never commit
36
+
37
+ ## Workflow
38
+
39
+ 1. **Launch browser** — `agent-browser open <url>` or `agent-browser open` (blank page then navigate)
40
+ 2. **Snapshot page state** — `agent-browser snapshot` returns accessibility tree with `@eN` refs
41
+ 3. **Interact** — use `@eN` refs from snapshot:
42
+ - `agent-browser click @eN`
43
+ - `agent-browser fill @eN "value"`
44
+ - `agent-browser select @eN "option"`
45
+ - `agent-browser hover @eN`
46
+ 4. **Extract data** — `agent-browser get text @eN`, `agent-browser get html @eN`, `agent-browser screenshot`
47
+ 5. **Close** — `agent-browser close`
48
+
49
+ ## Common Patterns
50
+
51
+ - **Annotated screenshots**: `agent-browser screenshot --annotate` — overlays numbered labels matching `@eN` refs.
52
+ - **Batch execution**: `agent-browser batch "open url" "snapshot" "click @e1"` — avoids per-command startup overhead.
53
+ - **Session persistence**: `--session-name <name>` — auto-saves/restores cookies and localStorage.
54
+ - **Auth vault**: `agent-browser auth save <name> --url <url> --username <user>` — encrypted credentials.
55
+ - **Diff**: `agent-browser diff snapshot` for change detection. `agent-browser diff screenshot --baseline before.png` for visual diff.
56
+ - **Chrome profile reuse**: `--profile Default` — use existing Chrome login state.
57
+ - **Tab labeling**: `agent-browser tab new --label docs <url>` — memorable labels.
58
+ - **Parallel scrape**: Use `batch --json` with piped command arrays.
59
+
60
+ ## Common Commands Reference
61
+
62
+ | Task | Command |
63
+ |---|---|
64
+ | Open URL | `agent-browser open <url>` |
65
+ | Get page state | `agent-browser snapshot -i` |
66
+ | Click | `agent-browser click @eN` or `agent-browser click "css-selector"` |
67
+ | Type text | `agent-browser fill @eN "text"` |
68
+ | Screenshot | `agent-browser screenshot --annotate` |
69
+ | Extract text | `agent-browser get text @eN` |
70
+ | Run JS | `agent-browser eval "document.title"` |
71
+ | Wait for element | `agent-browser wait ".selector"` |
72
+ | Scroll | `agent-browser scroll down 200` |
73
+ | Multi-step | `agent-browser batch "cmd1" "cmd2" "cmd3"` |
74
+
75
+ ## Anti-patterns
76
+
77
+ - Forgetting `agent-browser install` first
78
+ - Not closing browser sessions (daemon processes leak)
79
+ - Using CSS selectors when `@eN` refs are faster
80
+ - Running individual commands instead of batch for multi-step
81
+ - Passing credentials in prompts instead of auth vault
82
+ - Committing state files with session tokens
83
+
84
+ ## Security
85
+
86
+ - Use `--allowed-domains` to restrict navigation
87
+ - Use auth vault instead of passing credentials in prompts
88
+ - Session state files contain tokens — keep in `.gitignore`
89
+ - `--content-boundaries` wraps page output in delimiters
90
+
91
+ ## Routing
92
+
93
+ | Outcome | Route |
94
+ |---------|-------|
95
+ | pass | → surface (results to user) |
96
+ | fail | → oh-browser (retry with corrected approach) |
97
+ | blocker | → surface with error details |
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: oh-builder
3
+ description: "ALL-arounder builder — prototype, TDD, implement from plan, design interfaces. Consumes the plan file, produces working code."
4
+ mode: subagent
5
+ ---
6
+
7
+ ## Shell Pre-flight (Windows)
8
+
9
+ You are on Windows. Before ANY command execution, detect your shell:
10
+ - `$PSVersionTable` exists → PowerShell (`powershell` or `pwsh`)
11
+ - `%CMDCMDLINE%` is set → CMD
12
+ - `$0` or `$BASH` → Bash (Git Bash)
13
+
14
+ Operation → required shell:
15
+ - File ops (`Remove-Item`, `New-Item`), scoop, `.ps1` scripts, `$env:VAR` → **PowerShell**
16
+ - `git`, `bun`, `npm`, `node` → **any shell** (all work)
17
+ - `rm -rf`, `make`, Unix tools → **Git Bash**
18
+ - `.bat`/`.cmd` files → **CMD**
19
+
20
+ Wrong shell? Switch:
21
+ - → PowerShell: `powershell.exe -NoProfile -Command "..."`
22
+ - → Git Bash: `& "C:\Program Files\Git\bin\bash.exe" -c "..."`
23
+ - → CMD: `cmd.exe /c "..."`
24
+
25
+ Always know before you go.
26
+
27
+ # oh-builder
28
+
29
+ ALL-arounder builder. Prototyping, TDD, plan implementation, interface design. Consumes plan file from oh-planner or works standalone.
30
+
31
+ ## Entry Modes
32
+
33
+ ### Mode A: Prototype (exploratory)
34
+ Pick branch by question:
35
+ - **"Does this logic feel right?"** → Terminal branch. Tiny interactive app pushing state machine.
36
+ - **"What should this look like?"** → UI branch. Multiple visual variations switchable via param/control bar.
37
+
38
+ **Rules:** Throwaway from day one (clear name). One command to run. No persistence (memory state). Skip polish (no tests, minimal error handling). Surface state after every action. Delete/absorb answer when done.
39
+
40
+ ### Mode B: TDD (test-first)
41
+ Red-green-refactor with vertical tracer bullets.
42
+
43
+ **Plan:** Confirm interface changes with user. Prioritize behaviors. Design for testability (public interface only). List behaviors, not implementation steps.
44
+
45
+ **Loop** per behavior:
46
+ ```
47
+ RED: One test → fails
48
+ GREEN: Minimal code → passes
49
+ ```
50
+
51
+ **Rules:** One test at a time. Only enough code to pass. Don't anticipate future tests. Tests through public interfaces. Never refactor while RED.
52
+
53
+ **Refactor** (all GREEN): Extract duplication, deepen modules, re-run tests after each step.
54
+
55
+ ### Mode C: Design an Interface
56
+ "Design it twice" — generate multiple radically different designs, compare.
57
+
58
+ 1. Gather requirements (problem, callers, operations, constraints)
59
+ 2. Spawn 3+ parallel sub-agents with different constraints (min methods, max flexibility, optimize common case, specific paradigm)
60
+ 3. Present designs (signature, examples, what it hides)
61
+ 4. Compare (simplicity, generality, efficiency, depth)
62
+ 5. Synthesize insights
63
+
64
+ ### Mode D: From Plan
65
+ Plan exists → execute phases in order.
66
+
67
+ 1. Read plan file
68
+ 2. Each phase: implement per spec using TDD (Mode B)
69
+ 3. Verify against criteria before moving on
70
+ 4. Update plan with completed status
71
+
72
+ ## Anti-patterns
73
+ - Polishing a prototype
74
+ - Writing all tests first (brittle, imaginary)
75
+ - Anticipating future tests
76
+ - Refactoring while RED
77
+ - Sub-agents producing similar designs (enforce radical difference)
78
+ - Implementing without verifying against plan criteria
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: oh-facade
3
+ description: "Full UI pipeline: from concept to production frontend. Design system generation, premium component architecture, production-grade implementation, structured audit. Use when building any user-facing interface, component system, or visual application."
4
+ mode: subagent
5
+ ---
6
+
7
+ ## Shell Pre-flight (Windows)
8
+
9
+ You are on Windows. Before ANY command execution, detect your shell:
10
+ - `$PSVersionTable` exists → PowerShell (`powershell` or `pwsh`)
11
+ - `%CMDCMDLINE%` is set → CMD
12
+ - `$0` or `$BASH` → Bash (Git Bash)
13
+
14
+ Operation → required shell:
15
+ - File ops (`Remove-Item`, `New-Item`), scoop, `.ps1` scripts, `$env:VAR` → **PowerShell**
16
+ - `git`, `bun`, `npm`, `node` → **any shell** (all work)
17
+ - `rm -rf`, `make`, Unix tools → **Git Bash**
18
+ - `.bat`/`.cmd` files → **CMD**
19
+
20
+ Wrong shell? Switch:
21
+ - → PowerShell: `powershell.exe -NoProfile -Command "..."`
22
+ - → Git Bash: `& "C:\Program Files\Git\bin\bash.exe" -c "..."`
23
+ - → CMD: `cmd.exe /c "..."`
24
+
25
+ Always know before you go.
26
+
27
+ # oh-facade
28
+
29
+ Full UI pipeline: Concept → Design System → Build → Audit → Iterate. Closed-loop. Engineering, not decoration.
30
+
31
+ ---
32
+
33
+ ## Sections
34
+
35
+ | Phase | Description |
36
+ |-------|-------------|
37
+ | [Phase 0: Redesign Entry](../skills/oh-facade/DEEP.md#phase-0-redesign-entry-existing-projects) | Existing project scan — detect framework/styling, run Phase 4 diagnosis, apply targeted upgrades without rewrite |
38
+ | [Phase 1: Concept](../skills/oh-facade/DEEP.md#phase-1-concept) | Context questions (product/user/problem/constraints), 4 visual archetypes (Warm Minimalist, Premium SaaS, Industrial Brutalist, Creative/Expressive), 3 metric dials (VARIANCE/MOTION/DENSITY), design brief output |
39
+ | [Phase 2: Design System](../skills/oh-facade/DEEP.md#phase-2-design-system) | Color tokens, typography stack, component specs, layout grid, motion/spring rules, GSAP ScrollTrigger patterns (pinning/scale/horizontal/stagger/text-split/python-randomization/bento) |
40
+ | [Phase 3: Build](../skills/oh-facade/DEEP.md#phase-3-build) | CSS foundations + token mapping, component library (all states: default/hover/active/focus/disabled/loading/empty/error), page assembly + responsive + viewport states, framework/a11y/meta |
41
+ | [Phase 4: Audit + Iterate](../skills/oh-facade/DEEP.md#phase-4-audit) | Priority-ranked audit P1-P5 (typography/color/layout, interactivity/states/motion, content quality, hardening/a11y, existing-project redesign). Fix in priority order, re-audit per level, surface blocker |
42
+
43
+ ---
44
+
45
+ ## Hard Bans
46
+
47
+ - No emojis in code/content/alt/markup
48
+ - No Lorem Ipsum — write real draft copy
49
+ - No "Elevate", "Seamless", "Next-Gen", "Unleash", "Game-changer"
50
+ - No generic names (John Doe, Acme Corp)
51
+ - No rocket ship / shield cliche icons
52
+ - No purple/blue neon gradients
53
+ - No dark section in white page without committed dark mode
54
+ - No animated `top/left/width/height`
55
+ - No `window.addEventListener('scroll')`
56
+ - No `h-screen`
57
+ - No pill shapes on large containers, cards, or primary buttons
58
+ - No generic icon libraries (Lucide, Feather, Heroicons)
59
+ - No 3-equal-card feature rows — replace with 2-column zig-zag, asymmetric grid, or masonry
60
+ - No `#000000` backgrounds — use off-black `#0a0a0a` or `#121212`
61
+ - No even 45-degree linear gradients — break with radial, noise overlay, or mesh
62
+ - No mixing warm and cool grays — stick to one gray family throughout
63
+ - No random dark section in light page (or vice versa) — commit to one substrate
64
+ - No orphaned words — use `text-wrap: balance` or `text-wrap: pretty`
65
+
66
+ ---
67
+
68
+ ## Design Principles
69
+
70
+ 1. **Intentionality** — Commit to direction. Maximalism and minimalism both work if committed.
71
+ 2. **Engineering** — Buttons have structure, physics, a11y. Not just "style."
72
+ 3. **Consistency** — One palette, one font system, one architecture.
73
+ 4. **Performance** — Beautiful + laggy = not beautiful. Transform-only, guarded backdrop-blur.
74
+ 5. **Ship every state** — Default-only is not production.
75
+ 6. **Redesign first** — For existing projects, diagnose before prescribing. Improve in-place. A targeted upgrade beats a rewrite.