Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .agents/agents/docs-curator.md
Original file line number Diff line number Diff line change
@@ -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_CHECK_BEGIN -->

## 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
<!-- CHARTER_CHECK_END -->

## 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 <range> --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 <our.first.fluke@gmail.com>`
4 changes: 4 additions & 0 deletions .agents/agents/variants/claude.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"tools": "Read, Grep, Glob, Bash",
"maxTurns": 15,
"effort": "low"
},
"docs-curator": {
"tools": "Read, Write, Edit, Bash, Grep, Glob",
"maxTurns": 15
}
}
}
6 changes: 6 additions & 0 deletions .agents/agents/variants/codex.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
"extra": {
"sandbox_mode": "read-only"
}
},
"docs-curator": {
"effort": "medium",
"extra": {
"sandbox_mode": "workspace-write"
}
}
}
}
5 changes: 5 additions & 0 deletions .agents/agents/variants/cursor.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
"extra": {
"is_background": true
}
},
"docs-curator": {
"extra": {
"is_background": true
}
}
}
}
3 changes: 2 additions & 1 deletion .agents/agents/variants/gemini.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"qa-reviewer": {
"tools": ["bash", "glob", "grep", "read", "ask"]
}
},
"docs-curator": {}
}
}
77 changes: 46 additions & 31 deletions .agents/config/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# 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.
# 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)
Expand All @@ -16,12 +16,12 @@ agent_defaults:
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.3-codex", effort: "high" }
frontend: { model: "openai/gpt-5.4", effort: "high" }
mobile: { model: "openai/gpt-5.4", effort: "high" }
db: { model: "openai/gpt-5.3-codex", effort: "high" }
debug: { model: "openai/gpt-5.3-codex", effort: "high" }
tf-infra: { model: "openai/gpt-5.4", effort: "high" }
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:
Expand All @@ -43,16 +43,16 @@ runtime_profiles:
codex-only:
description: "Codex-only — ChatGPT Plus/Pro"
agent_defaults:
orchestrator: { model: "openai/gpt-5.4", effort: "medium" }
architecture: { model: "openai/gpt-5.4-pro", effort: "high" }
qa: { model: "openai/gpt-5.4", effort: "high" }
pm: { model: "openai/gpt-5.4", effort: "medium" }
backend: { model: "openai/gpt-5.3-codex", effort: "high" }
frontend: { model: "openai/gpt-5.4", effort: "high" }
mobile: { model: "openai/gpt-5.4", effort: "high" }
db: { model: "openai/gpt-5.3-codex", effort: "high" }
debug: { model: "openai/gpt-5.3-codex", effort: "high" }
tf-infra: { model: "openai/gpt-5.4", effort: "high" }
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-only:
Expand All @@ -77,25 +77,40 @@ runtime_profiles:
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.3-codex", effort: "high" }
frontend: { model: "openai/gpt-5.4", effort: "high" }
mobile: { model: "openai/gpt-5.4", effort: "high" }
db: { model: "openai/gpt-5.3-codex", effort: "high" }
debug: { model: "openai/gpt-5.3-codex", effort: "high" }
tf-infra: { model: "openai/gpt-5.4", effort: "high" }
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" }

cursor-only:
description: "Cursor-only — Cursor Pro / Pro Student"
agent_defaults:
orchestrator: { model: "cursor/composer-2-fast" }
architecture: { model: "cursor/composer-2" }
qa: { model: "cursor/composer-2-fast" }
pm: { model: "cursor/composer-2-fast" }
backend: { model: "cursor/composer-2" }
frontend: { model: "cursor/composer-2" }
mobile: { model: "cursor/composer-2" }
db: { model: "cursor/composer-2" }
debug: { model: "cursor/composer-2" }
tf-infra: { model: "cursor/composer-2" }
retrieval: { model: "cursor/composer-2-fast" }

qwen-only:
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-coder-plus", thinking: true }
qa: { model: "qwen/qwen3-coder-plus", thinking: true }
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-coder-plus", thinking: true }
frontend: { model: "qwen/qwen3-coder-plus", thinking: true }
mobile: { model: "qwen/qwen3-coder-plus", thinking: true }
db: { model: "qwen/qwen3-coder-plus", thinking: true }
debug: { model: "qwen/qwen3-coder-plus", thinking: true }
tf-infra: { model: "qwen/qwen3-coder-plus", thinking: true }
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 }
22 changes: 22 additions & 0 deletions .agents/hooks/core/constants.ts
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions .agents/hooks/core/fs-utils.ts
Original file line number Diff line number Diff line change
@@ -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;
}
90 changes: 90 additions & 0 deletions .agents/hooks/core/hook-output.ts
Original file line number Diff line number Diff line change
@@ -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, unknown>,
): 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,
},
});
}
}
Loading