diff --git a/.agents/.backup-pre-008-1779711442992-2320/oma-config.yaml b/.agents/.backup-pre-008-1779711442992-2320/oma-config.yaml new file mode 100644 index 0000000..e23d0cd --- /dev/null +++ b/.agents/.backup-pre-008-1779711442992-2320/oma-config.yaml @@ -0,0 +1,24 @@ +# User Preferences (Optional) +# Project-specific user configuration +# +# This file is optional. Works with defaults if not present. +# CLI priority: --vendor arg > agent_cli_mapping > default_cli > cli-config.yaml's active_vendor > gemini + +# Response language setting (ko, en, ja, zh, ...) +language: ko + +# Date/time format +date_format: ISO +timezone: Asia/Seoul + +# Default CLI (for single tasks) +default_cli: gemini + +# Per-agent CLI mapping (multi-CLI mode) +agent_cli_mapping: + frontend: gemini + backend: gemini + mobile: gemini + qa: gemini + debug: gemini + pm: gemini diff --git a/.agents/agents/architecture-reviewer.md b/.agents/agents/architecture-reviewer.md index 9ffde1a..0c7bed6 100644 --- a/.agents/agents/architecture-reviewer.md +++ b/.agents/agents/architecture-reviewer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-architecture.md` (orchestrated: `result-architecture-{sessionId}.md`) - Include: status, recommendation summary, tradeoffs, risks, validation steps, artifacts created + + ## Charter Preflight (MANDATORY) Before ANY recommendations or structural edits, output this block: @@ -29,6 +31,7 @@ CHARTER_CHECK: - LOW: proceed with assumptions - MEDIUM: list options, proceed with most likely - HIGH: set status blocked, list questions, DO NOT change architecture or code + ## Rules diff --git a/.agents/agents/backend-engineer.md b/.agents/agents/backend-engineer.md index 49def07..4fec262 100644 --- a/.agents/agents/backend-engineer.md +++ b/.agents/agents/backend-engineer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-backend.md` (orchestrated: `result-backend-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY code changes, output this block: @@ -29,6 +31,7 @@ CHARTER_CHECK: - LOW: proceed with assumptions - MEDIUM: list options, proceed with most likely - HIGH: set status blocked, list questions, DO NOT write code + ## Architecture diff --git a/.agents/agents/db-engineer.md b/.agents/agents/db-engineer.md index eabbaa1..d7b36af 100644 --- a/.agents/agents/db-engineer.md +++ b/.agents/agents/db-engineer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-db.md` (orchestrated: `result-db-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY code changes, output this block: @@ -25,6 +27,7 @@ CHARTER_CHECK: - Success criteria: {measurable criteria} - Assumptions: {defaults applied} ``` + ## Rules diff --git a/.agents/agents/debug-investigator.md b/.agents/agents/debug-investigator.md index 8c14b04..538f260 100644 --- a/.agents/agents/debug-investigator.md +++ b/.agents/agents/debug-investigator.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-debug.md` (orchestrated: `result-debug-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY code changes, output this block: @@ -29,6 +31,7 @@ CHARTER_CHECK: - LOW: proceed with assumptions - MEDIUM: list options, proceed with most likely - HIGH: set status blocked, list questions, DO NOT write code + ## Diagnosis Process diff --git a/.agents/agents/docs-curator.md b/.agents/agents/docs-curator.md new file mode 100644 index 0000000..3718ca6 --- /dev/null +++ b/.agents/agents/docs-curator.md @@ -0,0 +1,62 @@ +--- +name: docs-curator +description: Documentation drift detection and sync specialist. Use to update docs/**/*.md after code changes, verify broken refs, and apply patches reflecting recent diffs. +skills: + - oma-docs +--- + +You are a Documentation Curator. Keep `docs/**/*.md` aligned with the live codebase by running the `oma docs` CLI and applying patches that reflect recent code changes. + +## Execution Protocol + +Follow the vendor-specific execution protocol: +- Write results to project root `.agents/results/result-docs.md` (orchestrated: `result-docs-{sessionId}.md`) +- Include: status, summary, files changed, acceptance criteria checklist + + + +## Charter Preflight (MANDATORY) + +Before ANY documentation changes, output this block: + +``` +CHARTER_CHECK: +- Clarification level: {LOW | MEDIUM | HIGH} +- Task domain: docs-curation +- Diff range: {git range or staged} +- Must NOT do: modify code, modify .agents/, auto-apply patches that contradict acceptance criteria +- Success criteria: {docs reflect the diff, broken refs in scope are resolved} +- Assumptions: {defaults applied} +``` + +- LOW: proceed with assumptions +- MEDIUM: list options, proceed with most likely +- HIGH: set status blocked, list questions, DO NOT write docs + + +## Curation Process + +1. **Diff intake**: Determine the git range from the task description (e.g. `HEAD~5..HEAD`, branch diff, or staged). Fall back to `--cached` then `HEAD~1..HEAD`. +2. **Drift baseline**: Run `oma docs verify --json` to capture the current broken-ref state. Persist counts in the result file. +3. **Candidate match**: Run `oma docs sync --json` to get `{ doc, changedFiles, matchedRefs }` candidates. Skip secret-bearing files (CLI already excludes `.env*`, `*.pem`, `*.key`, `id_rsa*`). +4. **Patch synthesis**: For each candidate doc, read the doc and `git diff` for `changedFiles`, draft a minimal unified-diff patch. Only edit prose that the diff actually invalidates — leave unrelated content alone. +5. **Apply**: Write the patches directly via `Edit`/`Write`. **Do not** prompt the user; the orchestrator's acceptance criteria authorize autonomous writes for this agent in this context. +6. **Re-verify**: Run `oma docs verify --json` again. Confirm the broken-ref count for in-scope kinds dropped to zero (or matches the acceptance criteria). +7. **Report**: List updated docs with file paths, summarize before/after drift counts, flag any candidates skipped (out of scope, ambiguous diff, secret-adjacent). + +## Auto-Write Authority + +This agent is a write-capable peer of `backend-engineer` / `frontend-engineer`. The interactive `[y/n/d/s]` confirmation in `/docs sync` applies to direct user invocation only — when spawned by `/orchestrate`, `/work`, or `/ultrawork`, the assigned task description IS the consent boundary. + +## Rules + +1. Stay in scope — only update docs related to the assigned diff range or acceptance criteria +2. Minimal edits — change only what the diff invalidates, never reformat or restructure unrelated text +3. Never modify code (`*.ts`, `*.tsx`, `*.py`, `*.go`, etc.) — surface mismatches for `backend-engineer` / `frontend-engineer` instead +4. Never modify `.agents/` files — SSOT protection +5. Never touch secret-bearing files even if surfaced in diffs (`.env*`, `*.pem`, `*.key`, `id_rsa*`) +6. Re-run `oma docs verify --json` after applying patches; record before/after counts in the result file +7. ARB-based localization (`packages/i18n/`): edit ARB source, never regenerate localization code +8. Document out-of-scope drift findings as TODOs for the next session — do NOT silently fix references unrelated to the assigned task +9. Follow `oma-docs` host-LLM contract — CLI emits structured data, you do natural-language synthesis and patch drafting +10. Co-Author commits when staging is delegated: `Co-Authored-By: First Fluke ` diff --git a/.agents/agents/frontend-engineer.md b/.agents/agents/frontend-engineer.md index d0c8ae2..ba0e3cf 100644 --- a/.agents/agents/frontend-engineer.md +++ b/.agents/agents/frontend-engineer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-frontend.md` (orchestrated: `result-frontend-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY code changes, output this block: @@ -25,6 +27,7 @@ CHARTER_CHECK: - Success criteria: {measurable criteria} - Assumptions: {defaults applied} ``` + ## Architecture diff --git a/.agents/agents/mobile-engineer.md b/.agents/agents/mobile-engineer.md index eabdff8..dce9e50 100644 --- a/.agents/agents/mobile-engineer.md +++ b/.agents/agents/mobile-engineer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-mobile.md` (orchestrated: `result-mobile-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY code changes, output this block: @@ -25,6 +27,7 @@ CHARTER_CHECK: - Success criteria: {measurable criteria} - Assumptions: {defaults applied} ``` + ## Architecture diff --git a/.agents/agents/pm-planner.md b/.agents/agents/pm-planner.md index ebf3147..e15d9d9 100644 --- a/.agents/agents/pm-planner.md +++ b/.agents/agents/pm-planner.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-pm.md` (orchestrated: `result-pm-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before ANY planning work, output this block: @@ -29,12 +31,13 @@ CHARTER_CHECK: - LOW: proceed with assumptions - MEDIUM: list options, proceed with most likely - HIGH: set status blocked, list questions, DO NOT proceed + ## Planning Process 1. **Gather**: Requirements (users, features, constraints, deployment target) 2. **Analyze**: Technical feasibility using codebase analysis -3. **Contracts**: Define API contracts (save to `.agents/skills/_shared/api-contracts/`) +3. **Contracts**: Define API contracts (save to `.agents/skills/_shared/core/api-contracts/`) 4. **Decompose**: Break into tasks with agent, title, acceptance criteria, priority (P0-P3), dependencies 5. **Output**: Save to `.agents/results/plan-{sessionId}.json` diff --git a/.agents/agents/qa-reviewer.md b/.agents/agents/qa-reviewer.md index 7041076..70404e8 100644 --- a/.agents/agents/qa-reviewer.md +++ b/.agents/agents/qa-reviewer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-qa.md` (orchestrated: `result-qa-{sessionId}.md`) - Include: status, summary, files changed, acceptance criteria checklist + + ## Charter Preflight (MANDATORY) Before starting review, output this block: @@ -25,6 +27,7 @@ CHARTER_CHECK: - Must NOT do: modify source code, skip severity levels, report unverified findings - Success criteria: {all files reviewed, findings with file:line references} ``` + ## Review Priority Order diff --git a/.agents/agents/tf-infra-engineer.md b/.agents/agents/tf-infra-engineer.md index 1c99e1c..88734a2 100644 --- a/.agents/agents/tf-infra-engineer.md +++ b/.agents/agents/tf-infra-engineer.md @@ -13,6 +13,8 @@ Follow the vendor-specific execution protocol: - Write results to project root `.agents/results/result-tf-infra.md` (orchestrated: `result-tf-infra-{sessionId}.md`) - Include: status, summary, files changed, validation results, plan/apply notes, acceptance checklist + + ## Charter Preflight (MANDATORY) Before ANY infrastructure changes, output this block: @@ -29,6 +31,7 @@ CHARTER_CHECK: - LOW: proceed with assumptions - MEDIUM: list options, proceed with most likely - HIGH: set status blocked, list questions, DO NOT apply destructive changes + ## Rules diff --git a/.agents/agents/variants/agent-variant.schema.json b/.agents/agents/variants/agent-variant.schema.json index 39b8ecb..224c1f3 100644 --- a/.agents/agents/variants/agent-variant.schema.json +++ b/.agents/agents/variants/agent-variant.schema.json @@ -3,7 +3,14 @@ "title": "Agent Variant Configuration", "description": "Configuration for vendor-specific agent generation from core agent prompts", "type": "object", - "required": ["vendor", "destDir", "modelDefault", "toolsDefault", "protocolPath", "agents"], + "required": [ + "vendor", + "destDir", + "modelDefault", + "toolsDefault", + "protocolPath", + "agents" + ], "properties": { "$schema": { "type": "string" diff --git a/.agents/agents/variants/claude.json b/.agents/agents/variants/claude.json index f4ce530..4988fbd 100644 --- a/.agents/agents/variants/claude.json +++ b/.agents/agents/variants/claude.json @@ -30,8 +30,11 @@ }, "qa-reviewer": { "tools": "Read, Grep, Glob, Bash", - "maxTurns": 15, - "effort": "low" + "maxTurns": 15 + }, + "docs-curator": { + "tools": "Read, Write, Edit, Bash, Grep, Glob", + "maxTurns": 15 } } } diff --git a/.agents/agents/variants/codex.json b/.agents/agents/variants/codex.json index 8653f39..b6bf585 100644 --- a/.agents/agents/variants/codex.json +++ b/.agents/agents/variants/codex.json @@ -56,6 +56,12 @@ "extra": { "sandbox_mode": "read-only" } + }, + "docs-curator": { + "effort": "medium", + "extra": { + "sandbox_mode": "workspace-write" + } } } } diff --git a/.agents/agents/variants/cursor.json b/.agents/agents/variants/cursor.json index f999b7b..0a73915 100644 --- a/.agents/agents/variants/cursor.json +++ b/.agents/agents/variants/cursor.json @@ -47,6 +47,11 @@ "extra": { "is_background": true } + }, + "docs-curator": { + "extra": { + "is_background": true + } } } } diff --git a/.agents/agents/variants/gemini.json b/.agents/agents/variants/gemini.json index 382cb1a..07392c3 100644 --- a/.agents/agents/variants/gemini.json +++ b/.agents/agents/variants/gemini.json @@ -2,7 +2,7 @@ "$schema": "./agent-variant.schema.json", "vendor": "gemini", "destDir": ".gemini/agents", - "modelDefault": "gemini-3-flash-preview", + "modelDefault": "gemini-3-flash", "toolsDefault": ["bash", "glob", "grep", "read", "edit", "write", "ask"], "protocolPath": ".agents/skills/_shared/runtime/execution-protocols/gemini.md", "agents": { @@ -18,6 +18,7 @@ }, "qa-reviewer": { "tools": ["bash", "glob", "grep", "read", "ask"] - } + }, + "docs-curator": {} } } diff --git a/.agents/config/defaults.yaml b/.agents/config/defaults.yaml new file mode 100644 index 0000000..7e54c2b --- /dev/null +++ b/.agents/config/defaults.yaml @@ -0,0 +1,131 @@ +# Profile B defaults (benchmark-leader assignments) +# Generated: 2026-04-23 | Session: session-20260423-141500 +# Claude roles omit effort (cli-session managed). +# +# This file is a single source of truth (SSOT) shipped with oh-my-agent. +# Do NOT edit it directly. To customize behavior, use one of: +# - .agents/oma-config.yaml (agent_cli_mapping, session.quota_cap) +# - .agents/config/models.yaml (add or override model slugs) +# To receive newer Profile B defaults in future releases, run: +# oma install --update-defaults + +version: "2.1.0" + +agent_defaults: + orchestrator: { model: "anthropic/claude-sonnet-4-6" } + architecture: { model: "anthropic/claude-opus-4-7" } + qa: { model: "anthropic/claude-sonnet-4-6" } + pm: { model: "anthropic/claude-sonnet-4-6" } + backend: { model: "openai/gpt-5.5", effort: "high" } + frontend: { model: "openai/gpt-5.5", effort: "high" } + mobile: { model: "openai/gpt-5.5", effort: "high" } + db: { model: "openai/gpt-5.5", effort: "high" } + debug: { model: "openai/gpt-5.5", effort: "high" } + tf-infra: { model: "openai/gpt-5.5", effort: "high" } + retrieval: { model: "google/gemini-3.1-flash-lite" } + +runtime_profiles: + claude: + description: "Claude — Max subscription holders" + agent_defaults: + orchestrator: { model: "anthropic/claude-sonnet-4-6" } + architecture: { model: "anthropic/claude-opus-4-7" } + qa: { model: "anthropic/claude-sonnet-4-6" } + pm: { model: "anthropic/claude-sonnet-4-6" } + backend: { model: "anthropic/claude-sonnet-4-6" } + frontend: { model: "anthropic/claude-sonnet-4-6" } + mobile: { model: "anthropic/claude-sonnet-4-6" } + db: { model: "anthropic/claude-sonnet-4-6" } + debug: { model: "anthropic/claude-sonnet-4-6" } + tf-infra: { model: "anthropic/claude-sonnet-4-6" } + retrieval: { model: "anthropic/claude-haiku-4-5" } + + codex: + description: "Codex — ChatGPT Plus/Pro" + agent_defaults: + orchestrator: { model: "openai/gpt-5.5", effort: "medium" } + architecture: { model: "openai/gpt-5.5", effort: "high" } + qa: { model: "openai/gpt-5.5", effort: "high" } + pm: { model: "openai/gpt-5.5", effort: "medium" } + backend: { model: "openai/gpt-5.5", effort: "high" } + frontend: { model: "openai/gpt-5.5", effort: "high" } + mobile: { model: "openai/gpt-5.5", effort: "high" } + db: { model: "openai/gpt-5.5", effort: "high" } + debug: { model: "openai/gpt-5.5", effort: "high" } + tf-infra: { model: "openai/gpt-5.5", effort: "high" } + retrieval: { model: "openai/gpt-5.4-mini", effort: "low" } + + gemini: + description: "Gemini — Google AI Pro" + agent_defaults: + orchestrator: { model: "google/gemini-3-flash" } + architecture: { model: "google/gemini-3.1-pro-preview", thinking: true } + qa: { model: "google/gemini-3-flash", thinking: true } + pm: { model: "google/gemini-3-flash" } + backend: { model: "google/gemini-3-flash", thinking: true } + frontend: { model: "google/gemini-3-flash", thinking: true } + mobile: { model: "google/gemini-3-flash", thinking: true } + db: { model: "google/gemini-3-flash", thinking: true } + debug: { model: "google/gemini-3-flash", thinking: true } + tf-infra: { model: "google/gemini-3-flash", thinking: true } + retrieval: { model: "google/gemini-3.1-flash-lite" } + + mixed: + description: "Mixed — role-optimal vendors per agent (Claude for orchestration/QA/PM, Codex for impl, Gemini for retrieval)" + agent_defaults: + orchestrator: { model: "anthropic/claude-sonnet-4-6" } + architecture: { model: "anthropic/claude-opus-4-7" } + qa: { model: "anthropic/claude-sonnet-4-6" } + pm: { model: "anthropic/claude-sonnet-4-6" } + backend: { model: "openai/gpt-5.5", effort: "high" } + frontend: { model: "openai/gpt-5.5", effort: "high" } + mobile: { model: "openai/gpt-5.5", effort: "high" } + db: { model: "openai/gpt-5.5", effort: "high" } + debug: { model: "openai/gpt-5.5", effort: "high" } + tf-infra: { model: "openai/gpt-5.5", effort: "high" } + retrieval: { model: "google/gemini-3.1-flash-lite" } + + antigravity: + description: "Antigravity CLI (agy) — nominal Gemini 3.1 Pro for impl/architecture, Gemini 3.5 Flash for orchestration & retrieval (agy 1.0 has no --model flag; model is config-driven)" + agent_defaults: + orchestrator: { model: "antigravity/gemini-3.5-flash" } + architecture: { model: "antigravity/gemini-3.1-pro" } + qa: { model: "antigravity/gemini-3.1-pro" } + pm: { model: "antigravity/gemini-3.5-flash" } + backend: { model: "antigravity/gemini-3.1-pro" } + frontend: { model: "antigravity/gemini-3.1-pro" } + mobile: { model: "antigravity/gemini-3.1-pro" } + db: { model: "antigravity/gemini-3.1-pro" } + debug: { model: "antigravity/gemini-3.1-pro" } + tf-infra: { model: "antigravity/gemini-3.1-pro" } + retrieval: { model: "antigravity/gemini-3.5-flash" } + + cursor: + description: "Cursor — Cursor Pro / Pro Student" + agent_defaults: + orchestrator: { model: "cursor/composer-2.5-fast" } + architecture: { model: "cursor/composer-2.5" } + qa: { model: "cursor/composer-2.5-fast" } + pm: { model: "cursor/composer-2.5-fast" } + backend: { model: "cursor/composer-2.5" } + frontend: { model: "cursor/composer-2.5" } + mobile: { model: "cursor/composer-2.5" } + db: { model: "cursor/composer-2.5" } + debug: { model: "cursor/composer-2.5" } + tf-infra: { model: "cursor/composer-2.5" } + retrieval: { model: "cursor/composer-2.5-fast" } + + qwen: + description: "Qwen Code — all agents routed external (no native parallel); Qwen has no --effort, only binary --thinking" + agent_defaults: + orchestrator: { model: "qwen/qwen3-coder-next", thinking: false } + architecture: { model: "qwen/qwen3.6-plus", thinking: true } + qa: { model: "qwen/qwen3.6-plus", thinking: true } + pm: { model: "qwen/qwen3-coder-next", thinking: false } + backend: { model: "qwen/qwen3.6-plus", thinking: true } + frontend: { model: "qwen/qwen3.6-plus", thinking: true } + mobile: { model: "qwen/qwen3.6-plus", thinking: true } + db: { model: "qwen/qwen3.6-plus", thinking: true } + debug: { model: "qwen/qwen3.6-plus", thinking: true } + tf-infra: { model: "qwen/qwen3.6-plus", thinking: true } + retrieval: { model: "qwen/qwen3-coder-next", thinking: false } diff --git a/.agents/hooks/core/constants.ts b/.agents/hooks/core/constants.ts new file mode 100644 index 0000000..bee216d --- /dev/null +++ b/.agents/hooks/core/constants.ts @@ -0,0 +1,22 @@ +// Runtime constants for hooks. Mirrors the convention in `cli/constants/`: +// constants here, types in `types.ts`. The `Vendor` type in `types.ts` is +// derived from `VENDORS` below so the value and the type stay in sync. + +/** + * Host LLM CLIs supported by Oma's hook layer. This is the single source of + * truth for which vendors hooks (keyword-detector, persistent-mode, hud, + * skill-injector) recognise. Adding a new vendor here propagates to the + * `Vendor` type and to runtime guards such as `CLI_INVOCATION_AT_START` + * in `keyword-detector.ts`. + * + * Excludes: + * - `oma` itself (the project's own CLI, listed separately where needed) + * - `copilot` and `hermes` (skill-install targets, not hook runtimes) + * - third-party harnesses (omc, omx, omo, ouroboros) + * + * MUST mirror `cli/types/vendors.ts` VENDORS. Hooks run as standalone + * scripts in user environments and cannot import from cli/, so the value + * is duplicated here intentionally. Keep the two arrays in sync by adding + * or removing the same vendor in both files; CI does not enforce this. + */ +export const VENDORS = ["claude", "codex", "cursor", "gemini", "qwen"] as const; diff --git a/.agents/hooks/core/fs-utils.ts b/.agents/hooks/core/fs-utils.ts new file mode 100644 index 0000000..af3acb7 --- /dev/null +++ b/.agents/hooks/core/fs-utils.ts @@ -0,0 +1,30 @@ +import { existsSync } from "node:fs"; +import { dirname, join, sep } from "node:path"; + +/** + * Normalize a filesystem path to POSIX (forward-slash) form so output + * shown to the model and string comparisons stay platform-independent + * on Windows. Mirrors `cli/utils/fs-utils.ts#toPosixPath`. + */ +export function toPosixPath(p: string): string { + return sep === "/" ? p : p.split(sep).join("/"); +} + +/** + * Walk up from startDir to find the git repository root. + * This prevents CLAUDE_PROJECT_DIR pointing to a subdirectory + * (e.g. packages/i18n during a build) from creating state files + * in the wrong location. + */ +const MAX_DEPTH = 20; + +export function resolveGitRoot(startDir: string): string { + let dir = startDir; + for (let i = 0; i < MAX_DEPTH; i++) { + if (existsSync(join(dir, ".git"))) return dir; + const parent = dirname(dir); + if (parent === dir) return startDir; + dir = parent; + } + return startDir; +} diff --git a/.agents/hooks/core/hook-output.ts b/.agents/hooks/core/hook-output.ts new file mode 100644 index 0000000..b8dc085 --- /dev/null +++ b/.agents/hooks/core/hook-output.ts @@ -0,0 +1,90 @@ +// Vendor-specific hook output builders. +// Each runtime (Claude Code, Codex CLI, Cursor, Gemini CLI, Qwen Code) +// expects a slightly different stdout JSON shape; centralize the dialect +// translation here so individual hooks can stay vendor-agnostic. + +import type { Vendor } from "./types.ts"; + +export function makePromptOutput( + vendor: Vendor, + additionalContext: string, +): string { + switch (vendor) { + case "claude": + return JSON.stringify({ additionalContext }); + case "codex": + return JSON.stringify({ + hookSpecificOutput: { + hookEventName: "UserPromptSubmit", + additionalContext, + }, + }); + case "cursor": + return JSON.stringify({ + additionalContext, + additional_context: additionalContext, + hookSpecificOutput: { + hookEventName: "UserPromptSubmit", + additionalContext, + }, + }); + case "gemini": + return JSON.stringify({ + hookSpecificOutput: { + hookEventName: "BeforeAgent", + additionalContext, + }, + }); + case "qwen": + // Qwen Code fork uses hookSpecificOutput (same as Codex) + return JSON.stringify({ + hookSpecificOutput: { + hookEventName: "UserPromptSubmit", + additionalContext, + }, + }); + } +} + +export function makeBlockOutput(vendor: Vendor, reason: string): string { + switch (vendor) { + case "claude": + case "codex": + case "cursor": + case "qwen": + return JSON.stringify({ decision: "block", reason }); + case "gemini": + // Gemini AfterAgent uses "deny" to reject response and force retry + return JSON.stringify({ decision: "deny", reason }); + } +} + +export function makePreToolOutput( + vendor: Vendor, + updatedInput: Record, +): string { + switch (vendor) { + case "gemini": + return JSON.stringify({ + decision: "rewrite", + tool_input: updatedInput, + }); + case "cursor": + return JSON.stringify({ + updated_input: updatedInput, + hookSpecificOutput: { + hookEventName: "PreToolUse", + updatedInput, + }, + }); + case "claude": + case "codex": + case "qwen": + return JSON.stringify({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + updatedInput, + }, + }); + } +} diff --git a/.agents/hooks/core/hud.ts b/.agents/hooks/core/hud.ts index 597b95c..bdc4b6e 100644 --- a/.agents/hooks/core/hud.ts +++ b/.agents/hooks/core/hud.ts @@ -1,171 +1,372 @@ #!/usr/bin/env bun /** - * oh-my-agent — HUD Statusline + * oh-my-agent — HUD * - * Lightweight status display for Claude Code's statusLine feature. - * Shows: OMA label, model, context usage, session cost, rate limits, lines changed, active workflow. + * Lightweight status display. Two modes: * - * stdin: JSON from Claude Code (model, context_window, cwd, transcript_path) - * stdout: ANSI-colored status text + * - Claude Code / agy (statusLine): stdin = vendor payload, stdout = ANSI + * text consumed by the native status-line renderer. Field names line up + * across both vendors; vendor-specific extras are best-effort. + * - Gemini CLI (SessionStart, AfterTool, AfterAgent hooks): stdin = Gemini + * hook payload, stdout = `{}` (protocol no-op), side effect = best-effort + * bottom-row bar written to /dev/tty. + * + * Vendor is inferred from the installed script path: + * `.gemini/hooks/` → gemini bar mode + * `.claude/hooks/` or `.gemini/antigravity-cli/hooks/` → claude statusline mode + * + * Set `OMA_HUD_DEBUG=1` to dump the raw stdin payload to + * `/../last-hud-input.json` for schema reverse-engineering. */ -import { existsSync, readdirSync, readFileSync } from "node:fs" -import { join } from "node:path" -import type { ModeState } from "./types.ts" +import { + closeSync, + existsSync, + openSync, + readdirSync, + readFileSync, + writeFileSync, + writeSync, +} from "node:fs"; +import { join, sep } from "node:path"; +import type { ModeState } from "./types.ts"; + +type HudVendor = "claude" | "gemini"; + +function inferVendor(): HudVendor { + const path = import.meta.filename ?? ""; + // Strict match on `/.gemini/hooks/` so agy (`/.gemini/antigravity-cli/hooks/`) + // falls through to claude — agy's StatusLine uses Claude's stdout protocol. + if (path.includes(`${sep}.gemini${sep}hooks${sep}`)) return "gemini"; + return "claude"; +} // ── ANSI Colors ─────────────────────────────────────────────── -const dim = (s: string) => `\x1b[2m${s}\x1b[22m` -const bold = (s: string) => `\x1b[1m${s}\x1b[22m` -const green = (s: string) => `\x1b[32m${s}\x1b[39m` -const yellow = (s: string) => `\x1b[33m${s}\x1b[39m` -const red = (s: string) => `\x1b[31m${s}\x1b[39m` -const cyan = (s: string) => `\x1b[36m${s}\x1b[39m` +const dim = (s: string) => `\x1b[2m${s}\x1b[22m`; +const bold = (s: string) => `\x1b[1m${s}\x1b[22m`; +const green = (s: string) => `\x1b[32m${s}\x1b[39m`; +const yellow = (s: string) => `\x1b[33m${s}\x1b[39m`; +const red = (s: string) => `\x1b[31m${s}\x1b[39m`; +const cyan = (s: string) => `\x1b[36m${s}\x1b[39m`; function colorByThreshold(value: number, text: string): string { - if (value >= 85) return red(text) - if (value >= 70) return yellow(text) - return green(text) + if (value >= 85) return red(text); + if (value >= 70) return yellow(text); + return green(text); } -// ── Stdin Parsing ───────────────────────────────────────────── +// ── Stdin ───────────────────────────────────────────────────── interface RateLimit { - used_percentage?: number - resets_at?: string + used_percentage?: number; + resets_at?: string; } interface StatuslineStdin { - cwd?: string - model?: { id?: string; display_name?: string } + cwd?: string; + model?: { id?: string; display_name?: string }; context_window?: { - context_window_size?: number - used_percentage?: number - } + context_window_size?: number; + used_percentage?: number; + // agy 1.0.0 StatusLine adds these — Claude does not. + total_input_tokens?: number; + total_output_tokens?: number; + }; cost?: { - total_cost_usd?: number - total_lines_added?: number - total_lines_removed?: number - total_duration_ms?: number - } + total_cost_usd?: number; + total_lines_added?: number; + total_lines_removed?: number; + total_duration_ms?: number; + }; rate_limits?: { - five_hour?: RateLimit - seven_day?: RateLimit - } + five_hour?: RateLimit; + seven_day?: RateLimit; + }; + // agy-only fields (Antigravity hides $cost / rate-limits from StatusLine). + agent_state?: string; + sandbox?: { enabled?: boolean }; + product?: string; +} + +interface GeminiHookInput { + hook_event_name?: string; + cwd?: string; + tool_name?: string; + tool_response?: { exit_code?: number; success?: boolean } | unknown; + prompt?: string; + prompt_response?: string; + source?: string; } function readStdin(): StatuslineStdin { + const raw = (() => { + try { + return readFileSync(0, "utf-8"); + } catch { + return ""; + } + })(); + maybeDumpDebugPayload(raw); + try { + return JSON.parse(raw); + } catch { + return {}; + } +} + +function readRaw(): unknown { + const raw = (() => { + try { + return readFileSync(0, "utf-8"); + } catch { + return ""; + } + })(); + maybeDumpDebugPayload(raw); try { - return JSON.parse(readFileSync("/dev/stdin", "utf-8")) + return JSON.parse(raw); } catch { - return {} + return {}; + } +} + +/** + * When `OMA_HUD_DEBUG=1`, capture the raw stdin payload to a sibling file so + * vendor-specific schemas can be reverse-engineered (notably agy's StatusLine, + * which has no public docs at v1.0.0). Best-effort — failures are swallowed. + */ +function maybeDumpDebugPayload(raw: string): void { + if (process.env.OMA_HUD_DEBUG !== "1" || !raw) return; + try { + const target = join( + import.meta.dirname ?? process.cwd(), + "..", + "last-hud-input.json", + ); + writeFileSync(target, `${raw.trim()}\n`, "utf-8"); + } catch { + // intentionally silent } } // ── Active Workflow Detection ───────────────────────────────── function getActiveWorkflow(projectDir: string): ModeState | null { - const stateDir = join(projectDir, ".agents", "state") - if (!existsSync(stateDir)) return null + const stateDir = join(projectDir, ".agents", "state"); + if (!existsSync(stateDir)) return null; try { for (const file of readdirSync(stateDir)) { - if (!file.endsWith(".json") || !file.includes("-state-")) continue - const content = readFileSync(join(stateDir, file), "utf-8") - const state: ModeState = JSON.parse(content) + if (!file.endsWith(".json") || !file.includes("-state-")) continue; + const content = readFileSync(join(stateDir, file), "utf-8"); + const state: ModeState = JSON.parse(content); // Skip stale (>2h) - const elapsed = Date.now() - new Date(state.activatedAt).getTime() - if (elapsed > 2 * 60 * 60 * 1000) continue + const elapsed = Date.now() - new Date(state.activatedAt).getTime(); + if (elapsed > 2 * 60 * 60 * 1000) continue; - return state + return state; } } catch { // ignore } - return null + return null; } // ── Model Name Shortener ────────────────────────────────────── -function shortModel(model?: { id?: string; display_name?: string }): string { - const name = model?.display_name || model?.id || "" - if (!name) return "" - // "Claude Opus 4.6 (1M context)" → "Opus 4.6" - const match = name.match(/(Opus|Sonnet|Haiku)[\s.]*([\d.]*)/i) - if (match) return `${match[1]}${match[2] ? ` ${match[2]}` : ""}` - return name.split("/").pop()?.slice(0, 15) || "" +export function shortModel(model?: { + id?: string; + display_name?: string; +}): string { + const name = model?.display_name || model?.id || ""; + if (!name) return ""; + // Claude: "Claude Opus 4.6 (1M context)" → "Opus 4.6" + const claude = name.match(/(Opus|Sonnet|Haiku)[\s.]*([\d.]*)/i); + if (claude) return `${claude[1]}${claude[2] ? ` ${claude[2]}` : ""}`; + // Gemini / agy: "Gemini 3.5 Flash (High)" → "Gemini 3.5 Flash" + const gemini = name.match( + /(Gemini)\s+([\d.]+)\s+(Pro|Flash|Ultra|Nano|Thinking)/i, + ); + if (gemini) return `${gemini[1]} ${gemini[2]} ${gemini[3]}`; + return name.split("/").pop()?.slice(0, 20) || ""; } // ── Rate Limit Helpers ─────────────────────────────────────── function formatCountdown(resetsAt: string): string { - const remaining = new Date(resetsAt).getTime() - Date.now() - if (remaining <= 0) return "" - const h = Math.floor(remaining / 3_600_000) - const m = Math.floor((remaining % 3_600_000) / 60_000) - return h > 0 ? `${h}h${m}m` : `${m}m` + const remaining = new Date(resetsAt).getTime() - Date.now(); + if (remaining <= 0) return ""; + const h = Math.floor(remaining / 3_600_000); + const m = Math.floor((remaining % 3_600_000) / 60_000); + return h > 0 ? `${h}h${m}m` : `${m}m`; } function formatRateLimit(label: string, rl?: RateLimit): string | null { - if (!rl || rl.used_percentage == null) return null - const pct = Math.round(rl.used_percentage) - const countdown = rl.resets_at ? formatCountdown(rl.resets_at) : "" - const text = countdown ? `${label}:${pct}%(${countdown})` : `${label}:${pct}%` - return colorByThreshold(pct, text) + if (!rl || rl.used_percentage == null) return null; + const pct = Math.round(rl.used_percentage); + const countdown = rl.resets_at ? formatCountdown(rl.resets_at) : ""; + const text = countdown + ? `${label}:${pct}%(${countdown})` + : `${label}:${pct}%`; + return colorByThreshold(pct, text); } -// ── Main ────────────────────────────────────────────────────── +function formatTokens(n: number): string { + if (n < 1000) return `${n}`; + if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`; + return `${(n / 1_000_000).toFixed(1)}M`; +} -function main() { - const input = readStdin() - const projectDir = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd() - const parts: string[] = [] +// ── Gemini bar (DECSTBM-free best-effort bottom row) ────────── + +/** + * Write `line` to the controlling TTY at the bottom row, then restore the + * cursor so the host CLI's render is not visibly disturbed. Best-effort: + * silently no-ops in environments without /dev/tty (CI, piped runs); when + * the host CLI repaints, the bar is overwritten until the next event. + * Intentionally avoids DECSTBM scroll-region changes so scrollback survives + * a mid-write hook kill. + */ +function paintBottomBar(line: string): void { + let fd: number | null = null; + try { + fd = openSync("/dev/tty", "w"); + const SAVE = "\x1b7"; // DECSC: save cursor + attrs + const RESTORE = "\x1b8"; // DECRC: restore cursor + attrs + const TO_BOTTOM = "\x1b[999;1H"; // clamp to last row, col 1 + const CLEAR_LINE = "\x1b[2K"; + writeSync(fd, `${SAVE}${TO_BOTTOM}${CLEAR_LINE}\r${line}${RESTORE}`); + } catch { + // No tty available — silent. + } finally { + if (fd !== null) { + try { + closeSync(fd); + } catch { + // ignore close failures + } + } + } +} + +export function buildGeminiBar( + input: GeminiHookInput, + projectDir: string, +): string { + const parts: string[] = [bold(cyan("[OMA]"))]; + + const event = input.hook_event_name; + if (event) parts.push(dim(event)); + + const workflow = getActiveWorkflow(projectDir); + if (workflow) { + parts.push(yellow(`${workflow.workflow}:${workflow.reinforcementCount}`)); + } + + if (input.tool_name) { + const resp = input.tool_response as { exit_code?: number } | undefined; + const failed = typeof resp?.exit_code === "number" && resp.exit_code !== 0; + const label = `tool:${input.tool_name}`; + parts.push(failed ? red(label) : green(label)); + } + + const now = new Date(); + const hh = String(now.getHours()).padStart(2, "0"); + const mm = String(now.getMinutes()).padStart(2, "0"); + parts.push(dim(`${hh}:${mm}`)); + + return parts.join(dim(" │ ")); +} + +// ── Claude / agy statusline ─────────────────────────────────── + +export function buildClaudeStatusline(input: StatuslineStdin): string { + const projectDir = + process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd(); + const parts: string[] = []; // 1. OMA label - parts.push(bold(cyan("[OMA]"))) + parts.push(bold(cyan("[OMA]"))); // 2. Model - const model = shortModel(input.model) - if (model) parts.push(dim(model)) + const model = shortModel(input.model); + if (model) parts.push(dim(model)); // 3. Context % - const ctxPct = input.context_window?.used_percentage + const ctxPct = input.context_window?.used_percentage; if (ctxPct != null) { - parts.push(colorByThreshold(ctxPct, `ctx:${Math.round(ctxPct)}%`)) + parts.push(colorByThreshold(ctxPct, `ctx:${Math.round(ctxPct)}%`)); + } + + // 4. Tokens (agy exposes these; Claude does not). + const inTok = input.context_window?.total_input_tokens ?? 0; + const outTok = input.context_window?.total_output_tokens ?? 0; + if (inTok > 0 || outTok > 0) { + parts.push(dim(`tok:${formatTokens(inTok)}↑${formatTokens(outTok)}↓`)); } - // 4. Session cost - const cost = input.cost?.total_cost_usd + // 5. Session cost (Claude) + const cost = input.cost?.total_cost_usd; if (cost != null && cost > 0) { - parts.push(dim(`$${cost.toFixed(2)}`)) + parts.push(dim(`$${cost.toFixed(2)}`)); } - // 5. Rate limits (5h / 7d) - const rl5 = formatRateLimit("5h", input.rate_limits?.five_hour) - const rl7 = formatRateLimit("7d", input.rate_limits?.seven_day) + // 6. Rate limits (Claude) + const rl5 = formatRateLimit("5h", input.rate_limits?.five_hour); + const rl7 = formatRateLimit("7d", input.rate_limits?.seven_day); if (rl5 || rl7) { - parts.push([rl5, rl7].filter(Boolean).join(dim(" "))) + parts.push([rl5, rl7].filter(Boolean).join(dim(" "))); } - // 6. Lines changed - const added = input.cost?.total_lines_added - const removed = input.cost?.total_lines_removed + // 7. Lines changed (vendor-provided only; agy doesn't track this and we + // intentionally don't synthesize from git — keep what the vendor knows). + const added = input.cost?.total_lines_added; + const removed = input.cost?.total_lines_removed; if (added || removed) { - const diffParts: string[] = [] - if (added) diffParts.push(green(`+${added}`)) - if (removed) diffParts.push(red(`-${removed}`)) - parts.push(diffParts.join(dim("/"))) + const diffParts: string[] = []; + if (added) diffParts.push(green(`+${added}`)); + if (removed) diffParts.push(red(`-${removed}`)); + parts.push(diffParts.join(dim("/"))); } - // 7. Active workflow - const workflow = getActiveWorkflow(projectDir) + // 8. agy-only: surface non-idle agent state and sandbox flag. + if (input.agent_state && input.agent_state !== "idle") { + parts.push(yellow(input.agent_state)); + } + if (input.sandbox?.enabled) { + parts.push(dim("sandbox")); + } + + // 9. Active workflow + const workflow = getActiveWorkflow(projectDir); if (workflow) { - const label = `${workflow.workflow}:${workflow.reinforcementCount}` - parts.push(yellow(label)) + parts.push(yellow(`${workflow.workflow}:${workflow.reinforcementCount}`)); + } + + return parts.join(dim(" │ ")); +} + +// ── Main ────────────────────────────────────────────────────── + +function main() { + const vendor = inferVendor(); + + if (vendor === "gemini") { + const raw = readRaw() as GeminiHookInput; + const projectDir = + process.env.GEMINI_PROJECT_DIR || + process.env.ANTIGRAVITY_PROJECT_DIR || + raw.cwd || + process.cwd(); + paintBottomBar(buildGeminiBar(raw, projectDir)); + // Gemini hook protocol: empty object = no-op, do not influence the agent. + process.stdout.write("{}"); + return; } - process.stdout.write(parts.join(dim(" │ "))) + process.stdout.write(buildClaudeStatusline(readStdin())); } -main() +main(); diff --git a/.agents/hooks/core/keyword-detector.ts b/.agents/hooks/core/keyword-detector.ts index 0ce0d0e..ebee106 100644 --- a/.agents/hooks/core/keyword-detector.ts +++ b/.agents/hooks/core/keyword-detector.ts @@ -12,59 +12,296 @@ * exit 0 = always (allow) */ -import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs" -import { dirname, join } from "node:path" -import { type ModeState, makePromptOutput, resolveGitRoot, type Vendor } from "./types.ts" +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { join } from "node:path"; +import { VENDORS } from "./constants.ts"; +import { resolveGitRoot } from "./fs-utils.ts"; +import { makePromptOutput } from "./hook-output.ts"; +import type { ModeState, Vendor } from "./types.ts"; + +// ── Unicode normalization ───────────────────────────────────── + +/** + * Normalize text for keyword matching. + * NFKC converts fullwidth Latin characters produced by CJK IMEs + * (e.g. parallel → parallel) to their ASCII equivalents, + * then lowercases the result. + * + * Placed here so that Task 3 (KEYWORD_SKIP_PREDICATES) and any + * future layers can import and reuse the same normalization path. + */ +export function normalizeForMatching(text: string): string { + return text.normalize("NFKC").toLowerCase(); +} + +// ── CLI Invocation Guard ────────────────────────────────────── + +/** + * Brands that count as CLI invocations: Oma plus the host LLM CLIs declared + * in `VENDORS` (claude, codex, cursor, gemini, qwen). The vendor list is + * the single source of truth for hook-supported runtimes; pulling from it + * here keeps the brand set in sync when a new vendor is added. + * + * Third-party harnesses (omc, omx, omo) are intentionally NOT included: they + * are separate projects, not host CLIs a user would invoke from an Oma + * session. opencode is also not a supported vendor in this codebase. + */ +const CLI_INVOCATION_BRANDS = ["oma", ...VENDORS] as const; +const CLI_INVOCATION_SIGNALS = [ + "agent", + "auto", + "exec", + "run", + "spawn", + String.raw`--\S+`, + String.raw`\S+:\S+`, +] as const; + +const BRANDS_RE_SOURCE = CLI_INVOCATION_BRANDS.join("|"); +const SIGNALS_RE_SOURCE = CLI_INVOCATION_SIGNALS.join("|"); + +/** + * Matches CLI invocations at the start of the prompt. + * + * All brand names require an explicit CLI signal after the brand. Brand-only + * prefixes are NOT treated as CLI invocations because every brand name can + * appear in natural-language usage ('claude, review this code', 'oma + * 프로젝트의 brainstorm 알려줘', 'cursor in the editor moves'). Requiring + * an explicit signal avoids false-positive skips on conversational prompts. + * + * Two accepted invocation shapes: + * + * 1. Slash form: '/oma:brainstorm', '/claude:exec'. The leading slash + * plus brand-colon prefix is a definitive CLI marker. Matches + * '/:'. + * + * 2. Bare form: '\s+' where is one of the + * enumerated subcommand verbs (agent / auto / exec / run / spawn), + * a --flag, or a colon-namespaced subcommand ('agent:spawn'). + * Examples: 'oma agent:spawn brainstorm', 'claude --help', + * 'codex exec --workflow ralph', 'gemini agent', 'cursor agent', + * 'qwen run'. + */ +export const CLI_INVOCATION_AT_START = new RegExp( + `^\\s*(?:\\/(?:${BRANDS_RE_SOURCE}):|(?:${BRANDS_RE_SOURCE})\\s+(?:${SIGNALS_RE_SOURCE}))`, + "i", +); + +/** + * Per-workflow skip predicates. A workflow listed here will be skipped when + * its predicate returns true for the (already-normalized) cleaned text. + * The map is intentionally empty at boot — populate it to add workflow-specific + * overrides without restructuring the matching loop. + */ +export const KEYWORD_SKIP_PREDICATES: Record< + string, + (text: string) => boolean +> = {}; + +/** + * Default predicate: skip ALL workflow triggers when the prompt starts with a + * CLI invocation of `oma` or one of the host LLM CLIs in `VENDORS`. Applies + * to every workflow unless an explicit per-workflow predicate in + * KEYWORD_SKIP_PREDICATES overrides it. + * + * The regex is applied to the NFKC-lowercased `cleaned` text produced by + * normalizeForMatching. All brand names are ASCII so NFKC has no effect on + * them; the `^\s*` start-anchor is unaffected by normalization. + */ +export function shouldSkipAllWorkflows(text: string): boolean { + return CLI_INVOCATION_AT_START.test(text); +} + +// ── Guard 1: UserPromptSubmit-only trigger ──────────────────── +// Hook event names that represent genuine user input (not agent responses) +const VALID_USER_EVENTS = new Set([ + "UserPromptSubmit", + "beforeSubmitPrompt", // Cursor + "BeforeAgent", // Gemini (fires before agent processes user prompt) +]); + +/** + * Returns true if the hook input indicates this is a genuine user prompt, + * not an agent-generated response. Prevents re-trigger loops. + */ +export function isGenuineUserPrompt(input: Record): boolean { + const event = input.hook_event_name as string | undefined; + // If event is explicitly provided, validate it + if (event !== undefined) { + return VALID_USER_EVENTS.has(event); + } + // No event field — assume genuine (backward compat with vendors that omit it) + return true; +} + +// ── Guard 3: Reinforcement suppression ─────────────────────── + +const REINFORCEMENT_WINDOW_MS = 60_000; // 60 seconds +const REINFORCEMENT_MAX_COUNT = 2; // allow up to 2, suppress 3rd+ + +export interface KeywordDetectorState { + triggers: Record< + string, + { + lastTriggeredAt: string; // ISO timestamp + count: number; + } + >; +} + +function getKwStateFilePath(projectDir: string): string { + const dir = join(projectDir, ".agents", "state"); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + return join(dir, "keyword-detector-state.json"); +} + +/** + * Load the keyword-detector reinforcement state from disk. + * Resets gracefully if the file is missing or corrupt. + */ +export function loadKwState(projectDir: string): KeywordDetectorState { + const filePath = getKwStateFilePath(projectDir); + if (!existsSync(filePath)) return { triggers: {} }; + try { + const raw = readFileSync(filePath, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + if ( + typeof parsed === "object" && + parsed !== null && + "triggers" in parsed && + typeof (parsed as Record).triggers === "object" + ) { + return parsed as KeywordDetectorState; + } + return { triggers: {} }; + } catch { + // Corrupt file — reset + return { triggers: {} }; + } +} + +/** + * Save reinforcement state to disk. + */ +export function saveKwState( + projectDir: string, + state: KeywordDetectorState, +): void { + try { + const filePath = getKwStateFilePath(projectDir); + writeFileSync(filePath, JSON.stringify(state, null, 2)); + } catch { + // Non-fatal — reinforcement suppression is best-effort + } +} + +/** + * Returns true if the keyword should be suppressed due to reinforcement loop. + * A keyword is suppressed if it was triggered >= REINFORCEMENT_MAX_COUNT times + * within the last REINFORCEMENT_WINDOW_MS milliseconds. + */ +export function isReinforcementSuppressed( + state: KeywordDetectorState, + keyword: string, + nowMs?: number, +): boolean { + const now = nowMs ?? Date.now(); + const entry = state.triggers[keyword]; + if (!entry) return false; + const lastMs = new Date(entry.lastTriggeredAt).getTime(); + if (Number.isNaN(lastMs)) return false; + const withinWindow = now - lastMs < REINFORCEMENT_WINDOW_MS; + return withinWindow && entry.count >= REINFORCEMENT_MAX_COUNT; +} + +/** + * Record a keyword trigger in the reinforcement state. + * Resets count if the previous trigger was outside the window. + */ +export function recordKwTrigger( + state: KeywordDetectorState, + keyword: string, + nowMs?: number, +): KeywordDetectorState { + const now = nowMs ?? Date.now(); + const entry = state.triggers[keyword]; + let count = 1; + if (entry) { + const lastMs = new Date(entry.lastTriggeredAt).getTime(); + const withinWindow = + !Number.isNaN(lastMs) && now - lastMs < REINFORCEMENT_WINDOW_MS; + count = withinWindow ? entry.count + 1 : 1; + } + return { + ...state, + triggers: { + ...state.triggers, + [keyword]: { + lastTriggeredAt: new Date(now).toISOString(), + count, + }, + }, + }; +} // ── Vendor Detection ────────────────────────────────────────── function inferVendorFromScriptPath(): Vendor | null { - const path = import.meta.path - if (path.includes(`${join(".cursor", "hooks")}`)) return "cursor" - if (path.includes(`${join(".qwen", "hooks")}`)) return "qwen" - if (path.includes(`${join(".claude", "hooks")}`)) return "claude" - if (path.includes(`${join(".gemini", "hooks")}`)) return "gemini" - if (path.includes(`${join(".codex", "hooks")}`)) return "codex" - return null + const path = import.meta.filename; + if (path.includes(`${join(".cursor", "hooks")}`)) return "cursor"; + if (path.includes(`${join(".qwen", "hooks")}`)) return "qwen"; + if (path.includes(`${join(".claude", "hooks")}`)) return "claude"; + if (path.includes(`${join(".gemini", "hooks")}`)) return "gemini"; + if (path.includes(`${join(".codex", "hooks")}`)) return "codex"; + return null; } function detectVendor(input: Record): Vendor { - const event = input.hook_event_name as string | undefined - const byScriptPath = inferVendorFromScriptPath() - if (byScriptPath) return byScriptPath - if (event === "BeforeAgent") return "gemini" - if (event === "beforeSubmitPrompt") return "cursor" + const event = input.hook_event_name as string | undefined; + const byScriptPath = inferVendorFromScriptPath(); + if (byScriptPath) return byScriptPath; + if (event === "BeforeAgent") return "gemini"; + if (event === "beforeSubmitPrompt") return "cursor"; if (event === "UserPromptSubmit") { // Codex uses snake_case session_id, Claude uses camelCase sessionId - if ("session_id" in input && !("sessionId" in input)) return "codex" + if ("session_id" in input && !("sessionId" in input)) return "codex"; } // Qwen Code sets QWEN_PROJECT_DIR; Claude sets CLAUDE_PROJECT_DIR - if (process.env.QWEN_PROJECT_DIR) return "qwen" - return "claude" + if (process.env.QWEN_PROJECT_DIR) return "qwen"; + return "claude"; } function getProjectDir(vendor: Vendor, input: Record): string { - let dir: string + let dir: string; switch (vendor) { case "codex": case "cursor": - dir = (input.cwd as string) || process.cwd() - break + dir = (input.cwd as string) || process.cwd(); + break; case "gemini": - dir = process.env.GEMINI_PROJECT_DIR || process.cwd() - break + dir = process.env.GEMINI_PROJECT_DIR || process.cwd(); + break; case "qwen": - dir = process.env.QWEN_PROJECT_DIR || process.cwd() - break + dir = process.env.QWEN_PROJECT_DIR || process.cwd(); + break; default: - dir = process.env.CLAUDE_PROJECT_DIR || process.cwd() - break + dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + break; } - return resolveGitRoot(dir) + return resolveGitRoot(dir); } function getSessionId(input: Record): string { - return (input.sessionId as string) || (input.session_id as string) || "unknown" + return ( + (input.sessionId as string) || (input.session_id as string) || "unknown" + ); } // ── Config Loading ──────────────────────────────────────────── @@ -73,74 +310,114 @@ interface TriggerConfig { workflows: Record< string, { - persistent: boolean - keywords: Record + persistent: boolean; + keywords: Record; + patterns?: Record; } - > - informationalPatterns: Record - excludedWorkflows: string[] - cjkScripts: string[] - extensionRouting?: Record + >; + informationalPatterns: Record; + excludedWorkflows: string[]; + cjkScripts: string[]; + extensionRouting?: Record; } function loadConfig(): TriggerConfig { - const configPath = join(dirname(import.meta.path), "triggers.json") - return JSON.parse(readFileSync(configPath, "utf-8")) + const configPath = join(import.meta.dirname, "triggers.json"); + return JSON.parse(readFileSync(configPath, "utf-8")); } function detectLanguage(projectDir: string): string { - const prefsPath = join(projectDir, ".agents", "oma-config.yaml") - if (!existsSync(prefsPath)) return "en" + const prefsPath = join(projectDir, ".agents", "oma-config.yaml"); + if (!existsSync(prefsPath)) return "en"; try { - const content = readFileSync(prefsPath, "utf-8") - const match = content.match(/^language:\s*(\S+)/m) - return match?.[1] ?? "en" + const content = readFileSync(prefsPath, "utf-8"); + const match = content.match(/^language:\s*(\S+)/m); + return match?.[1] ?? "en"; } catch { - return "en" + return "en"; } } // ── Pattern Builder ─────────────────────────────────────────── export function escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -export function buildPatterns(keywords: Record, lang: string, cjkScripts: string[]): RegExp[] { +export function buildPatterns( + keywords: Record, + lang: string, + cjkScripts: string[], +): RegExp[] { const allKeywords = [ ...(keywords["*"] ?? []), ...(keywords.en ?? []), ...(lang !== "en" ? (keywords[lang] ?? []) : []), - ] + ]; return allKeywords.map((kw) => { - const escaped = escapeRegex(kw).replace(/\s+/g, "\\s+") - // biome-ignore lint/suspicious/noControlCharactersInRegex: ASCII range check for non-ASCII keywords - if (cjkScripts.includes(lang) || /[^\x00-\x7F]/.test(kw)) { - return new RegExp(escaped, "i") + const escaped = escapeRegex(kw).replace(/\s+/g, "\\s+"); + if (cjkScripts.includes(lang) || /[^\p{ASCII}]/u.test(kw)) { + return new RegExp(escaped, "i"); + } + return new RegExp(`(?:^|[^\\w-])${escaped}(?:$|[^\\w-])`, "i"); + }); +} + +/** + * Build raw regex patterns from a workflow's `patterns` field. + * Unlike buildPatterns, these strings are compiled directly without + * escaping or word-boundary wrapping — pattern authors are responsible + * for boundary handling. Invalid patterns are skipped silently. + */ +export function buildRawPatterns( + patterns: Record | undefined, + lang: string, +): RegExp[] { + if (!patterns) return []; + const all = [ + ...(patterns["*"] ?? []), + ...(patterns.en ?? []), + ...(lang !== "en" ? (patterns[lang] ?? []) : []), + ]; + const compiled: RegExp[] = []; + for (const raw of all) { + try { + compiled.push(new RegExp(raw, "iu")); + } catch { + // Skip invalid regex — surfaces during config edit, not at runtime } - return new RegExp(`\\b${escaped}\\b`, "i") - }) + } + return compiled; } -function buildInformationalPatterns(config: TriggerConfig, lang: string): RegExp[] { - const patterns = [...(config.informationalPatterns.en ?? [])] +function buildInformationalPatterns( + config: TriggerConfig, + lang: string, +): RegExp[] { + const patterns = [ + ...(config.informationalPatterns["*"] ?? []), + ...(config.informationalPatterns.en ?? []), + ]; if (lang !== "en") { - patterns.push(...(config.informationalPatterns[lang] ?? [])) + patterns.push(...(config.informationalPatterns[lang] ?? [])); } return patterns.map((p) => { - // biome-ignore lint/suspicious/noControlCharactersInRegex: ASCII range check for non-ASCII keywords - if (/[^\x00-\x7F]/.test(p)) return new RegExp(escapeRegex(p), "i") - return new RegExp(`\\b${escapeRegex(p)}\\b`, "i") - }) + if (/[^\p{ASCII}]/u.test(p)) return new RegExp(escapeRegex(p), "i"); + return new RegExp(`(?:^|[^\\w-])${escapeRegex(p)}(?:$|[^\\w-])`, "i"); + }); } // ── Filters ─────────────────────────────────────────────────── -export function isInformationalContext(prompt: string, matchIndex: number, infoPatterns: RegExp[]): boolean { - const windowStart = Math.max(0, matchIndex - 60) - const window = prompt.slice(windowStart, matchIndex + 60) - return infoPatterns.some((p) => p.test(window)) +export function isInformationalContext( + prompt: string, + matchIndex: number, + infoPatterns: RegExp[], +): boolean { + const windowStart = Math.max(0, matchIndex - 60); + const window = prompt.slice(windowStart, matchIndex + 60); + return infoPatterns.some((p) => p.test(window)); } /** @@ -148,12 +425,16 @@ export function isInformationalContext(prompt: string, matchIndex: number, infoP * only match keywords in the first N chars of the user's prompt. * Keywords deep in the prompt are likely from pasted content, not user intent. */ -const PERSISTENT_MATCH_LIMIT = 200 - -export function isPastedContent(matchIndex: number, isPersistent: boolean, promptLength: number): boolean { - if (!isPersistent) return false - if (promptLength <= PERSISTENT_MATCH_LIMIT) return false - return matchIndex > PERSISTENT_MATCH_LIMIT +const PERSISTENT_MATCH_LIMIT = 200; + +export function isPastedContent( + matchIndex: number, + isPersistent: boolean, + promptLength: number, +): boolean { + if (!isPersistent) return false; + if (promptLength <= PERSISTENT_MATCH_LIMIT) return false; + return matchIndex > PERSISTENT_MATCH_LIMIT; } /** @@ -165,6 +446,7 @@ const QUESTION_PATTERNS: RegExp[] = [ /^.*참고할/, /^.*비교해/, /^.*분석해/, + /^.*분석도/, /^.*있냐/, /^.*있나\?/, /^.*있는지/, @@ -174,17 +456,20 @@ const QUESTION_PATTERNS: RegExp[] = [ /^.*뭐가\s*있/, /^.*어떤\s*(게|것|거)\s*있/, /^.*차이가?\s*뭐/, + // Korean meta-continuation patterns (referring to prior discussion) + /^.*그것도/, + /^.*보강할/, // English question patterns /^.*\bis there\b/i, /^.*\bare there\b/i, /^.*\banything worth\b/i, /^.*\bwhat.*(feature|difference|reference)/i, /^.*\bcompare\b/i, -] +]; export function isAnalyticalQuestion(prompt: string): boolean { - const firstLine = prompt.split("\n")[0].trim() - return QUESTION_PATTERNS.some((p) => p.test(firstLine)) + const firstLine = (prompt.split("\n")[0] ?? "").trim(); + return QUESTION_PATTERNS.some((p) => p.test(firstLine)); } export function stripCodeBlocks(text: string): string { @@ -193,11 +478,39 @@ export function stripCodeBlocks(text: string): string { .replace(/(`{3,})[^\n]*\n[\s\S]*/g, "") // unclosed fenced blocks (strip to end) .replace(/`{3,}[^`]*`{3,}/g, "") // single-line fenced blocks (```...```) .replace(/`[^`\n]+`/g, "") // inline code (no newlines allowed) - .replace(/"[^"\n]*"/g, "") // quoted strings + .replace(/"[^"\n]*"/g, ""); // quoted strings +} + +// System echo block patterns — strip pasted hook self-output to prevent +// re-trigger loops where the user pastes back oma's own context messages. +const SYSTEM_ECHO_LINE_PATTERNS: RegExp[] = [ + /^.*\[OMA WORKFLOW:[^\]]*\].*$/gim, + /^.*\[OMA PERSISTENT MODE:[^\]]*\].*$/gim, + /^.*\[OMA AGENT HINT:[^\]]*\].*$/gim, + /^.*\[MAGIC KEYWORD:[^\]]*\].*$/gim, + /^.*\[MAGIC KEYWORDS? DETECTED:[^\]]*\].*$/gim, + /^.*Stop hook (?:blocking error|feedback|stopped continuation).*$/gim, + /^.*PreToolUse:[^\n]*hook additional context:.*$/gim, + /^.*PostToolUse:[^\n]*hook additional context:.*$/gim, + /^.*hookSpecificOutput.*$/gim, + /^.*The \/[a-z-]+ workflow is still active.*$/gim, +]; + +/** + * Strip pasted system-echo blocks (oma's own hook outputs) so meta-discussion + * about workflows doesn't re-trigger via paste-back. Operates line-by-line + * to preserve surrounding user text. + */ +export function stripSystemEchoes(text: string): string { + let cleaned = text; + for (const pattern of SYSTEM_ECHO_LINE_PATTERNS) { + cleaned = cleaned.replace(pattern, ""); + } + return cleaned; } export function startsWithSlashCommand(prompt: string): boolean { - return /^\/[a-zA-Z][\w-]*/.test(prompt.trim()) + return /^\/[a-zA-Z][\w-]*/.test(prompt.trim()); } // ── Extension Detection ────────────────────────────────────── @@ -228,62 +541,70 @@ const EXCLUDE_EXTS = new Set([ "eot", "map", "d", -]) +]); export function detectExtensions(prompt: string): string[] { - const extPattern = /\.([a-zA-Z]{1,12})\b/g - const extensions = new Set() - let match: RegExpExecArray | null - // biome-ignore lint/suspicious/noAssignInExpressions: standard regex.exec loop pattern - while ((match = extPattern.exec(prompt)) !== null) { - const ext = match[1].toLowerCase() - if (!EXCLUDE_EXTS.has(ext)) { - extensions.add(ext) + const extPattern = /\.([a-zA-Z]{1,12})\b/g; + const extensions = new Set(); + for (const match of prompt.matchAll(extPattern)) { + const ext = match[1]?.toLowerCase(); + if (ext && !EXCLUDE_EXTS.has(ext)) { + extensions.add(ext); } } - return [...extensions] + return [...extensions]; } -export function resolveAgentFromExtensions(extensions: string[], routing: Record): string | null { - if (extensions.length === 0) return null +export function resolveAgentFromExtensions( + extensions: string[], + routing: Record, +): string | null { + if (extensions.length === 0) return null; - const scores = new Map() + const scores = new Map(); for (const ext of extensions) { for (const [agent, agentExts] of Object.entries(routing)) { if (agentExts.includes(ext)) { - scores.set(agent, (scores.get(agent) ?? 0) + 1) + scores.set(agent, (scores.get(agent) ?? 0) + 1); } } } - if (scores.size === 0) return null + if (scores.size === 0) return null; - let best: string | null = null - let bestScore = 0 + let best: string | null = null; + let bestScore = 0; for (const [agent, score] of scores) { if (score > bestScore) { - bestScore = score - best = agent + bestScore = score; + best = agent; } } - return best + return best; } // ── State Management ────────────────────────────────────────── function getStateDir(projectDir: string): string { - const dir = join(projectDir, ".agents", "state") - if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) - return dir + const dir = join(projectDir, ".agents", "state"); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + return dir; } -function activateMode(projectDir: string, workflow: string, sessionId: string): void { +function activateMode( + projectDir: string, + workflow: string, + sessionId: string, +): void { const state: ModeState = { workflow, sessionId, activatedAt: new Date().toISOString(), reinforcementCount: 0, - } - writeFileSync(join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`), JSON.stringify(state, null, 2)) + }; + writeFileSync( + join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`), + JSON.stringify(state, null, 2), + ); } // ── Deactivation Detection ─────────────────────────────────── @@ -300,27 +621,35 @@ export const DEACTIVATION_PHRASES: Record = { ru: ["воркфлоу завершён", "рабочий процесс завершён"], nl: ["workflow voltooid", "workflow klaar"], pl: ["workflow zakończony", "workflow ukończony"], -} +}; export function isDeactivationRequest(prompt: string, lang: string): boolean { - const phrases = [...(DEACTIVATION_PHRASES.en ?? []), ...(lang !== "en" ? (DEACTIVATION_PHRASES[lang] ?? []) : [])] - const lower = prompt.toLowerCase() - return phrases.some((phrase) => lower.includes(phrase.toLowerCase())) + const phrases = [ + ...(DEACTIVATION_PHRASES.en ?? []), + ...(lang !== "en" ? (DEACTIVATION_PHRASES[lang] ?? []) : []), + ]; + const normalized = normalizeForMatching(prompt); + return phrases.some((phrase) => + normalized.includes(normalizeForMatching(phrase)), + ); } -export function deactivateAllPersistentModes(projectDir: string, sessionId?: string): void { - const stateDir = join(projectDir, ".agents", "state") - if (!existsSync(stateDir)) return +export function deactivateAllPersistentModes( + projectDir: string, + sessionId?: string, +): void { + const stateDir = join(projectDir, ".agents", "state"); + if (!existsSync(stateDir)) return; try { - const files = readdirSync(stateDir) + const files = readdirSync(stateDir); for (const file of files) { // Match session-scoped state files: {workflow}-state-{sessionId}.json if (sessionId) { if (file.endsWith(`-state-${sessionId}.json`)) { - unlinkSync(join(stateDir, file)) + unlinkSync(join(stateDir, file)); } } else if (/-state-/.test(file) && file.endsWith(".json")) { - unlinkSync(join(stateDir, file)) + unlinkSync(join(stateDir, file)); } } } catch { @@ -331,55 +660,88 @@ export function deactivateAllPersistentModes(projectDir: string, sessionId?: str // ── Main ────────────────────────────────────────────────────── async function main() { - const raw = readFileSync("/dev/stdin", "utf-8") - let input: Record + const raw = readFileSync(0, "utf-8"); + let input: Record; try { - input = JSON.parse(raw) + input = JSON.parse(raw); } catch { - process.exit(0) + process.exit(0); } - const vendor = detectVendor(input) - const projectDir = getProjectDir(vendor, input) - const sessionId = getSessionId(input) - const prompt = (input.prompt as string) ?? "" + // Guard 1: Only process genuine user prompts — skip agent-generated content + if (!isGenuineUserPrompt(input)) process.exit(0); + + const vendor = detectVendor(input); + const projectDir = getProjectDir(vendor, input); + const sessionId = getSessionId(input); + const prompt = (input.prompt as string) ?? ""; - if (!prompt.trim()) process.exit(0) - if (startsWithSlashCommand(prompt)) process.exit(0) + if (!prompt.trim()) process.exit(0); + if (startsWithSlashCommand(prompt)) process.exit(0); - const config = loadConfig() - const lang = detectLanguage(projectDir) + const config = loadConfig(); + const lang = detectLanguage(projectDir); // Check for deactivation request before workflow detection if (isDeactivationRequest(prompt, lang)) { - deactivateAllPersistentModes(projectDir, sessionId) - process.exit(0) + deactivateAllPersistentModes(projectDir, sessionId); + process.exit(0); } - const infoPatterns = buildInformationalPatterns(config, lang) - const cleaned = stripCodeBlocks(prompt) - const excluded = new Set(config.excludedWorkflows) + const infoPatterns = buildInformationalPatterns(config, lang); + // Guard 2: Strip code blocks, inline code, and pasted system-echo blocks + // before scanning for keywords. System echo stripping prevents oma's own + // hook outputs (when pasted back into the prompt) from re-triggering. + // NFKC normalization collapses fullwidth Latin from CJK IMEs onto ASCII + // so keyword regexes cannot be silently bypassed by parallel-style input. + const cleaned = normalizeForMatching( + stripSystemEchoes(stripCodeBlocks(prompt)), + ); + const excluded = new Set(config.excludedWorkflows); + + // Guard 3: Load reinforcement suppression state + const kwState = loadKwState(projectDir); // Skip persistent workflows entirely if the prompt is an analytical question - const analytical = isAnalyticalQuestion(cleaned) + const analytical = isAnalyticalQuestion(cleaned); for (const [workflow, def] of Object.entries(config.workflows)) { - if (excluded.has(workflow)) continue + if (excluded.has(workflow)) continue; + + // Global CLI-invocation guard: prompts that start with a CLI invocation + // of `oma` or a `VENDORS` entry are tool invocations, not natural-language + // workflow requests. Skip silently to avoid false-positive matches. + if (shouldSkipAllWorkflows(cleaned)) continue; + + // Per-workflow override: if a predicate is registered for this specific + // workflow, evaluate it and skip just this workflow when it returns true. + const workflowPredicate = KEYWORD_SKIP_PREDICATES[workflow]; + if (workflowPredicate?.(cleaned)) continue; // Analytical questions should never trigger persistent workflows - if (analytical && def.persistent) continue + if (analytical && def.persistent) continue; - const patterns = buildPatterns(def.keywords, lang, config.cjkScripts) + const patterns = [ + ...buildPatterns(def.keywords, lang, config.cjkScripts), + ...buildRawPatterns(def.patterns, lang), + ]; for (const pattern of patterns) { - const match = pattern.exec(cleaned) - if (!match) continue - if (isInformationalContext(cleaned, match.index, infoPatterns)) continue + const match = pattern.exec(cleaned); + if (!match) continue; + if (isInformationalContext(cleaned, match.index, infoPatterns)) continue; // Keywords deep in long prompts are likely pasted content, not user intent - if (isPastedContent(match.index, def.persistent, cleaned.length)) continue + if (isPastedContent(match.index, def.persistent, cleaned.length)) + continue; + + // Guard 3: Suppress if same workflow triggered too many times in 60s + if (isReinforcementSuppressed(kwState, workflow)) continue; if (def.persistent) { - activateMode(projectDir, workflow, sessionId) + activateMode(projectDir, workflow, sessionId); } + // Record this trigger for reinforcement tracking + const updatedState = recordKwTrigger(kwState, workflow); + saveKwState(projectDir, updatedState); const contextLines = [ `[OMA WORKFLOW: ${workflow.toUpperCase()}]`, @@ -387,26 +749,29 @@ async function main() { `Read and follow \`.agents/workflows/${workflow}.md\` step by step.`, `User request: ${prompt}`, `IMPORTANT: Start the workflow IMMEDIATELY. Do not ask for confirmation.`, - ] + ]; if (config.extensionRouting) { - const extensions = detectExtensions(prompt) - const agent = resolveAgentFromExtensions(extensions, config.extensionRouting) + const extensions = detectExtensions(prompt); + const agent = resolveAgentFromExtensions( + extensions, + config.extensionRouting, + ); if (agent) { - contextLines.push(`[OMA AGENT HINT: ${agent}]`) + contextLines.push(`[OMA AGENT HINT: ${agent}]`); } } - const context = contextLines.join("\n") + const context = contextLines.join("\n"); - process.stdout.write(makePromptOutput(vendor, context)) - process.exit(0) + process.stdout.write(makePromptOutput(vendor, context)); + process.exit(0); } } - process.exit(0) + process.exit(0); } if (import.meta.main) { - main().catch(() => process.exit(0)) + main().catch(() => process.exit(0)); } diff --git a/.agents/hooks/core/persistent-mode.ts b/.agents/hooks/core/persistent-mode.ts index 4936f4e..9ce7176 100644 --- a/.agents/hooks/core/persistent-mode.ts +++ b/.agents/hooks/core/persistent-mode.ts @@ -13,125 +13,157 @@ * exit 2 = block stop */ -import { existsSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs" -import { dirname, join } from "node:path" -import { isDeactivationRequest } from "./keyword-detector.ts" -import { type ModeState, makeBlockOutput, resolveGitRoot, type Vendor } from "./types.ts" - -const MAX_REINFORCEMENTS = 5 -const STALE_HOURS = 2 +import { + existsSync, + readdirSync, + readFileSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { join } from "node:path"; +import { resolveGitRoot } from "./fs-utils.ts"; +import { makeBlockOutput } from "./hook-output.ts"; +import { isDeactivationRequest } from "./keyword-detector.ts"; +import type { ModeState, Vendor } from "./types.ts"; + +const MAX_REINFORCEMENTS = 5; +const STALE_HOURS = 2; function detectLanguage(projectDir: string): string { - const prefsPath = join(projectDir, ".agents", "oma-config.yaml") - if (!existsSync(prefsPath)) return "en" + const prefsPath = join(projectDir, ".agents", "oma-config.yaml"); + if (!existsSync(prefsPath)) return "en"; try { - const content = readFileSync(prefsPath, "utf-8") - const match = content.match(/^language:\s*(\S+)/m) - return match?.[1] ?? "en" + const content = readFileSync(prefsPath, "utf-8"); + const match = content.match(/^language:\s*(\S+)/m); + return match?.[1] ?? "en"; } catch { - return "en" + return "en"; } } // ── Config Loading ──────────────────────────────────────────── interface TriggerConfig { - workflows: Record + workflows: Record; } function loadPersistentWorkflows(): string[] { - const configPath = join(dirname(import.meta.path), "triggers.json") + const configPath = join(import.meta.dirname, "triggers.json"); try { - const config: TriggerConfig = JSON.parse(readFileSync(configPath, "utf-8")) + const config: TriggerConfig = JSON.parse(readFileSync(configPath, "utf-8")); return Object.entries(config.workflows) .filter(([, def]) => def.persistent) - .map(([name]) => name) + .map(([name]) => name); } catch { - return ["ultrawork", "orchestrate", "work"] + return ["ultrawork", "orchestrate", "work"]; } } // ── Vendor Detection ────────────────────────────────────────── function detectVendor(input: Record): Vendor { - const event = input.hook_event_name as string | undefined - if (event === "AfterAgent") return "gemini" + const event = input.hook_event_name as string | undefined; + if (event === "AfterAgent") return "gemini"; if (event === "Stop") { - if ("session_id" in input && !("sessionId" in input)) return "codex" + if ("session_id" in input && !("sessionId" in input)) return "codex"; } - if (process.env.QWEN_PROJECT_DIR) return "qwen" - return "claude" + if (process.env.QWEN_PROJECT_DIR) return "qwen"; + return "claude"; } function getProjectDir(vendor: Vendor, input: Record): string { - let dir: string + let dir: string; switch (vendor) { case "codex": - dir = (input.cwd as string) || process.cwd() - break + dir = (input.cwd as string) || process.cwd(); + break; case "gemini": - dir = process.env.GEMINI_PROJECT_DIR || process.cwd() - break + dir = process.env.GEMINI_PROJECT_DIR || process.cwd(); + break; case "qwen": - dir = process.env.QWEN_PROJECT_DIR || process.cwd() - break + dir = process.env.QWEN_PROJECT_DIR || process.cwd(); + break; default: - dir = process.env.CLAUDE_PROJECT_DIR || process.cwd() - break + dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + break; } - return resolveGitRoot(dir) + return resolveGitRoot(dir); } function getSessionId(input: Record): string { - return (input.sessionId as string) || (input.session_id as string) || "unknown" + return ( + (input.sessionId as string) || (input.session_id as string) || "unknown" + ); } // ── State ───────────────────────────────────────────────────── function getStateDir(projectDir: string): string { - return join(projectDir, ".agents", "state") + return join(projectDir, ".agents", "state"); } -function readModeState(projectDir: string, workflow: string, sessionId: string): ModeState | null { - const path = join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`) - if (!existsSync(path)) return null +function readModeState( + projectDir: string, + workflow: string, + sessionId: string, +): ModeState | null { + const path = join( + getStateDir(projectDir), + `${workflow}-state-${sessionId}.json`, + ); + if (!existsSync(path)) return null; try { - return JSON.parse(readFileSync(path, "utf-8")) as ModeState + return JSON.parse(readFileSync(path, "utf-8")) as ModeState; } catch { - return null + return null; } } export function isStale(state: ModeState): boolean { - const elapsed = Date.now() - new Date(state.activatedAt).getTime() - return elapsed > STALE_HOURS * 60 * 60 * 1000 + const elapsed = Date.now() - new Date(state.activatedAt).getTime(); + return elapsed > STALE_HOURS * 60 * 60 * 1000; } -export function deactivate(projectDir: string, workflow: string, sessionId: string): void { - const path = join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`) - if (existsSync(path)) unlinkSync(path) +export function deactivate( + projectDir: string, + workflow: string, + sessionId: string, +): void { + const path = join( + getStateDir(projectDir), + `${workflow}-state-${sessionId}.json`, + ); + if (existsSync(path)) unlinkSync(path); } -function incrementReinforcement(projectDir: string, workflow: string, sessionId: string, state: ModeState): void { - state.reinforcementCount += 1 - writeFileSync(join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`), JSON.stringify(state, null, 2)) +function incrementReinforcement( + projectDir: string, + workflow: string, + sessionId: string, + state: ModeState, +): void { + state.reinforcementCount += 1; + writeFileSync( + join(getStateDir(projectDir), `${workflow}-state-${sessionId}.json`), + JSON.stringify(state, null, 2), + ); } // ── Main ────────────────────────────────────────────────────── async function main() { - const raw = readFileSync("/dev/stdin", "utf-8") - let input: Record + const raw = readFileSync(0, "utf-8"); + let input: Record; try { - input = JSON.parse(raw) + input = JSON.parse(raw); } catch { - process.exit(0) + process.exit(0); } - const vendor = detectVendor(input) - const projectDir = getProjectDir(vendor, input) - const sessionId = getSessionId(input) - const lang = detectLanguage(projectDir) + const vendor = detectVendor(input); + const projectDir = getProjectDir(vendor, input); + const sessionId = getSessionId(input); + const lang = detectLanguage(projectDir); // Check all text fields in stdin for deactivation phrases. // The assistant may have included "workflow done" in its response, @@ -144,60 +176,60 @@ async function main() { input.transcript, ] .filter((v): v is string => typeof v === "string") - .join(" ") + .join(" "); if (textToCheck && isDeactivationRequest(textToCheck, lang)) { // Deactivate all persistent workflows for this session - const stateDir = join(projectDir, ".agents", "state") + const stateDir = join(projectDir, ".agents", "state"); if (existsSync(stateDir)) { try { - const suffix = `-state-${sessionId}.json` + const suffix = `-state-${sessionId}.json`; for (const file of readdirSync(stateDir)) { if (file.endsWith(suffix)) { - unlinkSync(join(stateDir, file)) + unlinkSync(join(stateDir, file)); } } } catch { /* ignore */ } } - process.exit(0) + process.exit(0); } - const persistentWorkflows = loadPersistentWorkflows() + const persistentWorkflows = loadPersistentWorkflows(); for (const workflow of persistentWorkflows) { - const state = readModeState(projectDir, workflow, sessionId) - if (!state) continue + const state = readModeState(projectDir, workflow, sessionId); + if (!state) continue; if (isStale(state) || state.reinforcementCount >= MAX_REINFORCEMENTS) { - deactivate(projectDir, workflow, sessionId) - continue + deactivate(projectDir, workflow, sessionId); + continue; } - incrementReinforcement(projectDir, workflow, sessionId, state) + incrementReinforcement(projectDir, workflow, sessionId, state); - const stateFile = `.agents/state/${workflow}-state-${sessionId}.json` + const stateFile = `.agents/state/${workflow}-state-${sessionId}.json`; const reason = [ `[OMA PERSISTENT MODE: ${workflow.toUpperCase()}]`, `The /${workflow} workflow is still active (reinforcement ${state.reinforcementCount}/${MAX_REINFORCEMENTS}).`, `Continue executing the workflow. If all tasks are genuinely complete:`, ` 1. Delete the state file: Bash \`rm ${stateFile}\``, ` 2. Or ask the user to say "워크플로우 완료" / "workflow done"`, - ].join("\n") + ].join("\n"); - writeBlockAndExit(vendor, reason) + writeBlockAndExit(vendor, reason); } - process.exit(0) + process.exit(0); } export function writeBlockAndExit(vendor: Vendor, reason: string): never { - process.stderr.write(reason) - process.stdout.write(makeBlockOutput(vendor, reason)) - process.exit(2) + process.stderr.write(reason); + process.stdout.write(makeBlockOutput(vendor, reason)); + process.exit(2); } if (import.meta.main) { - main().catch(() => process.exit(0)) + main().catch(() => process.exit(0)); } diff --git a/.agents/hooks/core/skill-injector.ts b/.agents/hooks/core/skill-injector.ts index beda327..c287dbe 100644 --- a/.agents/hooks/core/skill-injector.ts +++ b/.agents/hooks/core/skill-injector.ts @@ -12,152 +12,169 @@ * persistent workflow is active (those modes own the session context). */ -import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs" -import { basename, dirname, join } from "node:path" -import { makePromptOutput, resolveGitRoot, type Vendor } from "./types.ts" - -const MAX_SKILLS = 3 -const SESSION_TTL_MS = 60 * 60 * 1000 -const DEFAULT_CJK_SCRIPTS = ["ko", "ja", "zh"] +import { + type Dirent, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + writeFileSync, +} from "node:fs"; +import { basename, dirname, join } from "node:path"; +import { resolveGitRoot, toPosixPath } from "./fs-utils.ts"; +import { makePromptOutput } from "./hook-output.ts"; +import type { Vendor } from "./types.ts"; + +const MAX_SKILLS = 3; +const SESSION_TTL_MS = 60 * 60 * 1000; +const DEFAULT_CJK_SCRIPTS = ["ko", "ja", "zh"]; // ── Vendor Detection ────────────────────────────────────────── function inferVendorFromScriptPath(): Vendor | null { - const path = import.meta.path - if (path.includes(`${join(".cursor", "hooks")}`)) return "cursor" - if (path.includes(`${join(".qwen", "hooks")}`)) return "qwen" - if (path.includes(`${join(".claude", "hooks")}`)) return "claude" - if (path.includes(`${join(".gemini", "hooks")}`)) return "gemini" - if (path.includes(`${join(".codex", "hooks")}`)) return "codex" - return null + const path = import.meta.filename; + if (path.includes(`${join(".cursor", "hooks")}`)) return "cursor"; + if (path.includes(`${join(".qwen", "hooks")}`)) return "qwen"; + if (path.includes(`${join(".claude", "hooks")}`)) return "claude"; + if (path.includes(`${join(".gemini", "hooks")}`)) return "gemini"; + if (path.includes(`${join(".codex", "hooks")}`)) return "codex"; + return null; } function detectVendor(input: Record): Vendor { - const event = input.hook_event_name as string | undefined - const byScriptPath = inferVendorFromScriptPath() - if (byScriptPath) return byScriptPath - if (event === "BeforeAgent") return "gemini" - if (event === "beforeSubmitPrompt") return "cursor" + const event = input.hook_event_name as string | undefined; + const byScriptPath = inferVendorFromScriptPath(); + if (byScriptPath) return byScriptPath; + if (event === "BeforeAgent") return "gemini"; + if (event === "beforeSubmitPrompt") return "cursor"; if (event === "UserPromptSubmit") { - if ("session_id" in input && !("sessionId" in input)) return "codex" + if ("session_id" in input && !("sessionId" in input)) return "codex"; } - if (process.env.QWEN_PROJECT_DIR) return "qwen" - return "claude" + if (process.env.QWEN_PROJECT_DIR) return "qwen"; + return "claude"; } function getProjectDir(vendor: Vendor, input: Record): string { - let dir: string + let dir: string; switch (vendor) { case "codex": case "cursor": - dir = (input.cwd as string) || process.cwd() - break + dir = (input.cwd as string) || process.cwd(); + break; case "gemini": - dir = process.env.GEMINI_PROJECT_DIR || process.cwd() - break + dir = process.env.GEMINI_PROJECT_DIR || process.cwd(); + break; case "qwen": - dir = process.env.QWEN_PROJECT_DIR || process.cwd() - break + dir = process.env.QWEN_PROJECT_DIR || process.cwd(); + break; default: - dir = process.env.CLAUDE_PROJECT_DIR || process.cwd() - break + dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + break; } - return resolveGitRoot(dir) + return resolveGitRoot(dir); } function getSessionId(input: Record): string { - return (input.sessionId as string) || (input.session_id as string) || "unknown" + return ( + (input.sessionId as string) || (input.session_id as string) || "unknown" + ); } // ── Config Loading ──────────────────────────────────────────── interface SkillsTriggerConfig { - skills?: Record }> - cjkScripts?: string[] + skills?: Record }>; + cjkScripts?: string[]; } function loadTriggersConfig(): SkillsTriggerConfig { - const configPath = join(dirname(import.meta.path), "triggers.json") - if (!existsSync(configPath)) return {} + const configPath = join(import.meta.dirname, "triggers.json"); + if (!existsSync(configPath)) return {}; try { - return JSON.parse(readFileSync(configPath, "utf-8")) + return JSON.parse(readFileSync(configPath, "utf-8")); } catch { - return {} + return {}; } } function detectLanguage(projectDir: string): string { - const prefsPath = join(projectDir, ".agents", "oma-config.yaml") - if (!existsSync(prefsPath)) return "en" + const prefsPath = join(projectDir, ".agents", "oma-config.yaml"); + if (!existsSync(prefsPath)) return "en"; try { - const content = readFileSync(prefsPath, "utf-8") - const match = content.match(/^language:\s*(\S+)/m) - return match?.[1] ?? "en" + const content = readFileSync(prefsPath, "utf-8"); + const match = content.match(/^language:\s*(\S+)/m); + return match?.[1] ?? "en"; } catch { - return "en" + return "en"; } } // ── Pattern Building ────────────────────────────────────────── export function escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -export function buildTriggerPatterns(triggers: string[], lang: string, cjkScripts: string[]): RegExp[] { +export function buildTriggerPatterns( + triggers: string[], + lang: string, + cjkScripts: string[], +): RegExp[] { return triggers.map((kw) => { - const escaped = escapeRegex(kw).replace(/\s+/g, "\\s+") - // biome-ignore lint/suspicious/noControlCharactersInRegex: ASCII range check for non-ASCII keywords - if (cjkScripts.includes(lang) || /[^\x00-\x7F]/.test(kw)) { - return new RegExp(escaped, "i") + const escaped = escapeRegex(kw).replace(/\s+/g, "\\s+"); + if (cjkScripts.includes(lang) || /[^\p{ASCII}]/u.test(kw)) { + return new RegExp(escaped, "i"); } - return new RegExp(`\\b${escaped}\\b`, "i") - }) + return new RegExp(`\\b${escaped}\\b`, "i"); + }); } // ── Skill Discovery ─────────────────────────────────────────── export interface SkillEntry { - name: string - absolutePath: string - relPath: string + name: string; + absolutePath: string; + relPath: string; } export function discoverSkills(projectDir: string): SkillEntry[] { - const skillsDir = join(projectDir, ".agents", "skills") - if (!existsSync(skillsDir)) return [] + const skillsDir = join(projectDir, ".agents", "skills"); + if (!existsSync(skillsDir)) return []; - const out: SkillEntry[] = [] - let entries: ReturnType + const out: SkillEntry[] = []; + let entries: Dirent[]; try { - entries = readdirSync(skillsDir, { withFileTypes: true }) + entries = readdirSync(skillsDir, { + withFileTypes: true, + encoding: "utf8", + }); } catch { - return out + return out; } for (const entry of entries) { - if (!entry.isDirectory()) continue - if (entry.name.startsWith("_")) continue + if (!entry.isDirectory()) continue; + if (entry.name.startsWith("_")) continue; - const skillPath = join(skillsDir, entry.name, "SKILL.md") - if (!existsSync(skillPath)) continue + const skillPath = join(skillsDir, entry.name, "SKILL.md"); + if (!existsSync(skillPath)) continue; out.push({ name: entry.name, absolutePath: skillPath, relPath: join(".agents", "skills", entry.name, "SKILL.md"), - }) + }); } - return out + return out; } // ── Matching ────────────────────────────────────────────────── export interface SkillMatch { - name: string - relPath: string - score: number - matchedTriggers: string[] + name: string; + relPath: string; + score: number; + matchedTriggers: string[]; } export function matchSkills( @@ -166,37 +183,39 @@ export function matchSkills( skills: SkillEntry[], config: SkillsTriggerConfig, ): SkillMatch[] { - const cjkScripts = config.cjkScripts ?? DEFAULT_CJK_SCRIPTS - const matches: SkillMatch[] = [] + const cjkScripts = config.cjkScripts ?? DEFAULT_CJK_SCRIPTS; + const matches: SkillMatch[] = []; for (const skill of skills) { - const jsonEntry = config.skills?.[skill.name] - if (!jsonEntry) continue + const jsonEntry = config.skills?.[skill.name]; + if (!jsonEntry) continue; const jsonTriggers = [ ...(jsonEntry.keywords["*"] ?? []), ...(jsonEntry.keywords.en ?? []), ...(lang !== "en" ? (jsonEntry.keywords[lang] ?? []) : []), - ] + ]; - const seen = new Set() - const allTriggers: string[] = [] + const seen = new Set(); + const allTriggers: string[] = []; for (const t of jsonTriggers) { - const key = t.toLowerCase() - if (seen.has(key)) continue - seen.add(key) - allTriggers.push(t) + const key = t.toLowerCase(); + if (seen.has(key)) continue; + seen.add(key); + allTriggers.push(t); } - if (allTriggers.length === 0) continue + if (allTriggers.length === 0) continue; - const patterns = buildTriggerPatterns(allTriggers, lang, cjkScripts) - const matched: string[] = [] - let score = 0 + const patterns = buildTriggerPatterns(allTriggers, lang, cjkScripts); + const matched: string[] = []; + let score = 0; for (let i = 0; i < patterns.length; i++) { - if (patterns[i].test(prompt)) { - matched.push(allTriggers[i]) - score += 10 + const pattern = patterns[i]; + const trigger = allTriggers[i]; + if (pattern && trigger && pattern.test(prompt)) { + matched.push(trigger); + score += 10; } } @@ -206,43 +225,45 @@ export function matchSkills( relPath: skill.relPath, score, matchedTriggers: matched, - }) + }); } } - matches.sort((a, b) => (b.score !== a.score ? b.score - a.score : a.name.localeCompare(b.name))) - return matches.slice(0, MAX_SKILLS) + matches.sort((a, b) => + b.score !== a.score ? b.score - a.score : a.name.localeCompare(b.name), + ); + return matches.slice(0, MAX_SKILLS); } // ── Session Dedup State ─────────────────────────────────────── interface SessionState { - sessions: Record + sessions: Record; } function getStatePath(projectDir: string): string { - return join(projectDir, ".agents", "state", "skill-sessions.json") + return join(projectDir, ".agents", "state", "skill-sessions.json"); } function readState(projectDir: string): SessionState { - const p = getStatePath(projectDir) - if (!existsSync(p)) return { sessions: {} } + const p = getStatePath(projectDir); + if (!existsSync(p)) return { sessions: {} }; try { - const parsed = JSON.parse(readFileSync(p, "utf-8")) + const parsed = JSON.parse(readFileSync(p, "utf-8")); if (parsed && typeof parsed === "object" && parsed.sessions) { - return parsed as SessionState + return parsed as SessionState; } } catch { // corrupted — reset } - return { sessions: {} } + return { sessions: {} }; } function writeState(projectDir: string, state: SessionState): void { - const p = getStatePath(projectDir) + const p = getStatePath(projectDir); try { - mkdirSync(dirname(p), { recursive: true }) - writeFileSync(p, JSON.stringify(state, null, 2)) + mkdirSync(dirname(p), { recursive: true }); + writeFileSync(p, JSON.stringify(state, null, 2)); } catch { // dedup failing open is acceptable } @@ -254,47 +275,148 @@ export function filterFreshMatches( sessionId: string, now: number = Date.now(), ): { fresh: SkillMatch[]; nextState: SessionState } { - const state = readState(projectDir) + const state = readState(projectDir); for (const [id, sess] of Object.entries(state.sessions)) { if (now - sess.timestamp > SESSION_TTL_MS) { - delete state.sessions[id] + delete state.sessions[id]; } } - const current = state.sessions[sessionId] - const alreadyInjected = new Set(current && now - current.timestamp <= SESSION_TTL_MS ? current.injected : []) + const current = state.sessions[sessionId]; + const alreadyInjected = new Set( + current && now - current.timestamp <= SESSION_TTL_MS + ? current.injected + : [], + ); - const fresh = matches.filter((m) => !alreadyInjected.has(m.relPath)) + const fresh = matches.filter((m) => !alreadyInjected.has(m.relPath)); if (fresh.length > 0) { - const existing = state.sessions[sessionId]?.injected ?? [] + const existing = state.sessions[sessionId]?.injected ?? []; state.sessions[sessionId] = { injected: [...new Set([...existing, ...fresh.map((m) => m.relPath)])], timestamp: now, - } + }; } - return { fresh, nextState: state } + return { fresh, nextState: state }; } // ── Workflow Guard ──────────────────────────────────────────── -export function isPersistentWorkflowActive(projectDir: string, sessionId: string): boolean { - const stateDir = join(projectDir, ".agents", "state") - if (!existsSync(stateDir)) return false +export function isPersistentWorkflowActive( + projectDir: string, + sessionId: string, +): boolean { + const stateDir = join(projectDir, ".agents", "state"); + if (!existsSync(stateDir)) return false; try { - const files = readdirSync(stateDir) - return files.some((f) => f.endsWith(`-state-${sessionId}.json`) && f !== "skill-sessions.json") + const files = readdirSync(stateDir); + return files.some( + (f) => + f.endsWith(`-state-${sessionId}.json`) && f !== "skill-sessions.json", + ); } catch { - return false + return false; } } // ── Prompt Sanitation ───────────────────────────────────────── export function startsWithSlashCommand(prompt: string): boolean { - return /^\/[a-zA-Z][\w-]*/.test(prompt.trim()) + return /^\/[a-zA-Z][\w-]*/.test(prompt.trim()); +} + +// Match an explicit `/` token at the very start of the prompt +// or after whitespace. Stays conservative to avoid path/URL false positives. +export function parseExplicitSlash(prompt: string): string | null { + const m = /(?:^|\s)\/([a-z][a-z0-9_-]{0,40})\b/i.exec(prompt); + return m?.[1] ?? null; +} + +// ── Claude Slash Skill Resolution ───────────────────────────── +// Claude Code deprecated `.claude/commands/` and now uses `.claude/skills/` +// for slash-invocable workflows. To express "user-only invocation" (slash +// command typed by the user but NOT auto-callable by the model), the +// Claude Code idiom is `disable-model-invocation: true` in SKILL.md +// frontmatter. Such skills are absent from the available-skills list, +// so when the user types / the model has no native signal that it +// exists. This resolver bridges that gap. Other vendors use different +// command/skill mechanisms; this is intentionally Claude-specific. + +export interface ClaudeSlashSkillEntry { + name: string; + skillRelPath: string; + body: string; +} + +export function parseSkillFrontmatter(content: string): { + frontmatter: Record; + body: string; +} { + const m = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/.exec(content); + if (!m) return { frontmatter: {}, body: content }; + const fm: Record = {}; + const block = m[1] ?? ""; + for (const line of block.split(/\r?\n/)) { + const kv = /^([a-z][\w-]*)\s*:\s*(.*)$/i.exec(line); + if (!kv) continue; + const key = kv[1]; + const rawValue = (kv[2] ?? "").trim(); + if (!key) continue; + if (rawValue === "true") fm[key] = true; + else if (rawValue === "false") fm[key] = false; + else fm[key] = rawValue.replace(/^['"]|['"]$/g, ""); + } + return { frontmatter: fm, body: m[2] ?? "" }; +} + +export function findClaudeSlashSkill( + name: string, + projectDir: string, +): ClaudeSlashSkillEntry | null { + const candidates = [ + join(projectDir, ".claude", "skills", name, "SKILL.md"), + join(projectDir, ".agents", "skills", name, "SKILL.md"), + ]; + + for (const skillPath of candidates) { + if (!existsSync(skillPath)) continue; + let content: string; + try { + content = readFileSync(skillPath, "utf-8"); + } catch { + continue; + } + const { frontmatter, body } = parseSkillFrontmatter(content); + if (frontmatter["disable-model-invocation"] !== true) continue; + const posixPath = toPosixPath(skillPath); + const posixRoot = toPosixPath(projectDir); + return { + name, + skillRelPath: posixPath.startsWith(`${posixRoot}/`) + ? posixPath.slice(posixRoot.length + 1) + : posixPath, + body: body.trim(), + }; + } + return null; +} + +export function formatClaudeSlashSkillContext( + entry: ClaudeSlashSkillEntry, +): string { + return [ + `[OMA CLAUDE SLASH SKILL INVOKED: ${entry.name}]`, + `User explicitly typed /${entry.name}. Claude Code deprecated \`.claude/commands/\`, so this slash-only workflow lives in SKILL.md with \`disable-model-invocation: true\` — it is NOT in the available-skills list and is NOT callable via the Skill tool.`, + "", + `Honor the user's explicit invocation by reading \`${entry.skillRelPath}\` and following its instructions:`, + "", + entry.body, + "", + "Read any referenced workflow / resource files and proceed step by step. Do NOT respond that the skill is unavailable.", + ].join("\n"); } export function stripCodeBlocks(text: string): string { @@ -303,7 +425,7 @@ export function stripCodeBlocks(text: string): string { .replace(/(`{3,})[^\n]*\n[\s\S]*/g, "") .replace(/`{3,}[^`]*`{3,}/g, "") .replace(/`[^`\n]+`/g, "") - .replace(/"[^"\n]*"/g, "") + .replace(/"[^"\n]*"/g, ""); } // ── Context Formatting ──────────────────────────────────────── @@ -313,55 +435,80 @@ export function formatContext(matches: SkillMatch[]): string { `[OMA SKILLS DETECTED: ${matches.map((m) => m.name).join(", ")}]`, "User intent matches the following skills:", "", - ] + ]; for (const m of matches) { - lines.push(`- **${m.name}** — \`${m.relPath}\``) - lines.push(` Matched triggers: ${m.matchedTriggers.join(", ")}`) + lines.push(`- **${m.name}** — \`${m.relPath}\``); + lines.push(` Matched triggers: ${m.matchedTriggers.join(", ")}`); } - lines.push("") - lines.push("Read the relevant SKILL.md before invoking. These suggestions are advisory — apply judgement.") - return lines.join("\n") + lines.push(""); + lines.push( + "Read the relevant SKILL.md before invoking. These suggestions are advisory — apply judgement.", + ); + return lines.join("\n"); } // ── Main ────────────────────────────────────────────────────── async function main() { - const raw = readFileSync("/dev/stdin", "utf-8") - let input: Record + const raw = readFileSync(0, "utf-8"); + let input: Record; try { - input = JSON.parse(raw) + input = JSON.parse(raw); } catch { - process.exit(0) + process.exit(0); } - const vendor = detectVendor(input) - const projectDir = getProjectDir(vendor, input) - const sessionId = getSessionId(input) - const prompt = (input.prompt as string) ?? "" + const vendor = detectVendor(input); + const projectDir = getProjectDir(vendor, input); + const sessionId = getSessionId(input); + const prompt = (input.prompt as string) ?? ""; + + if (!prompt.trim()) process.exit(0); + + // Claude-specific: when the user types /, surface the + // SKILL.md body for slash-only skills (disable-model-invocation: true). + // The model otherwise has no signal these skills exist — they are + // intentionally hidden from the available-skills list. Must run BEFORE + // the slash early-exit and persistent-workflow guard. + if (vendor === "claude") { + const slashName = parseExplicitSlash(prompt); + if (slashName) { + const slashSkill = findClaudeSlashSkill(slashName, projectDir); + if (slashSkill) { + process.stdout.write( + makePromptOutput(vendor, formatClaudeSlashSkillContext(slashSkill)), + ); + process.exit(0); + } + } + } - if (!prompt.trim()) process.exit(0) - if (startsWithSlashCommand(prompt)) process.exit(0) - if (isPersistentWorkflowActive(projectDir, sessionId)) process.exit(0) + if (startsWithSlashCommand(prompt)) process.exit(0); + if (isPersistentWorkflowActive(projectDir, sessionId)) process.exit(0); - const lang = detectLanguage(projectDir) - const config = loadTriggersConfig() - const cleaned = stripCodeBlocks(prompt) - const skills = discoverSkills(projectDir) + const lang = detectLanguage(projectDir); + const config = loadTriggersConfig(); + const cleaned = stripCodeBlocks(prompt); + const skills = discoverSkills(projectDir); - const matches = matchSkills(cleaned, lang, skills, config) - if (matches.length === 0) process.exit(0) + const matches = matchSkills(cleaned, lang, skills, config); + if (matches.length === 0) process.exit(0); - const { fresh, nextState } = filterFreshMatches(matches, projectDir, sessionId) - if (fresh.length === 0) process.exit(0) + const { fresh, nextState } = filterFreshMatches( + matches, + projectDir, + sessionId, + ); + if (fresh.length === 0) process.exit(0); - writeState(projectDir, nextState) - process.stdout.write(makePromptOutput(vendor, formatContext(fresh))) - process.exit(0) + writeState(projectDir, nextState); + process.stdout.write(makePromptOutput(vendor, formatContext(fresh))); + process.exit(0); } if (import.meta.main) { - main().catch(() => process.exit(0)) + main().catch(() => process.exit(0)); } // Avoid unused-import lint for basename when testing subsets of this module. -void basename +void basename; diff --git a/.agents/hooks/core/test-filter.ts b/.agents/hooks/core/test-filter.ts index a0ce2fc..f378075 100644 --- a/.agents/hooks/core/test-filter.ts +++ b/.agents/hooks/core/test-filter.ts @@ -1,51 +1,53 @@ // PreToolUse hook — Filter test output to show only failures // Works with: Claude Code, Codex CLI, Gemini CLI, Qwen Code -import { existsSync } from "node:fs" -import { join } from "node:path" -import { makePreToolOutput, resolveGitRoot, type Vendor } from "./types.ts" +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { resolveGitRoot } from "./fs-utils.ts"; +import { makePreToolOutput } from "./hook-output.ts"; +import type { Vendor } from "./types.ts"; // --- Vendor detection (same logic as keyword-detector.ts) --- function detectVendor(input: Record): Vendor { - const event = input.hook_event_name as string | undefined - if (event === "BeforeTool") return "gemini" + const event = input.hook_event_name as string | undefined; + if (event === "BeforeTool") return "gemini"; if (event === "PreToolUse") { - if ("session_id" in input && !("sessionId" in input)) return "codex" + if ("session_id" in input && !("sessionId" in input)) return "codex"; } - if (process.env.QWEN_PROJECT_DIR) return "qwen" - return "claude" + if (process.env.QWEN_PROJECT_DIR) return "qwen"; + return "claude"; } function getProjectDir(vendor: Vendor, input: Record): string { - let dir: string + let dir: string; switch (vendor) { case "codex": - dir = (input.cwd as string) || process.cwd() - break + dir = (input.cwd as string) || process.cwd(); + break; case "gemini": - dir = process.env.GEMINI_PROJECT_DIR || process.cwd() - break + dir = process.env.GEMINI_PROJECT_DIR || process.cwd(); + break; case "qwen": - dir = process.env.QWEN_PROJECT_DIR || process.cwd() - break + dir = process.env.QWEN_PROJECT_DIR || process.cwd(); + break; default: - dir = process.env.CLAUDE_PROJECT_DIR || process.cwd() - break + dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); + break; } - return resolveGitRoot(dir) + return resolveGitRoot(dir); } function getHookDir(vendor: Vendor): string { switch (vendor) { case "codex": - return ".codex/hooks" + return ".codex/hooks"; case "gemini": - return ".gemini/hooks" + return ".gemini/hooks"; case "qwen": - return ".qwen/hooks" + return ".qwen/hooks"; default: - return ".claude/hooks" + return ".claude/hooks"; } } @@ -78,66 +80,78 @@ const TEST_PATTERNS = [ /\brspec\b/, /\bmix\s+test\b/, /\bphpunit\b/, -] +]; // Commands that mention test runners but aren't running tests const EXCLUDE_PATTERNS = [ /\b(install|add|remove|uninstall|init)\b/, /\b(cat|head|tail|less|more|wc)\b.*\.(test|spec)\./, -] +]; // --- Hook input --- interface PreToolUseInput { - tool_name: string + tool_name: string; tool_input: { - command?: string - [key: string]: unknown - } - hook_event_name?: string - session_id?: string - sessionId?: string - cwd?: string + command?: string; + [key: string]: unknown; + }; + hook_event_name?: string; + session_id?: string; + sessionId?: string; + cwd?: string; } // --- Main --- -const raw = await Bun.stdin.text() -if (!raw.trim()) process.exit(0) +// Use fd 0 (sync) instead of Bun.stdin.text() — works under both Bun and +// Node, and avoids stdin-buffering timing differences between hosts. +// Fallback: when OMA_HOOK_INPUT_FILE is set, read from that file. This +// makes the hook testable from environments (vitest worker pools under +// bun) where piping stdin to a child process is unreliable. +const inputFile = process.env.OMA_HOOK_INPUT_FILE; +const raw = inputFile + ? readFileSync(inputFile, "utf-8") + : readFileSync(0, "utf-8"); +if (!raw.trim()) process.exit(0); -const input: PreToolUseInput = JSON.parse(raw) +const input: PreToolUseInput = JSON.parse(raw); // Gemini uses run_shell_command; Claude-family uses Bash. if (input.tool_name !== "Bash" && input.tool_name !== "run_shell_command") { - process.exit(0) + process.exit(0); } -const command = input.tool_input?.command -if (!command) process.exit(0) +const command = input.tool_input?.command; +if (!command) process.exit(0); // Check if this is a test command -const isTestCommand = TEST_PATTERNS.some((p) => p.test(command)) -if (!isTestCommand) process.exit(0) +const isTestCommand = TEST_PATTERNS.some((p) => p.test(command)); +if (!isTestCommand) process.exit(0); // Skip if it's a non-test use of test tool names (install, cat, etc.) -const isExcluded = EXCLUDE_PATTERNS.some((p) => p.test(command)) -if (isExcluded) process.exit(0) +const isExcluded = EXCLUDE_PATTERNS.some((p) => p.test(command)); +if (isExcluded) process.exit(0); // Detect vendor and resolve project dir -const vendor = detectVendor(input) -const projectDir = getProjectDir(vendor, input) -const filterScript = join(projectDir, getHookDir(vendor), "filter-test-output.sh") +const vendor = detectVendor(input); +const projectDir = getProjectDir(vendor, input); +const filterScript = join( + projectDir, + getHookDir(vendor), + "filter-test-output.sh", +); // Skip filtering if the script doesn't exist (hooks not fully installed) -if (!existsSync(filterScript)) process.exit(0) +if (!existsSync(filterScript)) process.exit(0); // Rewrite command to pipe through filter -const filteredCmd = `set -o pipefail; (${command}) 2>&1 | bash "${filterScript}"` +const filteredCmd = `set -o pipefail; (${command}) 2>&1 | bash "${filterScript}"`; // Return updated input with all original fields preserved const updatedInput: Record = { ...input.tool_input, command: filteredCmd, -} +}; -console.log(makePreToolOutput(vendor, updatedInput)) +console.log(makePreToolOutput(vendor, updatedInput)); diff --git a/.agents/hooks/core/triggers.json b/.agents/hooks/core/triggers.json index f404583..287223e 100644 --- a/.agents/hooks/core/triggers.json +++ b/.agents/hooks/core/triggers.json @@ -43,9 +43,35 @@ "全部お願い", "まとめてやって" ], - "zh": ["编排", "并行执行", "自动执行", "全部执行", "全部做", "自动处理", "一起做", "全做了", "帮我全做"], - "es": ["orquestar", "paralelo", "ejecutar todo", "hazlo todo", "ejecuta todo", "automatiza", "haz todo"], - "fr": ["orchestrer", "parallèle", "tout exécuter", "fais tout", "exécute tout", "automatise", "gère tout"], + "zh": [ + "编排", + "并行执行", + "自动执行", + "全部执行", + "全部做", + "自动处理", + "一起做", + "全做了", + "帮我全做" + ], + "es": [ + "orquestar", + "paralelo", + "ejecutar todo", + "hazlo todo", + "ejecuta todo", + "automatiza", + "haz todo" + ], + "fr": [ + "orchestrer", + "parallèle", + "tout exécuter", + "fais tout", + "exécute tout", + "automatise", + "gère tout" + ], "de": [ "orchestrieren", "parallel", @@ -55,7 +81,15 @@ "automatisieren", "alles auf einmal" ], - "pt": ["orquestrar", "paralelo", "executar tudo", "faça tudo", "execute tudo", "automatize", "resolva tudo"], + "pt": [ + "orquestrar", + "paralelo", + "executar tudo", + "faça tudo", + "execute tudo", + "automatize", + "resolva tudo" + ], "ru": [ "оркестровать", "параллельно", @@ -83,6 +117,15 @@ "zautomatyzuj", "wszystko naraz" ] + }, + "patterns": { + "*": [ + "\\b(build|create|make|develop|implement|scaffold)\\s+(?:me\\s+)?(?:an?|the)\\s+(?:[\\w-]+\\s+){0,3}(app|api|service|server|cli|tool|website|dashboard|system|feature|backend|frontend|prototype|mvp|bot)\\b", + "\\bi\\s+want\\s+(?:a|an)\\s+(?:[\\w-]+\\s+){0,3}(app|api|service|server|cli|tool|website|dashboard|system|feature|backend|frontend|prototype|mvp|bot)\\b" + ], + "ko": [ + "(앱|API|서비스|서버|CLI|도구|웹사이트|대시보드|시스템|기능|백엔드|프론트엔드|프로토타입|MVP|봇)\\s*(?:을|를|이|가)?\\s*(?:만들어\\s*(?:주세요|줘|줄래)?|구현해\\s*(?:주세요|줘|줄래)?|개발해\\s*(?:주세요|줘|줄래)?|만들자|구현하자|개발하자)" + ] } }, "ultrawork": { @@ -128,7 +171,16 @@ "トレードオフ", "品質特性" ], - "zh": ["架构", "系统设计", "软件设计", "架构评审", "模块边界", "服务边界", "权衡分析", "质量属性"], + "zh": [ + "架构", + "系统设计", + "软件设计", + "架构评审", + "模块边界", + "服务边界", + "权衡分析", + "质量属性" + ], "es": [ "arquitectura", "diseño de sistemas", @@ -205,7 +257,15 @@ "persistent": false, "keywords": { "*": ["task breakdown"], - "en": ["plan", "make a plan", "create a plan", "break down", "analyze requirements", "plan this", "decompose"], + "en": [ + "plan", + "make a plan", + "create a plan", + "break down", + "analyze requirements", + "plan this", + "decompose" + ], "ko": [ "계획", "요구사항 분석", @@ -235,7 +295,16 @@ "設計して", "プランを作って" ], - "zh": ["计划", "需求分析", "任务分解", "制定计划", "做个计划", "分析一下", "拆分任务", "规划一下"], + "zh": [ + "计划", + "需求分析", + "任务分解", + "制定计划", + "做个计划", + "分析一下", + "拆分任务", + "规划一下" + ], "es": [ "plan", "planificar", @@ -286,7 +355,15 @@ "разбей на задачи", "спланируй" ], - "nl": ["plan", "plannen", "vereistenanalyse", "maak een plan", "analyseer", "splits op", "plan dit"], + "nl": [ + "plan", + "plannen", + "vereistenanalyse", + "maak een plan", + "analyseer", + "splits op", + "plan dit" + ], "pl": [ "plan", "planować", @@ -303,7 +380,15 @@ "persistent": false, "keywords": { "*": ["code review", "security audit", "security review"], - "en": ["review", "review this", "review my code", "check my code", "audit", "inspect", "code check"], + "en": [ + "review", + "review this", + "review my code", + "check my code", + "audit", + "inspect", + "code check" + ], "ko": [ "리뷰", "코드 검토", @@ -330,7 +415,17 @@ "点検して", "コード確認" ], - "zh": ["审查", "代码审查", "安全审计", "审查一下", "检查一下", "看看代码", "检查代码", "代码检查", "安全检查"], + "zh": [ + "审查", + "代码审查", + "安全审计", + "审查一下", + "检查一下", + "看看代码", + "检查代码", + "代码检查", + "安全检查" + ], "es": [ "revisión", "revisar código", @@ -404,6 +499,41 @@ ] } }, + "deepsec": { + "persistent": false, + "keywords": { + "*": ["/deepsec", "deepsec workflow"], + "en": [ + "run deepsec", + "deepsec scan this repo", + "scan repo with deepsec", + "deepsec pr review", + "deepsec ci gate", + "deepsec triage", + "deepsec matchers" + ], + "ko": [ + "딥섹 워크플로우", + "딥섹 실행", + "딥섹 스캔", + "딥섹으로 검사", + "딥섹 PR 리뷰", + "딥섹 CI 게이트" + ], + "ja": [ + "ディープセック実行", + "deepsecワークフロー", + "deepsecでスキャン", + "deepsec PRレビュー" + ], + "zh": [ + "运行 deepsec", + "deepsec 工作流", + "用 deepsec 扫描", + "deepsec PR 审查" + ] + } + }, "debug": { "persistent": false, "keywords": { @@ -613,7 +743,17 @@ "アイデアちょうだい", "一緒に考えよう" ], - "zh": ["头脑风暴", "创意", "设计探索", "想想", "出主意", "有什么想法", "想个办法", "出点子", "集思广益"], + "zh": [ + "头脑风暴", + "创意", + "设计探索", + "想想", + "出主意", + "有什么想法", + "想个办法", + "出点子", + "集思广益" + ], "es": [ "lluvia de ideas", "idear", @@ -644,7 +784,16 @@ "vorschläge", "lass uns überlegen" ], - "pt": ["brainstorming", "idear", "explorar design", "pense em", "e se", "ideias para", "sugira", "imagine"], + "pt": [ + "brainstorming", + "idear", + "explorar design", + "pense em", + "e se", + "ideias para", + "sugira", + "imagine" + ], "ru": [ "мозговой штурм", "идеи", @@ -681,7 +830,13 @@ "persistent": true, "keywords": { "*": ["work", "step by step"], - "en": ["one by one", "guide me", "walk me through", "manual mode", "one step at a time"], + "en": [ + "one by one", + "guide me", + "walk me through", + "manual mode", + "one step at a time" + ], "ko": [ "단계별", "단계별로", @@ -693,9 +848,32 @@ "차근차근 해줘", "수동으로 해줘" ], - "ja": ["ステップバイステップ", "一歩ずつ", "ガイドして", "手動で", "一つずつ", "順番にやって", "手順を教えて"], - "zh": ["逐步", "一步一步", "指导我", "手动", "一个一个", "按顺序", "带我做"], - "es": ["paso a paso", "guíame", "uno por uno", "modo manual", "de a uno", "llévame paso a paso"], + "ja": [ + "ステップバイステップ", + "一歩ずつ", + "ガイドして", + "手動で", + "一つずつ", + "順番にやって", + "手順を教えて" + ], + "zh": [ + "逐步", + "一步一步", + "指导我", + "手动", + "一个一个", + "按顺序", + "带我做" + ], + "es": [ + "paso a paso", + "guíame", + "uno por uno", + "modo manual", + "de a uno", + "llévame paso a paso" + ], "fr": [ "étape par étape", "guide-moi", @@ -712,8 +890,22 @@ "zeig mir wie", "der reihe nach" ], - "pt": ["passo a passo", "me guie", "um por um", "modo manual", "me acompanhe", "me mostre passo a passo"], - "ru": ["шаг за шагом", "направь меня", "по одному", "ручной режим", "покажи по шагам", "веди меня"], + "pt": [ + "passo a passo", + "me guie", + "um por um", + "modo manual", + "me acompanhe", + "me mostre passo a passo" + ], + "ru": [ + "шаг за шагом", + "направь меня", + "по одному", + "ручной режим", + "покажи по шагам", + "веди меня" + ], "nl": [ "stap voor stap", "begeleid me", @@ -736,7 +928,14 @@ "persistent": false, "keywords": { "*": ["deepinit"], - "en": ["init project", "initialize", "setup project", "new project", "scaffold", "bootstrap"], + "en": [ + "init project", + "initialize", + "setup project", + "new project", + "scaffold", + "bootstrap" + ], "ko": [ "프로젝트 초기화", "코드베이스 초기화", @@ -757,7 +956,15 @@ "プロジェクトを作って", "プロジェクト設定" ], - "zh": ["项目初始化", "新项目", "设置项目", "搭建项目", "初始化", "创建项目", "项目配置"], + "zh": [ + "项目初始化", + "新项目", + "设置项目", + "搭建项目", + "初始化", + "创建项目", + "项目配置" + ], "es": [ "inicializar proyecto", "nuevo proyecto", @@ -1368,6 +1575,146 @@ "makieta" ] } + }, + "docs": { + "persistent": false, + "keywords": { + "*": ["oma-docs", "doc-refs", "docs verify", "docs sync"], + "en": [ + "verify docs", + "verify documentation", + "check docs", + "check documentation", + "docs drift", + "documentation drift", + "broken docs", + "broken doc links", + "stale docs", + "stale documentation", + "sync docs", + "sync documentation", + "update docs after change", + "patch docs", + "doc verify", + "doc sync" + ], + "ko": [ + "문서 검증", + "문서 검증해줘", + "문서 점검", + "문서 점검해줘", + "문서 드리프트", + "문서 동기화", + "문서 동기화해줘", + "문서 싱크", + "문서 싱크 맞춰", + "문서 싱크 맞춰줘", + "깨진 문서 링크", + "깨진 문서", + "오래된 문서", + "낡은 문서", + "문서 갱신", + "문서 업데이트", + "docs 검증", + "docs 싱크", + "docs 동기화" + ], + "ja": [ + "ドキュメント検証", + "ドキュメント点検", + "ドキュメントドリフト", + "ドキュメント同期", + "ドキュメントを同期", + "壊れたドキュメントリンク", + "古いドキュメント", + "ドキュメント更新", + "docsを検証", + "docsを同期" + ], + "zh": [ + "文档校验", + "文档检查", + "文档漂移", + "文档同步", + "同步文档", + "失效文档链接", + "陈旧文档", + "更新文档", + "docs 校验", + "docs 同步" + ], + "es": [ + "verificar documentación", + "comprobar documentación", + "deriva de documentación", + "sincronizar documentación", + "enlaces rotos en documentación", + "documentación obsoleta", + "actualizar documentación" + ], + "fr": [ + "vérifier la documentation", + "contrôler la documentation", + "dérive de documentation", + "synchroniser la documentation", + "liens cassés dans la documentation", + "documentation obsolète", + "mettre à jour la documentation" + ], + "de": [ + "Doku verifizieren", + "Dokumentation prüfen", + "Dokumentations-Drift", + "Doku synchronisieren", + "kaputte Doku-Links", + "veraltete Dokumentation", + "Doku aktualisieren" + ], + "pt": [ + "verificar documentação", + "checar documentação", + "drift de documentação", + "sincronizar documentação", + "links quebrados na documentação", + "documentação desatualizada", + "atualizar documentação" + ], + "ru": [ + "проверить документацию", + "верифицировать документацию", + "дрейф документации", + "синхронизировать документацию", + "битые ссылки в документации", + "устаревшая документация", + "обновить документацию" + ], + "nl": [ + "documentatie verifiëren", + "documentatie controleren", + "documentatie-drift", + "documentatie synchroniseren", + "kapotte documentatielinks", + "verouderde documentatie", + "documentatie bijwerken" + ], + "pl": [ + "zweryfikuj dokumentację", + "sprawdź dokumentację", + "dryf dokumentacji", + "zsynchronizuj dokumentację", + "zepsute linki w dokumentacji", + "przestarzała dokumentacja", + "zaktualizuj dokumentację" + ] + } + }, + "recap": { + "persistent": false, + "keywords": { + "*": ["recap"], + "ko": ["리캡"], + "ja": ["リキャップ"] + } } }, "skills": { @@ -1381,8 +1728,20 @@ "define boundaries", "architecture tradeoffs" ], - "ko": ["아키텍처 짜줘", "시스템 구조 설계", "경계 정의해줘", "구조 검토해줘", "아키텍처 문서"], - "ja": ["アーキテクチャを設計", "システム構成を考えて", "境界を定義", "構成レビュー", "アーキ文書"], + "ko": [ + "아키텍처 짜줘", + "시스템 구조 설계", + "경계 정의해줘", + "구조 검토해줘", + "아키텍처 문서" + ], + "ja": [ + "アーキテクチャを設計", + "システム構成を考えて", + "境界を定義", + "構成レビュー", + "アーキ文書" + ], "zh": ["设计架构", "系统架构方案", "定义边界", "架构文档", "架构权衡"] } }, @@ -1403,17 +1762,45 @@ "server implementation", "clean architecture" ], - "ko": ["api 만들어줘", "엔드포인트 추가", "백엔드 구현", "마이그레이션 작성", "인증 붙여줘"], - "ja": ["apiを作って", "エンドポイント追加", "バックエンド実装", "マイグレーション書いて", "認証を実装"], + "ko": [ + "api 만들어줘", + "엔드포인트 추가", + "백엔드 구현", + "마이그레이션 작성", + "인증 붙여줘" + ], + "ja": [ + "apiを作って", + "エンドポイント追加", + "バックエンド実装", + "マイグレーション書いて", + "認証を実装" + ], "zh": ["写个接口", "加接口", "后端实现", "写迁移", "加认证"] } }, "oma-brainstorm": { "keywords": { "*": [], - "en": ["toss around ideas", "kick around options", "spitball", "some ideas please", "ideation session"], - "ko": ["아이디어 좀 뽑아줘", "같이 고민해줘", "아이디어 내보자", "방향성 고민"], - "ja": ["アイデア出して", "一緒に考えて", "方向性を探りたい", "案を出して"], + "en": [ + "toss around ideas", + "kick around options", + "spitball", + "some ideas please", + "ideation session" + ], + "ko": [ + "아이디어 좀 뽑아줘", + "같이 고민해줘", + "아이디어 내보자", + "방향성 고민" + ], + "ja": [ + "アイデア出して", + "一緒に考えて", + "方向性を探りたい", + "案を出して" + ], "zh": ["帮我想想", "一起想想办法", "给点灵感"] } }, @@ -1430,8 +1817,18 @@ "cli handoff", "manual orchestration" ], - "ko": ["에이전트 조율", "에이전트끼리 협업", "수동으로 에이전트 돌려", "에이전트 순서 잡아줘"], - "ja": ["エージェントを調整", "エージェント連携", "手動でエージェント", "エージェントの順序"], + "ko": [ + "에이전트 조율", + "에이전트끼리 협업", + "수동으로 에이전트 돌려", + "에이전트 순서 잡아줘" + ], + "ja": [ + "エージェントを調整", + "エージェント連携", + "手動でエージェント", + "エージェントの順序" + ], "zh": ["协调代理", "代理之间协作", "手动跑代理", "代理之间衔接"] } }, @@ -1454,8 +1851,20 @@ "data migration", "capacity planning" ], - "ko": ["스키마 설계", "테이블 설계", "인덱스 튜닝", "쿼리 느려", "용량 산정"], - "ja": ["スキーマ設計", "テーブル設計", "インデックス調整", "クエリが遅い", "容量見積"], + "ko": [ + "스키마 설계", + "테이블 설계", + "인덱스 튜닝", + "쿼리 느려", + "용량 산정" + ], + "ja": [ + "スキーマ設計", + "テーブル設計", + "インデックス調整", + "クエリが遅い", + "容量見積" + ], "zh": ["设计表结构", "表设计", "索引优化", "查询很慢", "容量评估"] } }, @@ -1474,8 +1883,20 @@ "crash fix", "error investigation" ], - "ko": ["버그 찾아줘", "에러 원인", "크래시 분석", "스택트레이스 봐줘", "원인 파악해줘"], - "ja": ["バグを探して", "エラー原因", "クラッシュを分析", "スタックトレースを見て", "原因を特定"], + "ko": [ + "버그 찾아줘", + "에러 원인", + "크래시 분석", + "스택트레이스 봐줘", + "원인 파악해줘" + ], + "ja": [ + "バグを探して", + "エラー原因", + "クラッシュを分析", + "スタックトレースを見て", + "原因を特定" + ], "zh": ["找出 bug", "错误原因", "分析崩溃", "看堆栈", "定位原因"] } }, @@ -1493,8 +1914,19 @@ "responsive layout", "motion design" ], - "ko": ["디자인 토큰", "랜딩 만들어줘", "컬러 팔레트 잡아줘", "타이포 스케일", "모션 가이드"], - "ja": ["デザイントークン", "ランディング作成", "カラーパレット決めて", "モーション設計"], + "ko": [ + "디자인 토큰", + "랜딩 만들어줘", + "컬러 팔레트 잡아줘", + "타이포 스케일", + "모션 가이드" + ], + "ja": [ + "デザイントークン", + "ランディング作成", + "カラーパレット決めて", + "モーション設計" + ], "zh": ["设计令牌", "做个落地页", "定配色", "字体层级", "动效规范"] } }, @@ -1514,14 +1946,41 @@ "release automation", "build automation" ], - "ko": ["mise 태스크", "ci 파이프라인", "릴리즈 자동화", "깃 훅 설정", "모노레포 워크플로우"], - "ja": ["miseタスク", "ciパイプライン", "リリース自動化", "gitフック", "モノレポ作業"], - "zh": ["mise 任务", "ci 流水线", "发布自动化", "git 钩子", "monorepo 工作流"] + "ko": [ + "mise 태스크", + "ci 파이프라인", + "릴리즈 자동화", + "깃 훅 설정", + "모노레포 워크플로우" + ], + "ja": [ + "miseタスク", + "ciパイプライン", + "リリース自動化", + "gitフック", + "モノレポ作業" + ], + "zh": [ + "mise 任务", + "ci 流水线", + "发布自动化", + "git 钩子", + "monorepo 工作流" + ] } }, "oma-frontend": { "keywords": { - "*": ["shadcn", "FSD"], + "*": [ + "shadcn", + "FSD", + "next.js", + "nextjs", + "react", + "tailwind", + "tsx", + "frontend" + ], "en": [ "make a react component", "build a next page", @@ -1534,9 +1993,27 @@ "frontend ui", "FSD architecture" ], - "ko": ["리액트 컴포넌트", "넥스트 페이지", "tailwind로 스타일", "shadcn 붙여줘", "프론트 구현"], - "ja": ["reactコンポーネント", "nextページ", "tailwindで装飾", "shadcn導入", "フロント実装"], - "zh": ["写个 react 组件", "next 页面", "用 tailwind", "接入 shadcn", "前端实现"] + "ko": [ + "리액트 컴포넌트", + "넥스트 페이지", + "tailwind로 스타일", + "shadcn 붙여줘", + "프론트 구현" + ], + "ja": [ + "reactコンポーネント", + "nextページ", + "tailwindで装飾", + "shadcn導入", + "フロント実装" + ], + "zh": [ + "写个 react 组件", + "next 页面", + "用 tailwind", + "接入 shadcn", + "前端实现" + ] } }, "oma-hwp": { @@ -1551,7 +2028,16 @@ "hangul word processor", "hwp ingestion" ], - "ko": ["한글 파일", "한글 변환", "한글 파싱", "hwp 변환", "hwp 파싱", "hwp 마크다운", "hwpx 변환", "hwpx 파싱"], + "ko": [ + "한글 파일", + "한글 변환", + "한글 파싱", + "hwp 변환", + "hwp 파싱", + "hwp 마크다운", + "hwpx 변환", + "hwpx 파싱" + ], "ja": ["hwp変換", "hwpをマークダウン", "hwpを解析", "韓国語ワープロ"], "zh": ["hwp 转换", "hwp 解析", "hwp 转 markdown", "韩文文档"] } @@ -1571,9 +2057,307 @@ "mobile app", "android ios" ], - "ko": ["플러터 화면", "리액트 네이티브 화면", "다트 위젯", "안드로이드 아이폰 앱", "모바일 앱"], - "ja": ["flutter画面", "react native画面", "dartウィジェット", "iosアンドロイド", "モバイルアプリ"], - "zh": ["flutter 页面", "react native 页面", "dart 组件", "安卓 ios", "移动端应用"] + "ko": [ + "플러터 화면", + "리액트 네이티브 화면", + "다트 위젯", + "안드로이드 아이폰 앱", + "모바일 앱" + ], + "ja": [ + "flutter画面", + "react native画面", + "dartウィジェット", + "iosアンドロイド", + "モバイルアプリ" + ], + "zh": [ + "flutter 页面", + "react native 页面", + "dart 组件", + "安卓 ios", + "移动端应用" + ] + } + }, + "oma-market": { + "keywords": { + "*": [], + "en": [ + "market research", + "pain point", + "pain points", + "voice of customer", + "competitor research", + "competitor analysis", + "trend detection", + "user complaints", + "discovery research", + "market signal" + ], + "ko": [ + "시장조사", + "시장 조사", + "사용자 페인", + "페인 포인트", + "트렌드 분석", + "경쟁구도", + "경쟁사 분석", + "사용자 불만", + "경쟁 분석" + ], + "ja": [ + "市場調査", + "ペインポイント", + "ユーザー不満", + "競合調査", + "トレンド分析" + ] + } + }, + "oma-observability": { + "keywords": { + "*": [ + "OpenTelemetry", + "OTel", + "OTLP", + "W3C Trace Context", + "traceparent", + "MELT", + "APM", + "RUM", + "SLO", + "SLI", + "burn-rate", + "PromQL", + "Prometheus", + "Grafana", + "Jaeger", + "Tempo", + "Loki", + "Mimir", + "Fluent Bit", + "OpenCost", + "OpenFeature", + "Flagger", + "Falco", + "Parca", + "Pyroscope", + "Honeycomb", + "Datadog", + "Sentry", + "Crashlytics", + "Core Web Vitals" + ], + "en": [ + "observability", + "traceability", + "telemetry", + "distributed tracing", + "instrument my service", + "set up OTel", + "OTel pipeline", + "collector topology", + "tail sampling", + "cardinality budget", + "clock skew", + "error budget", + "burn rate alert", + "canary analysis", + "progressive delivery", + "feature flag observability", + "incident forensics", + "6-dimension localization", + "root cause across services", + "multi-tenant telemetry", + "per-tenant sampling", + "data residency telemetry", + "redact PII in logs", + "observability as code", + "dashboard as code", + "PrometheusRule CRD", + "Grafana Jsonnet", + "Perses dashboard", + "UDP MTU telemetry", + "StatsD fragmentation", + "OTLP gRPC vs HTTP", + "propagator matrix", + "BGP observability", + "QUIC observability", + "eBPF observability", + "service mesh tracing", + "zero code instrumentation", + "mobile crash analytics", + "crash-free rate", + "symbolication pipeline", + "offline telemetry queue", + "WAF observability", + "WAF rule hit rate", + "WAF false positive", + "edge 403 storm", + "ruleset rollback", + "cascading failure observability", + "third-party dependency outage", + "fail-closed dependency" + ], + "ko": [ + "관측성", + "관측 가능성", + "추적성", + "추적 가능성", + "텔레메트리", + "텔레메트리 수집", + "분산 트레이싱", + "OTel 도입", + "OTel 셋업", + "OTel 계측", + "OTel 파이프라인", + "컬렉터 토폴로지", + "테일 샘플링", + "카디널리티", + "카디널리티 관리", + "클록 스큐", + "시계 드리프트", + "에러 버짓", + "에러 예산", + "번레이트 알람", + "번레이트", + "카나리 분석", + "프로그레시브 딜리버리", + "점진 배포", + "피처 플래그 관측", + "사건 부검", + "장애 부검", + "장애 원인 분석", + "6차원 좁히기", + "멀티테넌트 관측", + "테넌트별 샘플링", + "데이터 거주 관측", + "로그 PII 제거", + "로그 익명화", + "로그 가명화", + "관측성 as code", + "대시보드 as code", + "대시보드 코드화", + "PrometheusRule", + "Grafana Jsonnet", + "Perses 대시보드", + "UDP MTU 튜닝", + "StatsD 단편화", + "OTLP gRPC 선택", + "전파자 매핑", + "BGP 관측", + "QUIC 관측", + "eBPF 관측", + "서비스 메시 트레이싱", + "zero-code 계측", + "모바일 크래시 분석", + "크래시 프리 레이트", + "심볼리케이션", + "오프라인 텔레메트리 큐", + "WAF 관측", + "WAF 차단", + "WAF 오탐", + "방화벽 차단", + "엣지 403 폭주", + "룰셋 롤백", + "연쇄 장애", + "디펜던시 장애", + "외부 의존성 장애", + "서드파티 장애", + "페일클로즈드" + ], + "ja": [ + "オブザーバビリティ", + "トレーサビリティ", + "テレメトリ", + "分散トレーシング", + "OTel導入", + "OTelパイプライン", + "コレクタ構成", + "テイルサンプリング", + "カーディナリティ予算", + "クロックスキュー", + "エラーバジェット", + "バーンレートアラート", + "カナリア分析", + "プログレッシブデリバリ", + "機能フラグ観測", + "インシデントフォレンジック", + "マルチテナント観測", + "データ居住性観測", + "ログPII除去", + "Observability as Code", + "Dashboard as Code", + "UDP MTUチューニング", + "StatsDフラグメンテーション", + "OTLP選択", + "プロパゲータマッピング", + "BGP観測", + "QUIC観測", + "eBPF観測", + "サービスメッシュトレース", + "モバイルクラッシュ分析", + "クラッシュフリーレート", + "シンボリケーション", + "オフラインテレメトリ", + "WAF観測", + "WAFブロック", + "WAF誤検知", + "ファイアウォールブロック", + "エッジ403", + "ルールセットロールバック", + "カスケード障害", + "依存サービス障害", + "サードパーティ障害", + "フェイルクローズド" + ], + "zh": [ + "可观测性", + "可追溯性", + "遥测", + "分布式追踪", + "OTel 接入", + "OTel 流水线", + "采集器拓扑", + "尾采样", + "基数预算", + "时钟漂移", + "错误预算", + "燃烧率告警", + "金丝雀分析", + "渐进式发布", + "特性开关观测", + "事件取证", + "多租户观测", + "数据驻留观测", + "日志脱敏", + "可观测性即代码", + "仪表盘即代码", + "UDP MTU 调优", + "StatsD 分片", + "OTLP 选择", + "传播器映射", + "BGP 观测", + "QUIC 观测", + "eBPF 观测", + "服务网格追踪", + "零代码探针", + "移动崩溃分析", + "崩溃无事率", + "符号化", + "离线遥测队列", + "WAF 观测", + "WAF 拦截", + "WAF 误报", + "防火墙拦截", + "边缘 403 风暴", + "规则集回滚", + "级联故障", + "依赖方故障", + "第三方故障", + "故障关闭" + ] } }, "oma-orchestrator": { @@ -1590,8 +2374,18 @@ "review loop", "mcp memory coordination" ], - "ko": ["에이전트 병렬 실행", "동시에 에이전트 돌려", "fan-out", "리뷰 루프 돌려"], - "ja": ["エージェント並列実行", "同時にエージェント", "fan-out", "レビューループ"], + "ko": [ + "에이전트 병렬 실행", + "동시에 에이전트 돌려", + "fan-out", + "리뷰 루프 돌려" + ], + "ja": [ + "エージェント並列実行", + "同時にエージェント", + "fan-out", + "レビューループ" + ], "zh": ["并行跑代理", "同时派发代理", "fan-out 任务", "评审循环"] } }, @@ -1628,8 +2422,20 @@ "scope definition", "prioritization matrix" ], - "ko": ["요구사항 정리", "스펙 문서", "우선순위 매겨줘", "스코프 정의", "제품 로드맵"], - "ja": ["要件を整理", "スペック作成", "優先度付け", "スコープ定義", "プロダクトロードマップ"], + "ko": [ + "요구사항 정리", + "스펙 문서", + "우선순위 매겨줘", + "스코프 정의", + "제품 로드맵" + ], + "ja": [ + "要件を整理", + "スペック作成", + "優先度付け", + "スコープ定義", + "プロダクトロードマップ" + ], "zh": ["梳理需求", "写规格书", "排优先级", "界定范围", "产品路线图"] } }, @@ -1647,7 +2453,12 @@ "test coverage" ], "ko": ["접근성 점검", "성능 점검", "커버리지 확인", "품질 게이트"], - "ja": ["アクセシビリティ確認", "パフォーマンス点検", "カバレッジ確認", "品質ゲート"], + "ja": [ + "アクセシビリティ確認", + "パフォーマンス点検", + "カバレッジ確認", + "品質ゲート" + ], "zh": ["无障碍检查", "性能检查", "覆盖率报告", "质量门禁"] } }, @@ -1666,8 +2477,20 @@ "transcript analysis", "multi tool recap" ], - "ko": ["오늘 한 일 정리", "하루 요약", "주간 요약", "작업 내용 정리", "대화 요약"], - "ja": ["今日の作業まとめ", "日次サマリ", "週次サマリ", "作業振り返り", "会話まとめ"], + "ko": [ + "오늘 한 일 정리", + "하루 요약", + "주간 요약", + "작업 내용 정리", + "대화 요약" + ], + "ja": [ + "今日の作業まとめ", + "日次サマリ", + "週次サマリ", + "作業振り返り", + "会話まとめ" + ], "zh": ["今天做了什么", "日报总结", "周报总结", "工作回顾", "对话总结"] } }, @@ -1685,7 +2508,12 @@ "git worktree" ], "ko": ["머지 충돌 해결", "리베이스해줘", "워크트리 써줘"], - "ja": ["マージ衝突解決", "リベースして", "リリースタグ", "worktree使って"], + "ja": [ + "マージ衝突解決", + "リベースして", + "リリースタグ", + "worktree使って" + ], "zh": ["解决合并冲突", "帮我 rebase", "打发布标签", "用 worktree"] } }, @@ -1705,8 +2533,20 @@ "library reference", "context7 docs" ], - "ko": ["검색해줘", "찾아줘", "레퍼런스 찾아", "문서 찾아줘", "라이브러리 찾아줘"], - "ja": ["検索して", "調べて", "ドキュメント探して", "ライブラリ調べて", "リファレンス探して"], + "ko": [ + "검색해줘", + "찾아줘", + "레퍼런스 찾아", + "문서 찾아줘", + "라이브러리 찾아줘" + ], + "ja": [ + "検索して", + "調べて", + "ドキュメント探して", + "ライブラリ調べて", + "リファレンス探して" + ], "zh": ["帮我查", "搜一下", "找找文档", "找个库", "查参考资料"] } }, @@ -1725,9 +2565,27 @@ "oidc setup", "cost optimization" ], - "ko": ["테라폼 플랜", "인프라 프로비저닝", "iac 모듈", "클라우드 리소스", "비용 최적화"], - "ja": ["terraformプラン", "インフラ構築", "iacモジュール", "クラウドリソース", "コスト最適化"], - "zh": ["terraform plan", "搭建基础设施", "iac 模块", "云资源", "成本优化"] + "ko": [ + "테라폼 플랜", + "인프라 프로비저닝", + "iac 모듈", + "클라우드 리소스", + "비용 최적화" + ], + "ja": [ + "terraformプラン", + "インフラ構築", + "iacモジュール", + "クラウドリソース", + "コスト最適化" + ], + "zh": [ + "terraform plan", + "搭建基础设施", + "iac 模块", + "云资源", + "成本优化" + ] } }, "oma-translator": { @@ -1744,46 +2602,392 @@ "multilingual content", "arb translation" ], - "ko": ["번역해줘", "번역 부탁", "다국어로", "영어로 바꿔줘", "현지화해줘"], + "ko": [ + "번역해줘", + "번역 부탁", + "다국어로", + "영어로 바꿔줘", + "현지화해줘" + ], "ja": ["翻訳して", "英訳", "多言語化", "ローカライズして", "訳して"], "zh": ["翻译一下", "帮我翻译", "多语言", "本地化", "翻成英文"] } + }, + "oma-deepsec": { + "keywords": { + "*": [ + "deepsec", + ".deepsec", + "bunx deepsec", + "pnpm deepsec", + "npx deepsec", + "deepsec.config", + "process --diff", + "INFO.md", + "MatcherPlugin", + "noiseTier", + "AI_GATEWAY_API_KEY" + ], + "en": [ + "scan repo for vulnerabilities", + "scan the repo for vulns", + "security scan with an agent", + "vulnerability scanner", + "agent security scan", + "run a security scan", + "pr security review", + "ci security gate", + "diff security review", + "write a custom matcher", + "revalidate findings", + "triage findings" + ], + "ko": [ + "취약점 스캔", + "보안 스캔", + "딥섹", + "딥섹 돌려", + "PR 보안 리뷰", + "보안 점검", + "매처 작성", + "파인딩 트리아지", + "파인딩 재검증" + ], + "ja": [ + "脆弱性スキャン", + "セキュリティスキャン", + "ディープセック", + "PRセキュリティレビュー", + "マッチャーを書く", + "ファインディングをトリアージ" + ], + "zh": [ + "漏洞扫描", + "安全扫描", + "深度安全扫描", + "PR 安全审查", + "编写自定义匹配器", + "重新验证发现" + ] + } + }, + "oma-image": { + "keywords": { + "*": [ + "nano-banana", + "nanobanana", + "gpt-image", + "pollinations", + "oma-image" + ], + "en": [ + "generate image", + "generate an image", + "create image", + "create an image", + "make a picture", + "make an image", + "render image", + "render a picture", + "draw me", + "draw a", + "ai image", + "image generation", + "generate a photo", + "create picture", + "picture of", + "image of" + ], + "ko": [ + "이미지 만들어", + "이미지 만들어줘", + "이미지 생성", + "이미지 생성해", + "이미지 생성해줘", + "사진 만들어", + "사진 만들어줘", + "그림 그려", + "그림 그려줘", + "이미지 뽑아", + "이미지 뽑아줘", + "이미지 그려줘", + "이미지 출력", + "나노바나나", + "나노 바나나", + "바나나로 뽑", + "이미지 생성기", + "ai 이미지" + ], + "ja": [ + "画像を生成", + "画像生成", + "画像を作", + "画像を作成", + "絵を描いて", + "画像出力", + "イラストを生成", + "写真を生成" + ], + "zh": [ + "生成图像", + "生成图片", + "生成一张", + "画一张", + "画一幅", + "帮我画", + "出图", + "图像生成", + "图片生成" + ], + "es": [ + "generar imagen", + "crear imagen", + "hazme una imagen", + "genera una foto" + ], + "fr": [ + "générer une image", + "créer une image", + "fais-moi une image", + "dessine-moi" + ], + "de": [ + "bild generieren", + "bild erstellen", + "erstelle ein bild", + "zeichne mir" + ] + } + }, + "oma-voice": { + "keywords": { + "*": ["voicebox", "oma-voice", "tts", "stt"], + "en": [ + "text to speech", + "speech to text", + "transcribe audio", + "transcribe this", + "generate speech", + "voice notification", + "narrate this", + "make a voiceover", + "create a voiceover", + "read this aloud", + "speak this", + "say this", + "dictation", + "voice memo", + "meeting transcript", + "audio to text" + ], + "ko": [ + "음성으로 알려", + "음성으로 읽어", + "음성으로 말해", + "음성 만들어", + "음성 만들어줘", + "음성 생성", + "음성 생성해", + "음성 생성해줘", + "보이스오버 만들어", + "보이스오버 생성", + "나레이션 만들어", + "오디오 파일 전사", + "오디오를 텍스트로", + "받아 적어", + "받아적어", + "받아 써", + "받아써", + "회의록 만들어", + "회의록 작성", + "음성 메모" + ], + "ja": [ + "音声を生成", + "音声生成", + "音声で読み上げ", + "ナレーション", + "テキストを読み上げ", + "音声に変換", + "文字起こし", + "音声を文字に", + "ボイスオーバー" + ], + "zh": [ + "生成语音", + "语音生成", + "朗读这段", + "文字转语音", + "语音转文字", + "音频转写", + "转录音频", + "语音备忘", + "会议转录" + ] + } } }, "informationalPatterns": { - "en": [ + "*": [ "what is", "what are", "how to", "how does", + "how do", + "how can", + "how would", "explain", "describe", "tell me about", "keyword", "false positive", + "false-positive", "detected", "detector", + "fires when", + "trigger when", + "auto-trigger", + "auto trigger", + "what triggers", + "should we trigger", + "if we trigger", + "trigger logic", + "trigger mechanism", + "should we", + "should i", + "should you", + "could we", + "would you", + "what if", + "what about", + "why build", + "why create", + "why make" + ], + "ko": [ "뭐야", + "뭐임", "무엇", - "是什么", - "とは" + "어떻게", + "설명해", + "알려줘", + "키워드", + "감지", + "오탐", + "트리거", + "발동", + "메타", + "트리거하면", + "트리거 해주면", + "트리거해야", + "키워드 나오면", + "왜 만들", + "어떻게 만들", + "어떨까", + "하면 좋을", + "한다면", + "할까요", + "보강할", + "에 대해", + "에 대한", + "한번 봐", + "깊게 봐", + "코드를 한번", + "그 워크플로우", + "이 워크플로우", + "워크플로우 자체" + ], + "ja": [ + "とは", + "って何", + "どうやって", + "説明して", + "キーワード", + "検出", + "誤検出" ], - "ko": ["뭐야", "뭐임", "무엇", "어떻게", "설명해", "알려줘", "키워드", "감지", "오탐"], - "ja": ["とは", "って何", "どうやって", "説明して", "キーワード", "検出", "誤検出"], "zh": ["是什么", "什么是", "怎么", "解释", "关键词", "检测", "误报"], - "es": ["qué es", "cómo", "explica", "palabra clave", "falso positivo", "detectado"], - "fr": ["c'est quoi", "comment", "explique", "mot-clé", "faux positif", "détecté"], - "de": ["was ist", "wie", "erkläre", "schlüsselwort", "falsch positiv", "erkannt"], - "pt": ["o que é", "como", "explique", "palavra-chave", "falso positivo", "detectado"], - "ru": ["что такое", "как", "объясни", "ключевое слово", "ложное срабатывание", "обнаружено"], - "nl": ["wat is", "hoe", "leg uit", "sleutelwoord", "vals positief", "gedetecteerd"], - "pl": ["co to", "jak", "wyjaśnij", "słowo kluczowe", "fałszywy alarm", "wykryto"] + "es": [ + "qué es", + "cómo", + "explica", + "palabra clave", + "falso positivo", + "detectado" + ], + "fr": [ + "c'est quoi", + "comment", + "explique", + "mot-clé", + "faux positif", + "détecté" + ], + "de": [ + "was ist", + "wie", + "erkläre", + "schlüsselwort", + "falsch positiv", + "erkannt" + ], + "pt": [ + "o que é", + "como", + "explique", + "palavra-chave", + "falso positivo", + "detectado" + ], + "ru": [ + "что такое", + "как", + "объясни", + "ключевое слово", + "ложное срабатывание", + "обнаружено" + ], + "nl": [ + "wat is", + "hoe", + "leg uit", + "sleutelwoord", + "vals positief", + "gedetecteerd" + ], + "pl": [ + "co to", + "jak", + "wyjaśnij", + "słowo kluczowe", + "fałszywy alarm", + "wykryto" + ] }, - "excludedWorkflows": ["tools", "stack-set", "exec-plan"], + "excludedWorkflows": ["tools", "stack-set"], "cjkScripts": ["ko", "ja", "zh"], "extensionRouting": { - "frontend-engineer": ["tsx", "jsx", "css", "scss", "less", "vue", "svelte", "html"], - "backend-engineer": ["go", "py", "java", "rs", "rb", "php", "controller", "service", "resolver"], + "frontend-engineer": [ + "tsx", + "jsx", + "css", + "scss", + "less", + "vue", + "svelte", + "html" + ], + "backend-engineer": [ + "go", + "py", + "java", + "rs", + "rb", + "php", + "controller", + "service", + "resolver" + ], "db-engineer": ["sql", "prisma", "graphql", "migration"], "mobile-engineer": ["dart", "swift", "kt", "xib", "storyboard"], "designer": ["figma", "sketch", "svg"] diff --git a/.agents/hooks/core/types.ts b/.agents/hooks/core/types.ts index f9bf420..7122c0f 100644 --- a/.agents/hooks/core/types.ts +++ b/.agents/hooks/core/types.ts @@ -1,137 +1,30 @@ -// Claude Code Hook Types for oh-my-agent -// Shared across Claude Code, Codex CLI, Cursor, Gemini CLI, and Qwen Code +// Hook-runtime types shared across Claude Code, Codex CLI, Cursor, +// Gemini CLI, and Qwen Code. Functions live in `fs-utils.ts` and +// `hook-output.ts`; this file is types-only. The `Vendor` type is derived +// from the `VENDORS` runtime constant in `constants.ts` so the two stay +// in sync. -import { existsSync } from "node:fs" -import { dirname, join } from "node:path" +import type { VENDORS } from "./constants.ts"; -// --- Project Root Resolution --- - -/** - * Walk up from startDir to find the git repository root. - * This prevents CLAUDE_PROJECT_DIR pointing to a subdirectory - * (e.g. packages/i18n during a build) from creating state files - * in the wrong location. - */ -const MAX_DEPTH = 20 - -export function resolveGitRoot(startDir: string): string { - let dir = startDir - for (let i = 0; i < MAX_DEPTH; i++) { - if (existsSync(join(dir, ".git"))) return dir - const parent = dirname(dir) - if (parent === dir) return startDir - dir = parent - } - return startDir -} - -// --- Vendor Detection --- - -export type Vendor = "claude" | "codex" | "cursor" | "gemini" | "qwen" - -// --- Hook Input (unified) --- +export type Vendor = (typeof VENDORS)[number]; export interface HookInput { - prompt?: string - sessionId?: string - session_id?: string - hook_event_name?: string - cwd?: string - workspace_roots?: string[] + prompt?: string; + sessionId?: string; + session_id?: string; + hook_event_name?: string; + cwd?: string; + workspace_roots?: string[]; // Gemini: AfterAgent fields - prompt_response?: string - stop_hook_active?: boolean + prompt_response?: string; + stop_hook_active?: boolean; // Claude/Qwen: Stop fields - stopReason?: string + stopReason?: string; } -// --- Hook Output Builders --- - -export function makePromptOutput(vendor: Vendor, additionalContext: string): string { - switch (vendor) { - case "claude": - return JSON.stringify({ additionalContext }) - case "codex": - return JSON.stringify({ - hookSpecificOutput: { - hookEventName: "UserPromptSubmit", - additionalContext, - }, - }) - case "cursor": - return JSON.stringify({ - additionalContext, - additional_context: additionalContext, - hookSpecificOutput: { - hookEventName: "UserPromptSubmit", - additionalContext, - }, - }) - case "gemini": - return JSON.stringify({ - hookSpecificOutput: { - hookEventName: "BeforeAgent", - additionalContext, - }, - }) - case "qwen": - // Qwen Code fork uses hookSpecificOutput (same as Codex) - return JSON.stringify({ - hookSpecificOutput: { - hookEventName: "UserPromptSubmit", - additionalContext, - }, - }) - } -} - -export function makeBlockOutput(vendor: Vendor, reason: string): string { - switch (vendor) { - case "claude": - case "codex": - case "cursor": - case "qwen": - return JSON.stringify({ decision: "block", reason }) - case "gemini": - // Gemini AfterAgent uses "deny" to reject response and force retry - return JSON.stringify({ decision: "deny", reason }) - } -} - -// --- PreToolUse Output Builder --- - -export function makePreToolOutput(vendor: Vendor, updatedInput: Record): string { - switch (vendor) { - case "gemini": - return JSON.stringify({ - decision: "rewrite", - tool_input: updatedInput, - }) - case "cursor": - return JSON.stringify({ - updated_input: updatedInput, - hookSpecificOutput: { - hookEventName: "PreToolUse", - updatedInput, - }, - }) - case "claude": - case "codex": - case "qwen": - return JSON.stringify({ - hookSpecificOutput: { - hookEventName: "PreToolUse", - updatedInput, - }, - }) - } -} - -// --- Shared Types --- - export interface ModeState { - workflow: string - sessionId: string - activatedAt: string - reinforcementCount: number + workflow: string; + sessionId: string; + activatedAt: string; + reinforcementCount: number; } diff --git a/.agents/hooks/variants/claude.json b/.agents/hooks/variants/claude.json index acd41a9..6694ae4 100644 --- a/.agents/hooks/variants/claude.json +++ b/.agents/hooks/variants/claude.json @@ -31,7 +31,12 @@ }, "extra": { "permissions": { - "allow": ["Bash(bun run:*)", "Bash(bunx tsx:*)", "Bash(git add:*)", "mcp__serena__*"] + "allow": [ + "Bash(bun run:*)", + "Bash(bunx tsx:*)", + "Bash(git add:*)", + "mcp__serena__*" + ] } } } diff --git a/.agents/hooks/variants/codex.json b/.agents/hooks/variants/codex.json index a6071a1..a1d5a18 100644 --- a/.agents/hooks/variants/codex.json +++ b/.agents/hooks/variants/codex.json @@ -30,7 +30,7 @@ "file": ".codex/config.toml", "section": "features", "flags": { - "codex_hooks": true + "hooks": true } } } diff --git a/.agents/hooks/variants/gemini.json b/.agents/hooks/variants/gemini.json index 7d4e1e4..f9e6565 100644 --- a/.agents/hooks/variants/gemini.json +++ b/.agents/hooks/variants/gemini.json @@ -23,10 +23,27 @@ "matcher": "run_shell_command", "timeout": 5000 }, - "AfterAgent": { - "hook": "persistent-mode.ts", + "AfterTool": { + "hook": "hud.ts", "matcher": "*", - "timeout": 5000 + "timeout": 3000 + }, + "AfterAgent": [ + { + "hook": "persistent-mode.ts", + "matcher": "*", + "timeout": 5000 + }, + { + "hook": "hud.ts", + "matcher": "*", + "timeout": 3000 + } + ], + "SessionStart": { + "hook": "hud.ts", + "matcher": "*", + "timeout": 3000 } } } diff --git a/.agents/mcp.json b/.agents/mcp.json index 137c5f9..9381a80 100644 --- a/.agents/mcp.json +++ b/.agents/mcp.json @@ -1,14 +1,11 @@ { "mcpServers": { "serena": { - "command": "uvx", + "command": "serena", "args": [ - "--from", - "git+https://github.com/oraios/serena", - "serena", "start-mcp-server", "--context", - "antigravity", + "claude-code", "--project", "." ], @@ -30,12 +27,40 @@ } }, "toolGroups": { - "memory": ["read_memory", "write_memory", "edit_memory", "list_memories", "delete_memory"], - "code-analysis": ["get_symbols_overview", "find_symbol", "find_referencing_symbols", "search_for_pattern"], - "code-edit": ["replace_symbol_body", "insert_after_symbol", "insert_before_symbol", "rename_symbol"], - "file-ops": ["list_dir", "find_file"], - "project": ["activate_project", "get_current_config", "check_onboarding_performed", "onboarding"], - "thinking": ["think_about_collected_information", "think_about_task_adherence", "think_about_whether_you_are_done"], + "memory": [ + "read_memory", + "write_memory", + "edit_memory", + "list_memories", + "delete_memory" + ], + "code-analysis": [ + "get_symbols_overview", + "find_symbol", + "find_referencing_symbols", + "search_for_pattern" + ], + "code-edit": [ + "replace_symbol_body", + "insert_after_symbol", + "insert_before_symbol", + "rename_symbol" + ], + "file-ops": [ + "list_dir", + "find_file" + ], + "project": [ + "activate_project", + "get_current_config", + "check_onboarding_performed", + "onboarding" + ], + "thinking": [ + "think_about_collected_information", + "think_about_task_adherence", + "think_about_whether_you_are_done" + ], "all": null } } diff --git a/.agents/mcp_config.json b/.agents/mcp_config.json new file mode 100644 index 0000000..18f5460 --- /dev/null +++ b/.agents/mcp_config.json @@ -0,0 +1,17 @@ +{ + "mcpServers": { + "serena": { + "command": "serena", + "args": [ + "start-mcp-server", + "--context", + "claude-code", + "--project", + "." + ], + "env": { + "SERENA_LOG_LEVEL": "info" + } + } + } +} diff --git a/.agents/oma-config.yaml b/.agents/oma-config.yaml index e23d0cd..b3319a9 100644 --- a/.agents/oma-config.yaml +++ b/.agents/oma-config.yaml @@ -1,24 +1,5 @@ -# User Preferences (Optional) -# Project-specific user configuration -# -# This file is optional. Works with defaults if not present. -# CLI priority: --vendor arg > agent_cli_mapping > default_cli > cli-config.yaml's active_vendor > gemini - -# Response language setting (ko, en, ja, zh, ...) +# Migrated by oma migration 008 — model_preset single-file config language: ko - -# Date/time format date_format: ISO timezone: Asia/Seoul - -# Default CLI (for single tasks) -default_cli: gemini - -# Per-agent CLI mapping (multi-CLI mode) -agent_cli_mapping: - frontend: gemini - backend: gemini - mobile: gemini - qa: gemini - debug: gemini - pm: gemini +model_preset: gemini diff --git a/.agents/rules/backend.md b/.agents/rules/backend.md index 290f07a..48ab767 100644 --- a/.agents/rules/backend.md +++ b/.agents/rules/backend.md @@ -18,7 +18,7 @@ alwaysApply: false 8. **Explicit ORM loading strategy**: do not rely on default relation loading when query shape matters 9. **Explicit transaction boundaries**: group one business operation into one request/service-scoped unit of work 10. **Safe ORM lifecycle**: do not share mutable ORM session/entity manager across concurrent work unless ORM explicitly supports it -11. **Config from environment**: DB URLs, API keys, secrets from env vars or secret managers — never hardcode +11. **Config from environment, with graceful fallback**: DB URLs, API keys, secrets from env vars or secret managers, never hardcode. When integrating a third-party API (OpenAI, Anthropic, Stripe, etc.), write BOTH paths: (a) real call when `process.env.` is present, (b) deterministic local fallback when absent. Mark the deferred branch with `// TODO(oma-deferred): integrate when key is provisioned`. Shipping only the fallback (no env-conditional branch) leaves the spec unmet; shipping only the real call without fallback breaks demos when the key is missing. 12. **Stateless services**: no in-memory session or user state between requests — use external stores 13. **Backing services as resources**: DB, queue, cache are swappable resources connected via config diff --git a/.agents/rules/frontend.md b/.agents/rules/frontend.md index cbd02e4..d85be94 100644 --- a/.agents/rules/frontend.md +++ b/.agents/rules/frontend.md @@ -9,13 +9,15 @@ alwaysApply: false ## Core Rules 1. **Component Reuse**: Use `shadcn/ui` components first. Extend via `cva` variants or composition. Avoid custom CSS. -2. **Design Fidelity**: Code must map 1:1 to Design Tokens. Resolve discrepancies before implementation. +2. **Design Fidelity**: Code must map 1:1 to `DESIGN.md` (Section 9 — Agent Prompt Guide) and Design Tokens. Resolve discrepancies before implementation. 3. **Rendering Strategy**: Default to Server Components for performance. Use Client Components only for interactivity and API integration. 4. **Accessibility**: Semantic HTML, ARIA labels, keyboard navigation, and screen reader compatibility are mandatory. 5. **Tool First**: Check for existing solutions and tools before coding. -6. **Proxy over Middleware**: Next.js 16+ uses `proxy.ts` for request proxying. Do NOT use `middleware.ts` for proxy/rewrite logic. +6. **Proxy over Middleware (BANNED)**: Next.js 16+ uses `proxy.ts` for request proxying. `middleware.ts` is NOT "deprecated" — it is forbidden in this project, touch it and you die. Do NOT create, recommend, suggest, or "restore" `middleware.ts`. Do NOT flag `proxy.ts` as dead code, unused, or not-wired. Do NOT demand a rename to `middleware.ts`. Any such finding is a fatal self-error — retract it immediately and write `proxy.ts`. 7. **No Prop Drilling**: Avoid passing props beyond 3 levels. Use Jotai atoms instead. Avoid React Context. 8. **Auth Boundary**: Frontend handles auth UI and token storage only. Never import database adapters, ORMs, or server-side auth libraries. +9. **Animation Library**: Use `motion` (import from `motion/react`). `framer-motion` is the legacy package name and is BANNED — never `import { motion } from 'framer-motion'`, never add `framer-motion` to `package.json`. Add the `motion` package via the project's package manager — detect from the lockfile (`bun.lock` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm); default to `bun` when no lockfile exists. Import as `import { motion, AnimatePresence } from 'motion/react'`. Respect `prefers-reduced-motion` via `useReducedMotion` from `motion/react`. +10. **Framework Version**: `next@16+` and `react@19+` are MANDATORY. When scaffolding or pinning `package.json`, set `"next": "^16"` (or higher) and `"react": "^19"`/`"react-dom": "^19"` — never pin `next` to `^15`, `~15`, or any range whose floor is below `16.0.0`. If `create-next-app` (or any scaffold tool) produces `next < 16`, immediately bump it before committing. This rule is paired with Core Rule #6 (`proxy.ts`), which assumes Next.js 16+. ## Architecture (FSD-lite) diff --git a/.agents/rules/i18n-guide.md b/.agents/rules/i18n-guide.md index 53bcf12..ef02ebe 100644 --- a/.agents/rules/i18n-guide.md +++ b/.agents/rules/i18n-guide.md @@ -21,6 +21,18 @@ Response language is determined by the following priority: language: ko # ko, en, ja, zh, ... ``` +## Translation Voice + +When translating user-facing content, the `translation_voice` field in `.agents/oma-config.yaml` controls global rhythm and formality. It is applied on top of `oma-translator` content-type persona routing. + +| Value | Effect | +|---|---| +| `formal` | strict complete sentences, no fragments, formal register only | +| `balanced` (default) | content-type defaults — fragments only in label/cell positions | +| `interpreter` | punchy, audience-first, spoken cadence; fragments allowed when natural | + +Workflows that translate user-facing content should respect this setting via the `oma-translator` skill rather than hardcoding a tone. + ## What to Localize | Category | Localize? | Example | diff --git a/.agents/rules/market.md b/.agents/rules/market.md new file mode 100644 index 0000000..42eaa9e --- /dev/null +++ b/.agents/rules/market.md @@ -0,0 +1,20 @@ +--- +description: Market research standards - trap detection, keyless-first sourcing, LAW enforcement, deterministic compute +globs: +alwaysApply: false +--- + +# Market Research Standards + +## Core Rules + +1. **Preflight first**: every market research task starts with `oma market detect-trap`. If it refuses, surface the reframe to the user — never bypass without `--force`. +2. **Keyless-first**: default to sources that need no API key (reddit, hn, bluesky, mastodon, github-issues, grounding). Paid sources (x, tiktok, instagram, youtube, perplexity) auto-enable only when env keys are present. +3. **Reuse oma-search**: all fetches go through `oma search fetch --only api`. Do not call platform APIs directly from market commands. Trust labels come from oma-search. +4. **Deterministic compute**: score, fuse, cluster stages must produce byte-identical output on `OMA_MARKET_MOCK=1` fixture replay. Stage stdin/stdout is pure JSON; stderr is warn/error only. +5. **LAW enforcement**: render output must pass the 8 LAWs (see `.agents/skills/oma-market/resources/output-laws.md`). LAW self-check is mandatory; `--no-self-check` is debug-only. +6. **Cite or fall back**: every representative cited in the brief is `[name](url)`. If URL is missing, use plain text — never `[name]()`. +7. **Coverage transparency**: when `sources_failed` is non-empty, render must annotate "coverage: N/M sources" in the engine footer. +8. **Intent → framework auto-toggle**: pain/trend → SWOT only; competitor → SWOT + Porter's 5F; discovery → SWOT + PESTEL. User `--frameworks` flag overrides. +9. **Single brief, single date**: one run = one md file at `.agents/results/market/{topic-slug}-{YYYYMMDD}.md`. Rerunning same day overwrites; previous run is responsible to git the file if needed. +10. **Personal data refuse**: detect-trap blocks queries that target a private individual's personal data. Founders, creators, and public handles are allowed (last30days person-mode parity). diff --git a/.agents/skills/_shared/conditional/exploration-loop.md b/.agents/skills/_shared/conditional/exploration-loop.md index 7f8200f..4dff249 100644 --- a/.agents/skills/_shared/conditional/exploration-loop.md +++ b/.agents/skills/_shared/conditional/exploration-loop.md @@ -49,7 +49,7 @@ Execute each hypothesis **in isolation**. Task: "Fix input validation using Hypothesis A: Zod schema at router level. Context: Previous attempt (raw regex) failed QA twice." ``` -- Agents use existing IDs — no new agent definitions needed +- Agents use existing IDs; no new agent definitions needed - Each agent works in a separate workspace (`-w ./hyp-a`, `-w ./hyp-b`) - Result files differentiated by workspace, not agent ID diff --git a/.agents/skills/_shared/conditional/quality-score.md b/.agents/skills/_shared/conditional/quality-score.md index 2b0c130..fe7bb20 100644 --- a/.agents/skills/_shared/conditional/quality-score.md +++ b/.agents/skills/_shared/conditional/quality-score.md @@ -1,7 +1,7 @@ # Quality Score Continuum Replaces binary PASS/FAIL gate evaluation with a **continuous quantitative score** (0-100). -Inspired by autoresearch's val_bpb metric — objective, comparable, and trackable over time. +Inspired by autoresearch's val_bpb metric: objective, comparable, and trackable over time. --- @@ -67,10 +67,10 @@ Quality Score is measured **on demand**, not at every step. Load `quality-score. | Range | Grade | Gate Decision | |-------|-------|---------------| -| 90-100 | A | PASS — proceed immediately | -| 75-89 | B | CONDITIONAL PASS — proceed with noted improvements | -| 60-74 | C | FAIL — must improve before proceeding | -| 0-59 | D | HARD FAIL — rollback, re-plan required | +| 90-100 | A | PASS, proceed immediately | +| 75-89 | B | CONDITIONAL PASS, proceed with noted improvements | +| 60-74 | C | FAIL, must improve before proceeding | +| 0-59 | D | HARD FAIL, rollback and re-plan required | --- @@ -82,9 +82,9 @@ Changes are evaluated by their **impact on the score**, not just by whether they IF score_after >= score_before: KEEP change ELSE IF (score_before - score_after) < 5: - REVIEW — minor regression, justify in experiment ledger + REVIEW (minor regression, justify in experiment ledger) ELSE: - DISCARD change — revert and try alternative + DISCARD change (revert and try alternative) ``` ### Delta Recording diff --git a/.agents/skills/_shared/core/clarification-protocol.md b/.agents/skills/_shared/core/clarification-protocol.md index 227851b..b9bf1d4 100644 --- a/.agents/skills/_shared/core/clarification-protocol.md +++ b/.agents/skills/_shared/core/clarification-protocol.md @@ -40,7 +40,7 @@ Automatically classify as MEDIUM/HIGH level in the following situations: ### LOW → Proceed (Assumed) ``` -⚠️ Assumptions applied: +Assumptions applied: - JWT authentication included - PostgreSQL database - REST API @@ -51,29 +51,29 @@ Proceeding with these defaults. Override if needed. ### MEDIUM → Request Selection (Options) ``` -🔍 Uncertainty detected: {specific issue} +Uncertainty detected: {specific issue} Option A: {approach} - ✅ Pros: {benefits} - ❌ Cons: {drawbacks} - 💰 Effort: {low/medium/high} + Pros: {benefits} + Cons: {drawbacks} + Effort: {low/medium/high} Option B: {approach} - ✅ Pros: {benefits} - ❌ Cons: {drawbacks} - 💰 Effort: {low/medium/high} + Pros: {benefits} + Cons: {drawbacks} + Effort: {low/medium/high} Option C: {approach} - ✅ Pros: {benefits} - ❌ Cons: {drawbacks} - 💰 Effort: {low/medium/high} + Pros: {benefits} + Cons: {drawbacks} + Effort: {low/medium/high} Which approach do you prefer? (A/B/C) ``` ### HIGH → Blocked ``` -❌ Cannot proceed: Requirements too ambiguous +Cannot proceed: Requirements too ambiguous Specific uncertainty: {what is unclear} @@ -91,7 +91,7 @@ Status: BLOCKED (awaiting clarification) ## Required Verification Items -If any of the items below are unclear, **do not assume** — explicitly record them. +If any of the items below are unclear, **do not assume**; explicitly record them. ### Common to All Agents | Item | Verification Question | Default (if assumed) | Uncertainty | @@ -138,7 +138,7 @@ Example: "Create a TODO app" **Response**: Apply defaults and record assumption list in result ``` -⚠️ Assumptions: +Assumptions: - JWT authentication included - PostgreSQL database - REST API @@ -150,7 +150,7 @@ Example: "Create a user management system" **Response**: Narrow scope to 3 core features, specify and proceed ``` -⚠️ Interpreted scope (3 core features): +Interpreted scope (3 core features): 1. User registration + login (JWT) 2. Profile management (view/edit) 3. Admin user list (admin role only) @@ -166,7 +166,7 @@ Example: "Create a good app", "Improve this" **Response**: Do not proceed, record clarification request in result ``` -❌ Cannot proceed: Requirements too ambiguous +Cannot proceed: Requirements too ambiguous Questions needed: 1. What is the app's primary purpose? diff --git a/.agents/skills/_shared/core/context-budget.md b/.agents/skills/_shared/core/context-budget.md index 2e647a8..2721775 100644 --- a/.agents/skills/_shared/core/context-budget.md +++ b/.agents/skills/_shared/core/context-budget.md @@ -7,10 +7,10 @@ Follow this guide to use context efficiently. ## Core Principles -1. **No full file reads** — Read only necessary functions/classes -2. **No duplicate reads** — Do not re-read files already read -3. **Lazy resource loading** — Load resources only when needed -4. **Maintain records** — Note read files and symbols in progress +1. **No full file reads**: Read only necessary functions/classes +2. **No duplicate reads**: Do not re-read files already read +3. **Lazy resource loading**: Load resources only when needed +4. **Maintain records**: Note read files and symbols in progress --- @@ -19,17 +19,17 @@ Follow this guide to use context efficiently. ### When Using Serena MCP (Recommended) ``` -❌ Bad: read_file("app/api/todos.py") ← entire file 500 lines -✅ Good: find_symbol("create_todo") ← just that function 30 lines -✅ Good: get_symbols_overview("app/api") ← function list only -✅ Good: find_referencing_symbols("TodoService") ← usage only +Bad: read_file("app/api/todos.py") ← entire file 500 lines +Good: find_symbol("create_todo") ← just that function 30 lines +Good: get_symbols_overview("app/api") ← function list only +Good: find_referencing_symbols("TodoService") ← usage only ``` ### When Reading Files Without Serena ``` -❌ Bad: Read entire file at once -✅ Good: Check first 50 lines (imports + class definitions) → read additional functions as needed +Bad: Read entire file at once +Good: Check first 50 lines (imports + class definitions) → read additional functions as needed ``` --- @@ -124,7 +124,7 @@ This approach: Long-running agents degrade in quality as context fills up. Rather than passively responding to symptoms, agents must actively detect and reset. Detection is the **Orchestrator's responsibility** via external observation. -Individual agents do NOT self-monitor for anxiety — they focus on their task. +Individual agents do NOT self-monitor for anxiety; they focus on their task. ### Detection (Orchestrator Only) diff --git a/.agents/skills/_shared/core/context-loading.md b/.agents/skills/_shared/core/context-loading.md index e9fbf05..863b779 100644 --- a/.agents/skills/_shared/core/context-loading.md +++ b/.agents/skills/_shared/core/context-loading.md @@ -8,11 +8,11 @@ This saves context window and prevents confusion from irrelevant information. ## Loading Order (Common to All Agents) ### Always Load (Required) -1. `SKILL.md` — Auto-loaded (provided by Antigravity) -2. `resources/execution-protocol.md` — Execution protocol +1. `SKILL.md`: Auto-loaded (provided by Antigravity) +2. `resources/execution-protocol.md`: Execution protocol ### Load at Task Start -3. `difficulty-guide.md` — Difficulty assessment (Step 0) +3. `difficulty-guide.md`: Difficulty assessment (Step 0) ### Load Based on Difficulty 4. **Simple**: Proceed to implementation without additional loading @@ -20,15 +20,15 @@ This saves context window and prevents confusion from irrelevant information. 6. **Complex**: `resources/examples.md` + `stack/tech-stack.md` + `stack/snippets.md` ### Load During Execution as Needed -7. `resources/checklist.md` — Load at Step 4 (Verify) -8. `resources/error-playbook.md` — Load only when errors occur -9. `common-checklist.md` — For final verification of Complex tasks -10. `../runtime/memory-protocol.md` — CLI mode only +7. `resources/checklist.md`: Load at Step 4 (Verify) +8. `resources/error-playbook.md`: Load only when errors occur +9. `common-checklist.md`: For final verification of Complex tasks +10. `../runtime/memory-protocol.md`: CLI mode only ### Load on Measurement / Exploration (Conditional) -11. `../conditional/quality-score.md` — Load when Quality Score measurement is needed (VERIFY/SHIP gates) -12. `../conditional/experiment-ledger.md` — Load when recording experiment results (after implementation changes) -13. `../conditional/exploration-loop.md` — Load only when a gate fails twice on the same issue +11. `../conditional/quality-score.md`: Load when Quality Score measurement is needed (VERIFY/SHIP gates) +12. `../conditional/experiment-ledger.md`: Load when recording experiment results (after implementation changes) +13. `../conditional/exploration-loop.md`: Load only when a gate fails twice on the same issue --- @@ -143,7 +143,7 @@ Prompt composition: 1. Agent SKILL.md's Core Rules section 2. execution-protocol.md 3. Resources matching task type (see tables above) -4. error-playbook.md (always include — recovery is essential) +4. error-playbook.md (always include; recovery is essential) 5. Serena Memory Protocol (CLI mode) ``` diff --git a/.agents/skills/_shared/core/difficulty-guide.md b/.agents/skills/_shared/core/difficulty-guide.md index 6d9dac9..d901517 100644 --- a/.agents/skills/_shared/core/difficulty-guide.md +++ b/.agents/skills/_shared/core/difficulty-guide.md @@ -28,7 +28,7 @@ All agents assess task difficulty at the start and apply the appropriate protoco ## Protocol Branching ### Simple → Fast Track -1. ~~Step 1 (Analyze)~~: Skip — proceed directly to implementation +1. ~~Step 1 (Analyze)~~: Skip; proceed directly to implementation 2. **Pre-check**: Confirm whether test files exist for the target module (e.g., `__tests__/`, `*.test.*`) 3. Step 3 (Implement): Implementation 4. Step 4 (Verify): Minimal checklist items: diff --git a/.agents/skills/_shared/core/evaluator-tuning.md b/.agents/skills/_shared/core/evaluator-tuning.md index 9f8ae97..d5f3d1c 100644 --- a/.agents/skills/_shared/core/evaluator-tuning.md +++ b/.agents/skills/_shared/core/evaluator-tuning.md @@ -2,7 +2,7 @@ QA prompts do not work well out of the box. Reliable evaluation requires iterative refinement based on observed judgment errors. -(ref: Anthropic harness design — "several rounds of development loop necessary") +(ref: Anthropic harness design, "several rounds of development loop necessary") This protocol is **semi-automated**: collection is automatic, analysis and patching require human review via `oma retro`. @@ -35,11 +35,11 @@ Sessions accumulate EA events in session-metrics.md | Error Pattern | Patch Target | Example | |--------------|-------------|---------| -| Missed bug category | QA `checklist.md` — add check item | "Race condition in concurrent writes" | -| Wrong severity | QA `execution-protocol.md` — add calibration rule | "Auth issues are always CRITICAL" | -| Missed stub | QA `checklist.md` — runtime verification section | "Check upload actually processes file" | -| False positive pattern | QA `execution-protocol.md` — add exclusion | "Unused imports in test files are OK" | -| Inconsistent depth | QA `execution-protocol.md` — difficulty link | "Complex tasks require full audit" | +| Missed bug category | QA `checklist.md`: add check item | "Race condition in concurrent writes" | +| Wrong severity | QA `execution-protocol.md`: add calibration rule | "Auth issues are always CRITICAL" | +| Missed stub | QA `checklist.md`: runtime verification section | "Check upload actually processes file" | +| False positive pattern | QA `execution-protocol.md`: add exclusion | "Unused imports in test files are OK" | +| Inconsistent depth | QA `execution-protocol.md`: difficulty link | "Complex tasks require full audit" | --- @@ -66,7 +66,7 @@ When `good_catch` events accumulate (>= 5 in rolling window): 3. If yes: Propose addition to `common-checklist.md` 4. Record in tuning log as positive reinforcement -This prevents tuning drift toward pure skepticism — QA must also know what it does well. +This prevents tuning drift toward pure skepticism; QA must also know what it does well. --- diff --git a/.agents/skills/_shared/core/lessons-learned.md b/.agents/skills/_shared/core/lessons-learned.md index 1a7a169..65f419b 100644 --- a/.agents/skills/_shared/core/lessons-learned.md +++ b/.agents/skills/_shared/core/lessons-learned.md @@ -168,7 +168,7 @@ Auto-generated lessons use the RCA Entry Format above, with these additions: - Append `(Source: Experiment Ledger #{N}, Session {session_id})` to the summary line - Append to the relevant domain section (based on agent type) -Only the Orchestrator performs this at session end — after all agents have completed and the ledger is finalized. +Only the Orchestrator performs this at session end, after all agents have completed and the ledger is finalized. ### When Lessons Become Too Many (50+) - Move old lessons (6+ months) to archive diff --git a/.agents/skills/_shared/core/prompt-structure.md b/.agents/skills/_shared/core/prompt-structure.md index 0dcff65..6f2bf91 100644 --- a/.agents/skills/_shared/core/prompt-structure.md +++ b/.agents/skills/_shared/core/prompt-structure.md @@ -28,7 +28,7 @@ Standards, architecture rules, safety requirements, or project conventions. - "Must be backward-compatible with v2 API" ### 4. Done When -How to verify the task is complete — testable, observable criteria. +How to verify the task is complete using testable, observable criteria. - "All existing tests pass + new tests for auth endpoints" - "The 500 error no longer occurs and returns 200" @@ -59,4 +59,4 @@ Use "Done When" criteria as the primary review checklist. A task is not complete - Starting implementation with only a Goal (no constraints or done-when) - Inventing constraints the user didn't specify -- Accepting vague done-when like "it works" — push for testable criteria +- Accepting vague done-when like "it works"; push for testable criteria diff --git a/.agents/skills/_shared/core/reasoning-templates.md b/.agents/skills/_shared/core/reasoning-templates.md index df44b6b..e521783 100644 --- a/.agents/skills/_shared/core/reasoning-templates.md +++ b/.agents/skills/_shared/core/reasoning-templates.md @@ -14,7 +14,7 @@ Repeat the loop below when finding the cause of a bug. After 3 iterations withou Observation: {error message, symptoms, reproduction conditions} Hypothesis: "{phenomenon} is caused by {suspected cause}" -Verification method: {how to verify — code reading, logs, tests, etc.} +Verification method: {how to verify; code reading, logs, tests, etc.} Verification result: {what was actually confirmed} Verdict: Correct / Incorrect diff --git a/.agents/skills/_shared/core/session-metrics.md b/.agents/skills/_shared/core/session-metrics.md index ab66d85..03307af 100644 --- a/.agents/skills/_shared/core/session-metrics.md +++ b/.agents/skills/_shared/core/session-metrics.md @@ -1,6 +1,6 @@ # Session Metrics & Clarification Debt Tracking -Tracks per-session agent performance metrics, with emphasis on **Clarification Debt (CD)** — the cost of unclear requirements, scope creep, and charter violations. +Tracks per-session agent performance metrics, with emphasis on **Clarification Debt (CD)**, the cost of unclear requirements, scope creep, and charter violations. --- @@ -124,7 +124,7 @@ At session end, if total CD >= 50: ``` Turn 3: frontend asked about icon library preference → clarify (+10) Turn 15: All tasks completed successfully -Total CD: 10 ✅ +Total CD: 10 ``` ### Unhealthy Session (CD = 95) @@ -133,7 +133,7 @@ Turn 2: backend assumed REST, user wanted GraphQL → correct (+25) Turn 8: backend used wrong auth method → correct (+25) Turn 12: frontend built wrong layout → redo (+40) Turn 14: Charter not checked before redo → modifier (+15, but capped) -Total CD: 95 ❌ → RCA REQUIRED +Total CD: 95 → RCA REQUIRED ``` --- @@ -149,7 +149,7 @@ When Quality Score measurement is active (see `quality-score.md`), the session l | Checkpoint | Phase | Composite | Grade | Delta | |-----------|-------|-----------|-------|-------| -| Baseline | IMPL end | 72 | C | — | +| Baseline | IMPL end | 72 | C | n/a | | Post-VERIFY | VERIFY end | 78 | B | +6 | | Post-REFINE | REFINE end | 84 | B | +6 | | Final | SHIP | 86 | B | +2 | @@ -183,17 +183,17 @@ This data is sourced from the Experiment Ledger at session end (see `experiment- QA agents improve only when their judgment errors are tracked. Unlike CD (tracked in real-time), Evaluator Accuracy (EA) is a -**retrospective metric** — most errors are discovered after the session ends. +**retrospective metric**; most errors are discovered after the session ends. ### Accuracy Events | Event | Points | When Discovered | |-------|--------|-----------------| -| `false_negative` | +30 | Next session or production — bug that QA missed | -| `false_positive` | +15 | During session — impl agent disputes QA finding successfully | -| `severity_mismatch` | +10 | During session or retro — wrong severity assigned | -| `missed_stub` | +20 | During session — runtime verification catches display-only feature | -| `good_catch` | -10 | During session — QA caught non-obvious bug (reward signal) | +| `false_negative` | +30 | Next session or production: bug that QA missed | +| `false_positive` | +15 | During session: impl agent disputes QA finding successfully | +| `severity_mismatch` | +10 | During session or retro: wrong severity assigned | +| `missed_stub` | +20 | During session: runtime verification catches display-only feature | +| `good_catch` | -10 | During session: QA caught non-obvious bug (reward signal) | ### Recording diff --git a/.agents/skills/_shared/runtime/execution-protocols/claude.md b/.agents/skills/_shared/runtime/execution-protocols/claude.md index c989ce3..85fe3bb 100644 --- a/.agents/skills/_shared/runtime/execution-protocols/claude.md +++ b/.agents/skills/_shared/runtime/execution-protocols/claude.md @@ -10,13 +10,13 @@ If Serena MCP is available, you may also use `read_memory`/`write_memory`/`edit_ ### Path Resolution (CRITICAL) -All result, progress, and state files MUST be written to the **project root** `.agents/` directory — never to a subdirectory's `.agents/`. +All result, progress, and state files MUST be written to the **project root** `.agents/` directory, never to a subdirectory's `.agents/`. - **Project root** = the git repository root (where `.git` exists) - **Session-scoped naming**: when running under an orchestration session, append session ID as suffix: - `result-{agent-id}-{sessionId}.md` (e.g., `result-frontend-session-20260405-100835.md`) - `progress-{agent-id}-{sessionId}.md` -- **Manual (non-orchestrated) runs**: no suffix — `result-{agent-id}.md` +- **Manual (non-orchestrated) runs**: no suffix, `result-{agent-id}.md` ## On Start diff --git a/.agents/skills/_shared/runtime/execution-protocols/codex.md b/.agents/skills/_shared/runtime/execution-protocols/codex.md index 75b08d4..9ca066b 100644 --- a/.agents/skills/_shared/runtime/execution-protocols/codex.md +++ b/.agents/skills/_shared/runtime/execution-protocols/codex.md @@ -8,13 +8,13 @@ Use file-based I/O for coordination. Write results to `.agents/results/`. ### Path Resolution (CRITICAL) -All result, progress, and state files MUST be written to the **project root** `.agents/` directory — never to a subdirectory's `.agents/`. +All result, progress, and state files MUST be written to the **project root** `.agents/` directory, never to a subdirectory's `.agents/`. - **Project root** = the git repository root (where `.git` exists) - **Session-scoped naming**: when running under an orchestration session, append session ID as suffix: - `result-{agent-id}-{sessionId}.md` (e.g., `result-frontend-session-20260405-100835.md`) - `progress-{agent-id}-{sessionId}.md` -- **Manual (non-orchestrated) runs**: no suffix — `result-{agent-id}.md` +- **Manual (non-orchestrated) runs**: no suffix, `result-{agent-id}.md` ## On Start diff --git a/.agents/skills/_shared/runtime/execution-protocols/gemini.md b/.agents/skills/_shared/runtime/execution-protocols/gemini.md index ba54775..3332637 100644 --- a/.agents/skills/_shared/runtime/execution-protocols/gemini.md +++ b/.agents/skills/_shared/runtime/execution-protocols/gemini.md @@ -15,12 +15,12 @@ Memory base path is configurable via `memoryConfig.basePath` (default: `.serena/ ### Path Resolution (CRITICAL) -All result, progress, and state files MUST be written to the **project root** memory path — never to a subdirectory's memory path. +All result, progress, and state files MUST be written to the **project root** memory path, never to a subdirectory's memory path. - **Session-scoped naming**: when running under an orchestration session, append session ID as suffix: - `result-{agent-id}-{sessionId}.md` (e.g., `result-frontend-session-20260405-100835.md`) - `progress-{agent-id}-{sessionId}.md` -- **Manual (non-orchestrated) runs**: no suffix — `result-{agent-id}.md` +- **Manual (non-orchestrated) runs**: no suffix, `result-{agent-id}.md` ## On Start diff --git a/.agents/skills/_shared/runtime/execution-protocols/qwen.md b/.agents/skills/_shared/runtime/execution-protocols/qwen.md index 4a3b540..1b17d98 100644 --- a/.agents/skills/_shared/runtime/execution-protocols/qwen.md +++ b/.agents/skills/_shared/runtime/execution-protocols/qwen.md @@ -13,12 +13,12 @@ Memory base path is configurable via `memoryConfig.basePath` (default: `.serena/ ### Path Resolution (CRITICAL) -All result, progress, and state files MUST be written to the **project root** memory path — never to a subdirectory's memory path. +All result, progress, and state files MUST be written to the **project root** memory path, never to a subdirectory's memory path. - **Session-scoped naming**: when running under an orchestration session, append session ID as suffix: - `result-{agent-id}-{sessionId}.md` (e.g., `result-frontend-session-20260405-100835.md`) - `progress-{agent-id}-{sessionId}.md` -- **Manual (non-orchestrated) runs**: no suffix — `result-{agent-id}.md` +- **Manual (non-orchestrated) runs**: no suffix, `result-{agent-id}.md` ## On Start diff --git a/.agents/skills/_shared/runtime/memory-protocol.md b/.agents/skills/_shared/runtime/memory-protocol.md index 6cf58ea..6d85f10 100644 --- a/.agents/skills/_shared/runtime/memory-protocol.md +++ b/.agents/skills/_shared/runtime/memory-protocol.md @@ -19,12 +19,12 @@ Memory base path is configurable via `memoryConfig.basePath` (default: `.serena/ ## Path Resolution (CRITICAL) -All result, progress, and state files MUST be written to the **project root** — never to a subdirectory. +All result, progress, and state files MUST be written to the **project root**, never to a subdirectory. - **Session-scoped naming**: when running under an orchestration session, append session ID as suffix: - `result-{agent-id}-{sessionId}.md` - `progress-{agent-id}-{sessionId}.md` -- **Manual (non-orchestrated) runs**: no suffix — `result-{agent-id}.md` +- **Manual (non-orchestrated) runs**: no suffix, `result-{agent-id}.md` ## On Start diff --git a/.agents/skills/_version.json b/.agents/skills/_version.json index 873bb87..10f19f9 100644 --- a/.agents/skills/_version.json +++ b/.agents/skills/_version.json @@ -1,3 +1,6 @@ { - "version": "5.13.0" -} \ No newline at end of file + "version": "8.7.0", + "schemaVersion": 2, + "mode": "project", + "installedAt": "2026-05-25T12:17:26.802Z" +} diff --git a/.agents/skills/architecture/SKILL.md b/.agents/skills/architecture/SKILL.md new file mode 100644 index 0000000..647164c --- /dev/null +++ b/.agents/skills/architecture/SKILL.md @@ -0,0 +1,8 @@ +--- +name: architecture +description: Software architecture workflow that diagnoses architecture problems, selects the right analysis method, compares options, synthesizes stakeholder input, and produces a recommendation, review, or ADR +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/architecture.md` step by step. diff --git a/.agents/skills/brainstorm/SKILL.md b/.agents/skills/brainstorm/SKILL.md new file mode 100644 index 0000000..f280a89 --- /dev/null +++ b/.agents/skills/brainstorm/SKILL.md @@ -0,0 +1,8 @@ +--- +name: brainstorm +description: Design-first ideation workflow that explores user intent, clarifies constraints, proposes approaches, and produces an approved design document before planning +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/brainstorm.md` step by step. diff --git a/.agents/skills/coordinate/SKILL.md b/.agents/skills/coordinate/SKILL.md new file mode 100644 index 0000000..4c54adf --- /dev/null +++ b/.agents/skills/coordinate/SKILL.md @@ -0,0 +1,8 @@ +--- +name: coordinate +description: Coordinate multiple agents for a complex multi-domain project using PM planning, parallel agent spawning, and QA review +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/coordinate.md` step by step. diff --git a/.agents/skills/debug/SKILL.md b/.agents/skills/debug/SKILL.md new file mode 100644 index 0000000..70372ae --- /dev/null +++ b/.agents/skills/debug/SKILL.md @@ -0,0 +1,8 @@ +--- +name: debug +description: Structured bug diagnosis and fixing workflow that reproduces, diagnoses root cause, applies a minimal fix, writes regression tests, and scans for similar patterns +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/debug.md` step by step. diff --git a/.agents/skills/deepinit/SKILL.md b/.agents/skills/deepinit/SKILL.md new file mode 100644 index 0000000..a7ec9a2 --- /dev/null +++ b/.agents/skills/deepinit/SKILL.md @@ -0,0 +1,8 @@ +--- +name: deepinit +description: Initialize project harness with AGENTS.md as table of contents, ARCHITECTURE.md as domain map, and a structured docs/ knowledge base +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/deepinit.md` step by step. diff --git a/.agents/skills/deepsec/SKILL.md b/.agents/skills/deepsec/SKILL.md new file mode 100644 index 0000000..5742df5 --- /dev/null +++ b/.agents/skills/deepsec/SKILL.md @@ -0,0 +1,8 @@ +--- +name: deepsec +description: Drive the `oma-deepsec` skill end-to-end. Installs `.deepsec/`, calibrates cost, runs the right scan/process/triage/revalidate/export pass, gates PRs with `process --diff`, writes custom matchers, and routes findings to follow-up specialists. +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/deepsec.md` step by step. diff --git a/.agents/skills/design/SKILL.md b/.agents/skills/design/SKILL.md new file mode 100644 index 0000000..562570e --- /dev/null +++ b/.agents/skills/design/SKILL.md @@ -0,0 +1,8 @@ +--- +name: design +description: Design workflow that creates design systems, DESIGN.md, and design tokens with anti-pattern enforcement and accessibility checks +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/design.md` step by step. diff --git a/.agents/skills/docs/SKILL.md b/.agents/skills/docs/SKILL.md new file mode 100644 index 0000000..043dece --- /dev/null +++ b/.agents/skills/docs/SKILL.md @@ -0,0 +1,8 @@ +--- +name: docs +description: Documentation drift detection and sync via `oma-docs`. Verify mode finds broken refs in docs/**/*.md, sync mode proposes patches for docs affected by a git diff. +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/docs.md` step by step. diff --git a/.agents/skills/exec-plan/SKILL.md b/.agents/skills/exec-plan/SKILL.md new file mode 100644 index 0000000..4b3e0d1 --- /dev/null +++ b/.agents/skills/exec-plan/SKILL.md @@ -0,0 +1,8 @@ +--- +name: exec-plan +description: Create, manage, and track execution plans as first-class repository artifacts in docs/exec-plans/ +disable-model-invocation: true +--- + + +Read and follow `.agents/workflows/exec-plan.md` step by step. diff --git a/.agents/skills/oma-academic-writer/SKILL.md b/.agents/skills/oma-academic-writer/SKILL.md new file mode 100644 index 0000000..98d006f --- /dev/null +++ b/.agents/skills/oma-academic-writer/SKILL.md @@ -0,0 +1,182 @@ +--- +name: oma-academic-writer +description: > + Academic writing specialist for publication-grade English prose. Drafts, revises, and + audits essays, reports, analysis sections, executive summaries, conclusions, and + literature reviews while enforcing sentence-structure variation, high-frequency + academic verbs, calibrated hedging, and anti-AI stylistic compliance. USE for + academic writing, essay polish, paragraph rewrite, prose revision against any + rubric tier (HD/D/C, A/B/C, top-band/mid-band, etc.), anti-AI audit, reverse + outlining, claim-evidence mapping, and rubric enforcement on assignments. +--- + +# Academic Writer: Publication-Grade English Prose Specialist + +## Scheduling + +### Goal +Produce, revise, and audit publication-grade academic English prose so that every output simultaneously satisfies the Sentence Structure Protocol, Verb Protocol, Hedging Protocol, and Anti-AI Compliance Checklist, with every claim mapped to verifiable evidence. + +### Intent signature +- "draft this essay / report / executive summary / conclusion / literature review" +- "rewrite this paragraph in academic English" +- "polish this draft to top-band quality" / "revise to match the rubric" +- "run an anti-AI audit on this prose" +- "check sentence structure variety" / "fix monotonous rhythm" +- "the prose sounds AI-generated, make it pass" +- "verify claims against evidence" / "reverse outline this section" + +### When to use +- Drafting or revising academic reports, essays, or analysis sections +- Writing executive summaries, conclusions, or literature reviews +- Rewriting AI-sounding prose into natural academic English +- Polishing draft text to achieve top-band rubric quality (HD, A, top-band, etc.) +- Reviewing prose for sentence variety, verb quality, hedging, and anti-AI compliance +- Any task requiring formal academic English output bound by a rubric + +### When NOT to use +- Translation tasks → use `oma-translator` +- Source discovery, citation gathering, or scholarly literature search → use `oma-scholar` +- Rubric / assignment-spec parsing and task decomposition → use `oma-pm` +- Code documentation, README, or API reference text → use the relevant domain skill (`oma-frontend`, `oma-backend`, `oma-mobile`, `oma-db`, etc.) +- Informal communication, chat, or marketing copy → no skill needed +- Non-English academic writing → call `oma-translator` for the target language after drafting in English + +### Expected inputs +- `mode`: one of `draft` | `revise` | `review` +- `rubric_or_constraint`: assignment brief, rubric file, or word/structure limits (path or inline text) +- `existing_draft`: prior text to revise or audit (path or inline text); required for `revise` and `review` +- `source_data`: available evidence, figures, citations the writer may use +- `target_register`: defaults to formal academic English + +### Expected outputs +- `draft` mode: section heading + drafted prose + Writing Notes (sentence mix, key verbs, anti-AI flags resolved, paragraph lengths) + Claim-Evidence Map +- `revise` mode: original block, revised block, list of specific changes (verb upgrades, structure variation, anti-AI fixes) +- `review` mode: PASS/FAIL Compliance Report across Sentence Structure, Verb Quality, Anti-AI, Specificity, Hedging, Paragraph Clarity, Rhythm/Burstiness, Claim-Evidence Alignment, plus recommended fixes + +### Dependencies +- `resources/anti-ai-checklist.md`: banned vocabulary, banned structural patterns, sentence-level checks +- `resources/sentence-structure-reference.md`: four sentence types, length targets, common errors +- `resources/academic-verb-tiers.md`: banned generic verbs and tiered academic-corpus replacements +- `resources/hedging-guide.md`: calibrated certainty expressions matched to evidence strength +- `../_shared/core/context-loading.md`: task-relevant resource loading +- `../_shared/core/quality-principles.md`: shared quality bar + +### Control-flow features +- Mode branching: `draft` vs `revise` vs `review` produce different output formats and pass sequences +- Rubric-quote gate: refuses to apply a rule until the literal constraint text is quoted from the source +- Citation gap branch: when a claim lacks evidence, weaken or remove rather than fabricate; optionally hand off to `oma-scholar` +- Language branch: non-English target hands off to `oma-translator` after the English pass +- Iterative AUDIT: every fix loops back through the anti-AI checklist before emit + +## Structural Flow + +### Entry +1. Identify the mode (`draft`, `revise`, `review`) and the rubric source. +2. Quote the exact constraint text (word limits, structural requirements, mandatory sections, rubric rows) before applying any rule. +3. If revising or reviewing, read the existing draft in full first; if drafting, confirm available source data and citations. +4. Index `resources/` and pre-select the verb tier and sentence mix targets for the section. + +### Scenes +1. **PREPARE**: load rubric, existing draft, source data; record quoted constraints; pick sentence mix and 2–3 anchor verbs per paragraph. +2. **ACQUIRE**: read `resources/sentence-structure-reference.md`, `academic-verb-tiers.md`, and `hedging-guide.md` only for the patterns relevant to the current section. +3. **ACT**: write or revise prose with the four protocols enforced simultaneously: Sentence Structure (4 types, varied length, varied openers), Verb (no banned generic verbs as main verbs; prefer tier-1/2 academic verbs), Hedging (match strength to evidence), and Topic-Support-Conclude paragraphing. +4. **VERIFY**: audit against `resources/anti-ai-checklist.md` (vocabulary clusters, structural patterns, sentence-level checks); apply reverse outlining and build the Claim-Evidence Map; weaken or remove unsupported claims. +5. **FINALIZE**: read-aloud test, cohesion check, specificity audit, word-count verification, paragraph-length variation, rhythm check; emit per the mode's output format. + +### Transitions +- If a rubric line is ambiguous → quote it back to the user and ask for interpretation; do not infer combined rules. +- If a claim cannot be supported by available evidence → weaken with hedging or remove; if a citation gap is structural, NOTIFY `oma-scholar`. +- If the target language is non-English → finish the English pass, then hand off to `oma-translator`. +- If the same anti-AI flag survives one fix attempt → restructure the surrounding two sentences instead of word-substitution alone. +- If an output mode mismatch is detected (e.g., user asked for review but supplied a fresh prompt) → confirm the mode before producing output. + +### Failure and recovery +| Failure | Recovery | +|---------|----------| +| Word count over / under target | Cut filler adverbs and redundant qualifiers, or expand with supporting evidence; re-run audit | +| Prose still sounds AI-generated after one pass | Vary sentence openers (subject, adverbial, participial, prepositional) and insert one short (≤10-word) sentence per paragraph; re-run audit | +| Rubric requirement unclear | Quote exact rubric text and ask user; do not combine rules | +| Claim lacks evidence | Add citation, hedge to match weaker evidence, or remove the claim entirely | +| Hedging miscalibrated | Replace double hedges; align hedge strength with `resources/hedging-guide.md` evidence-level table | +| Banned generic verb resists replacement | Restructure the sentence so the banned verb is not the main verb | +| Paragraph blocks are uniform 4–5 sentences | Insert a 2-sentence emphasis paragraph; re-run rhythm check | + +### Exit +- Success: every protocol PASSes, the Claim-Evidence Map has no unsupported entries, word count complies, and the mode-specific output format is fully populated. +- Partial success: emit prose with explicit `needs evidence` / `pending citation` markers and report which protocol items remain at risk; flag handoff candidates. +- Failure: refuse to emit and report the blocking ambiguity (rubric quote missing, source data absent, contradictory constraints). + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Read rubric / constraint and quote literal text | `READ` | Rubric file or assignment brief | +| Read existing draft (revise/review modes) | `READ` | Draft file or inline text | +| Index resources for the current section | `READ` | `resources/{anti-ai-checklist,sentence-structure-reference,academic-verb-tiers,hedging-guide}.md` | +| Select sentence mix and 2–3 anchor verbs per paragraph | `SELECT` | Sentence-structure & verb-tier tables | +| Plan paragraph as Topic-Support-Conclude | `INFER` | Outline notes | +| Draft / revise prose under all four protocols | `WRITE` | Generated prose | +| Audit prose against anti-AI checklist | `VALIDATE` | `resources/anti-ai-checklist.md` | +| Reverse outline + build Claim-Evidence Map | `VALIDATE` | Mapping table | +| Weaken or remove unsupported claims | `WRITE` | Revised claim line | +| Compare original vs revised (revise mode) | `COMPARE` | Diff block | +| Hand off non-English target | `NOTIFY` | `oma-translator` | +| Hand off citation gap | `NOTIFY` | `oma-scholar` | +| Hand off ambiguous rubric / spec | `NOTIFY` | `oma-pm` | +| Emit per mode output format | `WRITE` | Final artifact | +| Report compliance status | `NOTIFY` | PASS/FAIL summary or Writing Notes block | + +### Tools and instruments +- `Read` / `Edit` / `Write` for draft and rubric files +- `resources/anti-ai-checklist.md`, `sentence-structure-reference.md`, `academic-verb-tiers.md`, `hedging-guide.md` +- Topic-Support-Conclude paragraph template (inline) +- Claim-Evidence Map (inline 3-column table: Claim / Evidence / Status) +- Output-format blocks per mode (Draft / Revision / Review) + +### Canonical workflow path +1. **READ** rubric/draft and quote the exact literal constraint text; pin word limits, mandatory sections, and rubric rows. +2. **PLAN** each paragraph as Topic-Support-Conclude; pre-select the sentence-type mix and 2–3 anchor verbs from `academic-verb-tiers.md`. +3. **DRAFT** prose with Sentence Structure, Verb, Hedging, and Topic-Support-Conclude protocols enforced simultaneously. +4. **AUDIT** the draft against `resources/anti-ai-checklist.md` (banned vocabulary clusters, banned structural patterns, sentence-level checks) and fix every flag. +5. **REVERSE-OUTLINE** the section and build the Claim-Evidence Map; weaken or remove any unsupported claim. +6. **POLISH** with read-aloud, cohesion, specificity, word-count, rhythm, and paragraph-length-variation checks; emit in the mode's output format. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `LOCAL_FS` | Rubric, existing draft, generated prose output | +| `CODEBASE` | `resources/` 4 reference files, `_shared/core/{context-loading,quality-principles}.md` | +| `MEMORY` | Mode, quoted constraints, anchor verbs per paragraph, anti-AI flags resolved, Claim-Evidence Map | + +### Preconditions +- A rubric / constraint or an existing draft (or both) is provided. +- The target register is academic English. If the final deliverable is non-English, the user has agreed to a downstream `oma-translator` handoff. +- The source data needed to support claims is available, or unsupported claims are explicitly allowed to be weakened or removed. + +### Effects and side effects +- Writes drafted, revised, or reviewed prose to the user's working location (file or inline). +- Does not modify `resources/` reference files. +- Does not fetch external citations; defers to `oma-scholar` when discovery is required. +- May NOTIFY adjacent skills but does not auto-spawn them; user or workflow drives the actual handoff. + +### Guardrails +1. Every sentence must be verifiable; never fabricate data, statistics, or citations. +2. Quote-before-judgment: cite the literal constraint or rubric text before applying any rule. +3. Never combine distinct rules to invent a new constraint; apply rules exactly as written. +4. Banned generic verbs (`show`, `have`, `make`, `do`, `get`, `use`, `give`, `say`, `put`, `see`, `come`, `go`, `take`, `find`, `know`, `think`, `want`, `try`, `need`, `seem`, `become`, `keep`, `help`, `start`, `turn`, `bring`, `run`, `hold`, `set`) must not appear as main verbs; replace per `academic-verb-tiers.md`. +5. Never place 3+ sentences of the same structural type consecutively; vary length (short 8–15, medium 16–25, long 26–40 words) and openers. +6. Match hedge strength to evidence strength per `hedging-guide.md`; never use absolute claim words (`definitely`, `clearly`, `obviously`) outside mathematical facts; never first-person `I think` / `I believe`. +7. Never cluster 3+ flagged AI-vocabulary items in a single paragraph; never insert promotional or inflated language; never append superficial `-ing` clauses for analysis. +8. Em dashes ≤ 1 per paragraph; semicolons ≤ 2 per 1000 words; sentence-case headers; no didactic disclaimers (`It is important to note`) or summary phrases (`In summary`, `Overall`). +9. Every claim must map to evidence in the Claim-Evidence Map; weaken or remove unsupported claims rather than emit them. +10. Read aloud before emit; if a sentence does not flow naturally, restructure it. + +## References +- Anti-AI checklist: `resources/anti-ai-checklist.md` +- Sentence-structure reference: `resources/sentence-structure-reference.md` +- Academic verb tiers: `resources/academic-verb-tiers.md` +- Hedging guide: `resources/hedging-guide.md` +- Shared context loading: `../_shared/core/context-loading.md` +- Shared quality principles: `../_shared/core/quality-principles.md` diff --git a/.agents/skills/oma-academic-writer/resources/academic-verb-tiers.md b/.agents/skills/oma-academic-writer/resources/academic-verb-tiers.md new file mode 100644 index 0000000..e225fdc --- /dev/null +++ b/.agents/skills/oma-academic-writer/resources/academic-verb-tiers.md @@ -0,0 +1,181 @@ +# Academic verb tiers + +Ranked by frequency in a corpus of academic papers. Higher tiers are more universally appropriate; lower tiers are more specialised. + +Source: `src/_ingredients/verbs.md` (top 437 verbs from academic corpus). + +## Banned verbs (generic / low-level) + +These verbs lack precision and register in academic writing. Never use them as the main verb of a sentence. + +| Banned verb | Academic replacements | +|-------------|----------------------| +| show | illustrate, demonstrate, reveal, indicate, depict, exhibit | +| have | possess, maintain, exhibit, encompass, retain, display | +| make | generate, produce, construct, establish, formulate, create | +| do | perform, execute, conduct, accomplish, undertake | +| get | obtain, acquire, achieve, attain, derive, secure | +| use | employ, leverage, utilise, adopt, exploit, harness | +| give | provide, furnish, yield, deliver, grant, supply | +| say | argue, assert, contend, maintain, posit, state | +| put | position, allocate, situate, deploy, place | +| see | observe, identify, recognise, discern, perceive, detect | +| find | identify, determine, establish, ascertain, uncover | +| know | recognise, acknowledge, understand, appreciate | +| think | hypothesise, postulate, theorise, reason, infer | +| want | seek, aspire, endeavour, aim, intend | +| try | attempt, endeavour, pursue, strive, undertake | +| need | require, necessitate, demand, warrant, entail | +| seem | appear, suggest, indicate, manifest, resemble | +| help | facilitate, enable, support, contribute to, assist | +| start | initiate, commence, launch, introduce, inaugurate | +| turn | transform, convert, transition, shift, redirect | +| bring | introduce, yield, generate, contribute, produce | +| run | operate, execute, administer, manage, conduct | +| hold | maintain, retain, sustain, accommodate, contain | +| set | establish, configure, determine, specify, define | +| keep | maintain, preserve, retain, sustain, uphold | +| go | proceed, transition, advance, progress, extend | +| come | emerge, arise, originate, result, derive | +| take | adopt, assume, undertake, acquire, embrace | +| become | emerge, evolve, develop, transition, transform | + +## Tier 1: universal academic verbs (frequency rank 1–30) + +These are safe in any academic context. Use liberally. + +| Verb | Frequency Rank | Best Used For | +|------|---------------|---------------| +| perform | 8 | Describing actions, experiments, evaluations | +| provide | 10 | Presenting data, offering evidence, supplying context | +| evaluate | 11 | Assessment, measurement, comparison | +| require | 12 | Establishing necessity, conditions, prerequisites | +| include | 15 | Enumeration, scope definition | +| follow | 17 | Methodology, sequence, adherence | +| compare | 18 | Analysis, juxtaposition, relative assessment | +| achieve | 22 | Results, outcomes, attainment of goals | +| enable | 24 | Facilitation, capability description | +| improve | 28 | Enhancement, progress, optimisation of outcomes | +| describe | 29 | Characterisation, explanation, narration | +| demonstrate | 30 | Proof, evidence presentation, showing results | +| present | 32 | Introduction of findings, display of data | +| propose | 34 | Hypotheses, recommendations, new approaches | +| introduce | 35 | New concepts, methods, frameworks | +| allow | 39 | Permission, enablement, possibility | +| apply | 41 | Implementation, practical use, methodology | +| predict | 43 | Forecasting, modelling, anticipation | +| represent | 44 | Symbolisation, standing for, comprising | +| explore | 45 | Investigation, examination, discovery | +| combine | 46 | Integration, synthesis, merging | +| design | 47 | Creation, planning, structuring | +| execute | 48 | Implementation, carrying out procedures | +| leverage | 50 | Strategic use (use sparingly; borderline AI word) | +| generalise | 52 | Abstraction, broad application | +| study | 54 | Investigation, research, examination | +| utilise | 55 | Application (prefer "employ" or "use" in less formal contexts) | +| solve | 56 | Resolution, addressing problems | + +## Tier 2: strong academic verbs (frequency rank 31–80) + +Excellent for adding precision and variety. + +| Verb | Rank | Best Used For | +|------|------|---------------| +| indicate | 65 | Evidence pointing to conclusions | +| adopt | 68 | Taking up methods, approaches, strategies | +| observe | 70 | Empirical findings, noting phenomena | +| adapt | 71 | Modification, adjustment to conditions | +| specify | 72 | Defining precisely, setting parameters | +| focus | 73 | Directing attention, narrowing scope | +| correspond | 76 | Correlation, matching, alignment | +| employ | 79 | Using methods or tools (preferred over "utilise") | +| aim | 80 | Purpose, objective, intention | +| develop | 82 | Creation, evolution, progression | +| produce | 83 | Generation, creation, yielding outcomes | +| investigate | 85 | Systematic inquiry, research | +| support | 86 | Evidence corroboration, backing claims | +| contain | 89 | Inclusion, comprising, holding | +| involve | 92 | Participation, inclusion of elements | +| understand | 93 | Comprehension, grasp of concepts | +| refer | 95 | Citation, pointing to, mentioning | +| obtain | 96 | Acquisition, securing results | +| conduct | 97 | Carrying out research, experiments | +| incorporate | 101 | Integration, inclusion within a system | +| control | 102 | Regulation, management, experimental design | +| implement | 111 | Putting into practice, execution | +| exhibit | 113 | Displaying characteristics, showing qualities | +| assess | 119 | Evaluation, measurement, appraisal | +| illustrate | 122 | Visual representation, exemplification | +| reduce | 123 | Decrease, minimisation, simplification | +| address | 124 | Tackling issues, responding to concerns | +| extend | 126 | Expansion, broadening scope | +| denote | 127 | Signification, representation | +| select | 128 | Choosing, picking, sampling | +| serve | 132 | Function, role fulfillment | +| process | 133 | Handling, transformation, treatment | + +## Tier 3: precision verbs (frequency rank 81–200) + +For nuanced, specific claims. Excellent for adding sophistication without over-reaching. + +| Verb | Rank | Best Used For | +|------|------|---------------| +| suggest | 139 | Moderate-confidence claims, implications | +| capture | 142 | Recording, encapsulating, representing | +| summarise | 143 | Condensation, overview, synthesis | +| measure | 149 | Quantification, assessment | +| integrate | 150 | Combining, synthesising, unifying | +| mitigate | 154 | Reducing negative effects, lessening risk | +| align | 155 | Agreement, correspondence, matching | +| define | 156 | Specification, delimitation, characterisation | +| interpret | 161 | Meaning extraction, analysis, reading data | +| enhance | 162 | Improvement (use carefully; borderline AI word) | +| affect | 165 | Influence, impact on outcomes | +| ensure | 174 | Guaranteeing, securing, confirming | +| deploy | 177 | Implementation, putting into operation | +| simulate | 179 | Modelling, replicating conditions | +| determine | 207 | Establishing, deciding, finding out | +| rely | 210 | Dependency, foundation, based on | +| construct | 205 | Building, creating, assembling | +| attribute | 206 | Assigning cause, crediting | +| formulate | 243 | Creating plans, theories, equations | +| identify | 227 | Recognising, pinpointing, discovering | +| analyse | 229 | Examination, investigation, deconstruction | +| reveal | 259 | Discovery, making known, uncovering | +| establish | 289 | Founding, proving, confirming | +| operate | 291 | Functioning, running, working | +| recognise | 292 | Acknowledging, identifying, accepting | +| categorise | 293 | Classification, grouping, sorting | +| retain | 294 | Keeping, preserving, maintaining | +| highlight | 297 | Drawing attention (use sparingly; borderline AI word) | +| validate | 321 | Confirming, verifying, proving correct | +| constrain | 326 | Limiting, restricting, bounding | +| visualise | 329 | Representing graphically, depicting | +| resolve | 338 | Solving, addressing, settling | +| calculate | 350 | Computing, determining numerically | + +## Verb selection by rhetorical purpose + +| Purpose | Recommended Verbs | +|---------|-------------------| +| Presenting findings | demonstrate, reveal, indicate, illustrate, exhibit | +| Making an argument | argue, contend, assert, maintain, posit | +| Describing methodology | employ, adopt, implement, conduct, execute | +| Comparing | compare, contrast, distinguish, differentiate, juxtapose | +| Showing causation | cause, produce, generate, yield, result in | +| Hedging | suggest, appear, may indicate, seem to imply | +| Quantifying | measure, calculate, quantify, estimate, compute | +| Evaluating | assess, evaluate, appraise, judge, critique | +| Synthesising | integrate, combine, synthesise, consolidate, unify | +| Proposing | propose, recommend, suggest, advocate, put forward | +| Limiting scope | focus, confine, restrict, constrain, delimit | +| Citing work | note, report, document, record, observe | + +## Usage notes + +1. **Tier 1 verbs** are always safe; use these as your default vocabulary +2. **Tier 2 verbs** add precision; use 3–5 per paragraph for variety +3. **Tier 3 verbs** add sophistication; use 1–2 per paragraph to avoid overly dense prose +4. **Borderline AI words** (leverage, enhance, highlight, showcase): limit to 1 per page maximum; prefer alternatives +5. **Match verb to evidence strength**: "demonstrate" > "suggest" > "may indicate" in confidence +6. **Prefer single verbs over phrasal verbs**: "investigate" not "look into", "improve" not "make better" diff --git a/.agents/skills/oma-academic-writer/resources/anti-ai-checklist.md b/.agents/skills/oma-academic-writer/resources/anti-ai-checklist.md new file mode 100644 index 0000000..3699df5 --- /dev/null +++ b/.agents/skills/oma-academic-writer/resources/anti-ai-checklist.md @@ -0,0 +1,267 @@ +# Anti-AI Writing Checklist for Academic English + +Academic prose must read as authentically human-written. This checklist targets patterns that AI detection tools and experienced markers identify as machine-generated. + +## Pre-submission Scan + +Run through each category. A single FAIL requires revision before output. + +## 1. Vocabulary Clustering + +**Rule:** No more than 2 of the following words in any single paragraph. + +### Flagged Words (High AI Correlation) + +Additionally, align with, crucial, delve, emphasize/emphasizing, enduring, enhance, foster/fostering, garner, highlight (as verb), interplay, intricate/intricacies, key (as adjective), landscape (abstract), multifaceted, nuanced, pivotal, robust, seamless, showcase, synergy, tapestry (abstract), testament, underscore (as verb), valuable, vibrant, holistic, paradigm, cutting-edge, groundbreaking, comprehensive, Furthermore, Moreover, navigating, realm, embark, noteworthy + +### Self-check + +- [ ] Count flagged words per paragraph +- [ ] If 3+ found → replace with plain academic alternatives +- [ ] Check entire document for repeated use of the same flagged word + +## 2. Inflated Significance + +**Rule:** Never inflate the importance of a subject beyond what the evidence supports. + +### Banned Phrases + +| Phrase | Plain Alternative | +|--------|-------------------| +| stands/serves as | is | +| is a testament to | demonstrates / reflects | +| a vital/crucial/pivotal role | an important role / a role in | +| underscores/highlights its importance | shows / indicates | +| reflects broader trends | relates to | +| symbolizing its enduring legacy | (delete unless legacy is the subject) | +| setting the stage for | preceding / leading to | +| key turning point | a change / a shift | +| indelible mark | lasting effect | +| deeply rooted | established / longstanding | +| evolving landscape | changing conditions | +| groundbreaking | new / novel / significant | + +### Self-check + +- [ ] Does every significance claim have supporting evidence cited? +- [ ] Is the language proportional to the evidence? +- [ ] Would a sceptical reader accept the level of emphasis? + +## 3. Superficial -ing Analysis + +**Rule:** Never append a present participle clause as shallow analysis. + +### Pattern to Detect + +> "[Statement], **highlighting/ensuring/reflecting/contributing to/fostering** [vague significance]." + +### Fix + +- If the -ing clause adds genuine meaning → promote it to a full sentence with evidence +- If it adds no meaning → delete it entirely + +### Self-check + +- [ ] Search for -ing clauses at end of sentences +- [ ] For each: does it add substantive analysis or just filler? +- [ ] Rewrite or delete accordingly + +## 4. Copula Avoidance + +**Rule:** Use "is/are/has" when they are the natural choice. Do not replace them with fancier alternatives. + +### Pattern to Detect + +| AI Tendency | Natural Form | +|-------------|-------------| +| serves as a | is a | +| stands as | is | +| marks the | is the | +| represents a | is a | +| boasts / features / offers | has | + +### Self-check + +- [ ] Scan for "serves as", "stands as", "marks", "represents" used as copula substitutes +- [ ] Replace with "is/are" unless the verb genuinely adds meaning + +## 5. Structural Patterns + +### Negative Parallelisms + +- Avoid: "Not only X but also Y", "It's not just about X, it's about Y" +- Fix: State both facts directly without the parallelism + +### Rule of Three + +- Avoid: "adjective, adjective, and adjective" for shallow coverage +- Fix: Reduce to two descriptors, or expand each into substantive analysis + +### Elegant Variation (Synonym Cycling) + +- Avoid: Rotating terms for the same concept (students → learners → participants) +- Fix: Pick one term and use it consistently throughout + +### False Ranges + +- Avoid: "from X to Y" with unrelated or vaguely connected endpoints +- Fix: Drop the construction or specify a meaningful scale + +### Self-check + +- [ ] No negative parallelisms used for rhetorical effect alone +- [ ] No triple adjective/noun lists without substantive expansion +- [ ] Terminology is consistent (no synonym cycling) +- [ ] All "from X to Y" constructions have a meaningful scale + +## 6. Formatting Artefacts + +### Boldface + +- Do not bold terms mechanically in lists ("**Term**: description") +- Bold only for genuine emphasis in running prose + +### Em Dashes + +- Limit to 1 per paragraph maximum +- Prefer commas or parentheses +- Never use em dashes for emphasis that a natural sentence structure can deliver + +### Colons + +- **Prefer natural sentence flow over colon constructions.** Subordination (because, although, while) and coordination (and, but, so) almost always produce more readable prose than a colon. +- Colons are acceptable only for: + - Formal definitions: "Normalization is defined as: ..." + - Introducing block quotations + - Ratios or time stamps (e.g., 2:1, 14:30) +- **Avoid** colons that introduce inline lists, elaborations, or explanations mid-sentence. + - Bad: "The study examined three factors: temperature, humidity, and wind speed." + - Good: "The study examined temperature, humidity, and wind speed." + - Also good: "The study examined three factors, namely temperature, humidity, and wind speed." +- **Avoid** the "**Label**: description" pattern in running prose (this is a list/slide-deck pattern, not academic prose). + +### Title Case + +- Use sentence case for all section headings +- Exception: proper nouns + +### Tables + +- Do not present information as a table when prose is more appropriate +- Tables for quantitative comparison or reference data only + +### Self-check + +- [ ] No mechanical bold patterns +- [ ] Em dashes used sparingly (≤1 per paragraph) +- [ ] Colons used only for formal definitions, block quotations, or ratios +- [ ] All headings in sentence case +- [ ] Tables justified for the content type + +## 7. Meta-commentary + +### Banned Phrases + +| Phrase | Action | +|--------|--------| +| It is important to note | Delete; state the point directly | +| It should be noted that | Delete | +| Worth noting | Delete | +| In summary | Transition naturally | +| In conclusion | Transition naturally | +| Overall | Usually unnecessary; delete or restructure | +| As mentioned earlier | Delete or use a specific cross-reference | +| As discussed above | Delete or use a specific cross-reference | + +### Self-check + +- [ ] No meta-commentary phrases found +- [ ] Transitions use content-based links, not meta-phrases + +## 8. Sentence Openers + +### Rule: Vary how sentences begin + +**Avoid starting 3+ consecutive sentences with:** + +- The same word (especially "The", "This", "It", "However") +- Subject-verb pattern every time + +**Vary with:** + +- Adverbial phrases: "Between 2015 and 2020, ..." +- Prepositional phrases: "In the context of ..." +- Participial phrases: "Drawing on longitudinal data, ..." +- Dependent clauses: "Although the sample size was limited, ..." +- Transitional phrases (non-AI): "By contrast, ...", "More specifically, ...", "In parallel, ..." + +### Self-check + +- [ ] No 3+ consecutive sentences starting the same way +- [ ] At least 3 different opener types per paragraph + +## 9. Rhythm & Paragraph Length + +**Rule:** AI-generated text exhibits characteristically uniform sentence length and paragraph blocks. Natural academic writing has variation. + +### Sentence rhythm (burstiness) + +- If 5+ consecutive sentences all fall within the same narrow word-count range (e.g., all 20–25 words), flag for revision +- Insert a short sentence (≤10 words) to break metronomic patterns +- Combine two short sentences into one complex one if the pattern is monotonously short +- Read the paragraph aloud; if it feels metronomic, vary it + +### Paragraph length variation + +- Vary paragraph length naturally: 2–8 sentences per paragraph +- Uniform 4–5 sentence paragraphs signal AI; avoid this pattern +- Short paragraphs (2–3 sentences) create emphasis +- Longer paragraphs (6–8 sentences) develop complex arguments +- Never have 4+ consecutive paragraphs of the same length (±1 sentence) + +### Semicolons + +- Limit: ≤2 per 1000 words +- AI text chains independent clauses with semicolons where a period would be clearer +- Reserve semicolons for closely related parallel structures + +### Self-check + +- [ ] No 5+ consecutive sentences in the same word-count range +- [ ] Paragraph lengths vary (no 4+ consecutive same-length paragraphs) +- [ ] Semicolons ≤2 per 1000 words + +## 10. Chatbot Artefacts + +### Never Include + +- "I hope this helps" +- "Let me know if you need anything else" +- "Here is a breakdown of..." +- "Of course!", "Certainly!" +- "As an AI language model" +- Subject lines ("Subject: ...") +- Knowledge-cutoff disclaimers + +### Self-check + +- [ ] Zero chatbot artefacts in output + +## Final Verification + +Run all checks in sequence: + +1. [ ] Vocabulary clustering: no 3+ flagged words per paragraph +2. [ ] Inflated significance: proportional to evidence +3. [ ] No superficial -ing analysis +4. [ ] Natural copula usage: "is/are" used where appropriate +5. [ ] No banned structural patterns +6. [ ] Clean formatting: no artefacts (bold, em dash, colon, tables) +7. [ ] No meta-commentary +8. [ ] Varied sentence openers +9. [ ] Rhythm & paragraph length: no metronomic patterns or uniform blocks +10. [ ] Zero chatbot artefacts +11. [ ] Natural sentence flow: colons and em dashes not substituting for proper subordination/coordination +12. [ ] Claim-evidence alignment: every major claim has cited support + +**Result: PASS only if all 12 checks clear.** diff --git a/.agents/skills/oma-academic-writer/resources/hedging-guide.md b/.agents/skills/oma-academic-writer/resources/hedging-guide.md new file mode 100644 index 0000000..2b4305b --- /dev/null +++ b/.agents/skills/oma-academic-writer/resources/hedging-guide.md @@ -0,0 +1,130 @@ +# Hedging guide for academic English + +Academic writing requires calibrated certainty: neither overclaiming nor excessive caution. + +## Evidence-to-hedge mapping + +| Evidence Strength | Description | Hedge Level | Example Constructions | +|-------------------|-------------|-------------|----------------------| +| Definitive | Mathematical proof, logical necessity, definitional truth | None | "X **is** Y", "The data **confirm** that..." | +| Strong | Large-N replicated studies, meta-analyses, established consensus | Minimal | "The data **demonstrate** that...", "The evidence **establishes** that..." | +| Moderate | Single well-designed study, consistent preliminary findings | Moderate | "The findings **suggest** that...", "The results **indicate** that..." | +| Exploratory | Pilot study, limited sample, emerging trends | Strong | "This **may indicate** that...", "Preliminary evidence **points toward**..." | +| Interpretive | Author's own analysis without external validation | Attribution | "This pattern **appears to** reflect...", "One possible interpretation is that..." | +| Speculative | No direct evidence; inference from adjacent domains | Maximal | "It is **conceivable** that...", "**If** this trend continues, X **could** occur." | + +## Hedging devices + +### Modal verbs (ordered by certainty) + +| Certainty Level | Modals | +|----------------|--------| +| High | will, must, is (certain to) | +| Moderate | would, should, is likely to | +| Low | may, might, could, can | +| Speculative | would potentially, might conceivably | + +### Hedging verbs + +| Certainty Level | Verbs | +|----------------|-------| +| High | demonstrate, confirm, establish, verify, prove | +| Moderate | suggest, indicate, imply, point to, appear | +| Low | hint at, raise the possibility, seem to, tend to | + +### Hedging adverbs + +| Certainty Level | Adverbs | +|----------------|---------| +| High | clearly, definitively, conclusively, unequivocally | +| Moderate | generally, typically, largely, predominantly | +| Low | possibly, potentially, perhaps, arguably, presumably | +| Speculative | conceivably, hypothetically, tentatively | + +### Hedging nouns + +possibility, tendency, likelihood, indication, suggestion, assumption, interpretation, speculation + +### Hedging prepositional phrases + +according to, on the basis of, in light of, given the limitations of, with reference to, from the perspective of + +## Rules + +### 1. Match hedge to evidence + +Every claim in academic writing must carry the appropriate degree of certainty. + +**Strong evidence → assertive language:** +> "The regression analysis **demonstrates** a statistically significant correlation (p < .001) between match duration and surface type." + +**Moderate evidence → moderate hedge:** +> "The findings **suggest** that match duration **tends to** increase on clay surfaces." + +**Limited evidence → strong hedge:** +> "Preliminary data **may indicate** a relationship between surface type and match duration, although further investigation is warranted." + +### 2. Never over-hedge strong evidence + +If the data clearly support a claim, do not weaken it with unnecessary hedges. + +- Bad: "The data **might possibly seem to** suggest that the correlation **could potentially** exist." +- Good: "The data **demonstrate** a strong correlation." + +### 3. Never under-hedge weak evidence + +If the evidence is limited, do not present claims as established fact. + +- Bad: "Surface type **determines** match duration." (stated as fact from limited data) +- Good: "Surface type **appears to** influence match duration." + +### 4. Avoid double hedging + +Using two hedge devices where one suffices weakens the claim unnecessarily. + +- Bad: "It **might possibly** be the case that..." +- Good: "It **may** be the case that..." + +- Bad: "The results **seem to suggest** that..." +- Good: "The results **suggest** that..." or "The results **seem to indicate** that..." + +### 5. Use attribution hedging for interpretive claims + +When offering your own analysis (not reporting others' findings), attribute the interpretation explicitly. + +- "This pattern **appears to** reflect broader changes in playing strategy." +- "One interpretation of this trend is that..." +- "Based on this analysis, it is **reasonable to infer** that..." + +### 6. Avoid "I think" / "I believe" + +These are too informal for academic writing. Replace with: + +| Informal | Academic | +|----------|----------| +| I think | This analysis suggests | +| I believe | The evidence indicates | +| In my opinion | Based on the available data | +| I feel | It appears that | + +### 7. Never use "clearly/obviously/definitely" unless justified + +These intensifiers are appropriate only for: + +- Mathematical certainties: "The mean is **clearly** higher than the median." +- Logical necessities: "If A > B and B > C, then A is **definitively** greater than C." +- Universally accepted facts: appropriate in very limited contexts + +For all other claims, remove the intensifier and let the evidence speak. + +## Quick reference: hedge calibration checklist + +Before submitting any academic text: + +- [ ] Does every claim carry appropriate hedging for its evidence base? +- [ ] No double-hedging found? +- [ ] No "clearly/obviously" used without mathematical or logical justification? +- [ ] No "I think/believe" constructions? +- [ ] Interpretive claims attributed with "appears to", "one interpretation", etc.? +- [ ] Strong evidence not weakened by unnecessary hedges? +- [ ] Weak evidence not presented as established fact? diff --git a/.agents/skills/oma-academic-writer/resources/sentence-structure-reference.md b/.agents/skills/oma-academic-writer/resources/sentence-structure-reference.md new file mode 100644 index 0000000..74d781d --- /dev/null +++ b/.agents/skills/oma-academic-writer/resources/sentence-structure-reference.md @@ -0,0 +1,154 @@ +# Sentence structure reference for academic English + +## Four sentence types + +### 1. Simple sentence + +One independent clause in a subject-verb pattern. + +**Use for:** High-impact statements, clear assertions, topic sentences. + +**Examples:** + +- The Australian government introduced an official carbon tax on 1 July 2012. +- This dataset contains 120 years of match records. +- Performance declined sharply after the 2018 rule changes. + +**Target:** 20–30% of sentences per paragraph. + +### 2. Compound sentence + +Two independent clauses connected by a coordinating conjunction (FANBOYS: for, and, nor, but, or, yet, so). + +**Use for:** Balanced comparisons, contrasts, cause-effect pairs. + +**Examples:** + +- The Australian government introduced an official carbon tax on 1 July 2012, but this was met with opposition from the general public. +- Match duration increased by 15%, and spectator attendance declined in parallel. +- Nadal dominated the clay court, yet Djokovic maintained superiority on hard surfaces. + +**Target:** 15–25% of sentences per paragraph. + +### 3. Complex sentence + +One independent clause + one dependent clause (introduced by a subordinating conjunction: as, because, although, when, while, if, since, after, before, unless, until, whereas). + +**Use for:** Causal reasoning, conditional statements, temporal ordering (the backbone of academic analysis). + +**Examples:** + +- As the Australian government recognised the necessity to significantly reduce greenhouse gas emissions, it introduced an official carbon tax on 1 July 2012. +- Although the dataset spans 121 seasons, only matches after 1968 include detailed set-level data. +- Because five-set matches impose greater physical demands, average rally length decreases in the fourth and fifth sets. + +**Target:** 30–40% of sentences per paragraph (primary structure for academic prose). + +### 4. Compound-complex sentence + +Two or more independent clauses + one or more dependent clauses. + +**Use for:** Sophisticated synthesis, multi-factor analysis, nuanced arguments. + +**Examples:** + +- As the Australian government recognised the necessity to significantly reduce greenhouse gas emissions, it introduced an official carbon tax on 1 July 2012, but this was met with opposition from the general public. +- While set durations vary considerably between Grand Slam events, the median match length has increased by 12 minutes since 2000, and this trend correlates with advances in racquet technology. +- Because the 1905–1968 era lacked professional circuits, participation remained limited to amateur players; however, match records from this period still provide valuable longitudinal data. + +**Target:** 10–20% of sentences per paragraph (use sparingly for maximum impact). + +## Variation rules + +### Within a paragraph + +1. Never place 3+ sentences of the same type consecutively +2. Vary sentence length: + - Short: 8–15 words (for impact) + - Medium: 16–25 words (for flow) + - Long: 26–40 words (for depth) +3. Each paragraph should contain at least 3 of the 4 sentence types +4. Vary paragraph length (2–8 sentences); uniform blocks signal AI + +> For detailed burstiness detection, semicolon limits, and paragraph length variation rules, see `anti-ai-checklist.md` §9. + +### Sentence openers (vary these) + +| Opener Type | Example | +|-------------|---------| +| Subject-first | "The analysis reveals..." | +| Adverbial phrase | "Between 2015 and 2020, match duration..." | +| Prepositional phrase | "In the context of Grand Slam competition, ..." | +| Participial phrase | "Drawing on longitudinal data, this study..." | +| Dependent clause | "Although the sample size was limited, the trend..." | +| Transitional phrase | "By contrast, the women's draw exhibited..." | +| Inverted structure | "Particularly notable is the decline in..." | + +**Rule:** No 3+ consecutive sentences beginning with the same opener type. + +## Common errors to prevent + +### Sentence fragments + +A sentence missing subject, verb, or complete thought. + +| Error Type | Bad | Fixed | +|-----------|-----|-------| +| Missing subject | "Becoming extinct because of rising sea temperatures." | "Phytoplankton could become extinct because of rising sea temperatures." | +| Missing verb | "Significantly, one particular form of Western Australian finch." | "Significantly, one particular form of Western Australian finch has decreased in numbers." | +| Incomplete thought | "In a recent article about loss of habitat due to climate change." | "In a recent article about loss of habitat due to climate change, Australian animals were shown to be particularly vulnerable." | + +**Watch out for:** Sentences beginning with so, as, because, who, which, that, since these are often incomplete. + +### Run-on sentences + +Two independent clauses joined without proper punctuation or conjunction. + +| Bad | Fixed (conjunction) | Fixed (separate) | +|-----|-------|---------| +| "Poverty and famine are indicators of climate change these issues are not being addressed." | "Poverty and famine are indicators of climate change, **but** these issues are not being addressed." | "Poverty and famine are indicators of climate change. These issues are not being addressed." | + +### Lack of clear meaning + +Every sentence must be fully understandable when read in isolation. If a sentence requires mental gymnastics to parse, rewrite it more simply. + +## Paragraph cohesion model + +``` +[Topic Sentence — main point, often complex or compound-complex] +[Supporting Sentence 1 — evidence, often simple or complex] +[Supporting Sentence 2 — analysis, often complex] +[Supporting Sentence 3 — additional evidence or counter-argument, often compound] +[Concluding Sentence — link to broader argument or transition, often compound-complex] +``` + +### Bad example (monotonous structure) + +> Nursing education states that measures should be in place to avoid infection. Also, that infection rates tend to soar when hygiene standards decrease. Appropriate steps should be taken to decrease these risks. It is suggested that medical staff are educated to understand these risks. + +**Problems:** Same structure (simple/simple), same opener pattern ("X states", "Also, that"), same sentence length, no cohesion between sentences. + +### Good example (varied structure) + +> Nursing educators argue that strict measures should be implemented to avoid infection in medical institutions. There is also much evidence to demonstrate that infection rates rise dramatically when hygiene standards begin to fall. Therefore, it is argued that appropriate steps need to be in place to decrease and minimise these potential risks. Furthermore, aggressive steps should be taken to ensure that all staff maintain effective hygiene and infection control. + +**Improvements:** Varied openers, mixed simple/complex/compound structures, varied sentence lengths, logical flow from claim → evidence → argument → recommendation. + +## Quick reference: conjunction inventory + +### Coordinating (FANBOYS): for compound sentences + +for, and, nor, but, or, yet, so + +### Subordinating: for complex sentences + +**Cause/reason:** because, since, as, given that, owing to the fact that +**Contrast:** although, though, even though, whereas, while, whilst +**Condition:** if, unless, provided that, on condition that +**Time:** when, whenever, after, before, until, since, while, as soon as +**Purpose:** so that, in order that +**Result:** so ... that, such ... that + +### Transitional phrases (non-AI): for sentence openers + +By contrast, More specifically, In parallel, To this end, From a different perspective, On closer examination, Upon further analysis, In quantitative terms, At the aggregate level, Within this framework, Across all conditions diff --git a/.agents/skills/oma-architecture/SKILL.md b/.agents/skills/oma-architecture/SKILL.md index bd2a8a0..754850d 100644 --- a/.agents/skills/oma-architecture/SKILL.md +++ b/.agents/skills/oma-architecture/SKILL.md @@ -5,7 +5,17 @@ description: Architecture specialist for software/system design, module and serv # Architecture Agent - Software Architecture Specialist -## When to use +## Scheduling + +### Goal +Analyze, compare, and document software architecture decisions with explicit tradeoffs, risks, stakeholder concerns, and validation steps. + +### Intent signature +- User asks for architecture, system design, module/service boundaries, ADRs, or design tradeoffs. +- User needs a decision method such as diagnostic routing, design-twice comparison, ATAM-style risk analysis, or CBAM-style prioritization. +- User reports architecture pain such as change amplification, hidden dependencies, unclear ownership, or awkward APIs. + +### When to use - Choosing or reviewing system architecture - Defining module, service, or ownership boundaries - Comparing architectural options with explicit tradeoffs @@ -13,14 +23,116 @@ description: Architecture specialist for software/system design, module and serv - Prioritizing architecture investments or refactors - Writing architecture recommendations or ADRs -## When NOT to use +### When NOT to use - Visual design, design systems, branding, or landing pages -> use oma-design - Feature planning and task decomposition -> use oma-pm - Infrastructure provisioning or Terraform implementation -> use oma-tf-infra - Bug diagnosis and code fixes -> use oma-debug - Security/performance/accessibility review -> use oma-qa -## Core Rules +### Expected inputs +- Architecture question, pain point, or decision context +- Existing codebase, diagrams, docs, constraints, or stakeholder concerns +- Quality attributes such as scalability, reliability, security, operability, cost, and delivery speed +- Optional target artifact type such as recommendation, option comparison, or ADR + +### Expected outputs +- Architecture diagnosis, recommendation, comparison, prioritization, or ADR +- Assumptions, tradeoffs, risks, and validation steps +- Saved architecture artifacts under `.agents/results/architecture/` when producing durable outputs + +```yaml +outputs: + - name: architecture-artifact + description: ADR, comparison, or recommendation written to durable storage when the run is meant to persist + artifact: ".agents/results/architecture/*.md" + required: false +``` + +### Dependencies +- `resources/execution-protocol.md` for workflow +- `resources/methodology-selection.md` for method choice +- `resources/stakeholder-synthesis.md` when cross-cutting stakeholder consultation is justified +- `resources/output-templates.md` for final artifact shapes + +### Control-flow features +- Branches by request clarity, decision materiality, risk level, and need for stakeholder consultation +- May compare multiple options before recommending one +- Produces source-grounded docs rather than directly changing implementation + +## Structural Flow + +### Entry +1. Identify the architecture problem, decision, or pain signal. +2. Gather existing constraints, source evidence, and stakeholder context. +3. Select the lightest sufficient method. + +### Scenes +1. **PREPARE**: Clarify scope, quality attributes, constraints, and artifact target. +2. **ACQUIRE**: Read code/docs and collect stakeholder or operational evidence when needed. +3. **REASON**: Diagnose, compare options, analyze tradeoffs, and evaluate risks. +4. **VERIFY**: Check assumptions, validation steps, and fit against constraints. +5. **FINALIZE**: Produce recommendation, ADR, or architecture artifact. + +### Transitions +- If the request is vague, use Diagnostic Mode before recommending. +- If the decision is material, compare at least two genuinely different options. +- If risk/quality attributes dominate, use ATAM-style analysis. +- If prioritizing architecture investments, use CBAM-style cost/benefit framing. +- If the decision is final, format it as an ADR. + +### Failure and recovery +- If evidence is insufficient, state assumptions and request or search for missing context. +- If stakeholder interests conflict, synthesize tradeoffs instead of forcing consensus. +- If the task belongs to another domain, route to the relevant skill. + +### Exit +- Success: recommendation or artifact states assumptions, options, tradeoffs, risks, and validation. +- Partial success: unresolved assumptions or missing evidence are explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Classify architecture request | `SELECT` | Method selection summary | +| Read code/docs/context | `READ` | Source-grounded architecture evidence | +| Compare options | `COMPARE` | Design-twice or recommendation mode | +| Infer risks and tradeoffs | `INFER` | ATAM/CBAM-style analysis | +| Validate decision fit | `VALIDATE` | Checklist and validation steps | +| Write artifact | `WRITE` | ADR or architecture result | +| Notify outcome | `NOTIFY` | Final recommendation summary | + +### Tools and instruments +- Local file reading and search for codebase/docs +- Architecture method references and output templates +- Optional stakeholder-agent consultation only when cross-cutting enough to justify cost + +### Canonical workflow path +```bash +rg --files +rg "ADR|architecture|boundary|service|module|dependency|owner|interface" . +``` + +Then choose Diagnostic, Recommendation, Design-Twice, ATAM-style, CBAM-style, or ADR mode before writing the artifact. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `CODEBASE` | Architecture-relevant source files and docs | +| `LOCAL_FS` | `.agents/results/architecture/` artifacts | +| `MEMORY` | Assumptions, option matrix, tradeoff notes | + +### Preconditions +- The architecture concern or decision boundary is identifiable. +- Relevant context can be read or assumptions can be stated. + +### Effects and side effects +- Creates architecture recommendations or ADR-style records. +- May influence implementation direction, ownership boundaries, and future refactors. +- Does not directly modify product code unless a separate implementation task is requested. + +### Guardrails 1. Diagnose the architecture problem before selecting a method. 2. Use the lightest sufficient methodology for the current decision. 3. Distinguish architectural design from UI/visual design and from Terraform delivery. @@ -31,15 +143,7 @@ description: Architecture specialist for software/system design, module and serv 8. When a decision is material, compare at least two genuinely different options before recommending one. 9. Save architecture artifacts to `.agents/results/architecture/`. -## How to Execute -Follow `resources/execution-protocol.md` step by step. -See `resources/examples.md` for output examples. -Use `resources/methodology-selection.md` to select the right method. -Use `resources/stakeholder-synthesis.md` when stakeholder consultation is needed. -Use `resources/output-templates.md` to format the final artifact. -Before submitting, run `resources/checklist.md`. - -## Method Selection Summary +### Method Selection Summary - **Diagnostic Mode**: vague pain, unclear architecture symptom - **Recommendation Mode**: choose a direction for a concrete architecture decision - **Design-Twice Mode**: compare 2+ materially different designs before committing @@ -48,6 +152,12 @@ Before submitting, run `resources/checklist.md`. - **ADR Mode**: concise final decision record after analysis ## References +Follow `resources/execution-protocol.md` step by step. +See `resources/examples.md` for output examples. +Use `resources/methodology-selection.md` to select the right method. +Use `resources/stakeholder-synthesis.md` when stakeholder consultation is needed. +Use `resources/output-templates.md` to format the final artifact. +Before submitting, run `resources/checklist.md`. - Execution steps: `resources/execution-protocol.md` - Checklist: `resources/checklist.md` - Examples: `resources/examples.md` diff --git a/.agents/skills/oma-backend/SKILL.md b/.agents/skills/oma-backend/SKILL.md index e980300..6c32f3f 100644 --- a/.agents/skills/oma-backend/SKILL.md +++ b/.agents/skills/oma-backend/SKILL.md @@ -5,18 +5,126 @@ description: Backend specialist for APIs, databases, authentication with clean a # Backend Agent - API & Server Specialist -## When to use +## Scheduling + +### Goal +Implement or review backend APIs, authentication, database integration, server-side business logic, and migrations using the project's existing backend stack and clean architecture boundaries. + +### Intent signature +- User asks for API, endpoint, REST, GraphQL, auth, server, migration, repository, service, router, or background job work. +- User needs backend code that coordinates validation, business logic, persistence, transactions, and backing services. + +### When to use - Building REST APIs or GraphQL endpoints - Database design and migrations - Authentication and authorization - Server-side business logic - Background jobs and queues -## When NOT to use +### When NOT to use - Frontend UI -> use Frontend Agent - Mobile-specific code -> use Mobile Agent -## Core Principles +### Expected inputs +- Target feature, endpoint, migration, auth flow, or server behavior +- Existing backend stack files such as manifests, routes, services, models, and database config +- API contracts, schemas, validation rules, and persistence requirements +- Required verification commands or project conventions + +### Expected outputs +- Backend code changes in router, service, repository, model, migration, or test files +- Validated inputs, safe queries, transaction boundaries, and error handling +- Verification results from the execution checklist + +### Dependencies +- Project stack manifests and existing backend conventions +- `resources/execution-protocol.md`, `resources/checklist.md`, and `resources/orm-reference.md` +- Optional `stack/stack.yaml`, `stack/tech-stack.md`, snippets, and API templates +- Database, queue, cache, mail, auth, or external API resources configured through environment or secret managers + +### Control-flow features +- Branches by detected stack, ORM/query pattern, auth requirement, migration impact, and transaction scope +- Reads and writes codebase files +- May touch local database migrations or generated code +- Must not hardcode secrets or share unsafe ORM lifecycle objects across concurrent work + +## Structural Flow + +### Entry +1. Detect the backend stack from project files first. +2. Identify affected router, service, repository, model, migration, and test boundaries. +3. Load stack-specific references only when needed. + +### Scenes +1. **PREPARE**: Determine stack, architecture boundaries, and acceptance criteria. +2. **ACQUIRE**: Read existing routes, services, repositories, models, schemas, and config. +3. **ACT**: Implement backend changes with validation, business logic, persistence, and tests. +4. **VERIFY**: Run relevant lint, type, test, migration, and checklist commands. +5. **FINALIZE**: Report changed behavior, verification, and unresolved risks. + +### Transitions +- If stack files exist, follow them before generic guidance. +- If ORM performance, relationship loading, transactions, or N+1 risk appears, use `resources/orm-reference.md`. +- If database schema impact is primary and API work is secondary, coordinate with `oma-db`. +- If auth server setup touches DB adapters or server libraries, keep it in backend scope. + +### Failure and recovery +- If stack cannot be determined, ask the user or suggest running `/stack-set`. +- If verification fails, fix root cause before handoff. +- If required secrets or services are unavailable, document the blocker and keep code configurable. + +### Exit +- Success: backend change is implemented, tested, and aligned with local architecture. +- Partial success: blocker, missing dependency, or verification gap is explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Detect stack and conventions | `READ` | Manifests, stack files, existing code | +| Select implementation boundary | `SELECT` | Router/service/repository pattern | +| Validate inputs and schemas | `VALIDATE` | Stack validation library | +| Implement business logic | `WRITE` | Service layer code | +| Implement persistence | `WRITE` | Repository/model/migration code | +| Call external/backing services | `CALL_TOOL` | DB, queue, cache, auth, or API clients | +| Run verification | `CALL_TOOL` | Tests, typecheck, lint, migrations | +| Report result | `NOTIFY` | Final summary | + +### Tools and instruments +- Project language/framework toolchain +- ORM or database client +- Test, lint, typecheck, and migration commands +- Stack-specific templates and snippets when present + +### Canonical workflow path +```bash +rg --files +rg "route|router|service|repository|model|schema|migration" . +``` + +Then run the project's discovered verification commands, usually lint/typecheck/tests and migrations when schema changes are involved. Prefer `stack/stack.yaml` `verify:` commands when present. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `CODEBASE` | Backend source, tests, schemas, migrations | +| `LOCAL_FS` | Stack references and generated artifacts | +| `PROCESS` | Test, lint, typecheck, migration commands | +| `CREDENTIALS` | Environment-managed DB URLs, API keys, secrets | +| `NETWORK` | External APIs or backing services when required | + +### Preconditions +- Target behavior and affected backend boundary are identifiable. +- Project stack and verification commands can be inferred or are provided. +- Required credentials remain outside source code. + +### Effects and side effects +- Mutates backend source files, tests, and possibly migrations. +- May change database schema, API behavior, auth behavior, or service contracts. +- May require generated clients or migration artifacts. + +### Guardrails 1. **DRY (Don't Repeat Yourself)**: Business logic in `Service`, data access logic in `Repository` 2. **SOLID**: @@ -24,7 +132,7 @@ description: Backend specialist for APIs, databases, authentication with clean a - **Dependency Inversion**: Use your framework's DI mechanism 3. **KISS**: Keep it simple and clear -## Architecture Pattern +### Architecture Pattern ``` Router (HTTP) → Service (Business Logic) → Repository (Data Access) → Models @@ -42,7 +150,7 @@ Router (HTTP) → Service (Business Logic) → Repository (Data Access) → Mode - Receive HTTP requests, input validation, call Service, return response - No business logic, inject Service via DI -## Core Rules +### Core Rules 1. **Clean architecture**: router → service → repository → models 2. **No business logic in route handlers** @@ -54,37 +162,31 @@ Router (HTTP) → Service (Business Logic) → Repository (Data Access) → Mode 8. **Explicit ORM loading strategy**: do not rely on default relation loading when query shape matters 9. **Explicit transaction boundaries**: group one business operation into one request/service-scoped unit of work 10. **Safe ORM lifecycle**: do not share mutable ORM session/entity manager/client objects across concurrent work unless the ORM explicitly supports it -11. **Config from environment**: DB URLs, API keys, secrets, and feature flags come from env vars or secret managers — never hardcode in source -12. **Stateless services**: no in-memory session or user state between requests — use external stores (DB, Redis, cache) for shared state -13. **Backing services as resources**: DB, queue, cache, mail are swappable attached resources connected via config — Repository layer must not assume a specific instance +11. **Config from environment**: DB URLs, API keys, secrets, and feature flags come from env vars or secret managers; never hardcode in source +12. **Stateless services**: no in-memory session or user state between requests; use external stores (DB, Redis, cache) for shared state +13. **Backing services as resources**: DB, queue, cache, mail are swappable attached resources connected via config; Repository layer must not assume a specific instance -## Stack Detection (Priority Order) +### Stack Detection -1. **Project files first** — Read existing code, package manifests (pyproject.toml, package.json, Cargo.toml, go.mod, pom.xml, etc.) to determine the tech stack -2. **stack/ second** — If `stack/` exists, use it as supplementary reference for coding conventions and snippet templates -3. **Neither exists** — Ask the user or suggest running `/stack-set` +1. **Project files first**: Read existing code, package manifests (pyproject.toml, package.json, Cargo.toml, go.mod, pom.xml, etc.) to determine the tech stack +2. **stack/ second**: If `stack/` exists, use it as supplementary reference for coding conventions and snippet templates +3. **Neither exists**: Ask the user or suggest running `/stack-set` -## Stack-Specific Reference +### Stack-Specific Reference -- Tech stack & libraries: `stack/tech-stack.md` +- **Stack manifest (SSOT)**: `stack/stack.yaml`: structured declaration (`language`, `framework`, `orm`) and `verify:` contract consumed by `oma verify backend`. Schema: `variants/stack.schema.json`. +- Tech stack narrative: `stack/tech-stack.md`: human-readable reference only; `stack.yaml` wins on conflict. - Code snippets (copy-paste ready): `stack/snippets.md` - API template: `stack/api-template.*` -- Stack config: `stack/stack.yaml` -## How to Execute +## References Follow `resources/execution-protocol.md` step by step. See `resources/examples.md` for input/output examples. Use `resources/orm-reference.md` when the task involves ORM query performance, relationship loading, transactions, session/client lifecycle, or N+1 analysis. Before submitting, run `resources/checklist.md`. - -## Execution Protocol (CLI Mode) - Vendor-specific execution protocols are injected automatically by `oma agent:spawn`. Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - -## References - - Execution steps: `resources/execution-protocol.md` - Code examples: `resources/examples.md` - Checklist: `resources/checklist.md` @@ -95,3 +197,4 @@ Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - Clarification: `../_shared/core/clarification-protocol.md` - Context budget: `../_shared/core/context-budget.md` - Lessons learned: `../_shared/core/lessons-learned.md` +- Observability handoff: `../oma-observability/SKILL.md` §Integrations — propagators/baggage, span conventions, log correlation, PII redaction diff --git a/.agents/skills/oma-backend/resources/checklist.md b/.agents/skills/oma-backend/resources/checklist.md index 17b81fb..aa4c9fb 100644 --- a/.agents/skills/oma-backend/resources/checklist.md +++ b/.agents/skills/oma-backend/resources/checklist.md @@ -39,7 +39,7 @@ Run through every item before submitting your work. - [ ] Type annotations on all function signatures ## Cloud Readiness -- [ ] No hardcoded config values (DB URLs, API keys, ports) — all from env vars +- [ ] No hardcoded config values (DB URLs, API keys, ports); all from env vars - [ ] No in-process state between requests (sessions, caches, counters) -- [ ] Logs written to stdout/stderr, not file — structured format (JSON) preferred +- [ ] Logs written to stdout/stderr, not file; structured format (JSON) preferred - [ ] Graceful shutdown handled for background jobs and open connections diff --git a/.agents/skills/oma-backend/resources/error-playbook.md b/.agents/skills/oma-backend/resources/error-playbook.md index a968ea1..ef9fa46 100644 --- a/.agents/skills/oma-backend/resources/error-playbook.md +++ b/.agents/skills/oma-backend/resources/error-playbook.md @@ -9,9 +9,9 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: Module/package not found errors -1. Check the import path — typo? wrong package name? +1. Check the import path: typo? wrong package name? 2. Verify the dependency exists in your package manifest -3. If missing: note it in your result as "requires install the missing dependency" — do NOT install yourself +3. If missing: note it in your result as "requires install the missing dependency"; do NOT install yourself 4. If it's a local module: check the directory structure with `get_symbols_overview` 5. If the path changed: use `search_for_pattern("class ClassName")` to find the new location @@ -21,7 +21,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: test runner returns FAILED, assertion errors -1. Read the full error output — which test, which assertion, expected vs actual +1. Read the full error output: which test, which assertion, expected vs actual 2. `find_symbol("test_function_name")` to read the test code 3. Determine: is the test wrong or is the implementation wrong? - Test expects old behavior → update test @@ -36,7 +36,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: Migration command fails, `IntegrityError`, duplicate column -1. Read the error — is it a conflict with existing migration? +1. Read the error; is it a conflict with existing migration? 2. Check current DB state: Check current migration state 3. If migration conflicts: Rollback one migration step then fix migration script 4. If schema mismatch: compare model with actual DB schema @@ -72,7 +72,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: `429`, `RESOURCE_EXHAUSTED`, `rate limit exceeded` -1. **Stop immediately** — do not make additional API calls +1. **Stop immediately**; do not make additional API calls 2. Save current work to `progress-{agent-id}[-{sessionId}].md` 3. Record Status: `quota_exceeded` in `result-{agent-id}[-{sessionId}].md` 4. Specify remaining tasks so orchestrator can retry later @@ -95,4 +95,4 @@ Do NOT stop or ask for help until you have exhausted the playbook. - **After 3 failures**: If same approach fails 3 times, must try a different method - **Blocked**: If no progress after 5 turns, save current state and record `Status: blocked` in result -- **Out of scope**: If you find issues in another agent's domain, only record in result — do not modify directly +- **Out of scope**: If you find issues in another agent's domain, only record in result; do not modify directly diff --git a/.agents/skills/oma-backend/resources/execution-protocol.md b/.agents/skills/oma-backend/resources/execution-protocol.md index ac01a3f..c338b64 100644 --- a/.agents/skills/oma-backend/resources/execution-protocol.md +++ b/.agents/skills/oma-backend/resources/execution-protocol.md @@ -1,15 +1,15 @@ # Backend Agent - Execution Protocol ## Step 0: Prepare -1. **Assess difficulty** — see `../../_shared/core/difficulty-guide.md` +1. **Assess difficulty**: see `../../_shared/core/difficulty-guide.md` - **Simple**: Skip to Step 3 | **Medium**: All 4 steps | **Complex**: All steps + checkpoints -2. **Check lessons** — read your domain section in `../../_shared/core/lessons-learned.md` -3. **Clarify requirements** — follow `../../_shared/core/clarification-protocol.md` +2. **Check lessons**: read your domain section in `../../_shared/core/lessons-learned.md` +3. **Clarify requirements**: follow `../../_shared/core/clarification-protocol.md` - Check **Uncertainty Triggers**: business logic, security/auth, existing code conflicts? - Determine level: LOW → proceed | MEDIUM → present options | HIGH → ask immediately -4. **Budget context** — follow `../../_shared/core/context-budget.md` (read symbols, not whole files) +4. **Budget context**: follow `../../_shared/core/context-budget.md` (read symbols, not whole files) -**⚠️ Intelligent Escalation**: When uncertain, escalate early. Don't blindly proceed. +**Intelligent Escalation**: When uncertain, escalate early. Don't blindly proceed. Follow these steps in order (adjust depth by difficulty). diff --git a/.agents/skills/oma-brainstorm/SKILL.md b/.agents/skills/oma-brainstorm/SKILL.md index dbdf38f..c1f81bb 100644 --- a/.agents/skills/oma-brainstorm/SKILL.md +++ b/.agents/skills/oma-brainstorm/SKILL.md @@ -5,20 +5,116 @@ description: Design-first ideation that explores user intent, constraints, and a # Brainstorm - Design-First Ideation -## When to use +## Scheduling + +### Goal +Explore user intent, constraints, and alternative approaches before planning or implementation, then preserve an approved design for downstream planning. + +### Intent signature +- User says they have an idea, want to brainstorm, compare approaches, explore concepts, or design before planning. +- Request is ambiguous enough that implementation or task planning would be premature. + +### When to use - Exploring a new feature idea before planning - Understanding user intent and constraints before committing to an approach - Comparing multiple design approaches with trade-offs - When the user says "I have an idea" or "let's design something" - Before invoking `/plan` for complex or ambiguous requests -## When NOT to use +### When NOT to use - Requirements are already clear and well-defined -> use pm-agent directly - Implementing actual code -> delegate to specialized agents - Performing code reviews -> use QA Agent - Debugging existing issues -> use debug-agent -## Core Rules +### Expected inputs +- Early idea, ambiguous goal, product concept, design question, or set of constraints +- Existing project context when the idea must fit a codebase or product direction +- User preferences and approval gates + +### Expected outputs +- Clarified intent and constraints +- Two or three approaches with tradeoffs and a recommended option +- Section-by-section approved design document +- Saved design artifact before handoff to planning + +### Dependencies +- Shared context loading, reasoning templates, clarification protocol, quality principles, and skill routing +- Downstream PM workflow for task decomposition after design approval + +### Control-flow features +- Branches by ambiguity, user answers, approach comparison, and approval gates +- Asks one question at a time +- Stops before implementation or task planning + +## Structural Flow + +### Entry +1. Confirm that the request is exploratory rather than ready for implementation. +2. Load enough project context to understand constraints. +3. Start with intent and constraints, not solutions. + +### Scenes +1. **PREPARE**: Explore context and frame the design question. +2. **ACQUIRE**: Ask clarifying questions one at a time. +3. **REASON**: Generate two or three approaches with tradeoffs. +4. **VERIFY**: Get user approval section by section. +5. **FINALIZE**: Save design and transition to planning when appropriate. + +### Transitions +- If requirements become clear and implementation-ready, transition to PM planning. +- If user rejects an approach, revise before moving to detailed design. +- If implementation pressure appears early, defer it until design approval. + +### Failure and recovery +- If the user cannot answer a question, propose assumptions and ask for confirmation. +- If scope expands, split the design into smaller sections. +- If alternatives collapse into one option, identify the real constraint causing that. + +### Exit +- Success: approved design exists and is ready for planning. +- Partial success: open questions and assumptions are explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Read context and idea | `READ` | User prompt and project context | +| Ask targeted questions | `REQUEST` | Clarification phase | +| Compare approaches | `COMPARE` | Tradeoff matrix | +| Infer recommendation | `INFER` | Recommended option | +| Validate approval | `VALIDATE` | Section-by-section confirmation | +| Write design artifact | `WRITE` | `docs/plans/designs/` and memory | +| Transition to plan | `NOTIFY` | Handoff summary | + +### Tools and instruments +- Context loading, reasoning templates, clarification protocol +- Project memory and `docs/plans/designs/` for persisted designs + +### Canonical workflow path +```text +1. Ask one clarifying question at a time. +2. Present 2-3 approaches with tradeoffs and a recommended option. +3. Save the approved design to `docs/plans/designs/` before handing off to planning. +``` + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `MEMORY` | User intent, assumptions, decisions | +| `CODEBASE` | Existing project context when relevant | +| `LOCAL_FS` | Approved design artifacts | + +### Preconditions +- The user is still exploring or the request is ambiguous. +- The agent can ask clarifying questions before implementation. + +### Effects and side effects +- Produces design decisions and persisted design docs. +- Influences downstream planning but does not implement code. + +### Guardrails 1. **No implementation or planning before design approval** - brainstorm produces a design document, not code or task plans 2. **One question at a time** - ask clarifying questions sequentially, not in batches 3. **Always propose 2-3 approaches** - include a recommended option with trade-off analysis @@ -26,28 +122,25 @@ description: Design-first ideation that explores user intent, constraints, and a 5. **YAGNI** - do not over-engineer; design only what is needed for the stated goal 6. **Save design, then transition** - persist the approved design document before handing off to `/plan` -## How to Execute +### Execution Phases Follow the brainstorm workflow step by step: 1. **Phase 1 - Context**: Explore the existing codebase and understand the project landscape 2. **Phase 2 - Questions**: Ask clarifying questions one at a time to understand intent and constraints 3. **Phase 3 - Approaches**: Propose 2-3 approaches with a recommended option and trade-off matrix 4. **Phase 4 - Design**: Present the detailed design section by section, getting user approval at each step -5. **Phase 5 - Documentation**: Save the approved design to `docs/plans/` and project memory +5. **Phase 5 - Documentation**: Save the approved design to `docs/plans/designs/` and project memory 6. **Phase 6 - Transition**: Hand off to `/plan` for task decomposition -## Common Pitfalls +### Common Pitfalls - **Jumping to solutions**: Asking "how" before fully understanding "what" and "why" - **Too many questions at once**: Overwhelming the user with a wall of questions - **Single approach bias**: Presenting only one option without alternatives - **Over-engineering**: Designing for hypothetical future requirements instead of stated needs - **Skipping confirmation**: Moving forward without explicit user approval on design decisions -## Execution Protocol (CLI Mode) - +## References Vendor-specific execution protocols are injected automatically by `oma agent:spawn`. Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - -## References - Context loading: `../_shared/core/context-loading.md` - Reasoning templates: `../_shared/core/reasoning-templates.md` - Clarification protocol: `../_shared/core/clarification-protocol.md` diff --git a/.agents/skills/oma-coordination/SKILL.md b/.agents/skills/oma-coordination/SKILL.md index 5f00d6f..4510c2c 100644 --- a/.agents/skills/oma-coordination/SKILL.md +++ b/.agents/skills/oma-coordination/SKILL.md @@ -5,19 +5,116 @@ description: Guide for coordinating PM, Frontend, Backend, Mobile, and QA agents # Multi-Agent Workflow Guide -## When to use +## Scheduling + +### Goal +Guide manual multi-agent coordination for complex work that spans PM, frontend, backend, mobile, and QA responsibilities. + +### Intent signature +- User wants step-by-step coordination, manual agent spawning, or multi-domain work planning without full automation. +- Task spans multiple specialist agents and requires contract alignment. + +### When to use - Complex feature spanning multiple domains (full-stack, mobile) - Coordination needed between frontend, backend, mobile, and QA - User wants step-by-step guidance for multi-agent coordination -## When NOT to use +### When NOT to use - Simple single-domain task -> use the specific agent directly - User wants automated execution -> use orchestrator - Quick bug fixes or minor changes -## Core Rules +### Expected inputs +- Complex feature or project goal +- Required domains and priority tiers +- Workspace/session constraints and API/data contract needs + +### Expected outputs +- Manual coordination sequence +- PM task decomposition, agent spawn order, monitoring guidance, and QA review step +- API/data contract alignment checkpoints + +### Dependencies +- PM, frontend, backend, mobile, QA, and orchestrator skills +- `resources/examples.md` +- CLI `oma agent:spawn` and progress/result memory conventions + +### Control-flow features +- Branches by task complexity, priority tiers, dependency ordering, and whether automation is desired +- Spawns independent same-priority tasks in parallel when appropriate +- Monitors progress files and contract alignment + +## Structural Flow + +### Entry +1. Confirm the task is complex enough for multi-agent coordination. +2. Start with PM task decomposition. +3. Identify priority tiers and shared contracts. + +### Scenes +1. **PREPARE**: Define session, domains, and task decomposition needs. +2. **ACT**: Spawn agents by priority with separate workspaces. +3. **VERIFY**: Monitor progress and API/data contract alignment. +4. **FINALIZE**: Run QA review and coordinate remediation. + +### Transitions +- If task is simple, route to one specialist. +- If user wants automated execution, use orchestrator. +- If QA finds CRITICAL issues, re-spawn responsible agents. + +### Failure and recovery +- If contracts diverge, pause downstream frontend/mobile work until backend/API contract is reconciled. +- If agent workspaces conflict, split ownership boundaries. +- If progress stalls, inspect progress files and reissue focused instructions. + +### Exit +- Success: specialist outputs are coordinated and QA-reviewed. +- Partial success: blocked agents, contract conflicts, or QA failures are explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Read request and domains | `READ` | User prompt and project context | +| Select agent plan | `SELECT` | PM decomposition and priority tiers | +| Spawn agents | `CALL_TOOL` | `oma agent:spawn` | +| Monitor progress | `READ` | `progress-{agent}.md` | +| Validate contracts | `VALIDATE` | API/data model alignment | +| Notify coordination status | `NOTIFY` | Final coordination summary | + +### Tools and instruments +- `oma agent:spawn`, PM/frontend/backend/mobile/QA agents +- Memory/progress/result files +- Serena MCP for exploration and modification when used by specialists + +### Canonical command path +```bash +oma agent:spawn pm "" -w ./pm +oma agent:spawn backend "" -w ./backend & +oma agent:spawn frontend "" -w ./frontend & +wait +``` + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `LOCAL_FS` | Progress/result files and workspaces | +| `PROCESS` | Agent spawn commands | +| `MEMORY` | Session state and task board | +| `CODEBASE` | Shared contracts and implementation areas | + +### Preconditions +- Task requires multiple domains. +- PM decomposition can identify independent priority tiers. + +### Effects and side effects +- Spawns or guides multiple agents. +- Coordinates workspace ownership and QA feedback. + +### Guardrails 1. Always start with PM Agent for task decomposition 2. Spawn independent tasks in parallel (same priority tier) @@ -25,9 +122,9 @@ description: Guide for coordinating PM, Frontend, Backend, Mobile, and QA agents 4. QA review is always the final step 5. Assign separate workspaces to avoid file conflicts 6. Always use Serena MCP tools as the primary method for code exploration and modification -7. Never skip steps in the workflow — follow each step sequentially without omission +7. Never skip steps in the workflow; follow each step sequentially without omission -## Workflow +### Workflow ### Step 1: Plan with PM Agent @@ -58,7 +155,7 @@ wait Spawn QA Agent last to review all deliverables. Address CRITICAL issues by re-spawning agents. -## Automated Alternative +### Automated Alternative For fully automated execution without manual spawning, use the **orchestrator** skill instead. diff --git a/.agents/skills/oma-db/SKILL.md b/.agents/skills/oma-db/SKILL.md index 22227a0..e2102cb 100644 --- a/.agents/skills/oma-db/SKILL.md +++ b/.agents/skills/oma-db/SKILL.md @@ -5,7 +5,16 @@ description: Database specialist for SQL, NoSQL, and vector database modeling, s # DB Agent - Data Modeling & Database Architecture Specialist -## When to use +## Scheduling + +### Goal +Design, review, optimize, and document SQL, NoSQL, vector, and retrieval-oriented data systems with explicit schema layers, integrity rules, transaction behavior, capacity assumptions, and audit-aware tradeoffs. + +### Intent signature +- User asks about database, schema, ERD, table design, document model, vector index, RAG retrieval, migration, query tuning, glossary, backup, capacity, or database anti-patterns. +- User needs database recommendations aligned with security, continuity, integrity, or compliance concerns. + +### When to use - Relational database modeling, ERD, and schema design - NoSQL document, key-value, wide-column, or graph data modeling - Vector database and retrieval architecture design for semantic search and RAG @@ -17,12 +26,107 @@ description: Database specialist for SQL, NoSQL, and vector database modeling, s - Database anti-pattern review and remediation guidance - ISO 27001, ISO 27002, and ISO 22301-aware database design recommendations -## When NOT to use +### When NOT to use - API-only implementation without schema impact -> use Backend Agent - Infra provisioning only -> use TF Infra Agent - Final quality/security audit -> use QA Agent -## Core Rules +### Expected inputs +- Business entities, events, access patterns, volume, latency, retention, and recovery targets +- Existing schema, queries, migrations, indexes, data standards, or retrieval pipeline context +- Consistency, transaction, backup, audit, and compliance constraints +- Optional target deliverable such as ERD, migration plan, glossary, or capacity estimate + +### Expected outputs +- External, conceptual, and internal schema documentation +- Data standards, glossary, capacity estimate, indexing/partitioning plan, and backup/recovery strategy +- Integrity, transaction, isolation, and concurrency recommendations +- Vector/RAG-specific embedding, chunking, filtering, reranking, and re-index plans when relevant + +### Dependencies +- Existing database schemas, migration files, query logs, workload descriptions, and application access paths +- `resources/document-templates.md`, `resources/anti-patterns.md`, `resources/vector-db.md`, and `resources/iso-controls.md` +- SQL/NoSQL/vector database tools or project-specific migration toolchains when implementation is requested + +### Control-flow features +- Branches by workload type, database model, transaction criticality, scale, retrieval needs, and compliance posture +- May read schemas and write documentation, migrations, indexes, or query changes +- Treats vector DBs as retrieval infrastructure, not canonical source-of-truth storage + +## Structural Flow + +### Entry +1. Identify workload, data domain, existing schema state, and target deliverable. +2. Gather access patterns, consistency needs, volume, latency, retention, and recovery expectations. +3. Decide whether the task is design, optimization, review, remediation, or implementation. + +### Scenes +1. **PREPARE**: Classify workload and constraints. +2. **ACQUIRE**: Read schemas, migrations, queries, docs, and operational assumptions. +3. **REASON**: Model entities/aggregates, integrity, transactions, indexing, capacity, and compliance tradeoffs. +4. **ACT**: Produce schema docs, migration guidance, query/index changes, or retrieval design. +5. **VERIFY**: Run anti-pattern, integrity, consistency, and backup/recovery checks. +6. **FINALIZE**: Deliver artifacts and note residual risks or validation steps. + +### Transitions +- If relational workload dominates, enforce 3NF unless denormalization is justified. +- If distributed/non-relational workload dominates, model around aggregates and access paths. +- If vector/RAG is involved, include hybrid retrieval, embedding versioning, and re-embedding migration. +- If auditability or continuity is weakened, propose ISO-friendlier alternatives. + +### Failure and recovery +- If workload or access patterns are missing, state assumptions and ask for representative queries or flows. +- If integrity or transaction requirements conflict with chosen engine, surface the tradeoff. +- If implementation risk is high, separate design artifact from migration execution. + +### Exit +- Success: deliverables state model, constraints, integrity, transactions, capacity, and validation. +- Partial success: missing workload evidence or unresolved tradeoffs are explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Classify workload and model | `SELECT` | SQL, NoSQL, vector, cache, search, mixed | +| Read schema/query evidence | `READ` | Migrations, ERDs, query patterns | +| Compare design alternatives | `COMPARE` | Engine/model/index tradeoffs | +| Infer integrity and capacity risks | `INFER` | Constraints, transactions, growth assumptions | +| Validate anti-patterns | `VALIDATE` | Checklist and anti-pattern guide | +| Write schema docs or changes | `WRITE` | Deliverables, migrations, query/index changes | +| Report recommendation | `NOTIFY` | Final database guidance | + +### Tools and instruments +- Project DB schemas, migrations, query tools, and migration commands +- Document templates, anti-pattern guide, vector DB guide, and ISO control guide +- Optional spreadsheet or diagram artifacts when capacity or ERD output is requested + +### Canonical workflow path +```bash +rg --files -g '*.sql' -g '*prisma*' -g '*schema*' -g '*migration*' +rg "CREATE TABLE|model |index|foreign key|transaction|embedding|vector" . +``` + +Then run the project's migration, query-plan, or retrieval-quality commands only after identifying the database engine and migration tool. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `CODEBASE` | Schema, migration, query, ORM, and retrieval files | +| `LOCAL_FS` | Database design artifacts and result documents | +| `PROCESS` | Migration, query, lint, or validation commands | +| `USER_DATA` | Domain data definitions, retention rules, and sample access patterns | + +### Preconditions +- Target database concern and scope are identifiable. +- Existing schema/workload evidence is available or assumptions are stated. + +### Effects and side effects +- May create or change schema docs, migrations, indexes, queries, or retrieval configuration. +- May affect data integrity, performance, recovery posture, or compliance evidence. +- Should not execute risky migrations without explicit user intent and verification. + +### Guardrails 1. Choose model first, engine second: workload, access pattern, consistency, and scale drive DB selection. 2. For relational workloads, enforce at least **3NF** by default. Break 3NF only with explicit performance justification. 3. For distributed/non-relational workloads, model around aggregates and access paths; document **BASE** and consistency tradeoffs. @@ -39,7 +143,7 @@ description: Database specialist for SQL, NoSQL, and vector database modeling, s 14. Embeddings are schema-like assets: version model, dimension, chunking, and preprocessing, and plan re-embedding migrations explicitly. 15. Retrieval quality is won at chunking, filtering, reranking, and observability, not only at the vector index layer. -## Default Workflow +### Default Workflow 1. **Explore** - Identify business entities, events, access patterns, volume, latency, retention, and recovery targets - Classify workload: OLTP, analytics, eventing, cache, search, mixed @@ -54,7 +158,7 @@ description: Database specialist for SQL, NoSQL, and vector database modeling, s - For vector systems, tune ANN, chunking, filtering, reranking, and observability as one pipeline - Run anti-pattern review and update glossary and capacity estimation with every structural change -## Required Deliverables +### Required Deliverables - External schema summary by user/view/consumer - Conceptual schema with core entities or aggregates and relationships - Internal schema with physical storage, indexes, partitioning, and access paths @@ -64,7 +168,7 @@ description: Database specialist for SQL, NoSQL, and vector database modeling, s - Backup and recovery strategy including full + incremental backup cadence - For vector/RAG systems: embedding version policy, chunking policy, hybrid retrieval strategy, and re-index / re-embedding plan -## How to Execute +## References Follow `resources/execution-protocol.md` step by step. See `resources/examples.md` for input/output examples. Use `resources/document-templates.md` when you need concrete deliverable structure. @@ -72,13 +176,8 @@ Use `resources/anti-patterns.md` when reviewing or remediating logical, physical Use `resources/vector-db.md` when the task involves vector databases, ANN tuning, semantic search, or RAG retrieval. Use `resources/iso-controls.md` when the user needs security-control, continuity, or audit-oriented DB recommendations. Before submitting, run `resources/checklist.md`. - -## Execution Protocol (CLI Mode) - Vendor-specific execution protocols are injected automatically by `oh-my-agent agent:spawn`. Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - -## References - Execution steps: `resources/execution-protocol.md` - Self-check: `resources/checklist.md` - Examples: `resources/examples.md` @@ -92,3 +191,4 @@ Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - Clarification: `../_shared/core/clarification-protocol.md` - Context budget: `../_shared/core/context-budget.md` - Lessons learned: `../_shared/core/lessons-learned.md` +- Observability handoff: `../oma-observability/SKILL.md` §Integrations — DB span conventions (N+1, lock-wait, pool), cardinality budgets diff --git a/.agents/skills/oma-db/resources/execution-protocol.md b/.agents/skills/oma-db/resources/execution-protocol.md index dc06c09..aad89df 100644 --- a/.agents/skills/oma-db/resources/execution-protocol.md +++ b/.agents/skills/oma-db/resources/execution-protocol.md @@ -1,14 +1,14 @@ # DB Agent - Execution Protocol ## Step 0: Prepare -1. **Assess difficulty** — see `../../_shared/core/difficulty-guide.md` +1. **Assess difficulty**: see `../../_shared/core/difficulty-guide.md` - **Simple**: small schema adjustment or index review - **Medium**: new bounded context, migration, or backup/capacity update - **Complex**: engine selection, major redesign, multi-tenant or high-scale workload 2. **Clarify workload** - Functional flows, critical queries, write/read ratio, peak TPS, retention, RPO, RTO - Compliance or audit constraints, PII, multi-region, reporting needs -3. **Budget context** — follow `../../_shared/core/context-budget.md` +3. **Budget context**: follow `../../_shared/core/context-budget.md` 4. **If vector search is involved**, read `resources/vector-db.md` 5. **If security, audit, backup, or resilience requirements are central**, read `resources/iso-controls.md` diff --git a/.agents/skills/oma-debug/SKILL.md b/.agents/skills/oma-debug/SKILL.md index c6e9525..19edbfe 100644 --- a/.agents/skills/oma-debug/SKILL.md +++ b/.agents/skills/oma-debug/SKILL.md @@ -5,18 +5,120 @@ description: Bug diagnosis and fixing specialist - analyzes errors, identifies r # Debug Agent - Bug Fixing Specialist -## When to use +## Scheduling + +### Goal +Reproduce, diagnose, minimally fix, and regression-test bugs while preserving scope discipline and documenting root cause. + +### Intent signature +- User reports a bug, crash, traceback, exception, error message, performance issue, intermittent failure, or regression. +- User needs root cause analysis plus a minimal code fix and regression test. + +### When to use - User reports a bug with error messages - Something is broken and needs fixing - Performance issues or slowdowns - Intermittent failures or race conditions - Regression bugs -## When NOT to use +### When NOT to use - Building new features -> use Frontend/Backend/Mobile agents - General code review -> use QA Agent -## Core Rules +### Expected inputs +- Error message, failing behavior, reproduction steps, logs, test failure, or affected code path +- Existing tests, stack traces, environment notes, and suspected regression boundary + +### Expected outputs +- Root cause explanation +- Minimal fix scoped to the failing behavior +- Regression test or explicit reason it cannot be added +- Bug documentation under `.agents/results/bugs/` when appropriate + +### Dependencies +- `resources/execution-protocol.md`, examples, checklist, common patterns, and debugging checklist +- Local codebase search and symbol/reference tools +- Project test, lint, typecheck, and runtime commands + +### Control-flow features +- Branches by reproduction success, error class, suspected layer, and verification outcome +- Reads logs/code/tests and writes code/tests/docs +- Must search for similar patterns after fixing + +## Structural Flow + +### Entry +1. Capture the reported symptom and suspected scope. +2. Reproduce or establish the closest reliable failing signal. +3. Identify affected files, tests, and related patterns. + +### Scenes +1. **PREPARE**: Gather symptoms, logs, reproduction path, and verification command. +2. **ACQUIRE**: Read failing code, tests, references, and similar patterns. +3. **REASON**: Isolate root cause and reject unsupported hypotheses. +4. **ACT**: Apply minimal fix and regression test. +5. **VERIFY**: Re-run failing and related checks. +6. **FINALIZE**: Document root cause, fix, test, and residual risk. + +### Transitions +- If reproduction fails, use logs/tests to establish a weaker but explicit diagnostic signal. +- If the first fix fails verification, return to root-cause analysis. +- If similar patterns exist, inspect and patch only affected cases. +- If the request is actually feature work, route to the relevant implementation skill. + +### Failure and recovery +- If environment is missing, document the blocker and provide the closest static diagnosis. +- If no regression test is feasible, explain why and include manual verification. +- If fix scope grows, stop and call out the broader design issue. + +### Exit +- Success: bug is fixed, regression coverage exists, and checks pass. +- Partial success: root cause or verification blocker is explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Reproduce failure | `CALL_TOOL` | Test/runtime/log command | +| Search affected code | `READ` | Code, tests, symbols, references | +| Compare similar patterns | `COMPARE` | Pattern search | +| Infer root cause | `INFER` | Diagnostic reasoning | +| Write minimal fix | `WRITE` | Code patch | +| Write regression test | `WRITE` | Test patch | +| Verify behavior | `VALIDATE` | Tests/checks | +| Report result | `NOTIFY` | Root-cause summary | + +### Tools and instruments +- Project test, lint, typecheck, runtime, and logging commands +- Serena MCP symbol/reference/pattern search when available +- Debugging checklist and bug report template + +### Canonical workflow path +```bash +rg "" +rg --files +``` + +Then run the smallest reproduction command first, add a regression test, and re-run the failing check plus related tests. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `CODEBASE` | Failing source, tests, and related patterns | +| `LOCAL_FS` | Bug reports and result artifacts | +| `PROCESS` | Reproduction and verification commands | +| `MEMORY` | Hypotheses, root-cause notes, verification evidence | + +### Preconditions +- A bug signal, symptom, or failing behavior is available. +- Relevant code and verification path can be inspected or the blocker is stated. + +### Effects and side effects +- Mutates source/tests only as needed for the fix. +- May create bug documentation under `.agents/results/bugs/`. + +### Guardrails 1. Reproduce first, then diagnose - never guess at fixes 2. Identify root cause, not just symptoms 3. Minimal fix: change only what's necessary @@ -24,22 +126,17 @@ description: Bug diagnosis and fixing specialist - analyzes errors, identifies r 5. Search for similar patterns elsewhere after fixing 6. Document in `.agents/results/bugs/` -## How to Execute -Follow `resources/execution-protocol.md` step by step. -See `resources/examples.md` for input/output examples. -Before submitting, run `resources/checklist.md`. - -## Serena MCP +### Serena MCP - `find_symbol("functionName")`: Locate the function - `find_referencing_symbols("Component")`: Find all usages - `search_for_pattern("error pattern")`: Find similar issues -## Execution Protocol (CLI Mode) - +## References +Follow `resources/execution-protocol.md` step by step. +See `resources/examples.md` for input/output examples. +Before submitting, run `resources/checklist.md`. Vendor-specific execution protocols are injected automatically by `oma agent:spawn`. Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - -## References - Execution steps: `resources/execution-protocol.md` - Code examples: `resources/examples.md` - Checklist: `resources/checklist.md` @@ -51,3 +148,4 @@ Source files live under `../_shared/runtime/execution-protocols/{vendor}.md`. - Reasoning templates: `../_shared/core/reasoning-templates.md` - Context budget: `../_shared/core/context-budget.md` - Lessons learned: `../_shared/core/lessons-learned.md` +- Observability handoff: `../oma-observability/SKILL.md` §Integrations — traces/logs by `trace_id`, 6-dim forensics diff --git a/.agents/skills/oma-debug/resources/bug-report-template.md b/.agents/skills/oma-debug/resources/bug-report-template.md index 394292a..ebc9527 100644 --- a/.agents/skills/oma-debug/resources/bug-report-template.md +++ b/.agents/skills/oma-debug/resources/bug-report-template.md @@ -12,12 +12,12 @@ Save to: `.agents/results/bugs/bug-YYYYMMDD-[short-description].md` **Date Fixed**: YYYY-MM-DD (or "In Progress") **Reporter**: [User name or issue number] **Assignee**: [Agent that fixed it] -**Severity**: 🔴 CRITICAL | 🟠 HIGH | 🟡 MEDIUM | 🔵 LOW -**Status**: 🐛 OPEN | 🔧 IN PROGRESS | ✅ FIXED | ⏸️ ON HOLD | ❌ WON'T FIX +**Severity**: CRITICAL | HIGH | MEDIUM | LOW +**Status**: OPEN | IN PROGRESS | FIXED | ON HOLD | WON'T FIX --- -## 📝 Problem Description +## Problem Description **What happened?** [Clear description of the bug from user's perspective] @@ -32,7 +32,7 @@ Save to: `.agents/results/bugs/bug-YYYYMMDD-[short-description].md` --- -## 🔄 Reproduction Steps +## Reproduction Steps 1. Navigate to [page/route] 2. Click on [button/element] @@ -46,7 +46,7 @@ Save to: `.agents/results/bugs/bug-YYYYMMDD-[short-description].md` --- -## 🖼️ Evidence +## Evidence **Error Messages**: ``` @@ -77,7 +77,7 @@ Response: [relevant response data] --- -## 🌍 Environment +## Environment **Frontend**: - Browser: [Chrome 120 | Firefox 121 | Safari 17] @@ -97,7 +97,7 @@ Response: [relevant response data] --- -## 🔍 Investigation +## Investigation ### Initial Analysis @@ -130,7 +130,7 @@ const user = data.user.profile.name; // Crashes if profile is undefined --- -## 🔧 Solution +## Solution ### Fix Applied @@ -142,10 +142,10 @@ const user = data.user.profile.name; // Crashes if profile is undefined ```typescript // File: path/to/file.tsx (line 145) -// ❌ BEFORE (buggy code) +// BEFORE (buggy code) const user = data.user.profile.name; -// ✅ AFTER (fixed code) +// AFTER (fixed code) const user = data?.user?.profile?.name ?? 'Unknown'; ``` @@ -154,9 +154,9 @@ const user = data?.user?.profile?.name ?? 'Unknown'; ### Files Modified -- ✏️ `src/components/UserProfile.tsx` - Added null check for profile -- ✏️ `src/lib/api/users.ts` - Improved error handling -- ➕ `src/components/UserProfile.test.tsx` - Added regression test +- `src/components/UserProfile.tsx` - Added null check for profile +- `src/lib/api/users.ts` - Improved error handling +- `src/components/UserProfile.test.tsx` - Added regression test ### Migration/Deployment Notes @@ -167,7 +167,7 @@ const user = data?.user?.profile?.name ?? 'Unknown'; --- -## ✅ Verification +## Verification ### Testing Performed @@ -188,14 +188,14 @@ const user = data?.user?.profile?.name ?? 'Unknown'; ### Test Results -**Unit Tests**: ✅ 15/15 passing -**Integration Tests**: ✅ 8/8 passing -**E2E Tests**: ✅ 3/3 passing -**Manual QA**: ✅ Verified on Chrome, Firefox, Safari +**Unit Tests**: 15/15 passing +**Integration Tests**: 8/8 passing +**E2E Tests**: 3/3 passing +**Manual QA**: Verified on Chrome, Firefox, Safari --- -## 📚 Prevention +## Prevention ### How to Avoid Similar Bugs @@ -208,20 +208,20 @@ const user = data?.user?.profile?.name ?? 'Unknown'; ### Code Patterns to Follow ```typescript -// ✅ GOOD: Safe access with fallback +// GOOD: Safe access with fallback const name = user?.profile?.name ?? 'Anonymous'; -// ✅ GOOD: Explicit null check +// GOOD: Explicit null check if (user?.profile) { const name = user.profile.name; } -// ✅ GOOD: Early return +// GOOD: Early return if (!user?.profile) { return
No profile available
; } -// ❌ BAD: Unsafe nested access +// BAD: Unsafe nested access const name = user.profile.name; // Crashes if profile undefined ``` @@ -233,7 +233,7 @@ const name = user.profile.name; // Crashes if profile undefined --- -## 🔗 Related +## Related **Similar Bugs**: - Bug #123: Similar null check issue in `CommentList` @@ -252,7 +252,7 @@ const name = user.profile.name; // Crashes if profile undefined --- -## 📊 Metrics +## Metrics **Time to Fix**: [2 hours | 1 day | 1 week] **Lines Changed**: [+15 -5] @@ -261,7 +261,7 @@ const name = user.profile.name; // Crashes if profile undefined --- -## 💬 Communication +## Communication **Notified**: - [x] Product Manager - Impact assessment @@ -277,7 +277,7 @@ const name = user.profile.name; // Crashes if profile undefined --- -## 🎓 Lessons Learned +## Lessons Learned **What went well**: - Quick identification of root cause @@ -297,7 +297,7 @@ const name = user.profile.name; // Crashes if profile undefined --- -## 🏷️ Tags +## Tags `frontend` `null-check` `crash` `typescript` `user-profile` `high-priority` diff --git a/.agents/skills/oma-debug/resources/common-patterns.md b/.agents/skills/oma-debug/resources/common-patterns.md index 9ee1195..80d6f19 100644 --- a/.agents/skills/oma-debug/resources/common-patterns.md +++ b/.agents/skills/oma-debug/resources/common-patterns.md @@ -4,18 +4,18 @@ Quick reference guide for frequently encountered bugs and their fixes. --- -## 🔴 Frontend Bugs +## Frontend Bugs ### 1. Undefined/Null Errors -**❌ Problem**: `Cannot read property 'X' of undefined` +**Problem**: `Cannot read property 'X' of undefined` ```typescript // Crash when data not loaded yet const name = user.profile.name; ``` -**✅ Solutions**: +**Solutions**: ```typescript // Option 1: Optional chaining + nullish coalescing @@ -32,7 +32,7 @@ if (!user?.profile) return
Loading...
; ### 2. Stale Closures in useEffect -**❌ Problem**: Event handlers/callbacks use old state values +**Problem**: Event handlers/callbacks use old state values ```typescript function Counter() { @@ -49,7 +49,7 @@ function Counter() { } ``` -**✅ Solutions**: +**Solutions**: ```typescript // Option 1: Include dependency @@ -87,7 +87,7 @@ useEffect(() => { ### 3. Missing Cleanup in useEffect -**❌ Problem**: Memory leaks from subscriptions/listeners +**Problem**: Memory leaks from subscriptions/listeners ```typescript useEffect(() => { @@ -96,7 +96,7 @@ useEffect(() => { }, []); ``` -**✅ Solution**: +**Solution**: ```typescript useEffect(() => { @@ -119,7 +119,7 @@ useEffect(() => { ### 4. Race Conditions in Async Effects -**❌ Problem**: Old requests overwrite new ones +**Problem**: Old requests overwrite new ones ```typescript useEffect(() => { @@ -128,7 +128,7 @@ useEffect(() => { }, [userId]); ``` -**✅ Solution**: +**Solution**: ```typescript useEffect(() => { @@ -150,7 +150,7 @@ useEffect(() => { ### 5. Infinite Re-render Loops -**❌ Problem**: Component re-renders infinitely +**Problem**: Component re-renders infinitely ```typescript function Component() { @@ -164,7 +164,7 @@ function Component() { } ``` -**✅ Solutions**: +**Solutions**: ```typescript // Option 1: Remove problematic dependency @@ -191,7 +191,7 @@ useEffect(() => { ### 6. Key Prop Issues in Lists -**❌ Problem**: List items reordering incorrectly +**Problem**: List items reordering incorrectly ```typescript // Using index as key @@ -200,7 +200,7 @@ useEffect(() => { ))} ``` -**✅ Solution**: +**Solution**: ```typescript // Use stable, unique ID @@ -218,7 +218,7 @@ useEffect(() => { ### 7. Form Input Controlled/Uncontrolled Switch -**❌ Problem**: `Warning: A component is changing an uncontrolled input to be controlled` +**Problem**: `Warning: A component is changing an uncontrolled input to be controlled` ```typescript const [value, setValue] = useState(); // undefined initially @@ -226,7 +226,7 @@ const [value, setValue] = useState(); // undefined initially setValue(e.target.value)} /> ``` -**✅ Solution**: +**Solution**: ```typescript // Initialize with empty string @@ -240,11 +240,11 @@ const [value, setValue] = useState(''); --- -## 🔴 Backend Bugs +## Backend Bugs ### 1. SQL Injection -**❌ Problem**: User input directly in SQL query +**Problem**: User input directly in SQL query ```python # DANGEROUS! @@ -254,7 +254,7 @@ db.execute(query) # User can input: ' OR '1'='1 ``` -**✅ Solution**: +**Solution**: ```python # Use parameterized queries @@ -272,7 +272,7 @@ user = db.query(User).filter(User.email == email).first() ### 2. N+1 Query Problem -**❌ Problem**: One query per item in a loop +**Problem**: One query per item in a loop ```python # 1 query to get todos @@ -284,7 +284,7 @@ for todo in todos: print(f"{todo.title} by {user.name}") ``` -**✅ Solution**: +**Solution**: ```python # Use JOIN - single query @@ -300,7 +300,7 @@ for todo in todos: ### 3. Missing Authentication Check -**❌ Problem**: Protected endpoint accessible without auth +**Problem**: Protected endpoint accessible without auth ```python @app.get("/api/admin/users") @@ -308,7 +308,7 @@ async def get_all_users(db: DatabaseDep): return db.query(User).all() # Anyone can access! ``` -**✅ Solution**: +**Solution**: ```python @app.get("/api/admin/users") @@ -325,7 +325,7 @@ async def get_all_users( ### 4. Missing Input Validation -**❌ Problem**: Invalid data causes errors +**Problem**: Invalid data causes errors ```python @app.post("/api/users") @@ -336,7 +336,7 @@ async def create_user(email: str, age: int): db.commit() ``` -**✅ Solution**: +**Solution**: ```python from pydantic import BaseModel, EmailStr, Field @@ -357,7 +357,7 @@ async def create_user(user: UserCreate): ### 5. Unhandled Exceptions -**❌ Problem**: Server crashes on error +**Problem**: Server crashes on error ```python @app.post("/api/todos") @@ -368,7 +368,7 @@ async def create_todo(todo: TodoCreate, user: User = Depends(get_current_user)): return db_todo ``` -**✅ Solution**: +**Solution**: ```python from fastapi import HTTPException @@ -394,14 +394,14 @@ async def create_todo(todo: TodoCreate, user: User = Depends(get_current_user)): ### 6. Missing CORS Configuration -**❌ Problem**: Frontend can't call API +**Problem**: Frontend can't call API ``` Access to fetch at 'http://localhost:8000/api/todos' from origin 'http://localhost:3000' has been blocked by CORS policy ``` -**✅ Solution**: +**Solution**: ```python from fastapi.middleware.cors import CORSMiddleware @@ -422,13 +422,13 @@ app.add_middleware( ### 7. Password Storage -**❌ Problem**: Passwords stored in plain text +**Problem**: Passwords stored in plain text ```python user = User(email=email, password=password) # NEVER DO THIS! ``` -**✅ Solution**: +**Solution**: ```python from passlib.context import CryptContext @@ -446,11 +446,11 @@ if not pwd_context.verify(plain_password, user.password_hash): --- -## 🔴 Mobile Bugs +## Mobile Bugs ### 1. Memory Leaks in Flutter -**❌ Problem**: Controllers not disposed +**Problem**: Controllers not disposed ```dart class MyWidget extends StatefulWidget { @@ -469,7 +469,7 @@ class _MyWidgetState extends State { } ``` -**✅ Solution**: +**Solution**: ```dart class _MyWidgetState extends State { @@ -492,7 +492,7 @@ class _MyWidgetState extends State { ### 2. Platform-Specific Code Not Checked -**❌ Problem**: iOS-specific code crashes on Android +**Problem**: iOS-specific code crashes on Android ```dart // Crashes on Android @@ -501,7 +501,7 @@ import 'dart:io' show Platform; final deviceName = Platform.isIOS ? 'iPhone' : 'Unknown'; ``` -**✅ Solution**: +**Solution**: ```dart import 'dart:io' show Platform; @@ -522,11 +522,11 @@ if (Platform.isIOS) { --- -## 🔴 Performance Bugs +## Performance Bugs ### 1. Unnecessary Re-renders (React) -**❌ Problem**: Component re-renders on every parent render +**Problem**: Component re-renders on every parent render ```typescript function Parent() { @@ -541,7 +541,7 @@ function Parent() { } ``` -**✅ Solution**: +**Solution**: ```typescript // Memoize the expensive component @@ -568,7 +568,7 @@ function Parent() { ### 2. Large Bundle Size -**❌ Problem**: Importing entire library +**Problem**: Importing entire library ```typescript // Imports all of lodash (~70KB) @@ -577,7 +577,7 @@ import _ from 'lodash'; const unique = _.uniq(array); ``` -**✅ Solution**: +**Solution**: ```typescript // Import only what you need @@ -591,18 +591,18 @@ const unique = [...new Set(array)]; --- -## 🔴 Security Bugs +## Security Bugs ### 1. XSS (Cross-Site Scripting) -**❌ Problem**: User input rendered as HTML +**Problem**: User input rendered as HTML ```typescript // Dangerous!
``` -**✅ Solution**: +**Solution**: ```typescript // React escapes by default @@ -620,7 +620,7 @@ import DOMPurify from 'dompurify'; ### 2. Missing Rate Limiting -**❌ Problem**: API can be abused +**Problem**: API can be abused ```python @app.post("/api/auth/login") @@ -629,7 +629,7 @@ async def login(credentials: LoginRequest): ... ``` -**✅ Solution**: +**Solution**: ```python from slowapi import Limiter @@ -645,7 +645,7 @@ async def login(request: Request, credentials: LoginRequest): --- -## 📊 Common Error Messages & Solutions +## Common Error Messages & Solutions | Error | Likely Cause | Solution | |-------|--------------|----------| @@ -661,7 +661,7 @@ async def login(request: Request, credentials: LoginRequest): --- -## 🎯 Quick Debugging Commands +## Quick Debugging Commands ### Frontend ```bash @@ -706,7 +706,7 @@ flutter build apk --analyze-size --- -## 🔍 When to Use Each Agent +## When to Use Each Agent | Bug Type | Best Agent | Reason | |----------|-----------|---------| @@ -719,7 +719,7 @@ flutter build apk --analyze-size --- -## 💡 Prevention Tips +## Prevention Tips 1. **Write tests first** - Catch bugs before they ship 2. **Use TypeScript** - Catch type errors at compile time diff --git a/.agents/skills/oma-debug/resources/debugging-checklist.md b/.agents/skills/oma-debug/resources/debugging-checklist.md index 935339a..8e4bb9d 100644 --- a/.agents/skills/oma-debug/resources/debugging-checklist.md +++ b/.agents/skills/oma-debug/resources/debugging-checklist.md @@ -2,7 +2,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. -## 📋 Initial Information Gathering +## Initial Information Gathering - [ ] **Bug description** - What is the expected vs actual behavior? - [ ] **Error messages** - Exact error text, stack trace, error codes @@ -13,7 +13,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] **Recent changes** - New deploy? Code changes? Configuration updates? - [ ] **Screenshots/videos** - Visual evidence of the bug -## 🔍 Frontend Debugging +## Frontend Debugging ### JavaScript/TypeScript Errors @@ -72,7 +72,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] Disabled buttons during load? - [ ] Optimistic updates causing issues? -## 🖥️ Backend Debugging +## Backend Debugging ### Python/FastAPI Errors @@ -132,7 +132,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] External services reachable? - [ ] Database migrations applied? -## 📱 Mobile Debugging +## Mobile Debugging ### Platform-Specific Issues @@ -168,7 +168,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] Large images not optimized? - [ ] Too many simultaneous network requests? -## 🔐 Security Bugs +## Security Bugs - [ ] **Authentication bypassed?** - [ ] Token validation on all protected routes? @@ -192,7 +192,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] API keys exposed? - [ ] Error messages leaking info? -## 🐌 Performance Bugs +## Performance Bugs ### Frontend Performance @@ -226,7 +226,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] Query plan optimized? (EXPLAIN) - [ ] N+1 queries eliminated? -## 🔬 Root Cause Analysis +## Root Cause Analysis - [ ] **Reproduce the bug** - [ ] Follow exact reproduction steps @@ -248,7 +248,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] Why does this happen? - [ ] What assumption was wrong? -## ✅ Fix Verification +## Fix Verification - [ ] **Fix applied** - [ ] Code changed in correct file(s) @@ -272,7 +272,7 @@ Use this checklist when investigating bugs to ensure thorough analysis. - [ ] Fix explained - [ ] Prevention notes added -## 🚨 When to Escalate +## When to Escalate Escalate to other agents if: @@ -283,34 +283,34 @@ Escalate to other agents if: - [ ] Database schema changes needed → **Backend Agent** - [ ] Platform-specific mobile issue → **Mobile Agent** -## 📊 Priority Assessment +## Priority Assessment -**🔴 CRITICAL** - Fix immediately: +**CRITICAL** - Fix immediately: - [ ] App crashes on launch - [ ] Data loss or corruption - [ ] Security vulnerability - [ ] Payment/auth completely broken - [ ] Affects all users -**🟠 HIGH** - Fix within 24 hours: +**HIGH** - Fix within 24 hours: - [ ] Major feature broken - [ ] Affects >50% of users - [ ] No workaround available - [ ] Significant revenue impact -**🟡 MEDIUM** - Fix within sprint: +**MEDIUM** - Fix within sprint: - [ ] Minor feature broken - [ ] Affects <50% of users - [ ] Workaround exists - [ ] Moderate inconvenience -**🔵 LOW** - Schedule for future: +**LOW** - Schedule for future: - [ ] Edge case - [ ] Cosmetic issue - [ ] Rarely encountered - [ ] No user impact -## 📝 Documentation Template +## Documentation Template After fixing, document in `.agents/results/bugs/`: @@ -343,7 +343,7 @@ After fixing, document in `.agents/results/bugs/`: --- -## 💡 Pro Tips +## Pro Tips 1. **Read the error message** - It usually tells you exactly what's wrong 2. **Reproduce first** - Don't waste time fixing unconfirmed bugs @@ -352,7 +352,7 @@ After fixing, document in `.agents/results/bugs/`: 5. **Document everything** - Future you will be grateful 6. **Look for patterns** - One bug often reveals more -## 🛠️ Tools Reference +## Tools Reference - **Browser DevTools**: F12 (Console, Network, React DevTools) - **Serena MCP**: find_symbol, search_for_pattern, find_referencing_symbols diff --git a/.agents/skills/oma-debug/resources/error-playbook.md b/.agents/skills/oma-debug/resources/error-playbook.md index 1174ecc..d96bb1a 100644 --- a/.agents/skills/oma-debug/resources/error-playbook.md +++ b/.agents/skills/oma-debug/resources/error-playbook.md @@ -9,7 +9,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: Bug described by user but you can't trigger it -1. Re-read user's reproduction steps — are you following them exactly? +1. Re-read user's reproduction steps; are you following them exactly? 2. Check environment differences: browser, OS, node/python version 3. Check data-dependent: does it need specific DB state or test data? 4. Check timing: is it a race condition? Try adding delays or rapid repetition @@ -22,9 +22,9 @@ Do NOT stop or ask for help until you have exhausted the playbook. **Symptoms**: Original bug fixed but other tests break -1. Read the failing tests — are they testing the old (buggy) behavior? +1. Read the failing tests; are they testing the old (buggy) behavior? 2. If yes: update tests to reflect correct behavior -3. If no: your fix has side effects — revert and try a more targeted approach +3. If no: your fix has side effects. Revert and try a more targeted approach 4. `find_referencing_symbols("fixedFunction")` to check all callers 5. Consider: is the function contract changing? If so, update all callers @@ -37,7 +37,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. 1. Add logging at each step of the execution path 2. Binary search: is the bug before or after the midpoint? 3. `search_for_pattern("suspicious_pattern")` to find related code -4. Check git history: `git log --oneline -20 -- path/to/file` — when was it last changed? +4. Check git history: `git log --oneline -20 -- path/to/file`. When was it last changed? 5. Check: is it a dependency issue? Library version mismatch? 6. **No progress after 5 turns**: Record current analysis in progress, switch to different hypothesis @@ -53,7 +53,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. - What the correct behavior should be - Evidence (request/response logs, stack trace) 3. Record in result: `cross_domain_issue: {agent: "backend", description: "..."}` -4. **Do NOT modify directly** — touching another agent's code causes conflicts +4. **Do NOT modify directly**; touching another agent's code causes conflicts --- @@ -65,7 +65,7 @@ Do NOT stop or ask for help until you have exhausted the playbook. 2. Backend: enable SQL query logging, count queries, check `EXPLAIN ANALYZE` 3. Frontend: run Lighthouse, check React DevTools Profiler 4. Mobile: use Flutter DevTools performance tab -5. Profile before fixing — never optimize without data +5. Profile before fixing; never optimize without data --- @@ -91,4 +91,4 @@ Same as backend-agent playbook: See relevant sections. - **After 3 failures**: If same approach fails 3 times, must try a different method - **Blocked**: If no progress after 5 turns, save current state, `Status: blocked` -- **Out of scope**: Other agent's domain — only record, do not modify directly +- **Out of scope**: Other agent's domain. Only record, do not modify directly diff --git a/.agents/skills/oma-debug/resources/execution-protocol.md b/.agents/skills/oma-debug/resources/execution-protocol.md index 777995c..ff9bf85 100644 --- a/.agents/skills/oma-debug/resources/execution-protocol.md +++ b/.agents/skills/oma-debug/resources/execution-protocol.md @@ -1,16 +1,16 @@ # Debug Agent - Execution Protocol ## Step 0: Prepare -1. **Assess difficulty** — see `../../_shared/core/difficulty-guide.md` +1. **Assess difficulty**: see `../../_shared/core/difficulty-guide.md` - **Simple**: Skip to Step 3 | **Medium**: All 4 steps | **Complex**: All steps + checkpoints -2. **Check lessons** — read your domain section in `../../_shared/core/lessons-learned.md` -3. **Clarify requirements** — follow `../../_shared/core/clarification-protocol.md` +2. **Check lessons**: read your domain section in `../../_shared/core/lessons-learned.md` +3. **Clarify requirements**: follow `../../_shared/core/clarification-protocol.md` - Check **Uncertainty Triggers**: security/auth related bugs, existing code conflict potential? - Determine level: LOW → proceed | MEDIUM → present options | HIGH → ask immediately -4. **Use reasoning templates** — for Complex bugs, use `../../_shared/core/reasoning-templates.md` (hypothesis loop, execution trace) -5. **Budget context** — follow `../../_shared/core/context-budget.md` (use find_symbol, not read_file) +4. **Use reasoning templates**: for Complex bugs, use `../../_shared/core/reasoning-templates.md` (hypothesis loop, execution trace) +5. **Budget context**: follow `../../_shared/core/context-budget.md` (use find_symbol, not read_file) -**⚠️ Intelligent Escalation**: When uncertain, escalate early. Don't blindly proceed. +**Intelligent Escalation**: When uncertain, escalate early. Don't blindly proceed. Follow these steps in order (adjust depth by difficulty). diff --git a/.agents/skills/oma-deepsec/SKILL.md b/.agents/skills/oma-deepsec/SKILL.md new file mode 100644 index 0000000..b14d996 --- /dev/null +++ b/.agents/skills/oma-deepsec/SKILL.md @@ -0,0 +1,247 @@ +--- +name: oma-deepsec +description: > + Drive Vercel's `deepsec` agent-powered vulnerability scanner end-to-end: + installing the `.deepsec/` workspace, bootstrapping `INFO.md`, running + cost-aware `scan` / `process` / `triage` / `revalidate` / `export` passes, + gating PRs with `process --diff`, writing custom matchers, and triaging + findings. Use whenever the user mentions deepsec, asks an agent to scan a + repo for vulnerabilities, runs into `pnpm deepsec` / `bunx deepsec` + commands, wants a CI-based PR security review, sees a `.deepsec/` + directory, or asks about `INFO.md` / matchers / `process --diff` / + `revalidate`, even when the tool name is not spoken. Deepsec scans are + expensive (a single full scan can cost hundreds to tens of thousands of + dollars) so the skill exists in part to keep the user from getting + surprised. +--- + +# Deepsec: Agent-Powered Vulnerability Scanner Driver + +## Scheduling + +### Goal +Operate Vercel's `deepsec` security scanner inside a target repository safely and cost-consciously: bootstrap the `.deepsec/` workspace, write a tight `INFO.md`, run the right scan/process/triage/revalidate/export sequence, gate PRs in CI via `process --diff`, and grow project-specific matchers, surfacing real, revalidated findings without runaway spend. + +### Intent signature +- User mentions `deepsec`, "deep security scan", `bunx deepsec`, `pnpm deepsec`, `npx deepsec`. +- User asks an agent to scan a repository for vulnerabilities, security issues, or CVEs and the project has (or should have) a `.deepsec/` directory. +- User asks how to add a deepsec PR / CI security gate, or about `process --diff`, `--diff-staged`, `--diff-working`, `--files-from`, `--comment-out`. +- User mentions deepsec artefacts: `INFO.md`, `SETUP.md`, `data//files/`, `FileRecord`, `RunMeta`, `revalidation`, `triage`, custom matchers, `MatcherPlugin`, `noiseTier`, `priorityPaths`. +- User asks about deepsec configuration: `deepsec.config.ts`, `defaultAgent`, `AI_GATEWAY_API_KEY`, `VERCEL_OIDC_TOKEN`, AI Gateway, Vercel Sandbox, `--agent codex`, `--agent claude`. +- User asks how to lower deepsec cost, cut false-positive rate, or interpret severity / triage / revalidation verdicts. + +### When to use +- First-time deepsec install in a repo (`init`, `INFO.md` write, first calibration scan). +- Running a full or scoped scan and processing findings. +- Setting up a per-PR CI gate with `process --diff` and `--comment-out`. +- Writing a project-specific matcher to cover entry points the default set misses. +- Triaging a backlog of findings (severity bucketing, FP cuts via `revalidate`, exporting to issue tracker). +- Diagnosing deepsec failures: missing credentials, AI Gateway quota stops, refusals, sandbox auth. + +### When NOT to use +- Generic OWASP / lint-style review without deepsec → use `oma-qa`. +- Generic CVE / dependency advisories → use `oma-qa` or `oma-search`. +- Architecting a brand-new SAST pipeline that is not deepsec → use `oma-architecture`. +- Writing or auditing application code itself → route to `oma-backend` / `oma-frontend` / `oma-mobile`. +- Cloud / IAM / Terraform hardening → use `oma-tf-infra` (deepsec only scans the IaC; remediation lives there). +- Pure reasoning about a finding's fix in product code → use `oma-debug` once deepsec has produced the finding. + +### Expected inputs +- `target_repo_root`: absolute path of the codebase to scan (parent of `.deepsec/`). +- `intent`: one of `setup` | `scan` | `pr-review` | `matchers` | `triage` | `config` | `troubleshoot`. +- `credential_mode`: `ai-gateway-key` | `vercel-oidc` | `direct-anthropic` | `direct-openai` | `subscription`. +- `agent_choice`: `claude` (default `claude-opus-4-7`) or `codex` (default `gpt-5.5`). Asked once before the first paid call if not already provided. +- `severity_floor`: lowest severity worth surfacing (typically `HIGH`). +- Optional: existing `.deepsec/data//`, `deepsec.config.ts`, custom matchers, CI provider. + +### Expected outputs +- A working `.deepsec/` workspace registered against the target repo. +- A populated `data//INFO.md` (50-100 lines, project-specific, no line numbers). +- One or more completed `scan` → `process` (→ `triage`/`revalidate`) runs with reproducible cost notes. +- For PR mode: a CI workflow file using `process --diff ` with two-job split (no PR-write in PR-code job). +- For matchers: new `.deepsec/matchers/.ts` files wired through the inline plugin in `deepsec.config.ts`. +- A findings export (`md-dir` and/or `json`) plus a short summary of top severities and FP-rate notes. +- Explicit, dollar-and-time-bounded plan before any pass that may cost more than ~$25. + +### Dependencies +- Node.js **22+**, plus a package manager: `bun` / `bunx` (preferred in this monorepo), `pnpm`, `npm`, or `yarn`. +- A working AI credential: `AI_GATEWAY_API_KEY=vck_…`, or `VERCEL_OIDC_TOKEN`, or direct `ANTHROPIC_AUTH_TOKEN` + `ANTHROPIC_BASE_URL`, or a logged-in `claude` / `codex` CLI subscription. +- Git (history is consulted by `revalidate` and `--diff` modes). +- Optional: Vercel Sandbox auth for `deepsec sandbox …` distributed runs. +- Reference resources under `resources/` (loaded only when the scenario requires them). + +### Control-flow features +- Branches by `intent` (setup vs scan vs pr-review vs matchers vs triage vs config vs troubleshoot). +- Branches by repo size (calibrate with `--limit 50` before any large pass). +- Branches by credential source (gateway key, OIDC, direct, subscription). +- Stops on quota / credit exhaustion and resumes the same command after top-up. +- Refuses to launch an unbounded `process` when no calibration has been done and the repo is large. +- Reads codebase, writes `.deepsec/` files and CI configs, runs long-lived AI processes. + +## Structural Flow + +### Entry +1. Confirm whether `.deepsec/` already exists; if yes, treat the run as **incremental**, never re-init. +2. Resolve `intent` from the user prompt; if ambiguous (e.g. "scan this repo"), default to `setup` then `scan` (calibration mode). +3. Estimate scale: count source files (rough `rg --files | wc -l` excluding `node_modules`, `.git`, `dist`) to forecast cost before any AI pass. +4. Check for an AI credential in `.env.local` or shell env; if none, route to credential setup before any `process` / `revalidate` / `triage` call. +5. **Confirm agent choice with the user before the first paid call.** If `agent_choice` is not already in the prompt and `deepsec.config.ts` does not pin a `defaultAgent`, ask whether to run `claude` (`claude-opus-4-7`, the default; strongest reasoning, most expensive) or `codex` (`gpt-5.5`; runs in a strict sandbox, cheaper, grep-heavy). The two backends can be mixed via `--reinvestigate` and findings dedupe across agents. Skip the question if the user has already named an agent or has explicitly delegated the decision ("just pick reasonable defaults"). + +### Scenes +1. **PREPARE**: Resolve intent, repo root, credential, budget cap, severity floor, agent choice. Refuse to run blind on a repo of unknown scale. +2. **ACQUIRE**: Read `.deepsec/deepsec.config.ts`, `data//project.json`, `INFO.md`, last `runs/` entries, and target-repo signals (`README`, `AGENTS.md`/`CLAUDE.md`, framework configs, route directories) needed to author or verify `INFO.md`. +3. **REASON**: Pick the smallest pass that answers the user's question. Options include `scan` only, a `--limit 50` calibration, a full `process`, `process --diff`, a matcher-authoring loop, or troubleshoot-only. Always state cost forecast and stopping condition before AI passes. +4. **ACT**: Run the planned commands from inside `.deepsec/`. For matchers, write per-slug files and wire the inline plugin. For PR mode, scaffold the two-job CI workflow. +5. **VERIFY**: Use `deepsec status`, the run's `RunMeta`, exit code (`0` clean, `1` findings produced, other = error), candidate counts, and (when present) the `--comment-out` markdown to confirm output. +6. **FINALIZE**: Summarize findings by severity and verdict, list dollar cost and wall time, name files written, and call out follow-ups (revalidate `HIGH+`, write matchers for missed entry points, persist `data/` between CI runs). + +### Transitions +- If `.deepsec/` is missing and intent involves scanning → run `bunx deepsec init` (or `npx deepsec init`) and follow the printed prompt to populate `INFO.md` before any AI pass. +- If `INFO.md` is empty or template-shaped → write it (50-100 lines, project-specific, 3-5 examples per section, no line numbers, no generic CWE enumeration). +- If repo is > 500 files and no calibration has run → run a calibration pass first (deepsec docs recommend `--limit 50 --concurrency 5`) and report cost extrapolation before the full pass. +- If a `process` / `revalidate` run halts on quota → leave file locks intact, surface the exact remediation URL, **re-run the same command after top-up**. +- If the agent reports a refusal (`refused: true`) → never silently drop; document the affected files and either retry with the other backend or add the path to `config.json:ignorePaths` only if reproducible. +- If the user wants a CI gate → emit the two-job pattern (PR-code job has no `pull-requests: write`, comment job has no PR code). +- If the user wants more matcher coverage → run the matcher-authoring workflow against `data//files/` and the parent repo's entry points. + +### Failure and recovery +| Failure | Recovery | +|---------|----------| +| `Missing AI credentials for --agent claude` / `codex` | Pick a credential mode (gateway key / OIDC / direct / subscription) per `resources/config.md` and write `.env.local`. | +| `401 Unauthorized` from gateway | OIDC: re-run `vercel env pull` (12 h expiry). API key: regenerate. Confirm `.env.local` is in the cwd deepsec runs from. | +| `Stopped: AI Gateway credits exhausted` | Top up via the printed URL; re-run the same command, files already done are skipped. | +| `Stopped: Claude Pro/Max subscription exhausted` | Switch to AI Gateway; subscriptions don't carry full scans. | +| Persistent refusal on a single file (>5% of batches) | Add the path to `data//config.json:ignorePaths`, or run that file alone with `--batch-size 1`. | +| FP rate too high on `HIGH+` | Run `revalidate --min-severity HIGH`; tighten `INFO.md`'s threat model and FP notes; bias matchers to `precise`. | +| `noisy` matcher wedges scanner on a 100k-file repo | Tighten `filePatterns` to language- or directory-anchored globs. | +| Sandbox auth fails | OIDC: re-run `vercel env pull`. Access-token mode: verify `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID`. | +| User asks for full scan with no budget context | Halt; report file count and forecast cost band; require explicit go-ahead before the full pass. | + +### Exit +- **Success**: planned passes ran, findings exist with verdicts (or no findings produced), files written are listed, residual cost / followups are explicit. +- **Partial success**: some passes blocked on credentials/quota/refusal; the blocker, the safe-resume command, and the recommended next step are reported. +- **Failure**: nothing destructive happened, the user has the exact next command to unblock the work. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Detect existing workspace and credentials | `READ` | `.deepsec/`, `.env.local`, env vars | +| Estimate repo scale | `INFER` | `rg --files | wc -l` | +| Choose pass plan (calibrate vs full vs diff) | `SELECT` | File count, intent, budget cap | +| Init workspace | `CALL_TOOL` | `bunx deepsec init` | +| Write `INFO.md` | `WRITE` | `data//INFO.md` | +| Run scan | `CALL_TOOL` | `bunx deepsec scan` | +| Run AI investigation | `CALL_TOOL` | `bunx deepsec process` (`--limit`, `--concurrency`) | +| Triage / revalidate | `CALL_TOOL` | `bunx deepsec triage` / `revalidate --min-severity HIGH` | +| Export findings | `CALL_TOOL` | `bunx deepsec export --format md-dir|json` | +| PR-mode review | `CALL_TOOL` | `bunx deepsec process --diff --comment-out comment.md` | +| Author custom matcher | `WRITE` | `.deepsec/matchers/.ts` + inline plugin in `deepsec.config.ts` | +| Validate matcher hit rate | `VALIDATE` | `bunx deepsec scan --matchers ` candidate count | +| Verify and report | `NOTIFY` | `RunMeta`, severity counts, dollar cost, FP rate | +| Stop on budget breach | `TERMINATE` | Refuse unbounded `process` without calibration | + +### Tools and instruments +- **Package manager**: `bun` / `bunx` (preferred), `pnpm`, `npm`, `yarn` are interchangeable. +- **CLI commands**: `deepsec init`, `init-project`, `scan`, `process`, `process --diff`, `triage`, `revalidate`, `enrich`, `report`, `export`, `metrics`, `status`, `sandbox `. +- **Diff sources for PR mode**: `--diff `, `--diff-staged`, `--diff-working`, `--files `, `--files-from ` (or `-` for stdin). +- **Inspection**: `jq` over `data//files/**/*.json` for ad-hoc severity / TP queries. +- **Credentials**: `AI_GATEWAY_API_KEY`, `VERCEL_OIDC_TOKEN`, `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL`, `OPENAI_API_KEY` / `OPENAI_BASE_URL`, `claude login`, `codex login`. +- **Resource files** under `resources/` for setup, scanning, PR review, matchers, triage, config, load on demand. + +### Canonical workflow path +1. **Bootstrap** (one time per repo): + ```bash + cd + bunx deepsec init + cd .deepsec + bun install + # Edit .env.local: set AI_GATEWAY_API_KEY=vck_… (or VERCEL_OIDC_TOKEN via `vercel env pull`) + ``` + Then prompt the coding agent (this skill) to read + `.deepsec/node_modules/deepsec/SKILL.md` and `.deepsec/data//SETUP.md`, + skim `README` / `AGENTS.md` / `CLAUDE.md` and a handful of representative + files, and replace each section of `data//INFO.md` (50-100 lines, + 3-5 examples per section, no line numbers, no generic CWE rehash). +2. **Calibrate before any full pass.** The deepsec docs (`getting-started.md`, `vercel-setup.md`, `faq.md`) recommend `--limit 50 --concurrency 5` as the calibration starting point. + ```bash + bunx deepsec scan + bunx deepsec status + bunx deepsec process --limit 50 --concurrency 5 + ``` + Read the per-batch cost. Extrapolate to full repo. Get the user's explicit go-ahead before the full `process`. If the user names different `--limit` / `--concurrency` values, use theirs. +3. **Full investigation, triage, revalidate, export**: + ```bash + bunx deepsec process --concurrency 5 + bunx deepsec triage --severity HIGH + bunx deepsec revalidate --min-severity HIGH + bunx deepsec export --format md-dir --out ./findings + bunx deepsec metrics + ``` +4. **PR mode** (CI gate, scoped to changed files, exit code = 0/1): + ```bash + bunx deepsec process \ + --diff origin/${BASE_REF} \ + --comment-out comment.md + ``` + Wire the two-job CI pattern from `resources/pr-review.md`. Never grant `pull-requests: write` to the job that runs PR-controlled code. +5. **Custom matchers** (close entry-point gaps surfaced in step 3): + - Read the contract in `.deepsec/node_modules/deepsec/dist/config.d.ts` and the `samples/webapp/matchers/*` examples. + - Write `.deepsec/matchers/.ts`, wire it through the inline plugin in `.deepsec/deepsec.config.ts`. + - Verify hit rate: `bunx deepsec scan --matchers ` should land in 1-20 hits / 1k files (`precise`), 5-100 (`normal`), or roughly the framework entry-point count (`noisy`). +6. **Resume** after any quota stop, network blip, or Ctrl-C: re-run the same command. State is on disk under `.deepsec/data//`. + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `CODEBASE` | Target repo source files, framework configs, route directories, `README` / `AGENTS.md` / `CLAUDE.md`. | +| `LOCAL_FS` | `.deepsec/deepsec.config.ts`, `.deepsec/.env.local`, `.deepsec/matchers/`, `.deepsec/data//{project.json,INFO.md,config.json,files/,runs/,reports/}`, generated `findings/`, `comment.md`, CI workflow files. | +| `PROCESS` | `bunx deepsec scan|process|triage|revalidate|export|metrics|status|sandbox`, `bun install`, optional `vercel link` / `vercel env pull`. | +| `NETWORK` | Anthropic / OpenAI via Vercel AI Gateway (default) or direct provider endpoints; optional Vercel Sandbox microVM control plane. | +| `CREDENTIALS` | `AI_GATEWAY_API_KEY`, `VERCEL_OIDC_TOKEN`, `ANTHROPIC_AUTH_TOKEN`, `OPENAI_API_KEY`, `VERCEL_TOKEN` / `VERCEL_TEAM_ID` / `VERCEL_PROJECT_ID`, `claude` / `codex` subscription tokens. Consume read-only; never echo secrets back to the user or commit them. | +| `MEMORY` | User-stated budget cap, severity floor, and stop conditions for the current session. | + +### Preconditions +- Node.js 22+ is available. +- Repo is a git checkout (deepsec uses git history for `revalidate` and `--diff`). +- For any AI command: at least one credential mode is configured *before* the call, or the call is held until one is. +- For `sandbox` mode: Vercel auth is wired; otherwise stay local. +- For unbounded `process` runs on > 500-file repos: a `--limit` calibration pass has produced a cost number the user has acknowledged. + +### Effects and side effects +- Creates `.deepsec/` (config, lockfile, scaffolding) and `.deepsec/data//` (gitignored) inside the target repo. +- Writes `.env.local` (never commit) and may run `vercel link` / `vercel env pull` (writes `.vercel/project.json` + token). +- Spawns long-running AI processes that **cost real money**. Single full scans range from $25 to over $1,200 per the official cost guide and can climb to tens of thousands on very large repos. +- Reads source code; sends snippets to the configured LLM (gateway = zero retention; direct provider = subject to that provider's policy). Never exfiltrates secrets; the gateway key stays outside the worker sandbox in `sandbox` mode. +- May write `.github/workflows/deepsec.yml` (or analogue) when the user asks for a CI gate. +- Edits `deepsec.config.ts` and adds `.deepsec/matchers/*.ts` when authoring matchers. +- Does not commit, push, or open PRs unless the user explicitly authorizes a separate commit step (route via `oma-scm`). + +### Guardrails +1. **Never launch an unbounded `process` on a repo whose size you have not measured.** Always run a calibration pass first when file count is unknown or > 500 (deepsec docs recommend `--limit 50 --concurrency 5`; defer to a user-named value if given). +2. **State cost and stopping condition before any AI pass.** Use the published bands (100 files ≈ $25-60, 500 ≈ $130-300, 2,000 ≈ $500-1,200; ×2-3 swing). +3. **Resume, do not reset.** After any network / quota / Ctrl-C interruption, re-run the same command. Never delete `data//` to "start clean" without explicit user instruction. +4. **`INFO.md` stays short and project-specific.** 50-100 lines, 3-5 examples per section. Name primitives but no line numbers. Skip generic CWE categories; built-in matchers cover those. +5. **For PR/CI gates, keep PR-controlled code in a no-write job.** Never grant `pull-requests: write` to a job that executes PR-controlled `pnpm install` / config-loading. Use the two-job pattern in `resources/pr-review.md`. +6. **Pin actions to full SHAs** in production CI; major-version tags are for examples only. +7. **Never silently drop refusals.** If the agent reports `refused: true`, log it, retry with the other backend, or add the file to `ignorePaths` only when reproducible. +8. **Bias matchers toward `precise` when the bug shape is exact.** Reserve `noisy` for entry-point coverage and tight globs. +9. **Never echo or commit credentials** (`vck_…`, `sk-ant-…`, `sk-…`, OIDC tokens). Treat `.env.local` as secret. Treat `data/` as gitignored by default. +10. **Treat deepsec like an agent with shell access.** Recommend `sandbox` for prompt-injection-prone repos (vendored code, untrusted deps). +11. **Findings need verdicts.** For any HIGH+ surfaced to the user, prefer `revalidate`-tagged verdicts (`true-positive` / `false-positive` / `fixed` / `uncertain`) over raw `process` output. +12. **Do not invent CLI flags.** Anything beyond `resources/scanning.md`'s flag list must be checked against `--help` first. +13. **Ask agent choice before the first paid call.** If the user has not named an agent (`claude` vs `codex`) and `deepsec.config.ts` does not pin `defaultAgent`, ask once with the trade-off clearly stated. Do not also bargain over budget or severity; those are handled via the upstream calibration recommendation (`--limit 50 --concurrency 5` per deepsec docs) and the user-stated `severity_floor`. + +## References +- Workspace install + `INFO.md` bootstrap: `resources/setup.md` +- Full scan/process/triage/revalidate/export workflow + cost guide: `resources/scanning.md` +- PR / CI gate via `process --diff` (two-job pattern, exit-code semantics): `resources/pr-review.md` +- Authoring custom matchers (slugs, noise tiers, file globs, plugin wiring): `resources/matchers.md` +- Reading findings, severities, triage / revalidation verdicts, FP cuts: `resources/triage.md` +- `deepsec.config.ts` reference, env vars, plugin order, AI Gateway / Vercel Sandbox auth: `resources/config.md` +- Upstream docs (load only when a resource file points at one): + - Repo + README: https://github.com/vercel-labs/deepsec + - Per-topic docs at https://github.com/vercel-labs/deepsec/tree/main/docs (`getting-started`, `reviewing-changes`, `writing-matchers`, `configuration`, `models`, `plugins`, `architecture`, `data-layout`, `vercel-setup`, `supported-tech`, `faq`) +- Shared context loading: `../_shared/core/context-loading.md` +- Shared quality principles: `../_shared/core/quality-principles.md` diff --git a/.agents/skills/oma-deepsec/resources/config.md b/.agents/skills/oma-deepsec/resources/config.md new file mode 100644 index 0000000..8c51756 --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/config.md @@ -0,0 +1,202 @@ +# Configuration: `deepsec.config.ts`, env vars, plugins, models + +deepsec reads `deepsec.config.{ts,mjs,js,cjs}` from the current working directory, walking up. The CLI inherits whatever the file declares. + +```ts +import { defineConfig } from "deepsec/config"; +import myPlugin from "@my-org/deepsec-plugin-foo"; + +export default defineConfig({ + projects: [ + { id: "my-app", root: "../my-app" }, + { id: "service", root: "../service", + githubUrl: "https://github.com/me/service/blob/main" }, + ], + plugins: [myPlugin()], +}); +``` + +For a fully-worked example exercising every common field (`infoMarkdown`, `promptAppend`, `priorityPaths`, an inline plugin), see `samples/webapp/deepsec.config.ts` in the deepsec repo. + +## Top-level fields + +| Field | Type | Purpose | +|---|---|---| +| `projects` | `ProjectDeclaration[]` | Codebases deepsec knows about. | +| `plugins` | `DeepsecPlugin[]` | Loaded in order; later plugins override single-slot capabilities. | +| `matchers` | `{ only?: string[]; exclude?: string[] }` | Filter the matcher set used by `scan`. | +| `defaultAgent` | `"claude" | "codex"` | Default `--agent` value. | +| `dataDir` | `string` | Override the `data/` directory. Defaults to `./data`. | + +## `ProjectDeclaration` + +| Field | Type | Required | Purpose | +|---|---|---|---| +| `id` | `string` | yes | Used as `--project-id` and the data directory name. | +| `root` | `string` | yes | Absolute or relative path to the codebase. | +| `githubUrl` | `string` | no | `https://github.com/owner/repo/blob/branch` for clickable links in exports. Auto-detected from `git remote` if omitted. | +| `infoMarkdown` | `string` | no | Repo context injected into AI prompts. Overrides `data//INFO.md` if both are set. | +| `promptAppend` | `string` | no | Free-form text appended to the system prompt for this project. | +| `priorityPaths` | `string[]` | no | Path prefixes to process first. | + +## Per-project `data//config.json` + +Optional, read by `scan` and the AI agents. Overrides the same fields on the project declaration if both are present. + +```json +{ + "priorityPaths": ["app/api/", "lib/"], + "promptAppend": "Pay extra attention to the booking flow.", + "ignorePaths": ["**/legacy/**"] +} +``` + +## Matcher filtering + +```ts +matchers: { + only: ["sql-injection", "auth-bypass"], // run *only* these + exclude: ["framework-internal-header"], // skip these +} +``` + +If `only` is set, `exclude` is ignored. CLI flag `--matchers ` overrides the config when both are present. + +## Plugin order + +Plugins are evaluated in array order: + +```ts +plugins: [genericPlugin(), orgPlugin()] +``` + +| Slot | Behavior | +|---|---| +| `matchers`, `notifiers`, `agents` | **Additive.** Both plugins' contributions stack. | +| `ownership`, `people`, `executor` | **Last-write-wins.** `orgPlugin()`'s provider replaces `genericPlugin()`'s. | + +A monorepo gating example: + +```ts +const projectId = process.argv[process.argv.indexOf("--project-id") + 1]; +const isInternal = projectId?.startsWith("internal-") ?? false; + +export default defineConfig({ + projects: [ + { id: "internal-api", root: "../api" }, + { id: "open-source-app", root: "../app" }, + ], + plugins: isInternal ? [orgPlugin()] : [], +}); +``` + +The config file is real TypeScript. Any logic at module-load time works. + +## Plugin slots + +| Slot | Purpose | +|---|---| +| `matchers` | Additional regex matchers, registered alongside the built-ins. | +| `notifiers` | Where findings get reported (Slack, GitHub Issues, webhooks, …). | +| `ownership` | Map files to owning teams/people (e.g. an internal directory). | +| `people` | Look up a person by email/name (managers, on-call, contact info). | +| `executor` | Run a deepsec command on remote infrastructure. | + +```ts +export interface DeepsecPlugin { + name: string; + matchers?: MatcherPlugin[]; + notifiers?: NotifierPlugin[]; + ownership?: OwnershipProvider; + people?: PeopleProvider; + executor?: ExecutorProvider; + agents?: AgentPluginRef[]; + commands?: (program: unknown) => void; // commander program +} +``` + +A single plugin can fill any subset. For details see https://github.com/vercel-labs/deepsec/blob/main/docs/plugins.md. + +## Models + +| Backend | Default | Used by | +|---|---|---| +| `claude` (default) | `claude-opus-4-7` | `process`, `revalidate` | +| `claude` (triage) | `claude-sonnet-4-6` | `triage` | +| `codex` | `gpt-5.5` | `process`, `revalidate` | + +CLI selection: + +```bash +bunx deepsec process --agent claude --model claude-sonnet-4-6 # cheaper Claude +bunx deepsec process --agent codex --model gpt-5.4 # cheaper Codex +bunx deepsec triage --model claude-haiku-4-5 # cheaper triage +``` + +`--agent` and `--model` are accepted on `process`, `revalidate`, and `triage`. Set the workspace-wide default via `defaultAgent` in `deepsec.config.ts`. + +## Environment variables + +deepsec reads `.env.local` (auto-loaded by the CLI) or the process environment. + +### Required (one of) + +| Var | Used by | Purpose | +|---|---|---| +| `AI_GATEWAY_API_KEY` | all AI commands | Shortcut. Expands at startup into `ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY` / `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` (one key covers Claude **and** Codex through Vercel AI Gateway). Any of those four set explicitly always wins. Falls back to `VERCEL_OIDC_TOKEN` when unset. | +| `ANTHROPIC_AUTH_TOKEN` + `ANTHROPIC_BASE_URL` | `process`, `revalidate`, `triage` (Claude) | Direct Anthropic, or BYOK gateway-issued token. | +| `OPENAI_API_KEY` (+ optional `OPENAI_BASE_URL`) | `--agent codex` | Codex SDK token. | +| `claude login` / `codex login` session | local non-sandbox runs only | Subscription fallback. Generally lacks headroom for full scans. | + +### Optional + +| Var | Purpose | +|---|---| +| `DEEPSEC_AGENT_DEBUG` | Set to `1` for verbose agent logging. | +| `DEEPSEC_DATA_ROOT` | Override the data directory (= `dataDir` in config). | +| Plugin-specific | Each plugin documents its own env vars in its README. | + +### Vercel Sandbox (optional) + +For `bunx deepsec sandbox …`. Pick OIDC for local dev, access token for unattended CI: + +```bash +# OIDC (12 h expiry, re-pull when expired) +npx vercel link +npx vercel env pull # writes VERCEL_OIDC_TOKEN + +# Access token (long-lived, headless) +VERCEL_TOKEN=… +VERCEL_TEAM_ID=team_… +VERCEL_PROJECT_ID=prj_… +``` + +The Sandbox SDK reads these directly from `process.env` at `Sandbox.create()` time. The SDK prefers `VERCEL_OIDC_TOKEN` and falls back to access-token mode otherwise. + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `Missing AI credentials for --agent claude|codex` | No credential present. | Set `AI_GATEWAY_API_KEY=vck_…` in `.env.local`, or `claude login` / `codex login`. | +| `401 Unauthorized` on `process` / `revalidate` | Credential present but rejected. | OIDC: `vercel env pull` (12 h expiry). API key: regenerate in dashboard. Confirm `.env.local` is in cwd. | +| `Stopped: Vercel AI Gateway credits exhausted` | Gateway balance is $0. | Top up at the printed URL, then re-run the same command; it resumes. | +| `Stopped: Anthropic API credits exhausted` | Direct Anthropic out of credits. | Top up at console.anthropic.com, or switch to the gateway. | +| `Stopped: OpenAI API quota exhausted` | Direct OpenAI out of quota. | Top up in the OpenAI dashboard, or switch to the gateway. | +| `Stopped: Claude Pro/Max subscription exhausted` | Hit weekly / 5-hour cap. | Switch to AI Gateway. | +| `Stopped: ChatGPT subscription exhausted` | Hit ChatGPT Plus / Pro quota. | Switch to AI Gateway. | +| Sandbox spawn fails with auth error | OIDC expired or access-token vars wrong. | `vercel env pull`, or verify the three access-token vars. | +| Findings missing cost in the log | Pricing entry missing for a non-default Codex model. | Add a line to `MODEL_PRICING_USD_PER_M_TOKENS` in `packages/processor/src/agents/codex-sdk.ts` (only matters if you are extending deepsec itself). | +| Persistent refusal on a single file (>5 % of batches) | Hard-to-disambiguate exploit pattern. | Add to `data//config.json:ignorePaths`, or run with `--batch-size 1`. | + +After **any** quota / credit fix, `process` and `revalidate` resume on re-run. No recovery flag, no state to reset. Files already analyzed stay analyzed; only unfinished ones get picked up. Use `--reinvestigate` (process) or `--force` (revalidate) only when you specifically want to redo finished work. + +## Security model of deepsec itself + +Treat deepsec like a coding agent with full shell access on the machine it runs on. It is designed to run on trusted inputs (your source code), but you may still be concerned about prompt injection from external dependencies or vendored code. + +`deepsec sandbox …` substantially limits exposure: + +- API keys are injected outside the sandbox and cannot be exfiltrated. +- Worker-sandbox network egress is locked to the configured AI host. (Egress is allowed during bootstrap, before the coding agent starts.) + +Use sandbox mode for unfamiliar / vendored / contractor codebases. Local mode is fine for your own first-party code. diff --git a/.agents/skills/oma-deepsec/resources/matchers.md b/.agents/skills/oma-deepsec/resources/matchers.md new file mode 100644 index 0000000..f22868e --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/matchers.md @@ -0,0 +1,150 @@ +# Matchers: author project-specific entry-point coverage + +The default matcher set covers common CWE shapes (SQL injection, SSRF, path traversal, …) and a handful of popular framework shapes (Next.js, Prisma, Express, Hono, FastAPI, Django, Laravel, Rails, Gin/Echo/Fiber/Chi, …). It will miss patterns specific to your codebase: an internal RPC framework, a less common language, a custom auth helper, a non-default route layout. Custom matchers fill those gaps. + +The intended loop: + +``` +scan (fast, wide) → process (AI, slow + expensive) → revalidate → write better matchers +``` + +## When to write one + +- A revalidated true-positive needs a matcher to catch siblings on future scans. +- A cluster of `other-*` slugs in `bunx deepsec metrics` points at a real category deepsec has no name for. +- The target repo has **entry points the default matchers do not see**. Check `https://github.com/vercel-labs/deepsec/blob/main/docs/supported-tech.md` first; the framework may already be covered. +- You have an **organization-specific** pattern (internal auth helper, internal SDK call, custom middleware). + +## Where matchers live + +``` +.deepsec/ +├── deepsec.config.ts # inline plugin lists the matchers +└── matchers/ + ├── my-route-no-auth.ts + └── my-internal-rpc.ts +``` + +`deepsec.config.ts`: + +```ts +import { defineConfig, type DeepsecPlugin } from "deepsec/config"; +import { myRouteNoAuth } from "./matchers/my-route-no-auth.js"; +import { myInternalRpc } from "./matchers/my-internal-rpc.js"; + +const myPlugin: DeepsecPlugin = { + name: "my-app", + matchers: [myRouteNoAuth, myInternalRpc], +}; + +export default defineConfig({ + projects: [{ id: "my-app", root: ".." }], + plugins: [myPlugin], +}); +``` + +Slugs are unique. **If your slug collides with a built-in, your matcher wins.** This is useful for swapping in a tighter org-specific version. + +If a matcher is genuinely reusable across orgs (a CWE shape or a public-framework shape), consider upstreaming to https://github.com/vercel-labs/deepsec instead. + +## Workflow + +### 1. Run `scan` + `process` first + +You want real `data/` to point the agent at. + +```bash +bunx deepsec scan +bunx deepsec process --limit 50 # upstream-recommended calibration pass (deepsec docs) +bunx deepsec revalidate --min-severity HIGH +``` + +### 2. Hand the workspace to the agent + +Open the **parent repo** (the codebase being scanned) in your coding agent so it can read both source and `.deepsec/data/`. Then prompt: + +> I want to add custom matchers to deepsec for this repo. deepsec is already installed at `.deepsec/node_modules/deepsec/` and `.deepsec/data//` has at least one scan + process pass. +> +> **Read these first to understand the contract:** +> - `.deepsec/node_modules/deepsec/dist/config.d.ts` defines the `MatcherPlugin` interface and the `regexMatcher` helper signature. +> - `.deepsec/node_modules/deepsec/dist/samples/webapp/matchers/webapp-debug-flag.ts` is a small `normal`-tier matcher. +> - `.deepsec/node_modules/deepsec/dist/samples/webapp/matchers/webapp-route-no-rate-limit.ts` is a slightly larger matcher with a negative pre-check. +> - `.deepsec/node_modules/deepsec/dist/samples/webapp/deepsec.config.ts` shows how the inline plugin wires matchers into the config. +> +> **Then do the analysis:** +> 1. Walk `.deepsec/data//files/` and look at what the default matchers already cover. Note which `vulnSlug`s show up in `candidates[]` and where the AI's `findings[]` ended up landing after revalidation. +> 2. Compare against the **target repository** (root above `.deepsec/`). Identify the **major entry points**: public HTTP handlers, RPC entry points, queue consumers, cron jobs, CLI commands, anything that takes untrusted input from the outside. Walk route/handler/api directories and framework config files (`next.config.*`, `wrangler.toml`, `serverless.yml`, `Procfile`, `main.go`, `app.py`, …) to figure out the entry-point shape. +> 3. Decide which entry points the default matchers **do not reach**. Common gaps: +> - Frameworks deepsec does not ship a glob for (Hono, Elysia, Cloudflare Workers, Bun, Deno, FastAPI, Rails controllers, Go `chi`/`gin`, internal RPC). +> - Languages with thin built-in coverage (Go, Python, Ruby, Lua, shell, Terraform, SQL). +> - Custom org-specific wrappers (auth middleware, rate-limit wrappers, request-validation helpers) where deepsec's generic regexes do not know the convention. +> 4. **Then write matchers that cover those gaps.** Prefer one matcher per concern. For each: +> - **Slug** (kebab-case, names what it flags, e.g. `hono-route-no-auth`, `worker-fetch-handler`). +> - **Noise tier**: `precise` | `normal` | `noisy` (see below). +> - **`filePatterns`** as tight as you can make them (language- or directory-anchored). +> - **Regex(es)** that match the shape. Skip test files (`.test.`, `.spec.`, `__tests__`, `_test.go`, …). +> - Save to `.deepsec/matchers/.ts`. Import types from `"deepsec/config"`. +> 5. Wire the new matchers into the inline plugin in `.deepsec/deepsec.config.ts` (create the plugin if it does not exist yet). +> 6. Run `bunx deepsec scan --matchers ,,…` from `.deepsec/` and report how many candidates each matcher fired. Open 3 candidates per matcher to spot-check the regex is not producing obvious false positives. +> +> Bias toward `precise` when you can describe the bug exactly. Use `noisy` deliberately when the goal is **entry-point coverage**: you would rather the AI look at every `**/api/**/route.ts` than rely on a regex to predict which ones are vulnerable. +> +> Generalize the *shape* of the pattern, not specific identifiers. If the repo's auth helper is `requireSession()`, the matcher should catch any handler that does not call any session/auth helper, not the literal string `requireSession`. + +### 3. Tune and ship + +```bash +bunx deepsec scan --matchers +``` + +Watch the candidate count: + +| Tier | Sweet spot | +|---|---| +| `precise` | 1–20 hits per 1k files | +| `normal` | 5–100 hits per 1k files | +| `noisy` | ≈ entry-point count of the targeted framework (10s, not 1000s) | + +0 hits → too strict (loosen). >100 hits in a small repo → too loose (tighten). + +When happy, commit `.deepsec/deepsec.config.ts` and `.deepsec/matchers/`. The next full scan picks them up automatically. + +## Noise tiers + +| Tier | When | Example | +|---|---|---| +| `precise` | Pattern is unambiguous. | `prisma-raw-sql`: `\$queryRawUnsafe\s*\(` matches only the unsafe API. | +| `normal` | Pattern is broader; AI disambiguates. | `auth-bypass`: flags admin checks and skip-auth strings; AI judges. | +| `noisy` | Every file matching a glob should be reviewed by the AI. | `service-entry-point`: every `**/api/**/route.ts` becomes a candidate. | + +Tier also influences ordering. `precise` candidates are processed first because they have the highest signal per token. + +## File globs + +Set `filePatterns` tightly. A noisy matcher with `**/*.{ts,tsx}` wedges the scanner on a 100k-file repo. Prefer: + +- Language-specific: `**/*.go`, `**/*.lua`, `**/*.tf` +- Directory-anchored: `**/api/**/*.ts`, `**/services/**/handlers/*.ts` +- Combined: `**/services/**/*.{ts,go}` + +## Worked example: covering missing entry points (FastAPI) + +A team scans a FastAPI service. After a `process` pass, `data//files/` shows the default matchers fired plenty on `requirements.txt` and a few `*.sql` files but barely touched `app/routers/*.py`, where the actual HTTP handlers live. The default glob set is tilted toward TypeScript/Next.js. + +1. **Inspect coverage.** Walk `data//files/app/routers/`. Most `FileRecord`s have empty `candidates[]`; the AI never picks them up. +2. **Identify entry points.** Each router decorates handlers with `@router.get("/…")`, `@router.post("/…")`, etc. The team's convention: authenticated handlers depend on a `current_user: User = Depends(get_current_user)` parameter. +3. **Add a noisy entry-point matcher.** Slug `fastapi-route`, `noiseTier: "noisy"`, `filePatterns: ["app/routers/**/*.py", "app/api/**/*.py"]`, regex `/@\w+\.(get|post|put|delete|patch)\s*\(/`. Every router file becomes a candidate; the AI reads them on the next `process` pass. +4. **Add a precise auth-shape matcher.** Slug `fastapi-route-no-auth`, `noiseTier: "precise"`, same globs, regex sweep for `@\w+\.(get|post|...)` whose subsequent `def`/`async def` signature lacks `Depends(get_current_user)` or `Depends(require_*)`. + +Result on the next scan: the AI investigates every router file, and the precise matcher flags handlers that skip the auth dependency. + +## Generic vs plugin vs upstream contribution + +| Catches… | Where | +|---|---| +| An org-specific helper, package, or route layout | Your inline plugin (`.deepsec/matchers/`) | +| A reference to a concrete internal service name | Your inline plugin | +| A CWE shape (path traversal, SSRF, prototype pollution) the public set misses | Consider upstreaming to https://github.com/vercel-labs/deepsec | +| A shape for a popular OSS framework (Hono, FastAPI, Drizzle) | Upstreaming benefits everyone | + +For copy-paste starting points, see `.deepsec/node_modules/deepsec/dist/samples/webapp/matchers/`. diff --git a/.agents/skills/oma-deepsec/resources/pr-review.md b/.agents/skills/oma-deepsec/resources/pr-review.md new file mode 100644 index 0000000..10a04c1 --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/pr-review.md @@ -0,0 +1,173 @@ +# PR review: `process --diff` for CI gating + +Use direct mode when you want a fast, scoped read of the files changed in a PR rather than a whole-repo audit. + +```bash +bunx deepsec process --diff origin/main +``` + +## How direct mode differs from a full scan + +| Step | What it looks at | What it produces | +|---|---|---| +| Resolve files | `--diff` / `--diff-staged` / `--diff-working` / `--files` / `--files-from` | POSIX-relative file list under `rootPath` | +| Scoped scan | Only the listed files | Candidates as **prompt signals** (best-effort) | +| Always-process | The same listed files | AI findings, including files no matcher hit | + +Files with no regex hits still get a record and still get investigated as a holistic review. + +## Diff sources (mutually exclusive) + +| Flag | Meaning | +|---|---| +| `--diff ` | `git diff --name-only ` (e.g. `origin/main`, `HEAD~1..HEAD`) | +| `--diff-staged` | Index vs HEAD | +| `--diff-working` | Uncommitted + untracked | +| `--files ` | Explicit comma-separated list | +| `--files-from ` | Newline-delimited list (or `-` for stdin) | + +Other knobs: + +| Flag | Effect | +|---|---| +| `--no-ignore` | Bypass the default ignore filter (test files, `dist/`, `node_modules/`, …) | +| `--comment-out ` | Write a PR-comment-shaped markdown summary to `` (only when findings exist) | +| `--project-id ` | Override project id (auto-derived from `rootPath` basename otherwise) | +| `--root ` | Override project root | + +The usual `--agent`, `--model`, `--concurrency`, `--batch-size`, `--max-turns` flags work the same as in standard mode. + +## Auto-created projects + +You do not need to run `deepsec init` first. With a direct-mode flag, `process` will: + +1. Use `--project-id` if you pass one (if declared in `deepsec.config.ts`, the declared root is used; otherwise `--root` or cwd). +2. Otherwise derive the id from the resolved root's basename. +3. Write `data//project.json` if absent. + +Auto-creation is one-line and non-destructive. It never modifies your `deepsec.config.ts`. + +## Exit codes (gating contract) + +| Code | Meaning | +|---|---| +| `0` | No findings produced in this run | +| `1` | At least one **net-new** finding produced | +| other | Runtime error (bad input, missing credentials, …) | + +**Net-new findings only** count toward the exit code. Re-running on a file with existing findings does not fail the build unless something new is surfaced. Pre-existing findings on touched files are intentionally excluded. + +## PR-comment markdown + +`--comment-out ` writes a markdown body summarizing the **net-new** findings only (same scope as the exit-code gate). Descriptions and recommendations are truncated (600 / 400 chars) to stay under GitHub's 65 KiB comment limit; full text remains in `data//files/`. + +The file is only written when there are findings, so a green run leaves nothing on disk and your "post comment" step can short-circuit on `if: hashFiles('comment.md') != ''`. + +## Two-job CI pattern (recommended) + +Keep PR-controlled code in a no-write job; let a second, code-free job post the comment. + +```yaml +name: deepsec + +on: pull_request + +permissions: + contents: read + +jobs: + analyze: + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # need history for `git diff origin/` + + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: { node-version: 24, cache: pnpm } + + - run: pnpm install --frozen-lockfile + - run: npm install -g @anthropic-ai/claude-code + + - id: deepsec + env: + AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }} + CLAUDE_CODE_EXECUTABLE: claude + run: | + pnpm deepsec process \ + --diff origin/${{ github.event.pull_request.base.ref }} \ + --comment-out comment.md + + - if: always() && hashFiles('comment.md') != '' + uses: actions/upload-artifact@v4 + with: + name: deepsec-comment + path: comment.md + retention-days: 1 + + comment: + needs: analyze + if: always() && needs.analyze.result == 'failure' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: write + steps: + - id: dl + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: deepsec-comment + + - if: steps.dl.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: fs.readFileSync('comment.md', 'utf8'), + }); +``` + +> Swap `pnpm deepsec` for `bunx deepsec` / `npx -y deepsec` / `yarn deepsec` to match the project's package manager. If using `bun`, replace the `pnpm/action-setup` + `setup-node(cache: pnpm)` block with `oven-sh/setup-bun` and `bun install --frozen-lockfile`. + +### Why the split + +- **`analyze`** runs PR-controlled code (the user's `pnpm install`, their config, their source) with the AI gateway secret in scope but **no write permissions on the repo**. +- **`comment`** has `pull-requests: write` but never runs any PR code; it consumes only the sanitized `comment.md` artifact. +- A malicious PR cannot combine "execute arbitrary code" with "write to the repository" in a single privileged step. + +## Threat-model notes + +- **Do not grant `pull-requests: write` to a job that runs PR code.** A PR can add arbitrary code to its own `package.json` postinstall scripts or to a project config the CLI loads. Both run before any of your steps. +- **Pin actions to full SHAs in production.** The example uses major-version tags for readability. Swap each tag for the action's full commit SHA so a compromised tag cannot pivot into your secret-bearing job. (See GitHub's hardening guide.) +- **Same-repo-only gate** (`if: github.event.pull_request.head.repo.full_name == github.repository`) skips fork PRs, which already do not receive secrets under `pull_request`. Pure UX cleanup. +- **The AI gateway secret still flows through PR code** in `analyze`. The `author_association` / same-repo gate is what prevents that from being a vulnerability. For defense-in-depth, run `analyze` only after a label is applied: + ```yaml + if: contains(github.event.pull_request.labels.*.name, 'review-ok') + ``` + +## Cost notes + +Wide diffs are expensive: every file pays for an AI investigation. + +- For PRs against `main`, scope to the merge base (`origin/main`), **not** the entire branch ancestry. +- Drop generated / fixture files via `--files-from`: + ```bash + git diff --name-only origin/main \ + | grep -v '^generated/' \ + | bunx deepsec process --files-from - + ``` +- Add stable noise paths to ignore patterns in `data//config.json:ignorePaths` so they never enter the diff. + +## When NOT to use direct mode + +- **Initial sweep of a large repo.** Full `scan` + `process` orders by noise tier, parallelizes better, and benefits from whole-repo signal in matcher gating. Direct mode is for incremental review. +- **Revalidating existing findings.** Use `revalidate` with its own filters. diff --git a/.agents/skills/oma-deepsec/resources/scanning.md b/.agents/skills/oma-deepsec/resources/scanning.md new file mode 100644 index 0000000..6689818 --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/scanning.md @@ -0,0 +1,152 @@ +# Scanning: `scan` → `process` → `triage` → `revalidate` → `export` + +All commands run from inside `.deepsec/`. `bunx deepsec …` is interchangeable with `pnpm deepsec …`, `npm exec deepsec …`, `yarn deepsec …`. + +## Pipeline + +``` +scan process revalidate enrich export / report / metrics + │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ +candidates → findings TP/FP/Fixed verdict → +committers JSON / md-dir / aggregate + +ownership +``` + +Stages are idempotent and additive. Re-running merges new info instead of overwriting. State lives under `data//`. + +## Calibration first (mandatory on > 500-file repos) + +The deepsec docs (`getting-started.md`, `vercel-setup.md`, `faq.md`) recommend `--limit 50 --concurrency 5` as the calibration starting point. Defer to a user-named value if given. + +```bash +bunx deepsec scan +bunx deepsec status # show pending / scanned counts +bunx deepsec process --limit 50 --concurrency 5 # upstream-recommended calibration +``` + +`scan` runs ~110 regex matchers across the codebase. **No AI calls.** ~15s on 2k files. Output goes to `data//files/` as one `FileRecord` JSON per scanned source file. + +The calibration `process` is a budget-capped AI pass. Read the per-batch cost the CLI prints, multiply by `(total_files / 50)` to extrapolate. **Get the user's explicit go-ahead before launching the unbounded `process`.** + +## Cost guide (Claude Opus, default settings) + +| Files | Approx cost | Approx wall time | +|---|---|---| +| 100 | $25–60 | 5–15 min | +| 500 | $130–300 | 25–60 min | +| 2,000 | $500–1,200 | 1.5–4 hr | + +Costs swing 2–3× based on file complexity. Codex is cheaper per call; Opus is the precision benchmark. + +## Full investigation + +```bash +bunx deepsec process --concurrency 5 +``` + +Defaults: `--agent claude` (`claude-opus-4-7`), `--batch-size 5`, `--concurrency 5` ⇒ 25 files in flight at peak. Files are claimed atomically via `lockedByRunId`; multiple workers can run in parallel without stepping on each other. + +For a cheaper backend: + +```bash +bunx deepsec process --agent codex --model gpt-5.5 +``` + +Codex runs in a strict read-only sandbox and is fast at grep-heavy investigations. Backends mix freely within a project: re-process unconvincing findings with the other agent, and findings dedupe across agents. + +### Resume after interruption + +`process` and `revalidate` are safe to re-run. Network blip, transient model error, quota stop, Ctrl-C → re-run the **same** command. Files already finished are skipped. **Nothing to clean up.** Never `rm -rf data//` to "start clean" without explicit user instruction. + +### Reinvestigate finished work + +Use `--reinvestigate` (entire repo) or `--reinvestigate ` (wave marker) when a stronger model lands or you want a second opinion. Findings dedupe across agents; the new analysis appends to `analysisHistory` rather than overwriting. + +## Triage and revalidate + +```bash +bunx deepsec triage --severity HIGH +bunx deepsec revalidate --min-severity HIGH +``` + +| Stage | What | Cost | +|---|---|---| +| `triage` | Classifies findings P0/P1/P2/skip from finding text only (no code re-read). Claude Sonnet by default. | ~$0.01 / finding | +| `revalidate` | Re-reads code + git history, emits `true-positive` / `false-positive` / `fixed` / `uncertain` verdicts and may adjust severity. | Comparable to `process` | + +`revalidate` empirically cuts FP rate by 50%+ on most repos. Run it on `HIGH+` before surfacing anything to the user. + +## Export + +```bash +bunx deepsec export --format md-dir --out ./findings # one .md per finding under {CRITICAL,HIGH,…}/ +bunx deepsec export --format json --out findings.json # single JSON array, pipe-friendly +bunx deepsec metrics # aggregate counts, severities, TP rates +bunx deepsec report # per-project markdown + JSON summary +``` + +Each command takes `--project-id ` if your config has multiple projects. + +## Useful flags + +| Flag | Purpose | +|---|---| +| `--limit ` | Cap files processed in this run. | +| `--concurrency ` | Parallel batches in flight. Lower for laptop-friendliness or quota-friendliness. | +| `--batch-size ` | Files per batch (default 5). | +| `--max-turns ` | Cap agent conversation turns per batch. | +| `--agent claude|codex` | Backend selection. | +| `--model ` | Override per-backend model (`claude-sonnet-4-6`, `gpt-5.5-pro`, `claude-haiku-4-5`, …). | +| `--matchers ` | CSV of slugs; restricts the matcher set on `scan`. Overrides `matchers.only` in config when both are set. | +| `--reinvestigate` / `--reinvestigate ` | Force re-analysis on `process`. | +| `--force` | Force re-analysis on `revalidate`. | +| `--project-id ` | Pick a project when more than one is registered. | +| `--root ` | Override project root for one-off scans. | + +## Reading `data/` directly + +`data//files/**/*.json` are `FileRecord`s. Useful jq one-liners: + +```bash +# All TP HIGH+ findings +jq -r '. as $r | $r.findings[] | select(.revalidation.verdict=="true-positive") | select(.severity=="HIGH" or .severity=="CRITICAL") | [$r.filePath, .severity, .title] | @tsv' data//files/**/*.json + +# Total spend on this project +jq -s 'map(.analysisHistory[].costUsd // 0) | add' data//files/**/*.json + +# Files still pending after the latest run +jq -r 'select(.status=="pending") | .filePath' data//files/**/*.json +``` + +For richer queries, prefer `bunx deepsec export --format json`. Its filters match the rest of the CLI. + +## Cron / scheduled CI + +```bash +# Sunday cron: full scan +bunx deepsec scan +bunx deepsec process --concurrency 5 +bunx deepsec revalidate --min-severity HIGH +bunx deepsec export --format json --out findings.json +``` + +Persist `.deepsec/data/` between runs (cache it as a build artifact) or re-scan from scratch each time. The append-only model means cached `data/` strictly improves cost on the next run. + +## Distributed (`sandbox`) + +Large monorepos can fan work across Vercel Sandbox microVMs: + +```bash +bunx deepsec sandbox process --project-id my-app --sandboxes 10 --concurrency 4 +``` + +Local working tree is tarballed (`.git` excluded) and uploaded. Sandbox-level network egress is locked to the configured AI host(s); the gateway key is injected outside the sandbox so it cannot be exfiltrated. Use this when the repo is large enough that local concurrency saturates your machine, or when running unattended in CI/CD. + +See `config.md` for Sandbox auth (OIDC vs access token). + +## What `process` does not do + +- Does not modify source code. Findings are advisory. +- Does not commit / push / open PRs. Hand off to `oma-scm` if the user wants commits. +- Does not call out to non-AI external services unless a notifier plugin is configured. +- Does not phone home or report telemetry; `data//` stays on your machine unless explicitly exported. diff --git a/.agents/skills/oma-deepsec/resources/setup.md b/.agents/skills/oma-deepsec/resources/setup.md new file mode 100644 index 0000000..4cdcb85 --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/setup.md @@ -0,0 +1,113 @@ +# Setup: install `.deepsec/` and bootstrap `INFO.md` + +## 1. Install the workspace + +Requires **Node.js 22+**. Run from the **root of the codebase you want to scan**: + +```bash +bunx deepsec init # creates .deepsec/ and registers this repo +cd .deepsec +bun install # installs deepsec from npm + +# pnpm / npm / yarn equivalents work the same way: +# npx deepsec init && cd .deepsec && pnpm install +# npx deepsec init && cd .deepsec && npm install +# npx deepsec init && cd .deepsec && yarn install +``` + +`init` lays down a minimal scaffold inside `.deepsec/`: + +- `package.json` +- `deepsec.config.ts` (one `projects[]` entry pointing at `..`, id derived from the parent dir's basename) +- `data//INFO.md` (template with section placeholders) +- `data//SETUP.md` (per-project agent prompt) +- workspace-level `AGENTS.md` +- `.env.local` +- `.gitignore` (keeps `INFO.md`, `SETUP.md`, `deepsec.config.ts` tracked; ignores `data/*/files/`, `data/*/runs/`, etc.) + +No custom matchers in the scaffold. Add those only when a real finding shapes one for you. + +> To scan another codebase from the same `.deepsec/`: `bunx deepsec init-project ` (relative paths resolve against `.deepsec/`'s parent). + +## 2. Pick a credential + +Open `.deepsec/.env.local` and pick **one**: + +| Mode | When | Set | +|---|---|---| +| AI Gateway API key | Anywhere, simplest | `AI_GATEWAY_API_KEY=vck_…` from the Vercel AI Gateway API Keys page | +| Vercel OIDC token | Already linked to a Vercel project (or using Sandbox) | `npx vercel link && npx vercel env pull` writes `VERCEL_OIDC_TOKEN` (12 h expiry; re-pull on auth errors) | +| Direct Anthropic | BYOK / bypass gateway | `ANTHROPIC_AUTH_TOKEN=sk-ant-…` + `ANTHROPIC_BASE_URL=https://api.anthropic.com` | +| Direct OpenAI | Codex backend, BYOK | `OPENAI_API_KEY=sk-…` (+ `OPENAI_BASE_URL` only for proxies) | +| Subscription | Local-only evaluation | `claude login` and/or `codex login` already done; non-sandbox runs reuse the session, no token needed | + +`AI_GATEWAY_API_KEY` expands at CLI startup into `ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY` / `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL`. Any of those four set explicitly always wins. + +> Subscriptions are useful for evaluating deepsec but generally do not have enough headroom for full repo scans. Switch to the gateway once past evaluation. + +## 3. Verify the credential + +```bash +bunx deepsec scan --limit 20 # cheap, no AI calls +bunx deepsec process --limit 5 # exercises the gateway +``` + +If the second call returns `Missing AI credentials` or `401`, see `config.md` § Troubleshooting. + +## 4. Write `INFO.md` (do not skip) + +`INFO.md` is what makes deepsec project-aware. It is injected into the AI prompt for every batch, so vague content here means vague findings. + +### Recommended: agent-driven write-up + +Open the **parent repo** (the codebase you scanned, **not** `.deepsec/`) in your coding agent and paste the prompt that `deepsec init` printed (also in the project root README): + +> Read `.deepsec/node_modules/deepsec/SKILL.md` to understand the tool. Then read `.deepsec/data//SETUP.md` and follow it: skim this repo's README, any `AGENTS.md` / `CLAUDE.md`, and a handful of representative code files, then replace each section of `.deepsec/data//INFO.md`. +> +> Keep it SHORT: target 50–100 lines total. Pick 3–5 examples per section, not exhaustive enumeration. Name primitives (auth helpers, middleware) but no line numbers. Skip generic CWE categories; built-in matchers cover those. Cover only what is project-specific. `INFO.md` is injected into every scan batch; verbose context dilutes signal. + +### Manual write-up + +The processor auto-loads `data//INFO.md` from the workspace's data dir. Edit it directly; no extra wiring is needed in `deepsec.config.ts`. Even a single tight paragraph noticeably improves the AI's output. + +### What goes in `INFO.md` + +Project-specific only: + +- **What the codebase does** in a few sentences. +- **Auth shape**: names of helpers / middleware / decorators that gate access (`requireSession`, `Depends(get_current_user)`, etc.). Name them, do not quote them. +- **Threat model**: which surfaces matter (public HTTP, internal RPC, queue consumers, cron, CLI) and which are out of scope. +- **Known FP sources**: patterns the AI tends to over-flag in this repo. +- **Project-specific primitives**: internal SDK calls, custom validators, codified secret-loading paths. +- **Out of scope**: directories or file types the AI should ignore. + +### What stays out + +- Generic CWE category descriptions; built-in matchers cover those. +- Exhaustive enumeration. Pick 3–5 representative examples per section. +- Line numbers. They drift; the AI re-reads files anyway. +- Boilerplate intro paragraphs. + +## 5. `.gitignore` hygiene + +The scaffold's `.deepsec/.gitignore` already keeps `INFO.md`, `SETUP.md`, and `deepsec.config.ts` tracked (so teammates inherit project context) and ignores generated state. Do **not** unignore `data/*/files/` or `data/*/runs/` unless you have a deliberate reason (e.g. CI cache). + +`.env.local` must stay gitignored. Never commit `vck_…`, `sk-ant-…`, `sk-…`, or OIDC tokens. + +## 6. Multi-project workspaces + +To scan a *different* codebase from the same `.deepsec/`: + +```bash +bunx deepsec init-project +``` + +Each project gets its own `data//` subdirectory. Pass `--project-id ` to disambiguate any subsequent command (auto-resolution only kicks in with exactly one project). + +## 7. Sanity check before the first real run + +- [ ] `.deepsec/.env.local` has a working credential. +- [ ] `bunx deepsec scan --limit 20` succeeds. +- [ ] `bunx deepsec process --limit 5` succeeds and prints a per-batch cost number. +- [ ] `data//INFO.md` is filled in (50-100 lines, project-specific). +- [ ] You and the user agree on a calibration scope for the first `process` run (deepsec docs default: `--limit 50 --concurrency 5`). diff --git a/.agents/skills/oma-deepsec/resources/triage.md b/.agents/skills/oma-deepsec/resources/triage.md new file mode 100644 index 0000000..12ead17 --- /dev/null +++ b/.agents/skills/oma-deepsec/resources/triage.md @@ -0,0 +1,107 @@ +# Triage: read findings, cut false positives, prioritize work + +## Severity vocabulary + +| Severity | Meaning | +|---|---| +| `CRITICAL` | Pre-auth or trivially exploitable issue with broad blast radius. | +| `HIGH` | Real vulnerability, likely exploitable in this codebase's context. | +| `MEDIUM` | Conditional vulnerability or one with significant attacker prerequisites. | +| `LOW` | Defense-in-depth gap, or correctness issue with weak security framing. | +| `HIGH_BUG` / `BUG` | Real bug the agent declined to call a vulnerability; typically correctness with security-adjacent risk. | + +`triage` adds a `priority` field on top of severity: + +| Priority | Trigger | +|---|---| +| `P0` | Drop everything; the exploit is trivial and the impact is critical. | +| `P1` | This sprint. | +| `P2` | Backlog. | +| `skip` | Not worth fixing (nuance, intended behavior, false alarm). | + +## Read order + +Do not show the user raw `process` output for HIGH+ findings. The right pipeline is: + +1. `bunx deepsec process` (or `process --diff` for PR mode). +2. `bunx deepsec triage --severity HIGH` to bucket findings into P0/P1/P2 (~$0.01 / finding). +3. `bunx deepsec revalidate --min-severity HIGH` re-reads the code and git history, then emits a verdict. The cost is comparable to `process`, and FP rate drops by 50%+. +4. `bunx deepsec export --format md-dir --out ./findings` to surface results to the user. + +`revalidate` verdicts: + +| Verdict | Action | +|---|---| +| `true-positive` | Fix it. Hand off to `oma-debug` or the matching domain skill. | +| `false-positive` | Note in `INFO.md` if the FP shape is recurring. Adjust matchers if it is a regex-level over-match. | +| `fixed` | The finding refers to code that was already patched in git history; no action. | +| `uncertain` | Re-run with the other agent, or escalate to a human reviewer. | + +## Cutting FP rate + +Two things help most: + +1. **Always `revalidate` before acting on `HIGH+`.** Worth the cost. +2. **Tighten `INFO.md`.** Even one paragraph about the auth shape, threat model, and known FP sources improves precision a lot. See `setup.md` § 4. + +After revalidation, FP rate on `HIGH+` typically lands in the 10–29 % range. + +## Refusals + +Models occasionally refuse to investigate a candidate (exploit-shaped source, content filter). After every batch deepsec asks the agent whether anything was skipped; `refused: true` appears in `RunMeta` and on the `FileRecord.refusal` field. The per-batch log shows a `refusal` marker. + +Handling: + +- A refused batch produces no false negatives. Affected files stay `pending`, so re-run `--reinvestigate` against the **other** backend (Claude ↔ Codex) to pick up the dropped sites. Findings dedupe across agents. +- If a single file consistently triggers refusals (>5 % of batches), add it to `data//config.json:ignorePaths`, **or** run that file alone with `--batch-size 1` so a refusal does not take an otherwise-fine batch down with it. +- Never silently drop a refusal. Document it in the user-facing summary. + +## Reading severity counts + +```bash +bunx deepsec metrics +``` + +Shows cross-project counts: severities, vulns by type, TPs after revalidation. Use it to decide where matcher investment pays off (clusters of `other-*` slugs are the strongest signal). + +## Per-finding markdown export shape + +`bunx deepsec export --format md-dir --out ./findings` produces: + +``` +findings/ +├── CRITICAL/ +├── HIGH/ +├── MEDIUM/ +├── LOW/ +└── BUG/ +``` + +Each file contains: severity, title, `vulnSlug`, file path with line numbers, description, recommendation, confidence, triage verdict (if run), revalidation verdict (if run), and an `analysisHistory` summary. Use these as inputs to issue tracker tickets; the structure is friendly to GitHub Issues / Linear / Jira import scripts. + +## When to *not* surface a finding + +- `revalidation.verdict === "false-positive"`. +- `revalidation.verdict === "fixed"` and the fix matches the current `HEAD`. +- `triage.priority === "skip"` with reasoning the user agrees with. +- Severity below the user-stated `severity_floor`. + +For everything else: surface it, with verdict, recommendation, and the file path. + +## Hand-off + +Route by **the layer of the vulnerable file**, judged from each finding's `filePath` + `vulnSlug` + `revalidation.verdict` against the project's own signals: `data//tech.json`, `INFO.md`, `priorityPaths`, and the actual directory structure. Do not bake a slug or path enumeration into this skill. Deepsec evolves its matcher set and project layouts vary, so trust the artifact at runtime. + +| Layer of the vulnerable file | Specialist | +|---|---| +| Backend / server / API | `oma-backend` | +| Frontend / web client | `oma-frontend` | +| Mobile / native client | `oma-mobile` | +| IaC / cloud / network | `oma-tf-infra` | +| Database / data model | `oma-db` | +| CI / workflow / supply chain | `oma-dev-workflow` | +| Documentation drift surfaced by the run | `oma-docs` | + +**Ambiguity → `oma-debug` first.** Route to `oma-debug` whenever the layer is not obvious from the artifact: shared / isomorphic / utility code, an `other-*` slug, a fix that would touch multiple layers, `revalidation.verdict === "uncertain"`, or `BUG` / `HIGH_BUG` non-security correctness without an obvious owner. The hop is **triage, not fix**: pin the exact file:line and re-route to the right specialist with a layer-tagged finding. Fix inline only when the change is a single isolated line and the diagnosis is confident. Record the second-hop owner in the run summary. + +Attach to every routed item: file path, severity, `vulnSlug`, revalidation verdict, recommendation, and the export markdown path. diff --git a/.agents/skills/oma-design/SKILL.md b/.agents/skills/oma-design/SKILL.md index 52dd35f..c37f202 100644 --- a/.agents/skills/oma-design/SKILL.md +++ b/.agents/skills/oma-design/SKILL.md @@ -9,23 +9,150 @@ description: > # oma-design -## Role +## Scheduling + +### Goal Design specialist that defines, creates, and validates project design systems. -DESIGN.md is the central artifact — all design work revolves around it. +DESIGN.md is the central artifact; all design work revolves around it. + +### Intent signature +- User asks for design system, `DESIGN.md`, visual direction, typography, color, motion, accessibility, anti-pattern review, or component guidance. +- User needs design decisions before frontend implementation or wants UI quality audited from a design perspective. + +### When to use +- Defining or revising a project design system +- Creating or auditing `DESIGN.md` +- Selecting typography, color, layout, motion, or component direction +- Reviewing UI work for responsive behavior, accessibility, and visual quality +- Using optional vendor inspiration from Stitch MCP or getdesign + +### When NOT to use +- Implementing frontend components or application UI -> use `oma-frontend` +- Planning product scope or task breakdown -> use `oma-pm` +- Backend, database, infrastructure, or mobile implementation -> use the relevant specialist skill +- General quality/security review outside visual, interaction, and accessibility concerns -> use `oma-qa` + +### Expected inputs +- Product, brand, audience, platform, and UI/design problem +- Existing `.design-context.md`, `DESIGN.md`, screenshots, references, or component constraints +- Accessibility, responsive, language, and implementation constraints + +### Expected outputs +- Design direction, revised `DESIGN.md`, audit findings, component guidance, or handoff notes +- Responsive-first, WCAG-aware design recommendations +- Optional vendor seed attribution when getdesign is used + +```yaml +outputs: + - name: design-doc + description: Updated DESIGN.md when the run materially advances the design system + artifact: "DESIGN.md" + required: false + - name: design-context + description: Refreshed .design-context.md snapshot when a discovery pass runs + artifact: ".design-context.md" + required: false +``` + +### Dependencies +- `.design-context.md` and `DESIGN.md` +- Design resources, references, anti-pattern catalog, and optional Stitch/getdesign integrations +- shadcn/component library context when recommending components + +### Control-flow features +- Branches by missing context, CJK language support, vendor seed availability, and anti-pattern audit results +- May read/write design docs and call optional design/vendor tooling +- Requires user confirmation before generation when multiple directions exist + +## Structural Flow + +### Entry +1. Check `.design-context.md`; if missing, run setup before design work. +2. Identify target audience, platform, content language, and design artifact. +3. Decide whether vendor inspiration or Stitch integration is relevant. + +### Scenes +1. **PREPARE**: Load design context and constraints. +2. **ACQUIRE**: Extract existing design signals, references, and anti-pattern risks. +3. **REASON**: Propose directions, typography, color, layout, motion, and accessibility choices. +4. **ACT**: Generate or revise `DESIGN.md` and related guidance. +5. **VERIFY**: Audit responsive behavior, WCAG, Nielsen heuristics, and AI-slop patterns. +6. **FINALIZE**: Handoff design decisions and attribution where required. + +### Transitions +- If `.design-context.md` is missing, create it before continuing. +- If CJK support is needed, prioritize CJK-ready fonts. +- If vendor seed fetch fails, choose retry, continue without seed, or abort. +- If anti-patterns appear, surface alternatives before finalizing. -## Core Rules +### Failure and recovery +- If design context is insufficient, ask for one focused clarification or propose assumptions. +- If vendor inspiration is unavailable, continue with local design synthesis. +- If accessibility checks fail, revise before handoff. + +### Exit +- Success: design artifact is project-specific, responsive-first, accessible, and audit-ready. +- Partial success: missing context, vendor failure, or open design decision is explicit. + +## Logical Operations + +### Actions +| Action | SSL primitive | Evidence | +|--------|---------------|----------| +| Read design context | `READ` | `.design-context.md`, `DESIGN.md`, references | +| Select design direction | `SELECT` | 2-3 directions and recommended option | +| Infer visual system | `INFER` | Typography, color, layout, motion | +| Call optional tooling | `CALL_TOOL` | Stitch/getdesign/shadcn when relevant | +| Write design artifact | `WRITE` | `DESIGN.md` or audit output | +| Validate design quality | `VALIDATE` | Checklist, WCAG, anti-patterns | +| Report handoff | `NOTIFY` | Final design summary | + +### Tools and instruments +- Design references, anti-pattern catalog, checklist, Stitch integration, getdesign fetcher +- shadcn CLI recommendations when component guidance is needed + +### Canonical workflow path +```text +1. Check `.design-context.md`; create it if missing. +2. Produce 2-3 design directions and get confirmation. +3. Generate or revise `DESIGN.md`, then run the design checklist. +``` + +Optional vendor seed discovery: +```bash +bunx getdesign@latest list +``` + +### Resource scope +| Scope | Resource target | +|-------|-----------------| +| `LOCAL_FS` | `.design-context.md`, `DESIGN.md`, design resources | +| `CODEBASE` | Existing UI and component patterns | +| `NETWORK` | Optional getdesign/vendor references | +| `PROCESS` | Optional CLI/tool invocations | + +### Preconditions +- Target design problem and artifact are identifiable. +- Design context exists or setup can create it. + +### Effects and side effects +- May create or modify `DESIGN.md` and design context artifacts. +- May fetch vendor seed material and append MIT attribution. +- Does not implement frontend code directly. + +### Guardrails 1. Check `.design-context.md` before any design work. If missing, run Phase 1 (Setup) to create it. 2. System font stack as default (`system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`). Add custom fonts only with project justification. 3. If the service supports CJK languages (ko/ja/zh): prioritize CJK-ready fonts (Pretendard Variable > Noto Sans CJK > system-ui fallback). If latin-only: choose fonts appropriate for the target audience. -4. Enforce anti-patterns strictly — reject AI slop. See `resources/anti-patterns.md`. +4. Enforce anti-patterns strictly; reject AI slop. See `resources/anti-patterns.md`. 5. Name colors semantically with hex values: "Deep Ocean Navy (#1a2332)" not "dark blue". 6. Recommend components with install commands (shadcn CLI). 7. ALL output must be responsive-first (mobile layout as default, enhance upward). 8. WCAG AA minimum for all designs. Respect `prefers-reduced-motion`. -9. Stitch MCP is optional — all phases work without it. +9. Stitch MCP is optional; all phases work without it. 10. Present 2-3 design directions and get user confirmation before generating. -## Anti-Pattern Quick Reference +### Anti-Pattern Quick Reference ### Typography - DON'T: Default to custom Google Fonts when system fonts suffice @@ -40,7 +167,7 @@ DESIGN.md is the central artifact — all design work revolves around it. - DON'T: Gradient orbs/blobs as hero decoration ("AI SaaS look") - DON'T: Gradient + glassmorphism + blur combo (triple slop) - DON'T: Mesh gradient backgrounds as primary visual -- DON'T: Pure white (#fff) on pure black (#000) — too harsh +- DON'T: Pure white (#fff) on pure black (#000); too harsh - DO: Solid colors or subtle single-hue gradients - DO: Texture (noise, grain, dither) over plain gradients - DO: Derive gradients from brand colors with clear purpose @@ -61,16 +188,16 @@ DESIGN.md is the central artifact — all design work revolves around it. - DO: 150ms micro-interactions, 200-500ms transitions ### Components -- DON'T: Glassmorphism everywhere — use sparingly +- DON'T: Glassmorphism everywhere; use sparingly - DON'T: Hover-only interactions without touch/keyboard alternatives - DO: shadcn/ui for base, Aceternity UI / React Bits for accent effects - DO: All interactive elements must have visible focus states -## Workflow Summary +### Workflow Summary 7 phases: Setup → Extract → Enhance → Propose → Generate → Audit → Handoff. See `resources/execution-protocol.md` for full detail. -## Vendor Inspiration (getdesign) +### Vendor Inspiration (getdesign) Phase 2 can optionally seed from the community [getdesign](https://getdesign.md) catalog @@ -80,8 +207,8 @@ MIT). Trigger it by listing a supported vendor domain in the ```markdown ## Reference Sites -- [linear.app](https://linear.app) — clean dark UI, minimal, professional -- [stripe.com](https://stripe.com) — strong hierarchy, purposeful animation +- [linear.app](https://linear.app): clean dark UI, minimal, professional +- [stripe.com](https://stripe.com): strong hierarchy, purposeful animation ``` Any domain that matches a brand in the getdesign manifest triggers an @@ -105,28 +232,28 @@ required MIT compliance footer. Full fetcher rules, matching algorithm, injection defenses, and multi-vendor merge policy live in `resources/getdesign-fetcher.md`. -## Resources -- `resources/execution-protocol.md` — 7-phase workflow -- `resources/anti-patterns.md` — Full DO/DON'T catalog -- `resources/checklist.md` — Audit checklist (Responsive + WCAG + Nielsen + Slop) -- `resources/design-md-spec.md` — DESIGN.md generation guide (9 sections) -- `resources/design-tokens.md` — CSS/Tailwind/shadcn export templates -- `resources/prompt-enhancement.md` — Vague request → detailed spec -- `resources/stitch-integration.md` — Stitch MCP tool mapping (optional) -- `resources/getdesign-fetcher.md` — Vendor seed fetch, hash verify, seed rules -- `resources/error-playbook.md` — Design error recovery +### Resources +- `resources/execution-protocol.md`: 7-phase workflow +- `resources/anti-patterns.md`: Full DO/DON'T catalog +- `resources/checklist.md`: Audit checklist (Responsive + WCAG + Nielsen + Slop) +- `resources/design-md-spec.md`: DESIGN.md generation guide (9 sections) +- `resources/design-tokens.md`: CSS/Tailwind/shadcn export templates +- `resources/prompt-enhancement.md`: Vague request to detailed spec +- `resources/stitch-integration.md`: Stitch MCP tool mapping (optional) +- `resources/getdesign-fetcher.md`: Vendor seed fetch, hash verify, seed rules +- `resources/error-playbook.md`: Design error recovery ## References -- `reference/visual-hierarchy.md` — 7 hierarchy principles (Alignment, Color, Contrast, Proximity, Size, Texture, Time) -- `reference/typography.md` — Font selection, type scale, CJK -- `reference/color-and-contrast.md` — Color psychology, WCAG contrast -- `reference/spatial-design.md` — 8px grid, breakpoints, spacing -- `reference/motion-design.md` — motion/react, GSAP, Three.js, ogl, Temporal UX -- `reference/responsive-design.md` — Mobile-first, theme system -- `reference/component-patterns.md` — shadcn/Aceternity/React Bits catalog -- `reference/accessibility.md` — WCAG 2.2, ARIA, focus, reduced-motion -- `reference/shader-and-3d.md` — WebGL, R3F, ogl, performance - -## Examples -- `examples/design-context-example.md` — .design-context.md example -- `examples/landing-page-prompt.md` — Detailed landing page prompt +- `reference/visual-hierarchy.md`: 7 hierarchy principles (Alignment, Color, Contrast, Proximity, Size, Texture, Time) +- `reference/typography.md`: Font selection, type scale, CJK +- `reference/color-and-contrast.md`: Color psychology, WCAG contrast +- `reference/spatial-design.md`: 8px grid, breakpoints, spacing +- `reference/motion-design.md`: motion/react, GSAP, Three.js, ogl, Temporal UX +- `reference/responsive-design.md`: Mobile-first, theme system +- `reference/component-patterns.md`: shadcn/Aceternity/React Bits catalog +- `reference/accessibility.md`: WCAG 2.2, ARIA, focus, reduced-motion +- `reference/shader-and-3d.md`: WebGL, R3F, ogl, performance + +### Examples +- `examples/design-context-example.md`: .design-context.md example +- `examples/landing-page-prompt.md`: Detailed landing page prompt diff --git a/.agents/skills/oma-design/examples/design-context-example.md b/.agents/skills/oma-design/examples/design-context-example.md index 1aa5726..892e8c3 100644 --- a/.agents/skills/oma-design/examples/design-context-example.md +++ b/.agents/skills/oma-design/examples/design-context-example.md @@ -1,4 +1,4 @@ -# .design-context.md — Example +# .design-context.md: Example This is an example of what `.design-context.md` looks like after Phase 1 (Setup). The file lives in the project root and captures project-specific design decisions. @@ -12,7 +12,7 @@ The file lives in the project root and captures project-specific design decision ## Target Audience - **Role**: Sales leaders, revenue ops managers, growth teams -- **Tech level**: Moderate — comfortable with dashboards, not developers +- **Tech level**: Moderate; comfortable with dashboards, not developers - **Age range**: 28-45 - **Context**: Evaluating tools during work hours, often on laptop @@ -37,8 +37,8 @@ The file lives in the project root and captures project-specific design decision ## Color Direction - **Background**: Deep near-black (#0a0a0a) -- **Text**: Warm off-white (#f5f0eb) — not pure white -- **Primary accent**: Signal Green (#22c55e) — CTAs, success states +- **Text**: Warm off-white (#f5f0eb), not pure white +- **Primary accent**: Signal Green (#22c55e) for CTAs, success states - **Avoid**: Purple gradients, rainbow effects, mesh gradients - **Borders**: White at 10% opacity (rgba(255,255,255,0.1)) @@ -49,9 +49,9 @@ The file lives in the project root and captures project-specific design decision - **Touch targets**: 44x44pt minimum on mobile ## Reference Sites -- [linear.app](https://linear.app) — clean dark UI, minimal, professional -- [vercel.com](https://vercel.com) — developer-premium aesthetic, great typography -- [stripe.com](https://stripe.com) — strong hierarchy, purposeful animation +- [linear.app](https://linear.app): clean dark UI, minimal, professional +- [vercel.com](https://vercel.com): developer-premium aesthetic, great typography +- [stripe.com](https://stripe.com): strong hierarchy, purposeful animation > **Note**: every domain in this section is automatically matched > against the `getdesign` vendor catalog during Phase 1. All three @@ -60,7 +60,7 @@ The file lives in the project root and captures project-specific design decision > vendor matching, use a domain that is not in the catalog (e.g., an > internal design reference or a custom portfolio URL). See > `resources/getdesign-fetcher.md` for the matching algorithm and the -> Seed Application Rules — notably, Typography is never adopted from +> Seed Application Rules. Notably, Typography is never adopted from > the vendor seed, so the Pretendard Variable choice in this file will > still win on the Korean-localized project above. diff --git a/.agents/skills/oma-design/examples/landing-page-prompt.md b/.agents/skills/oma-design/examples/landing-page-prompt.md index 23b12d4..44c4c92 100644 --- a/.agents/skills/oma-design/examples/landing-page-prompt.md +++ b/.agents/skills/oma-design/examples/landing-page-prompt.md @@ -1,4 +1,4 @@ -# Landing Page Design Prompt — Example +# Landing Page Design Prompt: Example This is an example of the level of detail Phase 3 (Enhance) should produce. Based on motionsites.ai-level specifications. @@ -14,7 +14,7 @@ Based on motionsites.ai-level specifications. ## Design System ### Fonts -- Heading: Instrument Serif (italic) — display headings only +- Heading: Instrument Serif (italic) for display headings only - Body: system-ui stack (or Pretendard for CJK) ### CSS Variables @@ -48,7 +48,7 @@ Based on motionsites.ai-level specifications. ### HERO (full viewport) - **Layout**: centered, min-h-screen, flex column - **Background**: video (mp4, autoplay loop muted) with gradient overlay to black at bottom -- **Badge**: liquid-glass rounded-full pill — "New" tag + announcement text +- **Badge**: liquid-glass rounded-full pill with "New" tag + announcement text - **Heading**: BlurText component (motion/react), word-by-word blur-to-clear animation - text-6xl md:text-7xl lg:text-[5.5rem] font-heading italic - leading-[0.8] tracking-[-4px] @@ -61,7 +61,7 @@ Based on motionsites.ai-level specifications. ### PARTNERS BAR - **Layout**: centered column, below hero -- **Badge**: liquid-glass rounded-full — "Trusted by the teams behind" +- **Badge**: liquid-glass rounded-full labeled "Trusted by the teams behind" - **Names**: horizontal row, text-2xl md:text-3xl font-heading italic text-white, gap-12 - **Companies**: Stripe, Vercel, Linear, Notion, Figma - **Responsive**: reduce gap, text-xl on mobile, wrap if needed @@ -71,7 +71,7 @@ Based on motionsites.ai-level specifications. - **Background**: HLS video (hls.js), absolute cover, z-0 - Top + bottom fade gradients (200px each, black ↔ transparent) - **Content** (z-10, centered): - - Badge: liquid-glass rounded-full — "How It Works" + - Badge: liquid-glass rounded-full labeled "How It Works" - Heading: "You dream it. We ship it." - Subtext: description paragraph - Button: liquid-glass-strong rounded-full + ArrowUpRight @@ -82,7 +82,7 @@ Based on motionsites.ai-level specifications. - **Row 1** (text left, image right): - H3 + paragraph + CTA button - Image in liquid-glass rounded-2xl container -- **Row 2** (image left, text right — lg:flex-row-reverse): +- **Row 2** (image left, text right; lg:flex-row-reverse): - Same structure, reversed layout - **Responsive**: stack vertically, image above text on mobile @@ -125,7 +125,7 @@ Based on motionsites.ai-level specifications. ## Dependencies - hls.js (HLS video streaming) -- motion (animation — import from "motion/react") +- motion (animation; import from "motion/react") - lucide-react (icons) - tailwindcss-animate diff --git a/.agents/skills/oma-design/reference/accessibility.md b/.agents/skills/oma-design/reference/accessibility.md index 509c35d..2b57b6e 100644 --- a/.agents/skills/oma-design/reference/accessibility.md +++ b/.agents/skills/oma-design/reference/accessibility.md @@ -49,12 +49,12 @@ const prefersReduced = useReducedMotion() ### Landmarks Every page must use these semantic elements: -- `
` — site header with navigation -- `