Merge pull request #4389 from code-yeongyu/refactor/mode-prompts-to-prompts-core

refactor(mode-prompts): extract search/analyze/team/hyperplan to packages/prompts-core (PR3)
This commit is contained in:
YeonGyu-Kim
2026-05-24 18:38:03 +09:00
committed by GitHub
15 changed files with 191 additions and 52 deletions
+3 -1
View File
@@ -32,13 +32,15 @@ oh-my-opencode/
│ ├── openclaw/ # Bidirectional external integration (Discord/Telegram/HTTP/shell + reply listener daemon)
│ ├── generated/ # model-capabilities.generated.json (refreshed via build:model-capabilities)
│ └── testing/ # Test utilities + `create-plugin-module.ts` (extracted plugin entry factory, 182 LOC)
├── packages/ # 11 platform binaries + 2 MCP packages + 7 Core packages + web
├── packages/ # 11 platform binaries + 2 MCP packages + 9 Core packages + web
│ ├── utils/ # Shared utilities — deep-merge, snake-case, frontmatter, file-utils, etc.
│ ├── model-core/ # Model resolution pipeline with ProviderCache DI
│ ├── prompts-core/ # Markdown prompt loading + bundled mode prompts (search/analyze/team/hyperplan)
│ ├── rules-engine/ # Rule discovery + matching (renamed from rules-core)
│ ├── agents-md-core/ # AGENTS.md walk-up discovery + injection
│ ├── ast-grep-core/ # ast-grep types, pattern-hints, runner core with injectable spawn
│ ├── comment-checker-core/ # apply-patch parser, binary runner with injectable spawn
│ ├── hashline-core/ # Hashline edit primitives and diff helpers shared by adapter shims
│ ├── boulder-state/ # Work tracking state machine, split storage
│ └── web/ # Marketing site (Next.js 15 + Cloudflare Workers). Independent package with own bun.lock
├── bin/ # Platform-detection JS shim (oh-my-opencode + oh-my-openagent)
+4 -2
View File
@@ -4,7 +4,7 @@
## OVERVIEW
15 sibling packages across 4 roles. None of these are published as part of the main `oh-my-opencode` / `oh-my-openagent` npm dist (root `package.json` `files` only ships `dist/`, `bin/`, `postinstall.mjs`). They are sibling packages with their own publication / deployment targets.
23 sibling packages across 4 roles. None of these are published as part of the main `oh-my-opencode` / `oh-my-openagent` npm dist (root `package.json` `files` only ships `dist/`, `bin/`, `postinstall.mjs`). They are sibling packages with their own publication / deployment targets.
## ROLE MAP
@@ -12,7 +12,7 @@
|------|-------|----------|
| **Platform binaries** | 11 | One per (OS × arch × variant). Uniform layout: `bin/` + `package.json` only. Selected at install time by `bin/` shim + `postinstall.mjs`. |
| **MCP packages** | 2 | `lsp-tools-mcp` (git submodule), `ast-grep-mcp` |
| **Core packages** | 7 | `utils`, `model-core`, `rules-engine` (was `rules-core`), `agents-md-core`, `ast-grep-core`, `comment-checker-core`, `boulder-state` |
| **Core packages** | 9 | `utils`, `model-core`, `prompts-core`, `rules-engine` (was `rules-core`), `agents-md-core`, `ast-grep-core`, `comment-checker-core`, `hashline-core`, `boulder-state` |
| **Web** | 1 | `web` |
## PLATFORM BINARIES (11)
@@ -36,10 +36,12 @@ Each contains only a `bin/<binary>` and a `package.json`. Built by [`script/buil
|---------|--------|---------|
| `utils/` | `src/`, `tsconfig.json` | Shared utilities: deep-merge, snake-case, frontmatter, file-utils, etc. |
| `model-core/` | `src/`, `tsconfig.json` | Model resolution pipeline with ProviderCache dependency injection. |
| `prompts-core/` | `src/`, `prompts/`, `test/`, `tsconfig.json` | Harness-neutral markdown prompt loading, model-variant routing, and bundled mode prompts for search/analyze/team/hyperplan. |
| `rules-engine/` | `src/`, `tsconfig.json` | Rule discovery + matching engine (renamed from `rules-core`). |
| `agents-md-core/` | `src/`, `tsconfig.json` | AGENTS.md walk-up discovery and injection logic. |
| `ast-grep-core/` | `src/`, `tsconfig.json` | ast-grep types, pattern-hints, and runner core with injectable spawn. |
| `comment-checker-core/` | `src/`, `tsconfig.json` | apply-patch parser and binary runner with injectable spawn. |
| `hashline-core/` | `src/`, `tsconfig.json` | Hashline edit primitives and diff helpers shared by adapter shims. |
| `boulder-state/` | `src/`, `tsconfig.json` | Work tracking state machine with split storage. |
## WEB
@@ -0,0 +1,16 @@
[analyze-mode]
ANALYSIS MODE. Gather context before diving deep:
CONTEXT GATHERING (parallel):
- 1-2 explore agents (codebase patterns, implementations)
- 1-2 librarian agents (if external library involved)
- Direct tools: Grep, AST-grep, LSP for targeted searches
IF COMPLEX - DO NOT STRUGGLE ALONE. Consult specialists:
- **Oracle**: Conventional problems (architecture, debugging, complex logic)
- **Artistry**: Non-conventional problems (different approach needed)
SYNTHESIZE findings before proceeding.
---
MANDATORY delegate_task params: ALWAYS include load_skills and run_in_background when calling delegate_task. Evaluate available skills before dispatch - pass task-appropriate skills when relevant, pass [] ONLY when no skill matches the task domain.
Example: delegate_task(subagent_type="explore", prompt="...", run_in_background=true, load_skills=[])
@@ -0,0 +1,25 @@
<hyperplan-mode>
**MANDATORY**: Say "HYPERPLAN MODE ENABLED!" as your first response, exactly once.
The user invoked **hyperplan mode** — adversarial multi-agent planning via team-mode.
LOAD THE HYPERPLAN SKILL IMMEDIATELY:
```
skill(name="hyperplan")
```
After loading, follow the skill's full workflow EXACTLY:
1. Acknowledge and capture the planning request
2. Spawn the adversarial team via `team_create` with category members `unspecified-low`, `unspecified-high`, `ultrabrain`, and `artistry`; include `deep` only if the category is enabled
3. Round 1 — Independent analysis (each member produces findings)
4. Round 2 — Cross-attack (each member ruthlessly attacks the other 4's findings)
5. Round 3 — Defend, refine, or concede
6. Distill defensible insights into a structured bundle (Lead does NOT write the plan)
7. MANDATORY: hand the bundle to the `plan` agent via `task(subagent_type="plan", ...)` — the plan agent owns sequencing, parallelization, and verification gates
8. Present the plan agent's output verbatim with provenance line, then clean up the team
Do NOT improvise. Do NOT skip rounds. Do NOT write the plan yourself in step 6 — the handoff to the plan agent in step 7 is non-negotiable. Be the lead orchestrator and let the adversarial members do the cross-critique.
If team-mode is unavailable (`team_*` tools missing), instruct the user to set `team_mode.enabled: true` in `~/.config/opencode/oh-my-opencode.jsonc` and restart opencode.
</hyperplan-mode>
@@ -0,0 +1,6 @@
[search-mode]
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
- explore agents (codebase patterns, file structures, ast-grep)
- librarian agents (remote repos, official docs, GitHub examples)
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
NEVER stop at first result - be exhaustive.
@@ -0,0 +1,2 @@
[team-mode]
Team-mode reference detected. Orchestrate via team_* tools (team_create -> team_task_create + team_send_message); NEVER substitute with delegate_task — it is not equivalent. After every team_task_update that completes or fails a task, re-check team_task_list: if every task is terminal, run the closure sequence (team_shutdown_request + team_approve_shutdown per active member, then team_delete) in the same turn. Closing the team is the lead's responsibility, not the user's. If the team_* tools are absent, team_mode is disabled — tell the user to set team_mode.enabled=true and restart opencode.
+6
View File
@@ -15,3 +15,9 @@ export { atlasPromptVariants } from "./atlas-prompts"
export { resolveVariant } from "./variant-resolver"
export type { ResolveVariantInput } from "./variant-resolver"
export { loadPrompt, loadPromptSync, PromptFileNotFoundError, PromptPathTraversalError } from "./loader"
export {
ANALYZE_MODE_PROMPT,
HYPERPLAN_MODE_PROMPT,
SEARCH_MODE_PROMPT,
TEAM_MODE_PROMPT,
} from "./mode-prompts"
+4
View File
@@ -0,0 +1,4 @@
declare module "*.md" {
const markdown: string
export default markdown
}
+13
View File
@@ -0,0 +1,13 @@
import hyperplanModePrompt from "../prompts/mode/hyperplan.md" with { type: "text" }
import analyzeModePrompt from "../prompts/mode/analyze.md" with { type: "text" }
import searchModePrompt from "../prompts/mode/search.md" with { type: "text" }
import teamModePrompt from "../prompts/mode/team.md" with { type: "text" }
export const ANALYZE_MODE_PROMPT = stripFinalLineFeed(analyzeModePrompt)
export const HYPERPLAN_MODE_PROMPT = stripFinalLineFeed(hyperplanModePrompt)
export const SEARCH_MODE_PROMPT = stripFinalLineFeed(searchModePrompt)
export const TEAM_MODE_PROMPT = stripFinalLineFeed(teamModePrompt)
function stripFinalLineFeed(prompt: string): string {
return prompt.endsWith("\n") ? prompt.slice(0, -1) : prompt
}
+3 -16
View File
@@ -1,3 +1,5 @@
import { ANALYZE_MODE_PROMPT } from "@oh-my-opencode/prompts-core"
/**
* Analyze mode keyword detector.
*
@@ -12,19 +14,4 @@
export const ANALYZE_PATTERN =
/\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|분석|조사|파악|연구|검토|진단|이해|설명|원인|이유|뜯어봐|따져봐|평가|해석|디버깅|디버그|어떻게|왜|살펴|分析|調査|解析|検討|研究|診断|理解|説明|検証|精査|究明|デバッグ|なぜ|どう|仕組み|调查|检查|剖析|深入|诊断|解释|调试|为什么|原理|搞清楚|弄明白|phân tích|điều tra|nghiên cứu|kiểm tra|xem xét|chẩn đoán|giải thích|tìm hiểu|gỡ lỗi|tại sao/i
export const ANALYZE_MESSAGE = `[analyze-mode]
ANALYSIS MODE. Gather context before diving deep:
CONTEXT GATHERING (parallel):
- 1-2 explore agents (codebase patterns, implementations)
- 1-2 librarian agents (if external library involved)
- Direct tools: Grep, AST-grep, LSP for targeted searches
IF COMPLEX - DO NOT STRUGGLE ALONE. Consult specialists:
- **Oracle**: Conventional problems (architecture, debugging, complex logic)
- **Artistry**: Non-conventional problems (different approach needed)
SYNTHESIZE findings before proceeding.
---
MANDATORY delegate_task params: ALWAYS include load_skills and run_in_background when calling delegate_task. Evaluate available skills before dispatch - pass task-appropriate skills when relevant, pass [] ONLY when no skill matches the task domain.
Example: delegate_task(subagent_type="explore", prompt="...", run_in_background=true, load_skills=[])`
export const ANALYZE_MESSAGE = ANALYZE_MODE_PROMPT
@@ -1,3 +1,5 @@
import { HYPERPLAN_MODE_PROMPT } from "@oh-my-opencode/prompts-core"
/**
* Hyperplan keyword detector.
*
@@ -17,28 +19,4 @@
export const HYPERPLAN_PATTERN = /\bhyperplan\b|(?<![\w.])hpp\b/i
export const HYPERPLAN_MESSAGE = `<hyperplan-mode>
**MANDATORY**: Say "HYPERPLAN MODE ENABLED!" as your first response, exactly once.
The user invoked **hyperplan mode** — adversarial multi-agent planning via team-mode.
LOAD THE HYPERPLAN SKILL IMMEDIATELY:
\`\`\`
skill(name="hyperplan")
\`\`\`
After loading, follow the skill's full workflow EXACTLY:
1. Acknowledge and capture the planning request
2. Spawn the adversarial team via \`team_create\` with category members \`unspecified-low\`, \`unspecified-high\`, \`ultrabrain\`, and \`artistry\`; include \`deep\` only if the category is enabled
3. Round 1 — Independent analysis (each member produces findings)
4. Round 2 — Cross-attack (each member ruthlessly attacks the other 4's findings)
5. Round 3 — Defend, refine, or concede
6. Distill defensible insights into a structured bundle (Lead does NOT write the plan)
7. MANDATORY: hand the bundle to the \`plan\` agent via \`task(subagent_type="plan", ...)\` — the plan agent owns sequencing, parallelization, and verification gates
8. Present the plan agent's output verbatim with provenance line, then clean up the team
Do NOT improvise. Do NOT skip rounds. Do NOT write the plan yourself in step 6 — the handoff to the plan agent in step 7 is non-negotiable. Be the lead orchestrator and let the adversarial members do the cross-critique.
If team-mode is unavailable (\`team_*\` tools missing), instruct the user to set \`team_mode.enabled: true\` in \`~/.config/opencode/oh-my-opencode.jsonc\` and restart opencode.
</hyperplan-mode>`
export const HYPERPLAN_MESSAGE = HYPERPLAN_MODE_PROMPT
@@ -0,0 +1,96 @@
import { describe, expect, test } from "bun:test"
import { createHash } from "node:crypto"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
import { ANALYZE_MESSAGE, HYPERPLAN_MESSAGE, SEARCH_MESSAGE, TEAM_MESSAGE } from "./constants"
type PromptBaseline = {
readonly name: string
readonly message: string
readonly sha256: string
readonly byteLength: number
}
type ShimBaseline = {
readonly name: string
readonly filePath: string
}
const MODE_PROMPT_BASELINES: readonly PromptBaseline[] = [
{
name: "search",
message: SEARCH_MESSAGE,
sha256: "aa38d1011edcf083394441321564330661868411ec13b575e5994812fae27f62",
byteLength: 311,
},
{
name: "analyze",
message: ANALYZE_MESSAGE,
sha256: "63f9de6f7afb67ab68bc4abcc7e78c8450a6ce556378a7a5d2b9061a3c519d7f",
byteLength: 865,
},
{
name: "team",
message: TEAM_MESSAGE,
sha256: "21fd4110835ce380e307cf29e132753b04a58758b86cfaaf5dda26e0e3193d69",
byteLength: 614,
},
{
name: "hyperplan",
message: HYPERPLAN_MESSAGE,
sha256: "cea6f378370c736909be99bd9a66a06db1e4819848336dd7951298e949270ced",
byteLength: 1500,
},
]
const KEYWORD_DETECTOR_DIR = dirname(fileURLToPath(import.meta.url))
const MODE_SHIMS: readonly ShimBaseline[] = [
{ name: "search", filePath: join(KEYWORD_DETECTOR_DIR, "search", "default.ts") },
{ name: "analyze", filePath: join(KEYWORD_DETECTOR_DIR, "analyze", "default.ts") },
{ name: "team", filePath: join(KEYWORD_DETECTOR_DIR, "team", "default.ts") },
{ name: "hyperplan", filePath: join(KEYWORD_DETECTOR_DIR, "hyperplan", "default.ts") },
]
describe("keyword-detector mode prompt baselines", () => {
test("#given captured prompt baselines #then each mode message keeps the same bytes", () => {
for (const baseline of MODE_PROMPT_BASELINES) {
expect(hashPrompt(baseline.message), baseline.name).toBe(baseline.sha256)
expect(Buffer.byteLength(baseline.message, "utf8"), baseline.name).toBe(baseline.byteLength)
}
})
test("#given migrated mode shims #then each shim stays within the LOC ceiling", async () => {
for (const shim of MODE_SHIMS) {
const source = await Bun.file(shim.filePath).text()
expect(countPureLoc(source), shim.name).toBeLessThanOrEqual(20)
}
})
})
function hashPrompt(prompt: string): string {
return createHash("sha256").update(prompt, "utf8").digest("hex")
}
function countPureLoc(source: string): number {
let pureLoc = 0
let insideBlockComment = false
for (const rawLine of source.split("\n")) {
const line = rawLine.trim()
if (line.length === 0) continue
if (insideBlockComment) {
insideBlockComment = !line.includes("*/")
continue
}
if (line.startsWith("/*")) {
insideBlockComment = !line.includes("*/")
continue
}
if (line.startsWith("//")) continue
pureLoc += 1
}
return pureLoc
}
+3 -6
View File
@@ -1,3 +1,5 @@
import { SEARCH_MODE_PROMPT } from "@oh-my-opencode/prompts-core"
/**
* Search mode keyword detector.
*
@@ -12,9 +14,4 @@
export const SEARCH_PATTERN =
/\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|검색|찾아|탐색|조회|스캔|서치|뒤져|찾기|어디|추적|탐지|찾아봐|찾아내|보여줘|목록|検索|探して|見つけて|サーチ|探索|スキャン|どこ|発見|捜索|見つけ出す|一覧|搜索|查找|寻找|查询|检索|定位|扫描|发现|在哪里|找出来|列出|tìm kiếm|tra cứu|định vị|quét|phát hiện|truy tìm|tìm ra|ở đâu|liệt kê/i
export const SEARCH_MESSAGE = `[search-mode]
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
- explore agents (codebase patterns, file structures, ast-grep)
- librarian agents (remote repos, official docs, GitHub examples)
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
NEVER stop at first result - be exhaustive.`
export const SEARCH_MESSAGE = SEARCH_MODE_PROMPT
+3 -2
View File
@@ -1,3 +1,5 @@
import { TEAM_MODE_PROMPT } from "@oh-my-opencode/prompts-core"
/**
* Team mode keyword detector.
*
@@ -7,5 +9,4 @@
export const TEAM_PATTERN = /\bteam[\s_-]?mode\b/i
export const TEAM_MESSAGE = `[team-mode]
Team-mode reference detected. Orchestrate via team_* tools (team_create -> team_task_create + team_send_message); NEVER substitute with delegate_task — it is not equivalent. After every team_task_update that completes or fails a task, re-check team_task_list: if every task is terminal, run the closure sequence (team_shutdown_request + team_approve_shutdown per active member, then team_delete) in the same turn. Closing the team is the lead's responsibility, not the user's. If the team_* tools are absent, team_mode is disabled — tell the user to set team_mode.enabled=true and restart opencode.`
export const TEAM_MESSAGE = TEAM_MODE_PROMPT
+4
View File
@@ -0,0 +1,4 @@
declare module "*.md" {
const markdown: string
export default markdown
}