Skip to content

Commit 8e93ccb

Browse files
committed
chore(root): sync oh-my-agent with new skills and hook updates
- Add oma-hwp, oma-observability, oma-search skills - Add skill-injector hook across variants - Update keyword-detector, persistent-mode, hud, test-filter, triggers - Refresh frontend/mobile/backend/debug/qa/pm/tf-infra execution protocols - Sync mirrors (.claude, .codex, .cursor, .gemini, .qwen, .github)
1 parent 54fd228 commit 8e93ccb

125 files changed

Lines changed: 29225 additions & 1829 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/agents/variants/agent-variant.schema.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
"title": "Agent Variant Configuration",
44
"description": "Configuration for vendor-specific agent generation from core agent prompts",
55
"type": "object",
6-
"required": ["vendor", "destDir", "modelDefault", "toolsDefault", "protocolPath", "agents"],
6+
"required": [
7+
"vendor",
8+
"destDir",
9+
"modelDefault",
10+
"toolsDefault",
11+
"protocolPath",
12+
"agents"
13+
],
714
"properties": {
815
"$schema": {
916
"type": "string"

.agents/agents/variants/gemini.json

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,7 @@
33
"vendor": "gemini",
44
"destDir": ".gemini/agents",
55
"modelDefault": "gemini-3-flash-preview",
6-
"toolsDefault": [
7-
"bash",
8-
"glob",
9-
"grep",
10-
"read",
11-
"edit",
12-
"write",
13-
"ask"
14-
],
6+
"toolsDefault": ["bash", "glob", "grep", "read", "edit", "write", "ask"],
157
"protocolPath": ".agents/skills/_shared/runtime/execution-protocols/gemini.md",
168
"agents": {
179
"backend-engineer": {},
@@ -22,22 +14,10 @@
2214
"tf-infra-engineer": {},
2315
"mobile-engineer": {},
2416
"pm-planner": {
25-
"tools": [
26-
"bash",
27-
"glob",
28-
"grep",
29-
"read",
30-
"ask"
31-
]
17+
"tools": ["bash", "glob", "grep", "read", "ask"]
3218
},
3319
"qa-reviewer": {
34-
"tools": [
35-
"bash",
36-
"glob",
37-
"grep",
38-
"read",
39-
"ask"
40-
]
20+
"tools": ["bash", "glob", "grep", "read", "ask"]
4121
}
4222
}
4323
}

.agents/hooks/core/hud.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* stdout: ANSI-colored status text
1010
*/
1111

12-
import { readFileSync, readdirSync, existsSync } from "node:fs";
12+
import { existsSync, readdirSync, readFileSync } from "node:fs";
1313
import { join } from "node:path";
1414
import type { ModeState } from "./types.ts";
1515

@@ -111,15 +111,18 @@ function formatRateLimit(label: string, rl?: RateLimit): string | null {
111111
if (!rl || rl.used_percentage == null) return null;
112112
const pct = Math.round(rl.used_percentage);
113113
const countdown = rl.resets_at ? formatCountdown(rl.resets_at) : "";
114-
const text = countdown ? `${label}:${pct}%(${countdown})` : `${label}:${pct}%`;
114+
const text = countdown
115+
? `${label}:${pct}%(${countdown})`
116+
: `${label}:${pct}%`;
115117
return colorByThreshold(pct, text);
116118
}
117119

118120
// ── Main ──────────────────────────────────────────────────────
119121

120122
function main() {
121123
const input = readStdin();
122-
const projectDir = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();
124+
const projectDir =
125+
process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();
123126
const parts: string[] = [];
124127

125128
// 1. OMA label

.agents/hooks/core/keyword-detector.ts

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,21 @@
1212
* exit 0 = always (allow)
1313
*/
1414

15-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, readdirSync } from "node:fs";
16-
import { join, dirname } from "node:path";
17-
import { type Vendor, type ModeState, makePromptOutput, resolveGitRoot } from "./types.ts";
15+
import {
16+
existsSync,
17+
mkdirSync,
18+
readdirSync,
19+
readFileSync,
20+
unlinkSync,
21+
writeFileSync,
22+
} from "node:fs";
23+
import { dirname, join } from "node:path";
24+
import {
25+
type ModeState,
26+
makePromptOutput,
27+
resolveGitRoot,
28+
type Vendor,
29+
} from "./types.ts";
1830

1931
// ── Vendor Detection ──────────────────────────────────────────
2032

@@ -43,10 +55,7 @@ function detectVendor(input: Record<string, unknown>): Vendor {
4355
return "claude";
4456
}
4557

46-
function getProjectDir(
47-
vendor: Vendor,
48-
input: Record<string, unknown>,
49-
): string {
58+
function getProjectDir(vendor: Vendor, input: Record<string, unknown>): string {
5059
let dir: string;
5160
switch (vendor) {
5261
case "codex":
@@ -68,9 +77,7 @@ function getProjectDir(
6877

6978
function getSessionId(input: Record<string, unknown>): string {
7079
return (
71-
(input.sessionId as string) ||
72-
(input.session_id as string) ||
73-
"unknown"
80+
(input.sessionId as string) || (input.session_id as string) || "unknown"
7481
);
7582
}
7683

@@ -120,13 +127,13 @@ export function buildPatterns(
120127
): RegExp[] {
121128
const allKeywords = [
122129
...(keywords["*"] ?? []),
123-
...(keywords["en"] ?? []),
130+
...(keywords.en ?? []),
124131
...(lang !== "en" ? (keywords[lang] ?? []) : []),
125132
];
126133

127134
return allKeywords.map((kw) => {
128135
const escaped = escapeRegex(kw).replace(/\s+/g, "\\s+");
129-
if (cjkScripts.includes(lang) || /[^\x00-\x7F]/.test(kw)) {
136+
if (cjkScripts.includes(lang) || /[^\p{ASCII}]/u.test(kw)) {
130137
return new RegExp(escaped, "i");
131138
}
132139
return new RegExp(`\\b${escaped}\\b`, "i");
@@ -137,12 +144,12 @@ function buildInformationalPatterns(
137144
config: TriggerConfig,
138145
lang: string,
139146
): RegExp[] {
140-
const patterns = [...(config.informationalPatterns["en"] ?? [])];
147+
const patterns = [...(config.informationalPatterns.en ?? [])];
141148
if (lang !== "en") {
142149
patterns.push(...(config.informationalPatterns[lang] ?? []));
143150
}
144151
return patterns.map((p) => {
145-
if (/[^\x00-\x7F]/.test(p)) return new RegExp(escapeRegex(p), "i");
152+
if (/[^\p{ASCII}]/u.test(p)) return new RegExp(escapeRegex(p), "i");
146153
return new RegExp(`\\b${escapeRegex(p)}\\b`, "i");
147154
});
148155
}
@@ -209,11 +216,11 @@ export function isAnalyticalQuestion(prompt: string): boolean {
209216

210217
export function stripCodeBlocks(text: string): string {
211218
return text
212-
.replace(/(`{3,})[^\n]*\n[\s\S]*?\1/g, "") // multiline fenced blocks (3+ backticks, matched closing)
213-
.replace(/(`{3,})[^\n]*\n[\s\S]*/g, "") // unclosed fenced blocks (strip to end)
214-
.replace(/`{3,}[^`]*`{3,}/g, "") // single-line fenced blocks (```...```)
215-
.replace(/`[^`\n]+`/g, "") // inline code (no newlines allowed)
216-
.replace(/"[^"\n]*"/g, ""); // quoted strings
219+
.replace(/(`{3,})[^\n]*\n[\s\S]*?\1/g, "") // multiline fenced blocks (3+ backticks, matched closing)
220+
.replace(/(`{3,})[^\n]*\n[\s\S]*/g, "") // unclosed fenced blocks (strip to end)
221+
.replace(/`{3,}[^`]*`{3,}/g, "") // single-line fenced blocks (```...```)
222+
.replace(/`[^`\n]+`/g, "") // inline code (no newlines allowed)
223+
.replace(/"[^"\n]*"/g, ""); // quoted strings
217224
}
218225

219226
export function startsWithSlashCommand(prompt: string): boolean {
@@ -223,18 +230,37 @@ export function startsWithSlashCommand(prompt: string): boolean {
223230
// ── Extension Detection ──────────────────────────────────────
224231

225232
const EXCLUDE_EXTS = new Set([
226-
"md", "json", "yaml", "yml", "txt", "env", "git",
227-
"lock", "log", "toml", "cfg", "ini", "conf",
228-
"png", "jpg", "jpeg", "gif", "ico", "webp",
229-
"woff", "woff2", "ttf", "eot",
230-
"map", "d",
233+
"md",
234+
"json",
235+
"yaml",
236+
"yml",
237+
"txt",
238+
"env",
239+
"git",
240+
"lock",
241+
"log",
242+
"toml",
243+
"cfg",
244+
"ini",
245+
"conf",
246+
"png",
247+
"jpg",
248+
"jpeg",
249+
"gif",
250+
"ico",
251+
"webp",
252+
"woff",
253+
"woff2",
254+
"ttf",
255+
"eot",
256+
"map",
257+
"d",
231258
]);
232259

233260
export function detectExtensions(prompt: string): string[] {
234261
const extPattern = /\.([a-zA-Z]{1,12})\b/g;
235262
const extensions = new Set<string>();
236-
let match: RegExpExecArray | null;
237-
while ((match = extPattern.exec(prompt)) !== null) {
263+
for (const match of prompt.matchAll(extPattern)) {
238264
const ext = match[1].toLowerCase();
239265
if (!EXCLUDE_EXTS.has(ext)) {
240266
extensions.add(ext);
@@ -313,14 +339,17 @@ export const DEACTIVATION_PHRASES: Record<string, string[]> = {
313339

314340
export function isDeactivationRequest(prompt: string, lang: string): boolean {
315341
const phrases = [
316-
...(DEACTIVATION_PHRASES["en"] ?? []),
342+
...(DEACTIVATION_PHRASES.en ?? []),
317343
...(lang !== "en" ? (DEACTIVATION_PHRASES[lang] ?? []) : []),
318344
];
319345
const lower = prompt.toLowerCase();
320346
return phrases.some((phrase) => lower.includes(phrase.toLowerCase()));
321347
}
322348

323-
export function deactivateAllPersistentModes(projectDir: string, sessionId?: string): void {
349+
export function deactivateAllPersistentModes(
350+
projectDir: string,
351+
sessionId?: string,
352+
): void {
324353
const stateDir = join(projectDir, ".agents", "state");
325354
if (!existsSync(stateDir)) return;
326355
try {
@@ -387,7 +416,8 @@ async function main() {
387416
if (!match) continue;
388417
if (isInformationalContext(cleaned, match.index, infoPatterns)) continue;
389418
// Keywords deep in long prompts are likely pasted content, not user intent
390-
if (isPastedContent(match.index, def.persistent, cleaned.length)) continue;
419+
if (isPastedContent(match.index, def.persistent, cleaned.length))
420+
continue;
391421

392422
if (def.persistent) {
393423
activateMode(projectDir, workflow, sessionId);
@@ -403,7 +433,10 @@ async function main() {
403433

404434
if (config.extensionRouting) {
405435
const extensions = detectExtensions(prompt);
406-
const agent = resolveAgentFromExtensions(extensions, config.extensionRouting);
436+
const agent = resolveAgentFromExtensions(
437+
extensions,
438+
config.extensionRouting,
439+
);
407440
if (agent) {
408441
contextLines.push(`[OMA AGENT HINT: ${agent}]`);
409442
}

.agents/hooks/core/persistent-mode.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@
1313
* exit 2 = block stop
1414
*/
1515

16-
import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync } from "node:fs";
17-
import { join, dirname } from "node:path";
18-
import { type Vendor, type ModeState, makeBlockOutput, resolveGitRoot } from "./types.ts";
19-
import { DEACTIVATION_PHRASES, isDeactivationRequest } from "./keyword-detector.ts";
16+
import {
17+
existsSync,
18+
readdirSync,
19+
readFileSync,
20+
unlinkSync,
21+
writeFileSync,
22+
} from "node:fs";
23+
import { dirname, join } from "node:path";
24+
import { isDeactivationRequest } from "./keyword-detector.ts";
25+
import {
26+
type ModeState,
27+
makeBlockOutput,
28+
resolveGitRoot,
29+
type Vendor,
30+
} from "./types.ts";
2031

2132
const MAX_REINFORCEMENTS = 5;
2233
const STALE_HOURS = 2;
@@ -63,10 +74,7 @@ function detectVendor(input: Record<string, unknown>): Vendor {
6374
return "claude";
6475
}
6576

66-
function getProjectDir(
67-
vendor: Vendor,
68-
input: Record<string, unknown>,
69-
): string {
77+
function getProjectDir(vendor: Vendor, input: Record<string, unknown>): string {
7078
let dir: string;
7179
switch (vendor) {
7280
case "codex":
@@ -87,9 +95,7 @@ function getProjectDir(
8795

8896
function getSessionId(input: Record<string, unknown>): string {
8997
return (
90-
(input.sessionId as string) ||
91-
(input.session_id as string) ||
92-
"unknown"
98+
(input.sessionId as string) || (input.session_id as string) || "unknown"
9399
);
94100
}
95101

@@ -104,7 +110,10 @@ function readModeState(
104110
workflow: string,
105111
sessionId: string,
106112
): ModeState | null {
107-
const path = join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`);
113+
const path = join(
114+
getStateDir(projectDir),
115+
`${workflow}-state-${sessionId}.json`,
116+
);
108117
if (!existsSync(path)) return null;
109118
try {
110119
return JSON.parse(readFileSync(path, "utf-8")) as ModeState;
@@ -118,8 +127,15 @@ export function isStale(state: ModeState): boolean {
118127
return elapsed > STALE_HOURS * 60 * 60 * 1000;
119128
}
120129

121-
export function deactivate(projectDir: string, workflow: string, sessionId: string): void {
122-
const path = join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`);
130+
export function deactivate(
131+
projectDir: string,
132+
workflow: string,
133+
sessionId: string,
134+
): void {
135+
const path = join(
136+
getStateDir(projectDir),
137+
`${workflow}-state-${sessionId}.json`,
138+
);
123139
if (existsSync(path)) unlinkSync(path);
124140
}
125141

@@ -156,7 +172,7 @@ async function main() {
156172
// The assistant may have included "workflow done" in its response,
157173
// or it may appear in transcript/content fields depending on vendor.
158174
const textToCheck = [
159-
input.prompt_response, // Gemini AfterAgent
175+
input.prompt_response, // Gemini AfterAgent
160176
input.response,
161177
input.content,
162178
input.message,
@@ -176,7 +192,9 @@ async function main() {
176192
unlinkSync(join(stateDir, file));
177193
}
178194
}
179-
} catch { /* ignore */ }
195+
} catch {
196+
/* ignore */
197+
}
180198
}
181199
process.exit(0);
182200
}

0 commit comments

Comments
 (0)