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:
@@ -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
@@ -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.
|
||||
@@ -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
@@ -0,0 +1,4 @@
|
||||
declare module "*.md" {
|
||||
const markdown: string
|
||||
export default markdown
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module "*.md" {
|
||||
const markdown: string
|
||||
export default markdown
|
||||
}
|
||||
Reference in New Issue
Block a user