mirror of
https://github.com/kochetkov-ma/claude-brewcode.git
synced 2026-09-14 20:16:41 +08:00
v4.1.0: add /brewdoc:docsync doc-staleness tracker, remove auto-sync
- New /brewdoc:docsync skill: user-run generator installs project-local hooks (track/watch/gate) + config; tracks stale docs by last_updated date; modes init/status/sync/reread/frontmatter/uninstall; confirm-before-sync gate - Removed auto-sync skill, bd-auto-sync-processor agent, auto-sync:* frontmatter tags (~44 files), and skill-creator template injection - Docs updated at all levels (README, MDX, navigation, guide catalog, CLAUDE.md)
This commit is contained in:
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../.codex/marketplace.json
|
||||
@@ -6,13 +6,13 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Claude Code plugin suite: brewcode for infinite task execution, brewdoc for documentation tools, brewtools for text utilities, brewui for UI/visual/creative tools",
|
||||
"version": "4.0.6"
|
||||
"version": "4.1.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "brewcode",
|
||||
"description": "Brewcode - full-featured development platform for Claude Code: infinite focus tasks, prompt optimization, skill/agent creation, quorum reviews, rules management",
|
||||
"version": "4.0.6",
|
||||
"version": "4.1.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"brewcode",
|
||||
@@ -45,19 +45,17 @@
|
||||
},
|
||||
{
|
||||
"name": "brewdoc",
|
||||
"description": "Brewdoc - Claude Code documentation tools: auto-sync for skills/agents/rules, my-claude installation docs, memory optimization, md-to-pdf conversion",
|
||||
"version": "4.0.6",
|
||||
"description": "Brewdoc - Claude Code documentation tools: my-claude installation docs, memory optimization, md-to-pdf conversion",
|
||||
"version": "4.1.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"brewdoc",
|
||||
"documentation",
|
||||
"auto-sync",
|
||||
"memory",
|
||||
"claude-code"
|
||||
],
|
||||
"tags": [
|
||||
"documentation",
|
||||
"auto-sync",
|
||||
"memory-management",
|
||||
"my-claude"
|
||||
],
|
||||
@@ -74,7 +72,7 @@
|
||||
{
|
||||
"name": "brewtools",
|
||||
"description": "Brewtools - universal utilities for Claude Code: text optimization, humanization, secrets scanning",
|
||||
"version": "4.0.6",
|
||||
"version": "4.1.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"brewtools",
|
||||
@@ -102,7 +100,7 @@
|
||||
{
|
||||
"name": "brewui",
|
||||
"description": "Placeholder for future UI/visual/creative tools (currently empty, installable)",
|
||||
"version": "4.0.6",
|
||||
"version": "4.1.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"ui",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "claude-brewcode-codex-local",
|
||||
"interface": {
|
||||
"displayName": "Brewcode Codex Local"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "brewcode",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./.codex/plugins/brewcode"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Developer Tools"
|
||||
},
|
||||
{
|
||||
"name": "brewdoc",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./.codex/plugins/brewdoc"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
},
|
||||
{
|
||||
"name": "brewtools",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./.codex/plugins/brewtools"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Developer Tools"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "brewcode",
|
||||
"version": "4.0.6+codex.20260713192629",
|
||||
"description": "Codex-native workflows for agents, conventions, project rules, reviews, and teams.",
|
||||
"author": { "name": "Maksim Kochetkov", "email": "apmatypa88@gmail.com", "url": "https://github.com/kochetkov-ma" },
|
||||
"homepage": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"repository": "https://github.com/kochetkov-ma/claude-brewcode.git",
|
||||
"license": "MIT",
|
||||
"keywords": ["codex", "development", "rules", "agents", "review"],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Brewcode",
|
||||
"shortDescription": "Codex development workflows",
|
||||
"longDescription": "Reusable Codex workflows for agent authoring, conventions, indexed project rules, team coordination, and deep reviews.",
|
||||
"developerName": "Maksim Kochetkov",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": ["Read", "Write", "Interactive"],
|
||||
"websiteURL": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"defaultPrompt": ["Use Brewcode to prepare a verified implementation plan."]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readInput, respond } from './lib/io.mjs';
|
||||
import { PROMPT_CONTEXT, promptIsDue } from './lib/prompt-cadence.mjs';
|
||||
|
||||
try {
|
||||
const input = await readInput();
|
||||
if (input.hook_event_name !== 'UserPromptSubmit' || !promptIsDue(input.session_id)) {
|
||||
respond({});
|
||||
} else {
|
||||
const prompt = typeof input.prompt === 'string' ? input.prompt.trim() : '';
|
||||
const skip = /^(?:yes|no|y|n|ok|okay|thanks|done|cancel|stop|continue|proceed|approved?|confirm(?:ed)?|\d+)$/i;
|
||||
if (!prompt || prompt.startsWith('$') || skip.test(prompt)) {
|
||||
respond({});
|
||||
} else {
|
||||
respond({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'UserPromptSubmit',
|
||||
additionalContext: PROMPT_CONTEXT
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
respond({});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${PLUGIN_ROOT}/hooks/session-start.mjs\"",
|
||||
"timeout": 3,
|
||||
"statusMessage": "Loading Brewcode context"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${PLUGIN_ROOT}/hooks/forced-eval.mjs\"",
|
||||
"timeout": 2,
|
||||
"statusMessage": "Checking Brewcode skills"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export async function readInput() {
|
||||
const chunks = [];
|
||||
for await (const chunk of process.stdin) chunks.push(chunk);
|
||||
const raw = Buffer.concat(chunks).toString('utf8');
|
||||
const input = JSON.parse(raw);
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('hook input must be an object');
|
||||
if (process.env.NODE_ENV === 'test' && process.env.CODEX_HOOK_TEST_DELAY_MS) {
|
||||
const delay = Number(process.env.CODEX_HOOK_TEST_DELAY_MS);
|
||||
if (Number.isFinite(delay) && delay > 0) await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
export function respond(value = {}) {
|
||||
process.stdout.write(`${JSON.stringify(value)}\n`);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { appendFileSync, chmodSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const STATE_DIR = process.env.CODEX_BREWCODE_HOOK_STATE_DIR
|
||||
|| process.env.PLUGIN_DATA
|
||||
|| path.join(os.tmpdir(), 'codex-brewcode-hooks');
|
||||
const PROMPT_INTERVAL = 5;
|
||||
const STALE_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
export const PROMPT_CONTEXT = [
|
||||
'[SKILL?] Check the available Codex skills and invoke every matching skill before acting.',
|
||||
'[HINT] Use Codex sub-agent collaboration for substantial independent work.',
|
||||
'[ROLE] Coordinate specialized work when delegation is requested; keep simple tasks direct.'
|
||||
].join('\n');
|
||||
|
||||
function sessionKey(sessionId) {
|
||||
if (typeof sessionId !== 'string' || !sessionId || sessionId.length > 4096) return null;
|
||||
return createHash('sha256').update(sessionId, 'utf8').digest('hex');
|
||||
}
|
||||
|
||||
function ensureStateDir() {
|
||||
mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
||||
chmodSync(STATE_DIR, 0o700);
|
||||
}
|
||||
|
||||
function counterPath(key) {
|
||||
return path.join(STATE_DIR, `${key}.counter`);
|
||||
}
|
||||
|
||||
function pruneStaleCounters() {
|
||||
const cutoff = Date.now() - STALE_MS;
|
||||
for (const name of readdirSync(STATE_DIR)) {
|
||||
if (!name.endsWith('.counter')) continue;
|
||||
const file = path.join(STATE_DIR, name);
|
||||
try {
|
||||
if (statSync(file).mtimeMs < cutoff) unlinkSync(file);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export function resetPromptCounter(sessionId) {
|
||||
const key = sessionKey(sessionId);
|
||||
if (!key) return;
|
||||
try {
|
||||
ensureStateDir();
|
||||
const file = counterPath(key);
|
||||
writeFileSync(file, '', { mode: 0o600 });
|
||||
chmodSync(file, 0o600);
|
||||
pruneStaleCounters();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function promptIsDue(sessionId) {
|
||||
const key = sessionKey(sessionId);
|
||||
if (!key) return false;
|
||||
try {
|
||||
ensureStateDir();
|
||||
const file = counterPath(key);
|
||||
appendFileSync(file, 'x', { mode: 0o600 });
|
||||
chmodSync(file, 0o600);
|
||||
const count = statSync(file).size;
|
||||
return Number.isSafeInteger(count) && count > 0 && count % PROMPT_INTERVAL === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { readInput, respond } from './lib/io.mjs';
|
||||
import { PROMPT_CONTEXT, resetPromptCounter } from './lib/prompt-cadence.mjs';
|
||||
|
||||
try {
|
||||
const input = await readInput();
|
||||
if (input.hook_event_name !== 'SessionStart') {
|
||||
respond({});
|
||||
} else {
|
||||
resetPromptCounter(input.session_id);
|
||||
const pluginRoot = process.env.PLUGIN_ROOT || '';
|
||||
let version = 'unknown';
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginRoot, '.codex-plugin', 'plugin.json'), 'utf8'));
|
||||
if (typeof manifest.version === 'string') version = manifest.version;
|
||||
} catch {}
|
||||
respond({
|
||||
systemMessage: `Brewcode ${version} loaded for Codex`,
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: `Brewcode skills use Codex-native paths, tools, hooks, and sub-agent conventions.\n${PROMPT_CONTEXT}`
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
respond({});
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# Agents
|
||||
|
||||
Manages Codex subagents across all scopes — create new agents, improve existing ones, audit quality, or inspect what is installed. Input is ONE free-form natural-language prompt; there are no keyword subcommands.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
$brewcode:agents
|
||||
```
|
||||
|
||||
No arguments: presents the interactive menu with Status (agents) pre-selected as the recommended action.
|
||||
|
||||
Pass a free-form prompt to skip the menu entirely:
|
||||
|
||||
```
|
||||
$brewcode:agents create a backend validator agent for Java Spring projects
|
||||
```
|
||||
|
||||
## How It Works — Unified 6-Step Flow
|
||||
|
||||
Every invocation goes through the same flow:
|
||||
|
||||
1. **Input gate** — reads `<arguments>`; if empty, goes to the interactive menu.
|
||||
2. **Auto-mode select** — infers mode from the prompt and announces:
|
||||
`Mode: <mode> (agents) — chosen because <evidence>`
|
||||
3. **No-prompt menu** — when no arguments given, shows a single `request_user_input`:
|
||||
- Status (agents) [recommended]
|
||||
- Status (all: agents + rules + skills)
|
||||
- Create
|
||||
- Improve
|
||||
- Review
|
||||
- List (plain)
|
||||
- Cancel
|
||||
4. **Dispatch** — routes to `brewcode:agent-creator` subagent (create / improve / review / batch) or runs Glob `*.md` over agent scopes directly (list mode).
|
||||
5. **Real status** — rich inventory by scope showing agent names, models, trigger coverage, and last-modified — not a flat file listing.
|
||||
6. **Mandatory final output** — structured summary of what was created, modified, or reviewed. Omitted only for `list` mode.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | How it activates | What it does |
|
||||
|------|-----------------|--------------|
|
||||
| `status` | Default when no other mode is detected | Shows agents per scope, model breakdown, trigger coverage |
|
||||
| `list` | Explicit only — "list", "show agents", "what agents" | Globs `*.md` over all agent scopes, plain file listing |
|
||||
| `create` | "create", "add", "new agent" in prompt | agent-creator builds frontmatter + system prompt from description |
|
||||
| `improve` | "improve", "update", "refine", or agent name/path in prompt | agent-creator enhances an existing agent file per chosen focus |
|
||||
| `review` | "review", "check", "audit" in prompt | agent-creator audits agent files for quality and coverage gaps |
|
||||
| `batch` | "all", "multiple", "both" or plural scope detected | agent-creator fans out across all matching agents in one pass |
|
||||
|
||||
## Parameters for Create / Improve
|
||||
|
||||
| Parameter | Options | Notes |
|
||||
|-----------|---------|-------|
|
||||
| Scope | Project (`.codex/agents/`), Global (`~/.codex/agents/`), Plugin (`brewcode/agents/`) | Asked via single request_user_input |
|
||||
| Model | `balanced model` (recommended), `high-reasoning model` / `fable`, `fast model`, `inherit` | `inherit` omits the `model:` field entirely |
|
||||
| AGENTS.md update | Yes / No | Adds or updates the agents table row in AGENTS.md |
|
||||
| Improve focus | `triggers`, `system-prompt`, `both`, `full review` | Improve mode only |
|
||||
| Description budget | <=100 characters | Create mode — used as the agent's frontmatter `description` seed |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Open the interactive menu
|
||||
$brewcode:agents
|
||||
|
||||
# Check the current state of all installed agents
|
||||
$brewcode:agents what agents do we have
|
||||
|
||||
# Create a new agent with a plain-English description
|
||||
$brewcode:agents create a SQL migration reviewer for PostgreSQL
|
||||
|
||||
# Improve an existing agent by describing what to fix
|
||||
$brewcode:agents improve the reviewer agent's trigger keywords
|
||||
|
||||
# Audit all agents for quality issues
|
||||
$brewcode:agents review all project agents
|
||||
|
||||
# Plain listing of agent files across all scopes
|
||||
$brewcode:agents list
|
||||
```
|
||||
|
||||
## Output — Agent Scopes
|
||||
|
||||
Agent files are located in or written to the directory matching the chosen scope:
|
||||
|
||||
| Scope | Directory |
|
||||
|-------|-----------|
|
||||
| Project | `.codex/agents/` |
|
||||
| Global | `~/.codex/agents/` |
|
||||
| Plugin | `brewcode/agents/` |
|
||||
|
||||
Status and list modes report agents from all three scopes simultaneously. Create and improve modes write to whichever scope the user selects during the guided prompt.
|
||||
|
||||
## Tips
|
||||
|
||||
- Run `$brewcode:agents` with no arguments to get the menu — the guided flow is faster than remembering free-form phrases.
|
||||
- The **"inherit" model** option omits the `model:` field entirely, so the agent uses whatever model the calling session runs on.
|
||||
- After creation, verify the generated triggers in the agent's `description` frontmatter — trigger quality is the primary driver of automatic agent selection.
|
||||
- Use `improve` with focus `triggers` periodically on high-use agents to incorporate new project vocabulary and updated invocation patterns.
|
||||
- `list` is the fastest way to count agents and spot scope imbalance before a review session.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full docs: [agents](https://doc-claude.brewcode.app/brewcode/skills/agents/)
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: agents
|
||||
description: "Creates and improves Codex subagents. Triggers: create agent, improve agent, scaffold agent, fix agent."
|
||||
---
|
||||
|
||||
# Codex agent authoring
|
||||
|
||||
Create or improve project agents as TOML files under `.codex/agents/`. Inspect existing agents first, keep each role narrow, and use only supported keys such as `name`, `description`, and `developer_instructions`. Validate every result with Python `tomllib`. Do not create Markdown agent definitions or edit installed plugin caches.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
# agents Skill
|
||||
|
||||
> **Agent Management:** create, improve, review, and report on Codex agents from one free-form prompt.
|
||||
|
||||
<instructions>
|
||||
|
||||
## Constants
|
||||
|
||||
| Const | Value |
|
||||
|-------|-------|
|
||||
| ARTIFACT | `agents` |
|
||||
| SPECIALIST | `brewcode:agent-creator` |
|
||||
| LIST_CMD | Glob `*.md` over `.codex/agents/`, `~/.codex/agents/`, `brewcode/agents/` |
|
||||
|
||||
## Step 1 — Input gate
|
||||
|
||||
Treat the **entire** user input (`<arguments>`) as ONE free-form natural-language prompt.
|
||||
There is NO keyword grammar and NO argument parser — `argument-hint` is only a loose example.
|
||||
|
||||
- prompt non-empty -> go to **Step 2**
|
||||
- prompt empty / whitespace-only -> go to **Step 3**
|
||||
|
||||
## Step 2 — Auto-mode selection
|
||||
|
||||
Classify the prompt + recent conversation context into exactly ONE mode:
|
||||
|
||||
| Mode | Chosen when prompt signals |
|
||||
|------|----------------------------|
|
||||
| `status` | "статус", "что есть", "состояние", health / overview / "show me" (DEFAULT for any "show me" intent) |
|
||||
| `list` | explicit "список" / "list" / "перечисли" ONLY |
|
||||
| `create` | "создай" / "create" / "new" / "добавь" / "scaffold" |
|
||||
| `improve` | "улучши" / "improve" / "refactor" / "fix" / "почини", OR a bare existing name/path |
|
||||
| `review` | "ревью" / "review" / "validate" / "проверь корректность" |
|
||||
|
||||
**Batch flag:** plural form, "все" / "all", or multiple names/paths -> fan-out (one specialist spawn per item).
|
||||
|
||||
Then **ANNOUNCE the chosen mode (MANDATORY, before any work):**
|
||||
|
||||
```
|
||||
Mode: <mode> (agents) — chosen because <evidence quoted from the prompt>
|
||||
```
|
||||
|
||||
Proceed to **Step 4**.
|
||||
|
||||
## Step 3 — No-prompt menu (single request_user_input, scoped + cross-link)
|
||||
|
||||
Ask ONE request_user_input. Question: `What do you want to do with agents?`
|
||||
Options (in this order):
|
||||
|
||||
- `Status (agents)` — **(Recommended)** rich status of this artifact
|
||||
- `Status (all: agents+rules+skills)` — cross-link: run the collector for all three
|
||||
- `Create new agents`
|
||||
- `Improve existing agents`
|
||||
- `Review agents`
|
||||
- `List (plain)`
|
||||
- `Nothing / cancel`
|
||||
|
||||
After the choice:
|
||||
- `Nothing / cancel` -> stop.
|
||||
- `create` or `improve` -> ask ONE follow-up request_user_input for the target/description
|
||||
plus the artifact-specific params (see "Artifact-specific params" below).
|
||||
- Then ANNOUNCE the mode using the Step 2 format and proceed to **Step 4**.
|
||||
|
||||
## Step 4 — Dispatch
|
||||
|
||||
- `status` -> go to **Step 5**.
|
||||
- `status (all)` -> go to **Step 5**, running the collector for agents + rules + skills together.
|
||||
- `list` -> run `LIST_CMD`, print the plain inventory it produces, then STOP (no status assembly).
|
||||
- `create` -> gather minimal params (Step 3 / artifact-specific), spawn `SPECIALIST` via sub-agent task.
|
||||
Batch -> spawn one `SPECIALIST` per item, ALL in ONE message (parallel).
|
||||
- `improve` -> resolve target(s), spawn `SPECIALIST` via sub-agent task per target (parallel for batch).
|
||||
- `review` -> spawn `brewcode:reviewer` (two-phase: review -> double-check findings -> report).
|
||||
|
||||
## Step 5 — Real status (NOT a flat list)
|
||||
|
||||
Delegate collection to ONE Explore/Bash subagent, then assemble a rich status (never a bare list):
|
||||
|
||||
- **Inventory by scope:** plugin (BC) / project (`.codex/`) / global (`~/.codex/`) — counts + names + load path.
|
||||
- **State:** enabled/disabled (toggle markers `_SKILL.md` / `_<name>.md`), model.
|
||||
- **Overlaps / conflicts:** same-name across scopes (shadowing), duplicate triggers/descriptions, naming collisions.
|
||||
- **Health flags:** missing README/frontmatter; agents missing `Bash` in `tools:` (macOS search rule);
|
||||
skills with weak description triggers; rules duplicated in AGENTS.md.
|
||||
|
||||
For the `Status (all)` menu option: run the SAME collector for agents + rules + skills together.
|
||||
|
||||
## Step 6 — Final formatted output (MANDATORY for every run except `list`)
|
||||
|
||||
```
|
||||
# agents [<mode>]
|
||||
## Detection
|
||||
| Input | <prompt or "(none -> menu)"> |
|
||||
| Mode | <mode> |
|
||||
| Reason | <why this mode> |
|
||||
| Targets| <names/paths> |
|
||||
## Result
|
||||
(create/improve/review: each output path + specialist agent + scope/model)
|
||||
## Status
|
||||
(status mode: full table from Step 5; else short "what changed" for touched artifacts)
|
||||
## Next Steps
|
||||
(recommendations; ALWAYS remind to run /docs for any created/changed artifact)
|
||||
```
|
||||
|
||||
For `status` mode the report **is** the Step 5 status table.
|
||||
|
||||
## Artifact-specific params (create / improve only)
|
||||
|
||||
For `create`: ONE request_user_input batch — (Q1) scope: Project `.codex/agents/` /
|
||||
Global `~/.codex/agents/` / Plugin `brewcode/agents/`; (Q2) model: balanced model (Recommended) /
|
||||
high-reasoning model / fast model / inherit (omit model: field); (Q3) update AGENTS.md agents table? yes/no.
|
||||
Frontmatter description budget: <= 100 chars, single line, role + 2-3 triggers, EN only.
|
||||
Spawn SPECIALIST (brewcode:agent-creator) with the description, scope+path, model.
|
||||
After creation, if user approved, update the AGENTS.md agents table via Edit (add/replace row).
|
||||
For `improve`: resolve agent by name/path across the 3 scopes; ONE request_user_input —
|
||||
(Q1) focus: triggers / system-prompt / both (Recommended) / full review; (Q2) update AGENTS.md? yes/no.
|
||||
Spawn SPECIALIST to improve, then optional AGENTS.md row update.
|
||||
|
||||
</instructions>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Agents"
|
||||
short_description: "Creates and improves Codex subagents. Triggers: create agent, im"
|
||||
default_prompt: "Use $brewcode:agents for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Convention for Codex
|
||||
|
||||
Extracts evidence-backed project conventions from representative code and tests. When accepted conventions become durable rules, apply them directly under `.codex/rules/`, update the `AGENTS.md` rule index, and optimize the changed text through `$brewtools:text-optimize -l`.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: convention
|
||||
description: "Extracts etalon classes, patterns, architecture into convention docs. Triggers: extract conventions, etalon classes."
|
||||
---
|
||||
|
||||
# Extract conventions
|
||||
|
||||
Inspect representative production code and tests, identify repeated architectural and implementation patterns, and write concise convention documents to the user-selected Codex-owned path. Cite concrete repository files, distinguish enforced rules from observations, and avoid changing application code unless the user explicitly asks.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read the applicable `AGENTS.md` files and repository architecture documentation before analysis.
|
||||
2. Resolve the requested scope: full repository, convention documents only, rule extraction from existing convention documents, or explicit paths.
|
||||
3. Inventory languages, frameworks, module boundaries, build files, tests, migrations, and existing convention material with focused `rg` searches.
|
||||
4. Select representative production and test files for each relevant layer. Prefer repeated current patterns over isolated legacy examples.
|
||||
5. Record evidence for architecture boundaries, dependency direction, naming, data models, error handling, persistence, external integrations, testing, and deployment constraints.
|
||||
6. For each candidate convention, cite concrete paths, state whether it is enforced or observed, identify exceptions, and name the preferred reference implementation.
|
||||
7. Write compact English convention documents under the requested project `.codex/` path. Preserve unrelated content and do not duplicate full rule bodies in `AGENTS.md`.
|
||||
8. If the user requests durable rules, extract accepted candidates, deduplicate them against all `.codex/rules/*.md` files and applicable `AGENTS.md` instructions, and apply them directly without a dedicated organizer agent.
|
||||
9. Update the root `AGENTS.md` rule-index table so every project rule appears exactly once with columns `Rule`, `Load when`, and `Purpose`.
|
||||
10. Invoke `$brewtools:text-optimize -l` for every changed convention, rule, and index file. Compare semantics before accepting optimized text.
|
||||
11. Re-run the file inventory, validate every cited path, and report documents changed, rules added or merged, duplicates skipped, and unresolved conflicts.
|
||||
|
||||
Use Codex collaboration only when the user or active repository instructions explicitly require delegation. When delegation is allowed, prefer project-specific agents and keep one bounded evidence-gathering surface per agent.
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Convention"
|
||||
short_description: "Extracts etalon classes, patterns, architecture into convention"
|
||||
default_prompt: "Use $brewcode:convention for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -0,0 +1,35 @@
|
||||
# Analysis Layers
|
||||
|
||||
> 20 layers (14 main + 6 test) for project convention extraction.
|
||||
|
||||
## Main Layers (L1–L14)
|
||||
|
||||
| ID | Layer | File Patterns | Analyze | Etalon Criteria | Naming | Stack Notes |
|
||||
|----|-------|--------------|---------|-----------------|--------|-------------|
|
||||
| L1 | Build | `pom.xml`, `build.gradle*`, `Makefile`, `Cargo.toml` | Build tool/version, plugin config, profiles, multi-module structure | Clean plugin org (build vs reporting), consistent property mgmt, proper profile separation (dev/test/prod), correct multi-module inheritance | `pom.xml`, `build.gradle.kts`, `Makefile`, `Cargo.toml` | Java: Maven/Gradle plugins, BOM imports \| TS: `scripts` in package.json \| Python: pyproject.toml sections \| Rust: workspace Cargo.toml |
|
||||
| L2 | Dependencies | `pom.xml`, `package.json`, `requirements.txt`, `go.mod` | BOM/platform imports, shared vs module-specific deps, version property strategy, vulnerability/license compliance | BOM-first version mgmt, no duplicate version declarations, clear compile/test/runtime scope separation, minimal direct version overrides | version props: `{lib}.version`, BOM: `*-bom`, `*-dependencies` | Java: `<dependencyManagement>`, Spring Boot BOM \| TS: `peerDependencies` vs `dependencies` \| Python: `[project.dependencies]` vs `requirements.txt` |
|
||||
| L3 | CodeGen | `**/openapi*`, `**/jooq*`, `**/proto*`, `**/codegen*` | Generator types (OpenAPI, JOOQ, protobuf, GraphQL), config org, output dir conventions, custom templates | Generator config separated from build config, output in `generated-sources`, clean build lifecycle integration | `openapi-generator-config.yml`, `jooq-config.xml`, output: `target/generated-sources/{generator}/` | Java: maven-plugin execution \| TS: `openapi-generator-cli`, `graphql-codegen` \| Python: `openapi-python-client` |
|
||||
| L4 | Utilities | `**/util*`, `**/common*`, `**/shared*`, `**/helpers*` | `@UtilityClass` vs static vs singleton, converter patterns, shared constants/enums, cross-module reuse | `@UtilityClass` or equivalent static-only pattern, no DI deps, clear single responsibility, follows project naming | `*Utils`, `*Helper`, `*Converter`, `*Support`, `*Constants` | Java: `@UtilityClass` (Lombok) \| TS: barrel exports, pure functions \| Python: module-level functions, `@staticmethod` |
|
||||
| L5 | Controllers | `**/controller*`, `**/resource*`, `**/handler*`, `**/route*` | DI style, security annotations, HTTP mapping approach (generated interface vs manual), error handling, response types | Constructor injection with `@RequiredArgsConstructor`, implements generated API interface or clean manual endpoints, security annotations per method | `*Controller`, `*Resource`, `*Api`, `*Handler` | Java: Spring MVC `@RestController` \| TS: NestJS `@Controller`, Express routers \| Python: FastAPI routers, Flask blueprints |
|
||||
| L6 | Services | `**/service*` | DI patterns, transaction boundaries, interface+impl separation, Stream API vs loops, error handling strategy | `@RequiredArgsConstructor` + final fields, clear interface/impl separation, Stream API over loops, proper `@Transactional` boundaries, single responsibility | `*Service`, `*ServiceImpl`, `*UseCase`, `*Interactor` | Java: `@Service`, `@Transactional` \| TS: `@Injectable()` class services \| Python: service classes, dependency injection |
|
||||
| L7 | Providers/Clients | `**/client*`, `**/provider*`, `**/gateway*`, `**/adapter*` | Client type (Feign, RestTemplate, WebClient), resilience patterns, fallback strategies, integration facade wrapping | Declarative client (`@FeignClient` or equiv), resilience on every method (`@CircuitBreaker` + `@RateLimiter`), integration facade with null guards/logging/conversion, clean fallbacks | `*Client`, `*Provider`, `*Gateway`, `*Adapter` | Java: `@FeignClient`, Resilience4j \| TS: Axios/fetch wrappers \| Python: `httpx`, `aiohttp` clients |
|
||||
| L8 | Repositories/Data | `**/repositor*`, `**/dao*`, `**/store*`, `**/query*` | Data access strategy (JOOQ, Spring Data, raw SQL, ORM), Support/Helper companions, mapper patterns, dynamic query building (`noCondition`), multi-DB support | Support `@UtilityClass` with field constants and mappers, clean dynamic condition building, proper PREWHERE/WHERE separation (ClickHouse), interface+impl for multiple backends | `*Repository`, `*RepositoryImpl`, `*Support`, `*Dao` | Java: JOOQ DSL, Spring Data JPA, `@Repository` \| TS: TypeORM, Prisma, Drizzle \| Python: SQLAlchemy, raw SQL |
|
||||
| L9 | Business Logic | `**/domain/service*`, `**/rules*`, `**/engine*`, `**/workflow*` | Domain service vs application service patterns, rule engines, validation strategies, domain event handling | Pure domain logic (no infrastructure deps), clear validation rules, immutable domain operations, strategy/specification patterns where appropriate | `*DomainService`, `*Rule`, `*Engine`, `*Calculator`, `*Validator` | Java: DDD patterns, Spring \| TS: domain classes \| Python: domain services, business rules |
|
||||
| L10 | DTOs | `**/dto*`, `**/model*`, `**/request*`, `**/response*` | DTO style (records, `@Value @Builder`, mutable), naming conventions, validation annotations, nested DTOs | Immutable (records or `@Value @Builder`), clear suffix conventions (Request, Response, Filter), compact constructors with validation, no business logic | `*Request`, `*Response`, `*Dto`, `*Filter`, `*View` | Java: records (preferred), `@Value @Builder` \| TS: interfaces, type aliases \| Python: `@dataclass(frozen=True)`, Pydantic models |
|
||||
| L11 | Entities | `**/entity*`, `**/domain/model*`, `**/schema*` | Entity style (`@Value @Builder`, records, JPA `@Entity`), ID strategy (generated/natural/composite), relationship mapping, evolution toward immutable | `Entity` suffix consistently applied, immutable where possible, clean constructor with required field validation, no `@Data` on entities | `*Entity`, domain models: descriptive nouns without suffix | Java: `@Value @Builder`, records, JPA `@Entity` \| TS: TypeORM entities, Prisma models \| Python: SQLAlchemy models, dataclasses |
|
||||
| L12 | Migrations | `**/migration*`, `**/db*`, `**/liquibase*`, `**/flyway*` | Migration tool (Flyway, Liquibase, raw DDL, Alembic), naming conventions (versioned vs repeatable), schema mgmt strategy, data migration patterns | Consistent naming `V{N}__{description}.sql` or changelog format, separate DDL from DML, idempotent repeatable migrations, proper FK-safe table ordering | `V{N}__{desc}.sql`, `{N}-{desc}.xml`, `*_ddl_v{N}.sql` | Java: Flyway/Liquibase \| TS: Prisma migrations, Drizzle \| Python: Alembic, Django migrations |
|
||||
| L13 | Resources | `**/resources/**`, `**/static/**`, `**/templates/**` | Config file org (application.yml, profiles), template engine usage, static asset mgmt, property naming conventions | Profile-based config separation, consistent property naming (kebab-case for Spring), no secrets in committed configs, clear template org | `application-{profile}.yml`, `bootstrap.yml`, `*.properties` | Java: Spring profiles, YAML \| TS: `.env.*`, config modules \| Python: `settings.py`, `.env` |
|
||||
| L14 | Infrastructure | `**/config*`, `**/security*`, `**/cache*`, `**/scheduling*` | `@Configuration` patterns, security config (OAuth2, JWT, RBAC), cache strategy (local + distributed), scheduling patterns, cross-cutting concerns (AOP, interceptors) | Focused `@Configuration` (one concern per class), role-based security, dual cache (Caffeine + Redis), scheduled jobs with locks and feature flags, clean `@ConditionalOnProperty` | `*Configuration`, `*Config`, `*SecurityConfig`, `*CacheConfig` | Java: `@Configuration`, `@EnableScheduling` \| TS: NestJS modules, middleware \| Python: FastAPI middleware, settings classes |
|
||||
|
||||
> **L6/L9 Overlap:** L9 (Business Logic) applies only when project has explicit `domain/service/`, `rules/`, `engine/` packages (DDD structure). Otherwise, merge L9 analysis into L6 (Services) findings.
|
||||
|
||||
## Test Layers (T1–T6)
|
||||
|
||||
| ID | Layer | File Patterns | Analyze | Etalon Criteria | Naming | Stack Notes |
|
||||
|----|-------|--------------|---------|-----------------|--------|-------------|
|
||||
| T1 | Test Data | `**/test**/datasets/**`, `**/test**/fixtures/**`, `**/test**/data/**` | Data format (CSV, JSON, SQL, YAML), organization (per-test vs shared), placeholder/variable substitution, table ordering for FK constraints | Organized by feature/module, placeholder substitution for dynamic values, `table-ordering.txt` for FK-safe loading, minimal dataset per test | `{feature}/{test-name}/`, `table-ordering.txt`, `*.csv` | Java: DBRider CSV/JSON, `@DataSet` \| TS: fixtures, seed files \| Python: pytest fixtures, factory_boy |
|
||||
| T2 | Base Classes | `**/test**/Base*`, `**/test**/Abstract*` | Test hierarchy (base → domain-specific → concrete), shared setup (`@BeforeAll`, `@BeforeEach`), container mgmt (Testcontainers), composite annotations | Clear hierarchy (max 2-3 levels), composite annotations for common setup, Testcontainers in base class, cache cleanup in base setup | `Base*IntegrationTest`, `Abstract*Test` | Java: `@SpringBootTest`, Testcontainers \| TS: test setup files, beforeAll \| Python: pytest conftest.py, base classes |
|
||||
| T3 | Test Helpers | `**/test**/support*/**`, `**/test**/util*/**` | Helper types (mock builders, assertion helpers, data generators), test client wrappers, WireMock setup utilities, view refresh helpers | Focused single-responsibility helpers, `@UtilityClass` for stateless helpers, clean API for test setup, reused across multiple test classes | `*TestSupport`, `*TestHelper`, `*TestClient`, `*TestUtils` | Java: `@UtilityClass`, test builders \| TS: test utils, render helpers \| Python: conftest fixtures, helper modules |
|
||||
| T4 | Data Preparation | `**/test**/*Expected*`, `**/test**/*Requests*` | ExpectedData patterns (inner classes per scenario, constants), Requests companion classes (factory methods), three-class structure (Test + ExpectedData + Requests) | `@UtilityClass` with inner class per scenario, named constants over magic values, factory methods for request building, complete three-class triad for endpoint testing | `*ExpectedData`, `*Requests`, `*TestData` | Java: `@UtilityClass`, inner classes \| TS: test data factories, fixtures \| Python: dataclasses, named tuples |
|
||||
| T5 | Test Classes | `**/test**/*Test.*`, `**/test**/*Spec.*` | BDD style (GIVEN/WHEN/THEN comments), `@DisplayName` usage (methods only), AssertJ patterns (`.as()`, `satisfies`, `extracting`), test isolation, no IF in tests | GIVEN/WHEN/THEN/AND in every test, `@DisplayName` on methods only ("should..." sentences), `.as()` on every AssertJ assertion, no conditional logic, clean setup/teardown | `*Test`, `*IntegrationTest`, `*Spec` | Java: JUnit5, AssertJ, `@DisplayName` \| TS: Jest/Vitest, describe/it blocks \| Python: pytest, class-based tests |
|
||||
| T6 | Test Parameters | `**/test**/*Factory*`, `**/test**/*Provider*`, `**/test**/*Source*` | `@ParameterizedTest` with `@MethodSource`/`@CsvSource`, negative test factories, data provider patterns, stream-based test data generation | Interface-based factories for reusable test data streams, clear naming (`*TestFactory`, `*TestProvider`), `@MethodSource` with descriptive method names, negative test patterns (401/403/422) via shared factories | `*TestFactory`, `*TestProvider`, `*Arguments`, `Negative*TestFactories` | Java: `@ParameterizedTest`, `@MethodSource` \| TS: `it.each`, `describe.each` \| Python: `@pytest.mark.parametrize` |
|
||||
@@ -0,0 +1,225 @@
|
||||
# Conventions Guide
|
||||
|
||||
> Document templates and generation rules for convention output files.
|
||||
|
||||
## 1. General Rules
|
||||
|
||||
| Rule | Details |
|
||||
|------|---------|
|
||||
| Code snippets | Max 5-15 lines, stripped indentation, max 3 per layer |
|
||||
| Tables | Use for etalons, patterns, anti-patterns |
|
||||
| Cross-references | Lazy-load: `> See: testing-conventions.md#section` |
|
||||
| No duplicates | Each pattern in ONE doc only |
|
||||
| Headers | Max 3 levels (`##`, `###`, `####`) |
|
||||
| Imperative form | "Use X" not "You should use X" |
|
||||
| No filler | Cut "please note", "it's important", "remember to" |
|
||||
| Status markers | ✅ ❌ ⚠️ only |
|
||||
|
||||
## 2. reference-patterns.md Template (~300 lines)
|
||||
|
||||
Structure follows `.codex/convention/reference-patterns-example.md` if available in workspace (optional -- not included in plugin distribution).
|
||||
|
||||
```markdown
|
||||
# Reference Patterns & Etalon Classes
|
||||
|
||||
> Comprehensive reference for developers. Organized by layer with etalon classes and code snippets.
|
||||
|
||||
## {N}. {Layer Name} Layer
|
||||
|
||||
### Etalon Classes
|
||||
|
||||
| Aspect | Etalon | Path |
|
||||
|--------|--------|------|
|
||||
| {aspect} | `{ClassName}` | `{relative/path}` |
|
||||
|
||||
### Patterns
|
||||
|
||||
**{N}. {Pattern Name}:** {1-line description}. Etalon: `{ClassName}`
|
||||
|
||||
```java
|
||||
// 5-15 lines of actual code from etalon class
|
||||
```
|
||||
|
||||
| Variant | Usage | Etalon |
|
||||
|---------|-------|--------|
|
||||
| {approach} | {when to use} | `{ClassName}` |
|
||||
|
||||
### Anti-Patterns (Avoid)
|
||||
|
||||
| Class | Problem | Fix |
|
||||
|-------|---------|-----|
|
||||
| `{ClassName}` | {what's wrong} | {how to fix} |
|
||||
|
||||
## Quick Reference: Top Etalons by Role
|
||||
|
||||
| When writing... | Copy from... |
|
||||
|-----------------|-------------|
|
||||
| New {component type} | `{EtalonClass}` |
|
||||
```
|
||||
|
||||
### Layers to Include
|
||||
|
||||
| Priority | Layers | Content Focus |
|
||||
|----------|--------|---------------|
|
||||
| Required | L5 Controllers | REST, security, DI, OpenAPI |
|
||||
| Required | L8 Repositories | Data access, mappers, dynamic queries |
|
||||
| Required | L6+L9 Services | DI, transactions, business logic |
|
||||
| Required | L14 Infrastructure | Config, security, cache, scheduling |
|
||||
| Required | L10+L11 DTOs/Entities | Immutability, naming, evolution |
|
||||
| Optional | L4 Utilities | Helpers, converters (if significant) |
|
||||
| Optional | L7 Providers | Clients, resilience (if project uses) |
|
||||
|
||||
### Quality Rules
|
||||
|
||||
| Check | Requirement |
|
||||
|-------|-------------|
|
||||
| Etalon per layer | 1-2 primary etalons minimum |
|
||||
| Code snippets | Real code from etalon files, 5-15 lines |
|
||||
| Anti-patterns | At least 1 per major layer (L5, L6, L8) |
|
||||
| Quick reference | Complete table at end covering all layers |
|
||||
| Paths | Relative from project root |
|
||||
|
||||
## 3. testing-conventions.md Template (~150 lines)
|
||||
|
||||
```markdown
|
||||
# Testing Conventions & Etalon Classes
|
||||
|
||||
> Test patterns reference. Organized by test infrastructure layer.
|
||||
|
||||
## {N}. {Test Layer Name}
|
||||
|
||||
### Etalon Classes
|
||||
|
||||
| Aspect | Etalon | Path |
|
||||
|--------|--------|------|
|
||||
| {aspect} | `{ClassName}` | `{relative/path}` |
|
||||
|
||||
### Patterns
|
||||
|
||||
**{N}. {Pattern Name}:** {1-line description}. Etalon: `{ClassName}`
|
||||
|
||||
```java
|
||||
// 5-15 lines of test code
|
||||
```
|
||||
|
||||
| Pattern | When | Example |
|
||||
|---------|------|---------|
|
||||
| {pattern} | {scenario} | `{ClassName}` |
|
||||
|
||||
## Quick Reference: Test Etalons
|
||||
|
||||
| When writing... | Copy from... |
|
||||
|-----------------|-------------|
|
||||
| New integration test | `{TestClass}` |
|
||||
| New test data | `{ExpectedDataClass}` |
|
||||
```
|
||||
|
||||
### Layers to Include (T1-T6)
|
||||
|
||||
| Layer | Content Focus |
|
||||
|-------|---------------|
|
||||
| T1 Test Data | Fixtures, datasets, placeholders |
|
||||
| T2 Base Classes | Hierarchy, composite annotations, containers |
|
||||
| T3 Test Helpers | Utilities, mock builders, WireMock setup |
|
||||
| T4 Data Preparation | ExpectedData, Requests, three-class structure |
|
||||
| T5 Test Classes | BDD, assertions, @DisplayName |
|
||||
| T6 Test Parameters | @ParameterizedTest, factories, negative tests |
|
||||
|
||||
## 4. project-architecture.md Template (~200 lines)
|
||||
|
||||
```markdown
|
||||
# Project Architecture & Build Conventions
|
||||
|
||||
> Build, dependency, codegen, migration, and structural conventions.
|
||||
|
||||
## 1. Build Configuration
|
||||
|
||||
### Etalon Files
|
||||
|
||||
| Aspect | File | Path |
|
||||
|--------|------|------|
|
||||
| {aspect} | `{filename}` | `{relative/path}` |
|
||||
|
||||
### Build Patterns
|
||||
- {pattern description}
|
||||
|
||||
## 2. Dependency Management
|
||||
|
||||
| Strategy | Example | When |
|
||||
|----------|---------|------|
|
||||
| BOM import | `spring-boot-dependencies` | Framework deps |
|
||||
| Property | `{lib}.version` | Direct deps |
|
||||
|
||||
| Category | Libraries | Version Source |
|
||||
|----------|-----------|----------------|
|
||||
| {category} | {libs} | {source} |
|
||||
|
||||
## 3. Code Generation
|
||||
|
||||
| Generator | Config | Output | Trigger |
|
||||
|-----------|--------|--------|---------|
|
||||
| {name} | `{config file}` | `{output dir}` | {when runs} |
|
||||
|
||||
## 4. Migrations
|
||||
|
||||
| Tool | Location | Naming | Format |
|
||||
|------|----------|--------|--------|
|
||||
| {tool} | `{path}` | `{pattern}` | {SQL/XML/YAML} |
|
||||
|
||||
### Schema Management Rules
|
||||
- {rule}
|
||||
|
||||
## 5. Directory Structure
|
||||
|
||||
| Module | Package Structure |
|
||||
|--------|-------------------|
|
||||
| {module} | `{package layout}` |
|
||||
|
||||
```
|
||||
src/main/java/{base}/
|
||||
configuration/
|
||||
controllers/
|
||||
services/
|
||||
repositories/
|
||||
...
|
||||
```
|
||||
|
||||
## 6. Naming Conventions
|
||||
|
||||
| Entity Type | Pattern | Example |
|
||||
|-------------|---------|---------|
|
||||
| Controller | `*Controller` | `HistoryController` |
|
||||
| Service | `*Service` | `ProfileKeyCloakService` |
|
||||
| Repository | `*Repository` | `LoadsHistoryRepository` |
|
||||
|
||||
## 7. Constraints
|
||||
|
||||
| Constraint | Details |
|
||||
|------------|---------|
|
||||
| {constraint} | {explanation} |
|
||||
```
|
||||
|
||||
### Layers to Include
|
||||
|
||||
| Layer | Content Focus |
|
||||
|-------|---------------|
|
||||
| L1 Build | Build tool, plugins, profiles |
|
||||
| L2 Dependencies | BOMs, versions, scopes |
|
||||
| L3 CodeGen | Generators, configs, output |
|
||||
| L12 Migrations | DDL, changelog, schema management |
|
||||
| L13 Resources | Config organization, profiles |
|
||||
|
||||
## 5. Quality Checklist
|
||||
|
||||
| Check | Details |
|
||||
|-------|---------|
|
||||
| Etalon coverage | Every included layer has 1-2 etalons |
|
||||
| Code snippets | 5-15 lines each, real code, no boilerplate |
|
||||
| Anti-patterns | At least 1 per major layer (L5, L6, L8 in reference-patterns) |
|
||||
| Quick reference | "When writing... / Copy from..." table in each doc |
|
||||
| Cross-refs | No broken references between docs |
|
||||
| No duplicates | Each pattern in exactly one doc |
|
||||
| Paths | All relative from project root |
|
||||
| Line targets | reference-patterns ~300, testing ~150, architecture ~200 |
|
||||
| Naming | Consistent etalon class names across all docs |
|
||||
| Stack-appropriate | Include only patterns relevant to detected stack |
|
||||
@@ -0,0 +1,8 @@
|
||||
# Direct rule extraction
|
||||
|
||||
1. Extract durable avoid and best-practice candidates from accepted convention evidence.
|
||||
2. Read all existing `.codex/rules/*.md` files and the applicable `AGENTS.md` files.
|
||||
3. Skip semantic duplicates, merge partial overlaps, and keep the higher-priority instruction when guidance conflicts.
|
||||
4. Apply accepted English rule text directly to the narrowest existing rule file.
|
||||
5. Update the root `AGENTS.md` index with columns `Rule`, `Load when`, and `Purpose`.
|
||||
6. Invoke `$brewtools:text-optimize -l` for each changed rule and index file, compare semantics, then validate the full inventory.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Text Optimization: Compact Rules (Fallback)
|
||||
|
||||
> Use when brewtools is NOT installed and text-optimizer agent is unavailable.
|
||||
> For full rules: install brewtools plugin and use `text-optimizer` agent.
|
||||
|
||||
## Key Rules by Category
|
||||
|
||||
### C -- Codex Behavior
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| C.1 | Literal following | Instructions execute exactly as written -- be precise |
|
||||
| C.3 | Positive framing | "Do Y" not "Don't do X" |
|
||||
| C.5 | Descriptive over emphatic | "Use when..." not "CRITICAL: MUST..." |
|
||||
| C.6 | No overengineering | Codex follows literally -- simpler is better |
|
||||
| C.7 | No ALL-CAPS emphasis | Normal tone; aggressive caps causes overapplication |
|
||||
|
||||
### T -- Token Efficiency
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| T.1 | Tables over prose | Multi-column data ~30% savings; single-column use bullets |
|
||||
| T.2 | Bullets over numbered | `-` (1 char) vs `1. ` (3 chars), ~5-10% savings |
|
||||
| T.3 | One-liners for rules | `bad -> good` is self-documenting |
|
||||
| T.4 | Inline code over blocks | Inline `code` for <3 lines |
|
||||
| T.6 | Remove filler | Cut "please note", "it's important", "basically" |
|
||||
| T.7 | Comma-separated inline | `a, b, c` for 3-7 short items |
|
||||
| T.8 | Arrows for flow | `A -> B -> C` not prose sequences |
|
||||
|
||||
### S -- Structure
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| S.1 | XML tags for sections | `<rules>...</rules>` -- clear parsing boundaries |
|
||||
| S.2 | Imperative form | "Do X" not "You should do X" |
|
||||
| S.3 | Single source of truth | Merge duplicates; repetition wastes tokens |
|
||||
| S.6 | Progressive disclosure | Overview -> details -> examples; SKILL.md <500 lines |
|
||||
| S.7 | Consistent terminology | One term per concept, no synonyms |
|
||||
|
||||
### R -- Reference Integrity
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| R.1 | Verify file paths | Use Read/Glob to confirm before writing |
|
||||
| R.2 | Check URLs | Validate accessible URLs |
|
||||
|
||||
### P -- Perception
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| P.1 | Examples near rules | Inline, not in appendix |
|
||||
| P.2 | Max 3-4 header levels | Structured documents improve retrieval |
|
||||
| P.3 | Bold for keywords | Max 2-3 per 100 lines |
|
||||
| P.5 | Critical info first | First-position = strongest anchoring |
|
||||
|
||||
### L -- LLM Comprehension
|
||||
|
||||
| ID | Rule | Key Point |
|
||||
|----|------|-----------|
|
||||
| L.1 | Critical info at START or END | Middle content gets 40-50% less attention |
|
||||
| L.5 | Add WHY to instructions | Codex generalizes the reason to edge cases |
|
||||
|
||||
## Self-Apply Instructions
|
||||
|
||||
1. Read entire text to understand context
|
||||
2. Apply T.6 first (remove filler) -- easiest wins
|
||||
3. Apply T.1 (convert prose lists -> tables where applicable)
|
||||
4. Apply T.2-T.4 (compress remaining lists/code)
|
||||
5. Apply S.2 (imperative form)
|
||||
6. Verify: no information loss, all refs valid
|
||||
7. Target: 20-40% token reduction
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Avoid | Why |
|
||||
|-------|-----|
|
||||
| Remove examples | Hurts generalization (P.1) |
|
||||
| Over-abbreviate | Reduces readability |
|
||||
| Flatten hierarchy | Loses structure (P.2) |
|
||||
| Compress domain terms | 30+ point accuracy drops (T.5) |
|
||||
|
||||
## Compression Ratios
|
||||
|
||||
| Content Type | Typical Savings |
|
||||
|--------------|-----------------|
|
||||
| Prose docs | 40-50% |
|
||||
| Technical specs | 20-30% |
|
||||
| System prompts | 30-40% |
|
||||
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env sh
|
||||
# Brewcode Convention Script — stack detection, scanning, setup, validation
|
||||
# Usage: convention.sh <detect-stack|scan|setup|validate>
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "Usage: convention.sh <command>"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " detect-stack Detect tech stack from build/config files"
|
||||
echo " scan Scan project directory structure"
|
||||
echo " setup Create .codex/convention/ directory"
|
||||
echo " validate Check if convention files exist"
|
||||
exit 2
|
||||
}
|
||||
|
||||
err() { echo "$*" >&2; }
|
||||
|
||||
HAS_JQ=false
|
||||
command -v jq >/dev/null 2>&1 && HAS_JQ=true
|
||||
|
||||
is_skip_dir() {
|
||||
case "$1" in .*|node_modules|target|build|dist|vendor|__pycache__) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if dir has a build file; prints stack name if found
|
||||
has_build_file() {
|
||||
[ -f "$1/pom.xml" ] || [ -f "$1/build.gradle" ] || [ -f "$1/build.gradle.kts" ] || \
|
||||
[ -f "$1/package.json" ] || [ -f "$1/go.mod" ] || [ -f "$1/Cargo.toml" ] || \
|
||||
[ -f "$1/pyproject.toml" ] || [ -f "$1/mix.exs" ] || [ -f "$1/Gemfile" ] || \
|
||||
find "$1" -maxdepth 1 \( -name '*.sln' -o -name '*.csproj' \) -print -quit 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
# Append stack if not already present: add_stack "java"
|
||||
add_stack() {
|
||||
case ",$stacks," in *",$1,"*) return ;; esac
|
||||
stacks="${stacks:+$stacks,}$1"
|
||||
}
|
||||
|
||||
detect_stack() {
|
||||
stacks="" build_file="" modules=""
|
||||
|
||||
# Root-level detection (priority order)
|
||||
if [ -f pom.xml ]; then add_stack java; build_file="pom.xml"
|
||||
elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then
|
||||
add_stack java; build_file=$([ -f build.gradle.kts ] && echo "build.gradle.kts" || echo "build.gradle")
|
||||
fi
|
||||
if [ -f package.json ]; then
|
||||
if [ -f tsconfig.json ] || find . -maxdepth 2 -name '*.tsx' -print -quit 2>/dev/null | grep -q .; then
|
||||
add_stack typescript
|
||||
else add_stack javascript; fi
|
||||
[ -z "$build_file" ] && build_file="package.json"
|
||||
fi
|
||||
if [ -f pyproject.toml ] || [ -f setup.py ] || [ -f requirements.txt ]; then
|
||||
add_stack python
|
||||
if [ -z "$build_file" ]; then
|
||||
if [ -f pyproject.toml ]; then build_file="pyproject.toml"
|
||||
elif [ -f setup.py ]; then build_file="setup.py"
|
||||
else build_file="requirements.txt"; fi
|
||||
fi
|
||||
fi
|
||||
[ -f go.mod ] && { add_stack go; [ -z "$build_file" ] && build_file="go.mod"; }
|
||||
[ -f Cargo.toml ] && { add_stack rust; [ -z "$build_file" ] && build_file="Cargo.toml"; }
|
||||
if find . -maxdepth 1 \( -name '*.sln' -o -name '*.csproj' \) -print -quit 2>/dev/null | grep -q .; then
|
||||
add_stack dotnet
|
||||
[ -z "$build_file" ] && build_file=$(find . -maxdepth 1 \( -name '*.sln' -o -name '*.csproj' \) -print -quit 2>/dev/null | sed 's|^\./||')
|
||||
fi
|
||||
[ -f mix.exs ] && { add_stack elixir; [ -z "$build_file" ] && build_file="mix.exs"; }
|
||||
[ -f Gemfile ] && { add_stack ruby; [ -z "$build_file" ] && build_file="Gemfile"; }
|
||||
|
||||
# One level deep: monorepo modules
|
||||
for d in */; do
|
||||
[ -d "$d" ] || continue
|
||||
d_name=$(echo "$d" | sed 's|/$||')
|
||||
is_skip_dir "$d_name" && continue
|
||||
has_build_file "$d_name" || continue
|
||||
modules="${modules:+$modules,}\"$d_name\""
|
||||
[ -f "$d/pom.xml" ] || [ -f "$d/build.gradle" ] || [ -f "$d/build.gradle.kts" ] && add_stack java
|
||||
if [ -f "$d/package.json" ]; then
|
||||
if [ -f "$d/tsconfig.json" ]; then add_stack typescript; else add_stack javascript; fi
|
||||
fi
|
||||
{ [ -f "$d/pyproject.toml" ] || [ -f "$d/setup.py" ] || [ -f "$d/requirements.txt" ]; } && add_stack python
|
||||
[ -f "$d/go.mod" ] && add_stack go
|
||||
find "$d" -maxdepth 1 \( -name '*.sln' -o -name '*.csproj' \) -print -quit 2>/dev/null | grep -q . && add_stack dotnet
|
||||
done
|
||||
|
||||
primary=$(echo "$stacks" | cut -d',' -f1)
|
||||
if [ -z "$stacks" ]; then
|
||||
stacks_json=""
|
||||
else
|
||||
stacks_json=$(echo "$stacks" | sed 's/,/","/g')
|
||||
stacks_json="\"$stacks_json\""
|
||||
fi
|
||||
|
||||
if $HAS_JQ; then
|
||||
printf '{"stacks":[%s],"primary":"%s","build_file":"%s","modules":[%s]}' \
|
||||
"$stacks_json" "$primary" "$build_file" "$modules" | jq -c .
|
||||
else
|
||||
printf '{"stacks":[%s],"primary":"%s","build_file":"%s","modules":[%s]}\n' \
|
||||
"$stacks_json" "$primary" "$build_file" "$modules"
|
||||
fi
|
||||
}
|
||||
|
||||
scan_project() {
|
||||
src_dirs=""
|
||||
for d in src/main/java src/main/kotlin src/test/java src/test/kotlin \
|
||||
src/main/resources src/test/resources src lib app test tests cmd pkg internal api; do
|
||||
[ -d "$d" ] && src_dirs="${src_dirs:+$src_dirs,}\"$d\""
|
||||
done
|
||||
|
||||
file_counts="" total_files=0
|
||||
counts_raw=$(find . -type f -not -path '*/\.*' -not -path '*/node_modules/*' \
|
||||
-not -path '*/target/*' -not -path '*/build/*' -not -path '*/__pycache__/*' \
|
||||
-not -path '*/dist/*' -not -path '*/vendor/*' 2>/dev/null \
|
||||
| sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10)
|
||||
if [ -n "$counts_raw" ]; then
|
||||
while IFS= read -r line; do
|
||||
count=$(echo "$line" | awk '{print $1}')
|
||||
ext=$(echo "$line" | awk '{print $2}')
|
||||
case "$ext" in */*|"") continue ;; esac
|
||||
file_counts="${file_counts:+$file_counts,}\"$ext\":$count"
|
||||
total_files=$((total_files + count))
|
||||
done <<EOF
|
||||
$counts_raw
|
||||
EOF
|
||||
fi
|
||||
|
||||
mod_list=""
|
||||
for d in */; do
|
||||
[ -d "$d" ] || continue
|
||||
d_name=$(echo "$d" | sed 's|/$||')
|
||||
is_skip_dir "$d_name" && continue
|
||||
has_build_file "$d_name" && mod_list="${mod_list:+$mod_list,}\"$d_name\""
|
||||
done
|
||||
|
||||
if $HAS_JQ; then
|
||||
printf '{"source_dirs":[%s],"file_counts":{%s},"modules":[%s],"total_files":%d}' \
|
||||
"$src_dirs" "$file_counts" "$mod_list" "$total_files" | jq .
|
||||
else
|
||||
printf '{"source_dirs":[%s],"file_counts":{%s},"modules":[%s],"total_files":%d}\n' \
|
||||
"$src_dirs" "$file_counts" "$mod_list" "$total_files"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_convention() {
|
||||
mkdir -p .codex/convention
|
||||
printf '{"created":"%s","path":".codex/convention/"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
}
|
||||
|
||||
validate_convention() {
|
||||
errors=0 f1=false f2=false f3=false
|
||||
[ -f .codex/convention/reference-patterns.md ] && f1=true || errors=$((errors + 1))
|
||||
[ -f .codex/convention/testing-conventions.md ] && f2=true || errors=$((errors + 1))
|
||||
[ -f .codex/convention/project-architecture.md ] && f3=true || errors=$((errors + 1))
|
||||
valid=true; [ "$errors" -gt 0 ] && valid=false
|
||||
|
||||
if [ "$valid" = "true" ]; then err "All convention files present"
|
||||
else err "Missing $errors convention file(s)"; fi
|
||||
|
||||
if $HAS_JQ; then
|
||||
printf '{"valid":%s,"files":{"reference-patterns.md":%s,"testing-conventions.md":%s,"project-architecture.md":%s}}' \
|
||||
"$valid" "$f1" "$f2" "$f3" | jq .
|
||||
else
|
||||
printf '{"valid":%s,"files":{"reference-patterns.md":%s,"testing-conventions.md":%s,"project-architecture.md":%s}}\n' \
|
||||
"$valid" "$f1" "$f2" "$f3"
|
||||
fi
|
||||
return $errors
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
detect-stack) detect_stack ;;
|
||||
scan) scan_project ;;
|
||||
setup) setup_convention ;;
|
||||
validate) validate_convention || exit $? ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Rules for Codex
|
||||
|
||||
Maintains project rule bodies under `.codex/rules/` and a compact discovery table in the applicable `AGENTS.md`. The workflow runs directly without a dedicated organizer agent, invokes `$brewtools:text-optimize -l` for changed rule and index files, and validates that every project rule appears exactly once in the root index.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: rules
|
||||
description: "Maintains project .codex/rules and the AGENTS.md rule index. Explicit user invocation only."
|
||||
---
|
||||
|
||||
# Codex project rules
|
||||
|
||||
Use only when the user explicitly requests rule creation, synchronization, improvement, review, or inventory.
|
||||
|
||||
## Sources of truth
|
||||
|
||||
- Store rule bodies under the project `.codex/rules/`; never use personal or foreign-assistant rule paths.
|
||||
- Treat hierarchical `AGENTS.md` files as Codex's automatically loaded instruction surface.
|
||||
- Keep rule content in one file. `AGENTS.md` contains a compact index, not copied rule bodies.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read the applicable `AGENTS.md` files and inventory every project rule with `rg --files .codex/rules | sort`.
|
||||
2. Resolve the request to create, improve, review, list, or synchronize. Persist durable repository facts only; exclude secrets, transient session state, generated reports, and duplicate instructions.
|
||||
3. Create or update English rule files under `.codex/rules/`. Reuse an existing rule when its scope matches.
|
||||
4. Update the nearest applicable `AGENTS.md` rule-index table. The repository-root table lists every `.codex/rules/*.md` file exactly once with columns `Rule`, `Load when`, and `Purpose`. Nested tables add only subtree-specific rules not already covered by the inherited root index.
|
||||
5. Invoke `$brewtools:text-optimize -l` for every changed rule and `AGENTS.md` index. Preserve paths, scope qualifiers, safety constraints, and table structure; apply the optimized patch only after comparing semantics.
|
||||
6. Re-run the complete inventory. Fail validation when any rule path is missing from the root index, any indexed path is stale, or a rule body was duplicated into `AGENTS.md`.
|
||||
7. Report changed rules, index rows, optimization measurements, and validation evidence.
|
||||
|
||||
The index makes rules discoverable; it does not auto-load their bodies. During later work, read the indexed rule whose `Load when` condition matches the task.
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Rules"
|
||||
short_description: "Project rules and AGENTS.md index; user invoked"
|
||||
default_prompt: "Use $brewcode:rules to maintain project rules and the AGENTS.md rule index."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
#!/bin/bash
|
||||
# Brewcode Rules Script
|
||||
# Multi-function script for $brewcode:rules skill
|
||||
# Usage: rules.sh <mode> [options]
|
||||
#
|
||||
# Modes:
|
||||
# read <path> - Read knowledge file (first 100 lines)
|
||||
# check - Check existing rules files (main + specialized)
|
||||
# create - Create missing main rules from templates
|
||||
# create-specialized <prefix> - Create specialized rules (e.g., test-avoid.md)
|
||||
# list - List all rule files (*-avoid.md, *-best-practice.md)
|
||||
# validate - Validate table structure
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MODE="${1:-check}"
|
||||
ARG="${2:-}"
|
||||
|
||||
# Self-location: derive plugin root from script path
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Path: scripts/rules.sh -> skills/rules/scripts -> skills/rules -> skills -> PLUGIN_ROOT
|
||||
PLUGIN_ROOT="$(dirname "$(dirname "$(dirname "$SCRIPT_DIR")")")"
|
||||
PLUGIN_TEMPLATES="$PLUGIN_ROOT/templates"
|
||||
|
||||
# Validate plugin structure
|
||||
validate_plugin() {
|
||||
if [ ! -d "$PLUGIN_ROOT" ]; then
|
||||
echo "X Plugin root not found: $PLUGIN_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$PLUGIN_TEMPLATES" ]; then
|
||||
echo "X Templates not found: $PLUGIN_TEMPLATES"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Read knowledge file
|
||||
read_knowledge() {
|
||||
local path="$1"
|
||||
if [ -z "$path" ]; then
|
||||
echo "X Missing path argument"
|
||||
echo "Usage: rules.sh read <path>"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f "$path" ]; then
|
||||
head -100 "$path"
|
||||
else
|
||||
echo "X File not found: $path"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check existing rules files (main + specialized)
|
||||
check_rules() {
|
||||
echo "=== Check Rules Files ==="
|
||||
echo "--- Main Files ---"
|
||||
test -f .codex/rules/avoid.md && echo "V avoid.md exists" || echo "! avoid.md missing"
|
||||
test -f .codex/rules/best-practice.md && echo "V best-practice.md exists" || echo "! best-practice.md missing"
|
||||
|
||||
echo "--- Specialized Files ---"
|
||||
local specialized_avoid specialized_bp
|
||||
specialized_avoid=$(find .codex/rules -maxdepth 1 -name "*-avoid.md" 2>/dev/null | grep -v "^.codex/rules/avoid.md$" || true)
|
||||
specialized_bp=$(find .codex/rules -maxdepth 1 -name "*-best-practice.md" 2>/dev/null | grep -v "^.codex/rules/best-practice.md$" || true)
|
||||
|
||||
if [ -n "$specialized_avoid" ] || [ -n "$specialized_bp" ]; then
|
||||
echo "$specialized_avoid" | while read -r f; do [ -n "$f" ] && echo "V $(basename "$f")"; done
|
||||
echo "$specialized_bp" | while read -r f; do [ -n "$f" ] && echo "V $(basename "$f")"; done
|
||||
else
|
||||
echo " (none found)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create missing rules from templates
|
||||
create_rules() {
|
||||
echo "=== Create Rules ==="
|
||||
validate_plugin
|
||||
|
||||
mkdir -p .codex/rules
|
||||
|
||||
if [ ! -f .codex/rules/avoid.md ]; then
|
||||
cp "$PLUGIN_TEMPLATES/rules/avoid.md.template" .codex/rules/avoid.md
|
||||
echo "V Created: .codex/rules/avoid.md"
|
||||
else
|
||||
echo ">> Preserved: .codex/rules/avoid.md (exists)"
|
||||
fi
|
||||
|
||||
if [ ! -f .codex/rules/best-practice.md ]; then
|
||||
cp "$PLUGIN_TEMPLATES/rules/best-practice.md.template" .codex/rules/best-practice.md
|
||||
echo "V Created: .codex/rules/best-practice.md"
|
||||
else
|
||||
echo ">> Preserved: .codex/rules/best-practice.md (exists)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate table structure (main + specialized)
|
||||
validate_rules() {
|
||||
echo "=== Validate Rules Structure ==="
|
||||
ERRORS=0
|
||||
|
||||
# Validate main files
|
||||
if [ -f .codex/rules/avoid.md ]; then
|
||||
grep -q "^| #" .codex/rules/avoid.md && echo "V avoid.md valid structure" || { echo "X avoid.md invalid structure (missing table header)"; ERRORS=$((ERRORS+1)); }
|
||||
else
|
||||
echo "X avoid.md not found"
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
|
||||
if [ -f .codex/rules/best-practice.md ]; then
|
||||
grep -q "^| #" .codex/rules/best-practice.md && echo "V best-practice.md valid structure" || { echo "X best-practice.md invalid structure (missing table header)"; ERRORS=$((ERRORS+1)); }
|
||||
else
|
||||
echo "X best-practice.md not found"
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
|
||||
# Validate specialized files
|
||||
for f in .codex/rules/*-avoid.md .codex/rules/*-best-practice.md; do
|
||||
[ -f "$f" ] || continue
|
||||
# Skip main files
|
||||
[ "$(basename "$f")" = "avoid.md" ] && continue
|
||||
[ "$(basename "$f")" = "best-practice.md" ] && continue
|
||||
|
||||
if grep -q "^| #" "$f"; then
|
||||
echo "V $(basename "$f") valid structure"
|
||||
else
|
||||
echo "X $(basename "$f") invalid structure (missing table header)"
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
done
|
||||
|
||||
exit $ERRORS
|
||||
}
|
||||
|
||||
# List all rule files
|
||||
list_rules() {
|
||||
echo "=== Rule Files in .codex/rules/ ==="
|
||||
mkdir -p .codex/rules
|
||||
|
||||
echo "--- Avoid Files ---"
|
||||
local avoid_count=0
|
||||
for f in .codex/rules/avoid.md .codex/rules/*-avoid.md; do
|
||||
if [ -f "$f" ]; then
|
||||
rows=$(grep -c "^|" "$f" 2>/dev/null || echo 0)
|
||||
rows=$((rows - 2)) # Subtract header and separator
|
||||
[ $rows -lt 0 ] && rows=0
|
||||
echo " $(basename "$f") ($rows entries)"
|
||||
avoid_count=$((avoid_count + 1))
|
||||
fi
|
||||
done
|
||||
[ $avoid_count -eq 0 ] && echo " (none found)"
|
||||
|
||||
echo "--- Best Practice Files ---"
|
||||
local bp_count=0
|
||||
for f in .codex/rules/best-practice.md .codex/rules/*-best-practice.md; do
|
||||
if [ -f "$f" ]; then
|
||||
rows=$(grep -c "^|" "$f" 2>/dev/null || echo 0)
|
||||
rows=$((rows - 2)) # Subtract header and separator
|
||||
[ $rows -lt 0 ] && rows=0
|
||||
echo " $(basename "$f") ($rows entries)"
|
||||
bp_count=$((bp_count + 1))
|
||||
fi
|
||||
done
|
||||
[ $bp_count -eq 0 ] && echo " (none found)"
|
||||
|
||||
echo "---"
|
||||
echo "Total: $((avoid_count + bp_count)) rule files"
|
||||
}
|
||||
|
||||
# Create specialized rules from template with prefix
|
||||
create_specialized() {
|
||||
local prefix="$1"
|
||||
if [ -z "$prefix" ]; then
|
||||
echo "X Missing prefix argument"
|
||||
echo "Usage: rules.sh create-specialized <prefix>"
|
||||
echo "Example: rules.sh create-specialized test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Create Specialized Rules: $prefix ==="
|
||||
validate_plugin
|
||||
mkdir -p .codex/rules
|
||||
|
||||
local avoid_file=".codex/rules/${prefix}-avoid.md"
|
||||
local bp_file=".codex/rules/${prefix}-best-practice.md"
|
||||
|
||||
if [ ! -f "$avoid_file" ]; then
|
||||
# Create from template with prefix substitution
|
||||
sed "s/# Avoid/# ${prefix^} Avoid/" "$PLUGIN_TEMPLATES/rules/avoid.md.template" > "$avoid_file"
|
||||
echo "V Created: $avoid_file"
|
||||
else
|
||||
echo ">> Preserved: $avoid_file (exists)"
|
||||
fi
|
||||
|
||||
if [ ! -f "$bp_file" ]; then
|
||||
sed "s/# Best Practices/# ${prefix^} Best Practices/" "$PLUGIN_TEMPLATES/rules/best-practice.md.template" > "$bp_file"
|
||||
echo "V Created: $bp_file"
|
||||
else
|
||||
echo ">> Preserved: $bp_file (exists)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main dispatch
|
||||
case "$MODE" in
|
||||
read)
|
||||
read_knowledge "$ARG"
|
||||
;;
|
||||
check)
|
||||
check_rules
|
||||
;;
|
||||
create)
|
||||
create_rules
|
||||
;;
|
||||
create-specialized)
|
||||
create_specialized "$ARG"
|
||||
;;
|
||||
list)
|
||||
list_rules
|
||||
;;
|
||||
validate)
|
||||
validate_rules
|
||||
;;
|
||||
*)
|
||||
echo "Usage: rules.sh <mode> [options]"
|
||||
echo ""
|
||||
echo "Modes:"
|
||||
echo " read <path> - Read knowledge file (first 100 lines)"
|
||||
echo " check - Check existing rules files (main + specialized)"
|
||||
echo " create - Create missing main rules from templates"
|
||||
echo " create-specialized <prefix> - Create specialized rules (e.g., test-avoid.md)"
|
||||
echo " list - List all rule files"
|
||||
echo " validate - Validate table structure"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,96 @@
|
||||
# brewcode:superreview
|
||||
|
||||
A **HUMAN-invoked generator skill**. It analyzes a target project and writes a self-contained, project-local
|
||||
`.codex/skills/superreview/` into that repo — a deep code-review skill that merges the old `review` and
|
||||
`standards-review` into ONE pass.
|
||||
|
||||
Like `skill-creator` and `task-board-init`, this skill produces a working artifact (a skill) rather than doing the
|
||||
review itself. The **emitted** skill is the one that reviews code.
|
||||
|
||||
## What it generates
|
||||
|
||||
A project-tailored `superreview` skill modeled on the canonical finagra shape:
|
||||
|
||||
1. **Deterministic MODE resolution** — `FULL_PROJECT | EXPLICIT | UNCOMMITTED | LAST_COMMITS`, computed (not guessed),
|
||||
then ANNOUNCED (mode + branch + concrete scope + file count + focus) before any review.
|
||||
2. **Domain routing** — each changed file routed to its project domain-owner agent (from `.codex/agents/`).
|
||||
3. **Rule referencing** — points at the project's real `.codex/rules/*` + `.codex/convention/*` files; agents READ
|
||||
and CITE them (never restated in the skill).
|
||||
4. **ONE targeted parallel fan-out** — only the agents the changed files actually need, plus `{0,1,2}` general
|
||||
cross-cutting agents by judgement. Far fewer spawns than a quorum + separate standards pass + arbiter.
|
||||
5. **Per-finding adversarial VALIDATION gate** — one arbiter agent reverse-validates EVERY candidate, drops false
|
||||
positives, then merges + de-dups + prioritizes.
|
||||
6. **ONE merged P0-P3 report** at `.codex/reports/{TIMESTAMP}_superreview/REPORT.md`. **READ-ONLY** — it recommends
|
||||
`/simplify` and a Manager-mode fix session; it never edits code.
|
||||
|
||||
## How review + standards-review are merged
|
||||
|
||||
| From | Folded in as |
|
||||
|------|--------------|
|
||||
| `review` engine | Canonical structure: deterministic mode, two-phase find->validate, merged report, agent contract |
|
||||
| `standards-review` | Reuse/duplication focus (rank 3, 90/70/50% reuse matrix), stack detection, file-grouping, per-stack guidelines, `/simplify` hand-off |
|
||||
| `setup` Phase 3.5 | Tech-specific check tables (Java/Node/Python/Go) folded into the per-stack reference docs; the placeholder->concrete generation mechanism |
|
||||
|
||||
The finagra shape is the canonical structure; standards-review + the review template supply the per-stack checks,
|
||||
reuse matrix, and report scaffolding baked into that shape.
|
||||
|
||||
## Usage
|
||||
|
||||
Run inside the repo you want to wire up:
|
||||
|
||||
```
|
||||
$brewcode:superreview "<fine-tune-prompt>" [scope]
|
||||
```
|
||||
|
||||
- `<fine-tune-prompt>` — what to emphasize in the emitted skill's focus ordering (e.g. "weight reuse highest",
|
||||
"always treat auth as P0"). Woven into the emitted Focus table + emphasis line.
|
||||
- `[scope]` — optional hint.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
$brewcode:superreview "default ordering"
|
||||
$brewcode:superreview "focus on architecture boundaries and reuse"
|
||||
$brewcode:superreview "treat any security issue as P0"
|
||||
```
|
||||
|
||||
After generation, run the emitted skill in that project:
|
||||
|
||||
```
|
||||
/superreview "<focus>" [scope: commit|branch|folder]
|
||||
```
|
||||
|
||||
## How it works (generator flow)
|
||||
|
||||
| Phase | Action |
|
||||
|-------|--------|
|
||||
| 0 | Read the emit templates this skill ships (`references/`) |
|
||||
| 1 | `generate.sh scan` + analysis: tech stack, build, test, DB, `.codex/agents`, rules/convention, source groups |
|
||||
| 1.5 | request_user_input for genuinely ambiguous params (arbiter agent, domain mapping, dominant stack) |
|
||||
| 2 | Export scalar placeholders -> `generate.sh emit` (sed substitution, copies templates + chosen stack ref) |
|
||||
| 3 | AI fills BLOCK placeholders (agent table, rule pointers, file-group map, focus table) via Edit |
|
||||
| 4 | `generate.sh validate` — fail if any setup-time `{PLACEHOLDER}` remains |
|
||||
| 5 | Report what was written |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `SKILL.md` | The generator orchestrator |
|
||||
| `scripts/generate.sh` | `scan` / `emit` / `validate` |
|
||||
| `references/SKILL.md.template` | The emitted finagra-shape SKILL.md (placeholder slots) |
|
||||
| `references/agent-prompt.md` | Emitted domain-owner prompt contract |
|
||||
| `references/report-template.md` | Emitted merged-report layout |
|
||||
| `references/python.md` · `java-kotlin.md` · `typescript-react.md` · `go.md` | Per-stack reference docs (one emitted) |
|
||||
|
||||
## Re-run triggers
|
||||
|
||||
Regenerate when: a project agent is added/renamed, a rule/convention file changes, the stack changes, or a new source
|
||||
group is added. Re-running re-wires the emitted skill to the current project shape.
|
||||
|
||||
## Notes
|
||||
|
||||
- The emitted skill is **self-contained** — no plugin dependency, no sibling-skill orchestration. It uses only
|
||||
project-local agents (`.codex/agents/`) and built-in `Explore` / `reviewer`.
|
||||
- Stack-generic: Java/Kotlin, Node/TypeScript, Python, Go.
|
||||
- READ-ONLY by design: it reports, it does not fix.
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
name: superreview
|
||||
description: "Generates a project-tailored deep-review skill (review+standards merged). Triggers: superreview, generate review skill"
|
||||
---
|
||||
|
||||
# Project-tailored review
|
||||
|
||||
Inspect repository instructions, architecture, tests, and recent changes, then create a focused Codex review skill in the user-selected project `.codex/skills/` path. Encode evidence-based checks, severity guidance, and verification commands. Validate the generated skill and do not create Markdown agent definitions.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
# Super Review Generator (brewcode:superreview)
|
||||
|
||||
**ROLE:** GENERATOR. This skill is HUMAN-invoked. It analyzes the TARGET project, then WRITES a self-contained,
|
||||
project-local `.codex/skills/superreview/` into that project — the merged deep-review skill (`review` + `standards-review`
|
||||
folded into one). It does NOT review code itself; it EMITS the skill that does.
|
||||
|
||||
**OUTPUT:** A project-local skill at `<target>/.codex/skills/superreview/` (SKILL.md + references) modeled exactly on
|
||||
the canonical finagra shape: deterministic MODE resolution -> ANNOUNCE -> route changed files to project domain-owner
|
||||
agents -> reference (not restate) the project's `.codex/rules` + convention files -> ONE targeted parallel fan-out ->
|
||||
per-finding adversarial VALIDATION gate -> ONE merged P0-P3 report at `.codex/reports/{TIMESTAMP}_superreview/REPORT.md`,
|
||||
READ-ONLY (recommends `/simplify`, never edits).
|
||||
|
||||
> Like `skill-creator` / `task-board-init`: a multi-step analysis that deploys a working artifact into ANY repo.
|
||||
> The emitted skill is generic-capable (Java/Kotlin, Node/TS, Python, Go) and self-contained — NO plugin dependency,
|
||||
> NO sibling-skill orchestration once generated.
|
||||
|
||||
**Arguments:** `<arguments>` — `<fine-tune-prompt>` (free text: what to emphasize in the emitted skill's focus ordering)
|
||||
plus optional `[scope]` hint. The fine-tune prompt is woven into the emitted skill's Focus ordering + emphasis.
|
||||
|
||||
---
|
||||
|
||||
## What the emitted skill merges (review + standards-review)
|
||||
|
||||
| Source | What it contributes to the emitted skill |
|
||||
|--------|------------------------------------------|
|
||||
| `review` engine | Canonical STRUCTURE: deterministic mode, two-phase **find -> validate**, single merged P0-P3 report, agent prompt contract, report scaffolding |
|
||||
| `standards-review` | The **reuse/duplication** focus (rank 3: search-first 90/70/50% reuse matrix), tech-stack detection, file-grouping-by-type, per-stack reviewer guidelines, `/simplify` hand-off |
|
||||
| `setup` Phase 3.5 | Tech-specific check tables (Java/Node/Python/Go) folded into the per-stack reference docs; the placeholder -> concrete generation mechanism |
|
||||
|
||||
> Reconcile rule: the **finagra shape is the canonical structure**; `standards-review` + the `review` template supply
|
||||
> the per-stack checks, the reuse matrix, and the report scaffolding that get baked INTO that shape.
|
||||
|
||||
---
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 0 — Pre-analysis (read THIS skill's emit material)
|
||||
|
||||
1. Read the emit templates this generator ships (relative to `<skill-directory>`):
|
||||
- `references/SKILL.md.template` — the finagra-shape emitted SKILL.md (with `{PLACEHOLDER}` slots)
|
||||
- `references/agent-prompt.md` — domain-owner agent prompt contract (emitted verbatim, scalar-substituted)
|
||||
- `references/report-template.md` — emitted report layout
|
||||
- `references/{python,java-kotlin,typescript-react,go}.md` — per-stack reference docs (one is emitted)
|
||||
2. Confirm the TARGET project is the current working directory (the repo to be reviewed). All emitted paths are
|
||||
relative to that repo root.
|
||||
|
||||
### Phase 1 — Analyze the TARGET project
|
||||
|
||||
Gather everything the emitted skill must be wired to. Prefer `grepai_search` first for code exploration; fall back to
|
||||
Bash search (`grep`->ugrep / `find`->bfs on macOS CC).
|
||||
|
||||
**EXECUTE** using shell (project scan):
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/generate.sh" scan && echo "✅ scan" || echo "❌ scan FAILED"
|
||||
```
|
||||
|
||||
The scan reports: build files, `.codex/agents/*`, `.codex/rules/*` + `.codex/convention/*`, test dirs, source dirs.
|
||||
From it (plus your own reads) determine:
|
||||
|
||||
| Aspect | How to detect | Drives placeholder |
|
||||
|--------|---------------|--------------------|
|
||||
| Tech stack | build files: `pom.xml`/`build.gradle*` -> Java/Kotlin; `package.json`+react/ts -> Node/TS; `pyproject.toml`/`requirements*.txt` -> Python; `go.mod` -> Go | `STACK_LABEL`, `STACK_REF`, `SOURCE_GLOB`, `PATHSPEC_GLOBS` |
|
||||
| Project agents | `.codex/agents/*.toml` (name + description); map each to the path group it owns | `DOMAIN_AGENTS_TABLE`, `FILE_GROUP_MAP`, `GENERAL_AGENTS_TABLE` |
|
||||
| Arbiter / validator | an architecture/reviewer agent in `.codex/agents/`, else built-in `general-purpose` | `ARBITER_AGENT`, `VALIDATOR_AGENT` |
|
||||
| Rule + convention files | `.codex/rules/*.md` + `.codex/convention/*.md` + `AGENTS.md` | `RULE_POINTER_TABLE`, `RULE_PREFLIGHT_LIST` |
|
||||
| Source path groups | top-level source dirs / service dirs / module layout | `FILE_GROUP_MAP` |
|
||||
| DB / test stack | testcontainers, JPA/JOOQ, pytest, jest, etc. | folded into the per-stack reference note |
|
||||
|
||||
**Multi-stack repos:** if more than one stack is detected, pick the DOMINANT one for the emitted `STACK_REF`, and note
|
||||
the secondary stack(s) in `DOMAIN_AGENTS_TABLE` / `FILE_GROUP_MAP`. (One stack reference doc is emitted; the rule
|
||||
pointers cover the rest.)
|
||||
|
||||
### Phase 1.5 — Clarify genuinely ambiguous params (request_user_input)
|
||||
|
||||
Use request_user_input ONLY for params you cannot reliably infer. Never auto-guess a non-obvious choice. Typical questions:
|
||||
|
||||
- Which agent is the **architecture arbiter / Phase-3 validator** when several plausible reviewer/architect agents exist.
|
||||
- The **domain-owner mapping** when an agent's owned path group is unclear.
|
||||
- Whether to include a **general second-pass agent** mapping (built-in `general-purpose`) when no project reviewer exists.
|
||||
- Confirm the **dominant stack** when the repo is genuinely multi-stack.
|
||||
|
||||
> Weave the `<fine-tune-prompt>` argument into the emitted Focus ordering: if the user said "focus on X / weight Y
|
||||
> higher", reorder/emphasize the emitted `FOCUS_TABLE` accordingly (e.g. push security to P0-always, or raise reuse to
|
||||
> rank 1). Record the emphasis in `FOCUS_EMPHASIS`.
|
||||
|
||||
### Phase 2 — Resolve placeholders + emit (scalar substitution)
|
||||
|
||||
Export the SCALAR placeholder values, then run the emit step (mirrors `setup.sh copy_review_skill()` — sed with a
|
||||
control-char separator; values MUST be single-line):
|
||||
|
||||
```bash
|
||||
export PROJECT_NAME="<repo name>"
|
||||
export STACK_LABEL="<Java/Kotlin | Node/TypeScript | Python | Go>"
|
||||
export STACK_REF="<python.md | java-kotlin.md | typescript-react.md | go.md>"
|
||||
export SOURCE_GLOB="<*.py | *.java | *.ts | *.go ...>"
|
||||
export PATHSPEC_GLOBS="<'*.py' 'requirements*.txt' 'pyproject.toml' 'Dockerfile*' 'docker-compose.yml' '.github/workflows/*.yml'>"
|
||||
export ARBITER_AGENT="<project architect agent | general-purpose>"
|
||||
export VALIDATOR_AGENT="<project arbiter agent | general-purpose>"
|
||||
```
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/generate.sh" emit && echo "✅ emit" || echo "❌ emit FAILED"
|
||||
```
|
||||
|
||||
> **STOP if ❌** — verify `<skill-directory>/references/SKILL.md.template` exists and the target `.codex/` is writable.
|
||||
|
||||
This writes `<target>/.codex/skills/superreview/SKILL.md` (scalars substituted), copies `agent-prompt.md` +
|
||||
`report-template.md` (scalar-substituted), and copies the chosen `${STACK_REF}` into the emitted `references/`.
|
||||
|
||||
### Phase 3 — Adapt the BLOCK placeholders (AI Edit)
|
||||
|
||||
Multi-row tables cannot go through sed (newlines). Using the **Edit** tool, replace each block placeholder in the
|
||||
EMITTED `<target>/.codex/skills/superreview/SKILL.md` with a table you build from Phase 1 analysis:
|
||||
|
||||
| Block placeholder | Replace with |
|
||||
|-------------------|--------------|
|
||||
| `{DOMAIN_AGENTS_TABLE}` | one row per project agent: `\| <agent> \| Domain owner — <path glob>: <responsibility> \|` (built-in `Explore` as graceful fallback row) |
|
||||
| `{GENERAL_AGENTS_TABLE}` | the `{0,1,2}` cross-cutting agents: `<reviewer>` (quality/correctness 2nd pass) + `<ARBITER_AGENT>` (boundary/architecture arbiter + Phase-3 validator), each with an "include WHEN" rule |
|
||||
| `{RULE_POINTER_TABLE}` | one row per real `.codex/rules/*.md` + `.codex/convention/*.md`: `\| <file> \| <one-line gist> \|` — REFERENCE, never restate |
|
||||
| `{RULE_PREFLIGHT_LIST}` | the bash `for f in ...; do [ -f "$f" ] || echo "WARN missing rule: $f"; done` listing the SAME real rule files |
|
||||
| `{FILE_GROUP_MAP}` | one row per source group: `\| <group> \| <path pattern> \| <subagent_type> \|` mapping each group to its domain owner; tests group -> the test agent (with test-bloat audit); build group -> the CI/build agent |
|
||||
| `{FOCUS_TABLE}` | the ranked focus ordering (default: 1 correctness, 2 architecture/boundary, 3 reuse, 4 version pins, 5 business-reqs), REORDERED per the `<fine-tune-prompt>` |
|
||||
| `{FOCUS_EMPHASIS}` | one-line note capturing the fine-tune-prompt emphasis (or "default ordering" if none) |
|
||||
| `{SIMPLIFY_AGENTS}` | comma list of the domain-owner agents the fix-session should delegate to |
|
||||
|
||||
> Keep every emitted row pointing at a REAL agent (`.codex/agents/` or built-in `Explore`/`Plan`/`general-purpose`) and a REAL rule
|
||||
> file. Do NOT invent agents or rules. Built-in `Explore` is the only allowed fallback for an unavailable domain agent.
|
||||
|
||||
### Phase 4 — Validate (NO `{PLACEHOLDER}` may remain)
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/generate.sh" validate && echo "✅ validate" || echo "❌ validate FAILED"
|
||||
```
|
||||
|
||||
> **STOP if ❌** — the script lists any unresolved setup-time `{PLACEHOLDER}` left in the emitted SKILL.md / references
|
||||
> (runtime tokens like `{MODE}`, `{COUNT}`, `{FILE_LIST}` are allow-listed and expected to remain). Go back to Phase 3
|
||||
> and Edit the leftovers, then re-run validate.
|
||||
|
||||
### Phase 5 — Report
|
||||
|
||||
Print the generation summary:
|
||||
|
||||
```
|
||||
superreview generated -> <target>/.codex/skills/superreview/
|
||||
|
||||
Stack: {STACK_LABEL} (reference: {STACK_REF})
|
||||
Domain agents wired: {N} ({list})
|
||||
General agents: {reviewer?}, {ARBITER_AGENT} (arbiter + validator)
|
||||
Rule pointers: {N} files referenced (not restated)
|
||||
File groups: {N} ({group->agent})
|
||||
Focus: {resolved ordering — fine-tune: <emphasis>}
|
||||
|
||||
Files written:
|
||||
- .codex/skills/superreview/SKILL.md
|
||||
- .codex/skills/superreview/references/agent-prompt.md
|
||||
- .codex/skills/superreview/references/report-template.md
|
||||
- .codex/skills/superreview/references/{STACK_REF}
|
||||
|
||||
Run it: /superreview "<focus>" [scope] (in the target project)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How the emitted skill behaves (what you are generating)
|
||||
|
||||
Recap of the canonical shape the emitted SKILL.md implements (full text in `references/SKILL.md.template`):
|
||||
|
||||
| Phase | Behavior |
|
||||
|-------|----------|
|
||||
| Mode detection | Deterministic `FULL_PROJECT \| EXPLICIT \| UNCOMMITTED \| LAST_COMMITS`, COMPUTED not guessed; then ANNOUNCE mode+branch+scope+count+focus BEFORE any review |
|
||||
| Routing | Group changed files by path; enable ONLY non-empty groups; route each to its domain-owner agent; add `{0,1,2}` general agents by judgement |
|
||||
| Fan-out | ONE parallel message with the selected agents (find phase); each returns the shared JSON finding contract; search-first before flagging reuse/duplication |
|
||||
| Validation | ONE arbiter agent reverse-validates EVERY candidate (adversarial, per-finding gate), merges + de-dups + prioritizes P0-P3 |
|
||||
| Report | ONE merged report at `.codex/reports/{TIMESTAMP}_superreview/REPORT.md`, sorted P0->P3; READ-ONLY; recommends `/simplify` + a Manager-mode fix session; never edits code |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| Emit target | `<cwd>/.codex/skills/superreview/` | Where the generated skill is written |
|
||||
| Emit templates | `<skill-directory>/references/` | Source templates for the generation |
|
||||
| Generation script | `<skill-directory>/scripts/generate.sh` | `scan` \| `emit` \| `validate` |
|
||||
| Stack reference | one of `python.md \| java-kotlin.md \| typescript-react.md \| go.md` | Emitted per the dominant detected stack |
|
||||
| Block placeholders | AI-filled (Edit) | Tables that cannot go through sed; validated post-emit |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| No `.codex/agents/` in target | Emit with built-in `general-purpose` + `Explore` only; warn the user the routing is generic |
|
||||
| No `.codex/rules/`/`.codex/convention/` | Emit a minimal rule-pointer table (`AGENTS.md` only); WARN; the emitted skill degrades gracefully (preflight WARN) |
|
||||
| Unknown / unsupported stack | Emit with the closest per-stack ref + project rules only; warn |
|
||||
| Multi-stack repo | Pick dominant stack for `STACK_REF`; note secondaries in the agent/group tables |
|
||||
| Unresolved `{PLACEHOLDER}` after Phase 3 | `validate` fails listing them; fix via Edit, re-run validate |
|
||||
| Target `.codex/` not writable | STOP — ask the user to run from the repo root |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- `references/SKILL.md.template` — the finagra-shape emitted SKILL.md (placeholder slots).
|
||||
- `references/agent-prompt.md` — domain-owner agent prompt contract (emitted).
|
||||
- `references/report-template.md` — emitted merged-report layout.
|
||||
- `references/{python,java-kotlin,typescript-react,go}.md` — per-stack reference docs (one is emitted).
|
||||
- `scripts/generate.sh` — `scan` / `emit` / `validate`.
|
||||
|
||||
<!--
|
||||
SKILL METADATA — brewcode:superreview (GENERATOR)
|
||||
|
||||
HUMAN-invoked generator. Analyzes a target project and emits a self-contained project-local deep-review skill
|
||||
(review + standards-review merged) modeled on the canonical finagra shape. Stack-generic (Java/Kotlin, Node/TS,
|
||||
Python, Go). The EMITTED skill is the one that reviews code; this skill only writes it.
|
||||
|
||||
Re-run triggers:
|
||||
- New/renamed agent in target .codex/agents/ -> re-emit to refresh routing
|
||||
- New rule/convention file -> re-emit to refresh pointers
|
||||
- Stack change / new source group -> re-emit
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Superreview"
|
||||
short_description: "Generates a project-tailored deep-review skill (review+standards"
|
||||
default_prompt: "Use $brewcode:superreview for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,398 @@
|
||||
---
|
||||
name: superreview
|
||||
description: "Deep {PROJECT_NAME} code review the MODEL or the USER can invoke: routes changed files to domain-owner agents, checks correctness + architecture + reuse + version pins against the project rules, reverse-validates every finding, writes one merged report. Triggers: review code, deep review, superreview, super review, validate changes, check architecture, check standards, check reuse."
|
||||
user-invocable: true
|
||||
argument-hint: "<prompt-or-file-path> [scope: commit|branch|folder]"
|
||||
---
|
||||
|
||||
# Super Review ({PROJECT_NAME})
|
||||
|
||||
**ROLE:** Self-contained deep-review coordinator + final validator.
|
||||
**OUTPUT:** ONE merged, de-duplicated, severity-prioritized, validated report.
|
||||
|
||||
The most thorough review in this repo, run INLINE — no sibling skills, no plugin dependency. It:
|
||||
|
||||
0. **deterministically resolves ONE review MODE** and ANNOUNCES it to the user (mode + branch + concrete scope)
|
||||
BEFORE any review work — no interactive guessing,
|
||||
1. routes each changed file to its **domain-owner agent** (uses project agents to the MAX),
|
||||
2. checks **functional correctness + architecture + reuse + version pins + business-requirements** against the
|
||||
focus ordering below (folds in the old standards-review + review essence),
|
||||
3. runs a **single, targeted, parallel fan-out** — only the agents the changed files actually need (FEWER spawns
|
||||
than a multi-vote quorum + a separate standards pass + a separate arbiter),
|
||||
4. ends with a **VALIDATION phase**: a per-finding adversarial double-check that re-verifies EVERY candidate is
|
||||
REAL (rejects false positives) BEFORE it lands in the report, then merges + checks consistency.
|
||||
|
||||
> Two-phase by design: **find -> validate**. Only findings that survive per-finding reverse-validation are reported.
|
||||
|
||||
All agents are project-local (`.codex/agents/`) or a built-in (`Explore`/`Plan`/`general-purpose`). This skill
|
||||
never invokes any sibling skill, NO plugin cache; the validator/arbiter fallback is the
|
||||
built-in `general-purpose`.
|
||||
|
||||
---
|
||||
|
||||
## Mode Detection (deterministic — run FIRST, then ANNOUNCE)
|
||||
|
||||
**Arguments:** `<arguments>`
|
||||
|
||||
At launch, resolve EXACTLY ONE review `{MODE}` by the strict priority order below, then PRINT the resolved mode +
|
||||
current branch + concrete scope to the user BEFORE any review. The mode is COMPUTED, never guessed interactively;
|
||||
the same rule applies on `master`/`main` and on feature branches.
|
||||
|
||||
| # | Condition (checked in order) | Resolved `{MODE}` | Scope reviewed |
|
||||
|---|------------------------------|-------------------|----------------|
|
||||
| 1 | User prompt asks for the WHOLE project, in any language ("whole project", "entire project", "everything", "all the code", or the same intent in another language) | `FULL_PROJECT` | ALL tracked `{SOURCE_GLOB}` + build/CI files in the repo |
|
||||
| 2 | User passed an explicit scope token (commit SHA, branch name, or folder path) | `EXPLICIT` | that commit / branch-vs-main / folder |
|
||||
| 3a | DEFAULT + working tree has UNCOMMITTED changes (`git status --porcelain` non-empty) | `UNCOMMITTED` | working-tree diff vs `HEAD` |
|
||||
| 3b | DEFAULT + clean tree (everything committed) | `LAST_COMMITS` | last 1-2 commits: `HEAD~2..HEAD` if it exists, else `HEAD~1..HEAD`, else the single root commit |
|
||||
|
||||
> A text prompt that is NOT a whole-project request and NOT a scope token is treated as a `{FOCUS}` directive
|
||||
> (see Focus below); the mode still resolves via rule 3.
|
||||
|
||||
**Mode-resolution commands** (runnable as-written; `PATHSPEC` = the file globs every mode reviews):
|
||||
|
||||
```bash
|
||||
PATHSPEC=({PATHSPEC_GLOBS})
|
||||
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
PORCELAIN=$(git status --porcelain)
|
||||
# robust MAIN fallback: the `|| echo main` must bind to git, not sed
|
||||
MAIN=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null); MAIN=${MAIN##*/}; MAIN=${MAIN:-main}
|
||||
|
||||
# Resolve FILES for the chosen mode, then derive an EXACT count (no head-truncation of the count).
|
||||
# FULL_PROJECT (rule 1): all tracked source + build/CI files
|
||||
FILES=$(git ls-files -- "${PATHSPEC[@]}")
|
||||
|
||||
# EXPLICIT commit (rule 2): FILES=$(git diff --name-only "${SHA}^..${SHA}" -- "${PATHSPEC[@]}")
|
||||
# EXPLICIT branch (rule 2): FILES=$(git diff --name-only "${MAIN}...HEAD" -- "${PATHSPEC[@]}")
|
||||
# EXPLICIT folder (rule 2): build files are included too, not just source
|
||||
# FILES=$(find "$FOLDER" -type f \( -name '{SOURCE_GLOB}' -o -name 'Dockerfile*' -o -name 'docker-compose.yml' -o -name '*.yml' \))
|
||||
|
||||
# UNCOMMITTED (rule 3a): working-tree diff vs HEAD (staged + unstaged)
|
||||
# FILES=$(git diff --name-only HEAD -- "${PATHSPEC[@]}")
|
||||
|
||||
# LAST_COMMITS (rule 3b): nested fallback so a single-commit repo never errors
|
||||
# if HEAD~2 exists -> HEAD~2..HEAD ; elif HEAD~1 exists -> HEAD~1..HEAD ; else the single root commit
|
||||
# if git rev-parse --verify -q HEAD~2 >/dev/null; then
|
||||
# FILES=$(git diff --name-only "HEAD~2..HEAD" -- "${PATHSPEC[@]}")
|
||||
# elif git rev-parse --verify -q HEAD~1 >/dev/null; then
|
||||
# FILES=$(git diff --name-only "HEAD~1..HEAD" -- "${PATHSPEC[@]}")
|
||||
# else
|
||||
# FILES=$(git show --name-only --pretty="" HEAD -- "${PATHSPEC[@]}") # root commit
|
||||
# fi
|
||||
|
||||
# EXACT count for the mandatory announcement (count, never the displayed list, is truncated):
|
||||
COUNT=$(printf '%s\n' "$FILES" | grep -c .)
|
||||
echo "$FILES" | head -50 # DISPLAY only — truncating the shown list is fine; COUNT above stays exact
|
||||
```
|
||||
|
||||
**MANDATORY announcement to the user (before reviewing):**
|
||||
|
||||
```
|
||||
superreview MODE = {MODE}
|
||||
Branch: {BRANCH}
|
||||
Scope: {commit range | branch-vs-main | folder path | working-tree diff vs HEAD | full project}
|
||||
Files: {COUNT} ({list, or "see range"})
|
||||
Focus: {resolved focus — user directive, else default ordering}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Focus (user directive, else default ordering)
|
||||
|
||||
If the user passed a focus directive in the prompt ("focus on X"), PRIORITIZE it. Otherwise use this DEFAULT focus
|
||||
order (highest first). Bake this exact ordering into BOTH the shared agent prompt and the validator prompt.
|
||||
|
||||
{FOCUS_TABLE}
|
||||
|
||||
> **Project emphasis:** {FOCUS_EMPHASIS}
|
||||
|
||||
> **Security is explicitly NOT a priority.** Report a security finding ONLY when it is CRITICAL (P0) — e.g. logged
|
||||
> secret, missing auth on a public path, injection. Do NOT spend agent effort on low/medium security.
|
||||
|
||||
---
|
||||
|
||||
## Project Agents (project-local + built-in ONLY)
|
||||
|
||||
> Every `subagent_type` below resolves to a real file in `.codex/agents/`, or a built-in (`Explore`/`Plan`/`general-purpose`).
|
||||
> Do NOT pass a `model` override to project agents — they define their own. NO external/plugin agent is referenced.
|
||||
|
||||
{DOMAIN_AGENTS_TABLE}
|
||||
|
||||
PLUS up to two GENERAL cross-cutting agents — they are NOT auto-spawned every time; **the model DECIDES** whether
|
||||
(and which) to include, by JUDGEMENT of the scope. Each, if included, runs ONCE over the full changed set:
|
||||
|
||||
{GENERAL_AGENTS_TABLE}
|
||||
|
||||
> For a SMALL, single-domain, low-risk change, the model MAY skip one or BOTH general agents and rely on the
|
||||
> domain owner alone. Do NOT spawn them reflexively — include only when the guidance above applies.
|
||||
|
||||
---
|
||||
|
||||
## {PROJECT_NAME} rules — REFERENCE the canonical files, do NOT restate them
|
||||
|
||||
The project rules are authoritative in their own files; this skill does NOT duplicate them. Every agent READS the
|
||||
files relevant to its area and CITES the rule number it enforces (`avoid#N`, `architecture#N`, `containers#N`, …).
|
||||
One-line gist per pointer only — the file is the authority. Phase 0 PREFLIGHT-validates that these exist (see below).
|
||||
|
||||
{RULE_POINTER_TABLE}
|
||||
|
||||
> `AGENTS.md` (+ any per-module `AGENTS.md`) auto-load and add project context.
|
||||
> Breach of any cited rule = P0/P1 candidate (per the Focus ordering; security only as P0).
|
||||
|
||||
---
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 0 — Preflight, Resolve MODE, ANNOUNCE, read references
|
||||
|
||||
1. **PREFLIGHT — validate the rule files before relying on them.** Verify the canonical rule/convention files
|
||||
referenced above EXIST; WARN per missing file and degrade gracefully (do NOT hard-fail):
|
||||
|
||||
```bash
|
||||
{RULE_PREFLIGHT_LIST}
|
||||
```
|
||||
|
||||
2. **Resolve `{MODE}` + `{FOCUS}`** per the **Mode Detection** and **Focus** sections above (deterministic; not
|
||||
interactive). Run the mode-resolution commands to get `{BRANCH}`, `{SCOPE}`, the `FILES` list, and EXACT `COUNT`.
|
||||
3. Compute a single `TIMESTAMP` for the report dir:
|
||||
|
||||
```bash
|
||||
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
|
||||
```
|
||||
|
||||
4. **ANNOUNCE to the user** (MANDATORY, before any review) the resolved mode block — `{COUNT}` is the EXACT count
|
||||
computed in Mode Detection (never truncated); include any `WARN missing rule` lines:
|
||||
|
||||
```
|
||||
superreview MODE = {MODE}
|
||||
Branch: {BRANCH}
|
||||
Scope: {commit range | branch-vs-main | folder | working-tree diff vs HEAD | full project}
|
||||
Files: {COUNT} ({displayed list — may be truncated; COUNT stays exact})
|
||||
Focus: {resolved focus — user directive, else default ordering}
|
||||
Rules preflight: {OK | "WARN missing: <files>"}
|
||||
```
|
||||
|
||||
5. **READ** `references/{STACK_REF}` (relative to this skill dir) — {STACK_LABEL} stack guidelines passed to every
|
||||
agent. Also note `references/agent-prompt.md` (the domain-owner prompt contract).
|
||||
6. If `FILES` is empty (`COUNT == 0`) -> **exit** ("Nothing to deep-review for {MODE} / {SCOPE}.").
|
||||
7. If `COUNT > 50` (and `{MODE}` != `FULL_PROJECT`) -> warn the user; suggest narrowing (per-folder or per-commit).
|
||||
For `FULL_PROJECT`, proceed but warn it is a large, slower pass.
|
||||
8. Optionally study context with a few parallel read-only `Explore` agents (changed-file modules + `.codex/rules/**`).
|
||||
This is context only, NOT findings.
|
||||
|
||||
### Phase 1 — Agent selection (route changed files to domain owners)
|
||||
|
||||
Group the `{FILE_LIST}` by path. **Enable ONLY the groups whose files actually changed** — this is the key
|
||||
"fewer agents" constraint: ONE targeted spawn per relevant group, NOT a quorum × N + a separate full standards pass.
|
||||
|
||||
{FILE_GROUP_MAP}
|
||||
|
||||
PLUS up to two GENERAL cross-cutting agents (see **Project Agents** above). They are NOT auto-spawned every time;
|
||||
**the model DECIDES** whether (and which) to include, by JUDGEMENT of the scope. Each, if included, runs ONCE over
|
||||
the full changed set (not per group).
|
||||
|
||||
> Total spawns = (number of NON-EMPTY domain groups) + **{0, 1, or 2} general agents (model's call)**.
|
||||
> Examples: a tiny single-file tweak = its domain owner alone (**1 agent**); a change with non-trivial logic =
|
||||
> domain owner + `{VALIDATOR_AGENT}` (**2**); a change spanning multiple domains/abstractions = both domain owners +
|
||||
> `{ARBITER_AGENT}` (+ `{VALIDATOR_AGENT}` if logic is risky) (**3-4**). Far fewer than the old quorum + standards + arbiter
|
||||
> combo. If a mapped domain agent is unavailable, fall back to built-in `Explore`; note it in the report.
|
||||
|
||||
> **Test-bloat audit:** when the `tests` group is non-empty, its prompt MUST also audit for TEST OVER-PROLIFERATION
|
||||
> (LLMs over-write tests) — cite the project `testing` rule, do NOT restate it. Flag, as `category: test-quality`:
|
||||
> redundant tests to DELETE (duplicate coverage, trivial getters, internal-mock-only tests); tests to COLLAPSE/MERGE
|
||||
> or PARAMETRIZE via HELPER FUNCTIONS; over-granular micro-tests violating "FEW targeted scenario tests over BIG user
|
||||
> journeys". **NON-NEGOTIABLE:** reducing test COUNT must NOT cost quality — every remaining/merged test stays
|
||||
> ISOLATED + FAST + REAL (fakes-over-mocks). Also flag any slow or non-isolated test as its own finding; never
|
||||
> recommend a merge that makes a test slow or non-isolated. Full prompt text in `references/agent-prompt.md`.
|
||||
|
||||
### Phase 2 — ONE parallel fan-out (find candidates)
|
||||
|
||||
Spawn ALL selected agents (the non-empty domain owners + whichever general agents the model chose in Phase 1) in
|
||||
**ONE message** (parallel). Use the prompt contract in `references/agent-prompt.md`: pass `{FILE_LIST}` (scoped to that
|
||||
group for domain owners; full set for any included general agent), the resolved `{FOCUS}`, and the
|
||||
`references/{STACK_REF}` guidelines. Each prompt MUST instruct the agent to **apply `.codex/rules/*` +
|
||||
`.codex/convention/*` for its area and CITE the rule number it enforces** — the skill does NOT restate the rules; the
|
||||
agents read the canonical files. Each prompt MUST also embed the focus ordering verbatim so agents spend effort in
|
||||
priority order, and **report security findings ONLY when CRITICAL (P0)**. Each prompt MUST also include the
|
||||
**over-complexity / over-engineering** dimension: flag speculative abstractions, needless params/config/methods "just
|
||||
in case", premature generalization, KISS/YAGNI-removable indirection, and duplicated logic to collapse — `category:
|
||||
over-complexity`, citing the project best-practices + avoid rules. Every agent returns the SAME JSON shape:
|
||||
|
||||
```
|
||||
{
|
||||
"findings": [{
|
||||
"file": "path/to/file{SOURCE_GLOB}", "lineStart": 42, "lineEnd": 45,
|
||||
"category": "boundary|architecture|reuse|over-complexity|security|logic|persistence|test-quality|pins|style",
|
||||
"severity": "blocker|critical|major|minor",
|
||||
"rule": "avoid#N|best-practices#N|architecture#N|containers#N|... or null",
|
||||
"title": "...", "description": "...", "suggestion": "...",
|
||||
"existing": "path|null", "reuse": "REUSE|EXTEND|CONSIDER|KEEP_NEW|null",
|
||||
"confidence": 0.85
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Each agent MUST search-first (grep / verify imports, prefer `mcp__grepai__search` if available) before flagging any
|
||||
reuse/duplicate, and read the ACTUAL code at every cited line. Collect every agent's findings into one pool
|
||||
`{CANDIDATES}` (tag each finding with its producing agent as `source`).
|
||||
|
||||
### Phase 3 — VALIDATION (per-finding adversarial reverse-check, the GATE)
|
||||
|
||||
Spawn ONE `{VALIDATOR_AGENT}` that **independently RE-VERIFIES EVERY candidate finding in reverse** against the real
|
||||
code BEFORE anything reaches the user report, AND merges the survivors into one consistent, de-duplicated, prioritized
|
||||
list. This is a per-finding gate, NOT a sample: a finding reaches the report ONLY after it survives reverse-validation.
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="{VALIDATOR_AGENT}", message="
|
||||
## superreview — per-finding VALIDATION + Merge ({PROJECT_NAME})
|
||||
|
||||
You are the adversarial validator + final arbiter. For EVERY candidate finding (no sampling, no skipping),
|
||||
READ the ACTUAL code at the cited file:line and try to DISPROVE it. Decide CONFIRM or REJECT per finding. Only
|
||||
CONFIRMed findings may appear in the final report. Then merge the survivors.
|
||||
|
||||
**Focus ordering (effort + tie-breaks, highest first):** {FOCUS}
|
||||
Security = report ONLY if CRITICAL (P0); ignore low/medium.
|
||||
Also validate OVER-COMPLEXITY findings (category over-complexity): speculative abstraction, gold-plating,
|
||||
premature generalization, KISS/YAGNI-removable indirection, collapsible duplication. Keep them (with the simpler
|
||||
shape) when real; drop if the complexity is justified.
|
||||
|
||||
**Candidate findings (pool from the domain owners + any included general agents):**
|
||||
{CANDIDATES}
|
||||
**Files under review:** {FILE_LIST}
|
||||
**Focus:** {FOCUS}
|
||||
|
||||
### 1. Reverse-validate EACH candidate (per finding — drop false positives)
|
||||
a. Existence — does the cited code/line actually exist and exhibit the issue NOW? (REJECT if not / already fixed)
|
||||
b. Accuracy — is the claim a correct reading of the code? (REJECT if it misreads the code)
|
||||
c. Actionability — is there a concrete fix path? (REJECT if vague / not actionable)
|
||||
d. Severity — is the severity right for the focus ordering? (adjust if wrong)
|
||||
Adversarial: actively argue AGAINST the finding first. It survives ONLY if it withstands a-d. Do this for every
|
||||
single candidate; none is reported unverified.
|
||||
|
||||
### 2. Merge + de-duplicate ACROSS agents
|
||||
Same file +/-5 lines + same category = ONE finding. Keep the most detailed description, highest severity,
|
||||
union of sources (comma-join). A 'duplicate/reuse' miss + an 'architecture' flag on the same code = one row.
|
||||
|
||||
### 3. Prioritize (MANDATORY P0 -> P3)
|
||||
- P0 = architecture/boundary BLOCKERS + CRITICAL security (logged secret, missing auth, injection) + any miss you RESTORE.
|
||||
- P1 = confirmed functional-correctness + architecture/boundary issues.
|
||||
- P2 = reuse misses/duplication + over-complexity + library version-pin violations rated 'error'.
|
||||
- P3 = business-requirements nits + minor over-complexity + warnings + minor/info.
|
||||
|
||||
### 4. Consistency check on the merged list
|
||||
No duplicate rows, severities monotonic with priority, every row has file:line + actionable suggestion,
|
||||
findings ORDERED P0 -> P3.
|
||||
|
||||
**Output JSON ONLY:**
|
||||
{
|
||||
\"findings\": [{
|
||||
\"id\": \"P0-1\", \"priority\": \"P0|P1|P2|P3\", \"source\": \"agent(s)\",
|
||||
\"file\": \"path\", \"lineStart\": 42, \"lineEnd\": 45,
|
||||
\"category\": \"boundary|architecture|reuse|over-complexity|security|logic|persistence|test-quality|pins|style\",
|
||||
\"severity\": \"blocker|critical|major|minor\",
|
||||
\"title\": \"...\", \"description\": \"...\", \"suggestion\": \"...\",
|
||||
\"existing\": \"path|null\", \"reuse\": \"REUSE|EXTEND|CONSIDER|KEEP_NEW|null\",
|
||||
\"verdict\": \"CONFIRM\", \"confidence\": 0.9
|
||||
}],
|
||||
\"dropped\": [{\"title\": \"...\", \"reason\": \"already-fixed|false-positive|not-actionable|duplicate-of:P0-1\"}],
|
||||
\"verdict\": \"APPROVED|CONDITIONAL|REWORK\",
|
||||
\"stats\": {\"p0\": 0, \"p1\": 0, \"p2\": 0, \"p3\": 0, \"overComplexity\": 0, \"candidates\": 0, \"confirmed\": 0, \"dropped\": 0}
|
||||
}
|
||||
|
||||
### Verdict rule
|
||||
- REWORK if any P0; CONDITIONAL if any P1/P2 (no P0); APPROVED if only P3 / none.
|
||||
")
|
||||
```
|
||||
|
||||
Record output as `{MERGED}`. If `{VALIDATOR_AGENT}` is unavailable, run the SAME prompt on the built-in `general-purpose`.
|
||||
|
||||
### Phase 4 — Write the merged report
|
||||
|
||||
```bash
|
||||
REPORT_DIR=".codex/reports/${TIMESTAMP}_superreview"
|
||||
mkdir -p "${REPORT_DIR}"
|
||||
```
|
||||
|
||||
Write ONE consolidated report to `.codex/reports/{TIMESTAMP}_superreview/REPORT.md` using the layout in
|
||||
`references/report-template.md`. Findings section is MANDATORY-sorted by priority P0 -> P3 (highest severity first).
|
||||
|
||||
### Phase 5 — Chat summary
|
||||
|
||||
```
|
||||
Super Review complete — MODE={MODE} (branch {BRANCH}), {COUNT} files, {N} agents.
|
||||
|
||||
VERDICT: {APPROVED | CONDITIONAL | REWORK}
|
||||
|
||||
Priority breakdown (sorted P0 -> P3):
|
||||
- P0 (architecture blockers + CRITICAL security + validator-restored): {N}
|
||||
- P1 (confirmed correctness + architecture/boundary): {N}
|
||||
- P2 (reuse misses + over-complexity + version-pin errors): {N}
|
||||
- P3 (business-requirements nits + warnings): {N}
|
||||
Over-complexity findings: {OC}
|
||||
|
||||
Per-finding reverse-validation dropped {N} candidates (false-positive/already-fixed/duplicate).
|
||||
|
||||
Report: .codex/reports/{TIMESTAMP}_superreview/REPORT.md
|
||||
|
||||
Recommendations / next steps (superreview is READ-ONLY — it does not apply fixes):
|
||||
- To fix: new session (English) in Manager mode (++m); delegate to the domain-owner agents ({SIMPLIFY_AGENTS});
|
||||
P0/P1 first, then P2/P3.
|
||||
- To reduce over-complexity: {OC} finding(s).{if >0: run the built-in /simplify skill (reviews + APPLIES
|
||||
reuse/simplification/efficiency cleanups), then re-run superreview.}{if 0: /simplify optional.}
|
||||
/simplify is a BUILT-IN skill (not this skill, not a plugin); skip if unavailable.
|
||||
- Optional: /code-review (built-in) for a focused correctness diff pass.
|
||||
superreview does NOT run /simplify or any skill and does NOT edit code — these are recommendations only.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| Mode | deterministic | `FULL_PROJECT` \| `EXPLICIT` \| `UNCOMMITTED` \| `LAST_COMMITS`; computed + announced |
|
||||
| Focus | ordered | user directive wins; default = correctness, architecture, reuse, pins, business-reqs; security only if P0 |
|
||||
| Fan-out | targeted | non-empty domain groups + {0,1,2} general agents, model's call |
|
||||
| Validation | ALWAYS, per-finding | Phase 3 reverse-checks EVERY candidate; only CONFIRMed findings reported |
|
||||
| Validator agent | `{VALIDATOR_AGENT}` (fallback built-in `general-purpose`) | Phase 3 merge + validate |
|
||||
| Report dir | `.codex/reports/{TIMESTAMP}_superreview/` | Merged report, findings sorted P0 -> P3 |
|
||||
| Max files | 50 (except `FULL_PROJECT`) | Warn + suggest narrowing above this |
|
||||
| Search tool | `mcp__grepai__search` if available, else `Grep`/`Glob`/Bash | reuse-first search; note which in report |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| No changed files for scope | **Exit** ("Nothing to deep-review for {SCOPE}") |
|
||||
| >50 files | Warn the user; suggest narrowing (per-folder or per-commit) |
|
||||
| A domain agent unavailable | Fall back to built-in `Explore` with the same prompt; note in report |
|
||||
| `{VALIDATOR_AGENT}` unavailable | Run Phase 3 validation prompt on the built-in `general-purpose`; note in report |
|
||||
| `grepai` unavailable | Fall back to `Grep`/`Glob`/Bash; state which path was used in the report |
|
||||
| Agent timeout | Retry once, then mark that source unavailable + warn in report |
|
||||
| Validation rejects everything | Report "No issues survived validation" — verdict APPROVED |
|
||||
| All sources clean | Report "No issues found across standards, architecture, and correctness" — verdict APPROVED |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- `references/{STACK_REF}` — {STACK_LABEL} stack guidelines (passed to every agent).
|
||||
- `references/agent-prompt.md` — domain-owner agent prompt contract (Phase 2) + the group->agent map.
|
||||
- `references/report-template.md` — merged-report layout (Phase 4).
|
||||
|
||||
<!--
|
||||
SKILL METADATA — generated {GENERATED_AT}
|
||||
|
||||
Self-contained project-local deep-review skill for {PROJECT_NAME}. NO sibling-skill orchestration, never invokes
|
||||
another skill, NO plugin dependency. Two-phase: targeted domain-owner fan-out (find) -> adversarial validation (gate)
|
||||
-> ONE merged report at .codex/reports/{TIMESTAMP}_superreview/REPORT.md.
|
||||
Agents: project-local (.codex/agents/) + built-in (Explore/Plan/general-purpose).
|
||||
|
||||
Re-adopt triggers:
|
||||
- New / renamed agent in .codex/agents/
|
||||
- AGENTS.md / .codex/rules invariants updated
|
||||
- New service group (path pattern) added
|
||||
Regenerate with: $brewcode:superreview
|
||||
-->
|
||||
@@ -0,0 +1,92 @@
|
||||
# Domain-Owner Agent Prompt Template (superreview Phase 2 — {PROJECT_NAME})
|
||||
|
||||
Each changed-file group is routed to its DOMAIN-OWNER agent (see the group->agent map in `SKILL.md`). Spawn ALL
|
||||
non-empty groups in ONE message (parallel). Every agent gets the SAME finding contract so Phase 3 can validate and
|
||||
Phase 4 can merge.
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="{AGENT}", message="
|
||||
## superreview — {GROUP} pass ({PROJECT_NAME})
|
||||
|
||||
You review ONLY the files below. Read the ACTUAL code. Report STANDARDS + ARCHITECTURE + CORRECTNESS issues.
|
||||
|
||||
**SEARCH-FIRST (HARD rule — reuse-first):** before flagging a 'duplicate' or 'reuse' miss, grep the repo
|
||||
(Bash grep/find over the shared/util/common/domain/adapters dirs) and verify imports. If grepai
|
||||
(mcp__grepai__search) is available, prefer it for semantic search.
|
||||
|
||||
**Files:** {FILE_LIST}
|
||||
**Focus:** {FOCUS}
|
||||
|
||||
### Focus ordering — spend effort in this priority (highest first)
|
||||
1. Functional correctness — does the code do what it should? logic, edge cases, race conditions.
|
||||
2. Clean architecture / boundary compliance — module/service boundaries, seams, layering, idempotency.
|
||||
3. Reuse of EXISTING code — stdlib/native, existing project modules, already-imported libs; do NOT reinvent.
|
||||
Flag duplication + missed reuse (cite the project reuse-first rule).
|
||||
4. Library version pins — exact X.Y.Z, no floating/stale (cite the project pins rule).
|
||||
5. Business-requirements compliance.
|
||||
SECURITY is NOT a priority: report a security finding ONLY when CRITICAL (P0) — logged secret, missing auth on a
|
||||
public path, injection. Do NOT spend effort on low/medium security.
|
||||
(If the project fine-tune emphasis in SKILL.md reorders this, follow that ordering.)
|
||||
|
||||
### OVER-COMPLEXITY / over-engineering — report it as findings (category \"over-complexity\")
|
||||
Actively flag code more complex than the requirement needs: speculative abstractions, needless params/config/methods
|
||||
'just in case', premature generalization, indirection KISS/YAGNI would remove, duplicated logic that should be
|
||||
collapsed. Cite the project rule (do NOT restate it): best-practices (ship the simplest version that works) + avoid
|
||||
(no gold-plating) + avoid (reuse-first). Severity like any other finding; suggest the simpler shape (delete the layer,
|
||||
inline the one-caller, collapse the dup, reuse existing code).
|
||||
|
||||
### Apply the canonical project rules — READ them, do not assume; CITE the rule # you enforce
|
||||
The rules are NOT restated here. READ the files relevant to your area (the rule-pointer table in SKILL.md lists them:
|
||||
`.codex/rules/*` + `.codex/convention/*`) and enforce them; put the exact rule number in each finding's \"rule\"
|
||||
field (avoid#N, architecture#N, containers#N, best-practices#N, testing#N, …). A breach of any cited rule = P0/P1
|
||||
candidate (per the Focus ordering; security only as P0).
|
||||
|
||||
**Output JSON ONLY:**
|
||||
{
|
||||
\"findings\": [{
|
||||
\"file\": \"path/to/file{SOURCE_GLOB}\",
|
||||
\"lineStart\": 42,
|
||||
\"lineEnd\": 45,
|
||||
\"category\": \"boundary|architecture|reuse|over-complexity|security|logic|persistence|test-quality|pins|style\",
|
||||
\"severity\": \"blocker|critical|major|minor\",
|
||||
\"rule\": \"avoid#N|best-practices#N|architecture#N|containers#N|... (project rule namespace, or null)\",
|
||||
\"title\": \"Short summary (<=80 chars)\",
|
||||
\"description\": \"What is wrong + which invariant/rule it breaks\",
|
||||
\"suggestion\": \"Concrete fix / where code belongs / what to reuse\",
|
||||
\"existing\": \"path/to/similar|null (for reuse/duplicate findings)\",
|
||||
\"reuse\": \"REUSE|EXTEND|CONSIDER|KEEP_NEW|null\",
|
||||
\"confidence\": 0.85
|
||||
}]
|
||||
}
|
||||
|
||||
**Severity guide:**
|
||||
- blocker: prod outage / security breach / data loss / boundary violation in a critical path.
|
||||
- critical: significant bug, perf degradation, boundary violation.
|
||||
- major: important maintainability/correctness issue.
|
||||
- minor: style, naming, minor improvement.
|
||||
|
||||
Report ONLY issues (not positives). Reference exact lines. Provide actionable suggestions. Read the real code.
|
||||
")
|
||||
```
|
||||
|
||||
> Domain-owner map (Phase 2) lives in `SKILL.md` (the `FILE_GROUP_MAP`). Built-in `Explore` is the only allowed
|
||||
> fallback if a mapped agent is unavailable.
|
||||
|
||||
## test agent — also audit for TEST BLOAT / over-testing (tests group only)
|
||||
|
||||
When the `tests` group is non-empty, the test agent's prompt MUST add this block (cite the project `testing` rule, do
|
||||
NOT restate it). Use category `test-quality`; severity per impact. GOAL = reduce test COUNT; isolation + speed +
|
||||
real-ness are NON-NEGOTIABLE.
|
||||
|
||||
```
|
||||
### Test bloat / over-proliferation audit (cite the project testing rule)
|
||||
LLMs over-write tests — hunt for and report:
|
||||
- Too many / redundant tests that should be DELETED: duplicate coverage, trivial getters, internal-mock-only
|
||||
'did we call X once' tests.
|
||||
- Tests to COLLAPSE/MERGE, or to PARAMETRIZE via HELPER FUNCTIONS passing args (per the project test convention).
|
||||
- Over-granular micro-tests violating 'FEW targeted scenario tests over BIG user journeys'.
|
||||
NON-NEGOTIABLE — never trade quality for fewer tests: every remaining/merged test MUST stay ISOLATED + FAST + REAL
|
||||
(fakes-over-mocks, testcontainers/real deps where needed). Also FLAG any test that is slow or non-isolated (shared
|
||||
mutable state, order-dependence, network/real-clock) — that is its own finding. Do NOT recommend a merge that would
|
||||
make a test slow or non-isolated. Report all as category test-quality, citing the relevant project testing rule #.
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
# Go Standards Reference
|
||||
|
||||
Standards for Go projects. The project's own rules in `.codex/rules/*` + `.codex/convention/*` are authoritative —
|
||||
where this guidance conflicts, the **project rule WINS**. Cite the project rule # when enforcing.
|
||||
|
||||
## Tech-Specific Checks (priority dimensions)
|
||||
|
||||
| Category | Checks |
|
||||
|----------|--------|
|
||||
| Error handling | Wrap with `fmt.Errorf("...: %w", err)`, sentinel errors, `errors.Is`/`errors.As`, never ignore `err` |
|
||||
| Concurrency | Goroutine leaks, channel close ownership, `context.Context` propagation, `sync` primitives, race-free |
|
||||
| Memory | Slice capacity/aliasing, pointer vs value semantics, `defer` for cleanup |
|
||||
| Reuse | stdlib (`strings`, `slices`, `maps`, `errors`, `io`) + existing internal packages before new code |
|
||||
| Security | SQL parameterization, input validation, no command injection (report only if CRITICAL/P0) |
|
||||
| Interfaces | Small interfaces, accept interfaces / return structs, composition over inheritance |
|
||||
|
||||
## File Patterns
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Source | `*.go` (non-test) |
|
||||
| Tests | `*_test.go` |
|
||||
| Build/Config | `go.mod`, `go.sum`, `Dockerfile*`, `docker-compose.yml`, `.github/workflows/*.yml` |
|
||||
|
||||
## Naming
|
||||
|
||||
| Type | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| Packages | short, lowercase, no underscores | `httpclient` |
|
||||
| Exported | PascalCase | `NewUserService` |
|
||||
| Unexported | camelCase | `parseConfig` |
|
||||
| Interfaces | `-er` suffix where natural | `Reader`, `Validator` |
|
||||
| Errors | `Err*` sentinel / `*Error` type | `ErrNotFound` |
|
||||
|
||||
## Error Handling
|
||||
|
||||
```go
|
||||
// Wrap with context, preserve the chain
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch user %d: %w", id, err)
|
||||
}
|
||||
|
||||
// Sentinel comparison
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Never discard `err` (`_ = f()`) without justification | VIOL |
|
||||
| Wrap with `%w` to preserve chain | REQ |
|
||||
| `errors.Is`/`errors.As` over `==`/type assert | PREF |
|
||||
| No `panic` for ordinary errors (only truly unrecoverable) | REQ |
|
||||
|
||||
## Concurrency
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Propagate `context.Context` as first arg | REQ |
|
||||
| Channel close owned by the sender | REQ |
|
||||
| Guard shared state (`sync.Mutex` / channels) — no data races | REQ |
|
||||
| No goroutine leaks (every goroutine has a clear exit) | REQ |
|
||||
| `errgroup` / `sync.WaitGroup` for fan-out | PREF |
|
||||
|
||||
## Interfaces & Structure
|
||||
|
||||
Accept interfaces, return concrete structs. Keep interfaces small (1-3 methods). Define interfaces at the consumer,
|
||||
not the producer. Prefer composition (embedding) over large interfaces.
|
||||
|
||||
## Logging
|
||||
|
||||
Use the project logger (`slog` / `zap` / `zerolog`); no `fmt.Println` in prod; structured fields over string
|
||||
concatenation; never log secrets (security — P0); main code at warn/error level.
|
||||
|
||||
## Testing
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Table-driven tests via HELPER, GIVEN/WHEN/THEN comments | REQ |
|
||||
| `t.Run(name, ...)` subtests for scenarios | PREF |
|
||||
| Concrete assertions (`got == want`, full struct compare) over weak nil checks | REQ |
|
||||
| No conditional asserts (`if` deciding which assert runs) | VIOL |
|
||||
| `t.Parallel()` only where the test is truly isolated | PREF |
|
||||
| Real deps / testcontainers where integration matters; fakes over mocks | REQ |
|
||||
|
||||
```go
|
||||
func TestGetUser(t *testing.T) {
|
||||
// GIVEN
|
||||
svc := newServiceWithFake(t)
|
||||
// WHEN
|
||||
got, err := svc.GetUser(ctx, 1)
|
||||
// THEN
|
||||
require.NoError(t, err)
|
||||
want := User{ID: 1, Name: "John"}
|
||||
require.Equal(t, want, got)
|
||||
}
|
||||
```
|
||||
|
||||
## Common Violations
|
||||
|
||||
| # | Violation | Fix |
|
||||
|---|-----------|-----|
|
||||
| 1 | Ignored `err` | Handle or wrap with `%w` |
|
||||
| 2 | `panic` for ordinary error | Return an error |
|
||||
| 3 | Missing `context.Context` propagation | Pass `ctx` as first arg |
|
||||
| 4 | Goroutine leak | Ensure a clear exit / `ctx` cancel |
|
||||
| 5 | Data race on shared state | Mutex / channel |
|
||||
| 6 | Large interface | Split into small interfaces |
|
||||
| 7 | `fmt.Println` in prod | Project structured logger |
|
||||
| 8 | Reinventing stdlib (`slices`/`maps`) | Reuse stdlib |
|
||||
| 9 | Logged secret | Redact (P0) |
|
||||
| 10 | Floating dependency / `replace` to a branch | Pin exact module version |
|
||||
|
||||
## Search Locations (reuse-first)
|
||||
|
||||
`internal/`, `pkg/`, `cmd/`, `**/util/`, `**/common/`, `**/shared/`.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
`go.mod` with exact module versions; run `go mod tidy`; avoid `replace` to a moving branch. Pin tool versions used in
|
||||
CI. The project's canonical version policy (if any) is authoritative — bump in lockstep.
|
||||
|
||||
## Tools
|
||||
|
||||
`go build` / `go test` / `go vet`, `golangci-lint`, `gofmt`/`goimports`, `testify` or stdlib `testing`,
|
||||
testcontainers-go, `errgroup`.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Java/Kotlin Standards Reference
|
||||
|
||||
Standards for Java/Kotlin enterprise projects. The project's own rules in `.codex/rules/*` + `.codex/convention/*`
|
||||
are authoritative — where this guidance conflicts, the **project rule WINS**. Cite the project rule # when enforcing.
|
||||
|
||||
## Tech-Specific Checks (priority dimensions)
|
||||
|
||||
| Category | Checks |
|
||||
|----------|--------|
|
||||
| DI | Constructor injection only, no field `@Autowired`, `@RequiredArgsConstructor` + final fields |
|
||||
| Transactions | `@Transactional` scope (service not repository), rollback rules, isolation |
|
||||
| Null-safety | `Optional` usage, `@NonNull`/`@Nullable`, `Objects.requireNonNull` |
|
||||
| N+1 | Eager vs lazy loading, batch fetching, entity graphs |
|
||||
| Reuse | JDK -> Apache Commons -> Guava before writing utility code |
|
||||
| Security | `@PreAuthorize`, input validation, SQL injection (report only if CRITICAL/P0) |
|
||||
| Lombok | `@Value`/`@Builder`/`@Slf4j`; `@Data` only on mutable entities |
|
||||
|
||||
## File Patterns
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Source | `*.java`, `*.kt`, `*.kts` |
|
||||
| Build | `pom.xml`, `build.gradle`, `build.gradle.kts` |
|
||||
| Tests | `*Test.java`, `*Test.kt`, `*IT.java` |
|
||||
| Config | `application.yml`, `application.properties` |
|
||||
|
||||
## Naming
|
||||
|
||||
| Type | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| Entity | `*Entity` suffix | `UserEntity` |
|
||||
| DTO Response | `*Response` | `UserResponse` |
|
||||
| DTO Request | `*Request` | `CreateUserRequest` |
|
||||
| Repository | `*Repository` | `UserRepository` |
|
||||
| Service | `*Service` | `UserService` |
|
||||
| Controller | `*Controller` | `UserController` |
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository; // final + constructor
|
||||
private final EmailService emailService;
|
||||
}
|
||||
```
|
||||
|
||||
Field injection (`@Autowired` on a field) = VIOLATION (harder to test, hidden deps).
|
||||
|
||||
## Stream API & Functional Style
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Prefer Stream API over imperative loops | REQ |
|
||||
| Method references over lambdas (`User::getName`) | PREF |
|
||||
| No side effects in streams | REQ |
|
||||
| `toList()` over `collect(Collectors.toList())` (Java 16+) | PREF |
|
||||
|
||||
## Immutability
|
||||
|
||||
`final` fields by default; `List.of()`/`Set.of()`/`Map.of()` for immutable collections; Lombok `@Value` for
|
||||
immutable DTOs; `@Builder` for complex construction.
|
||||
|
||||
## Library Usage Priority (reuse-first)
|
||||
|
||||
| Priority | Library | Common APIs |
|
||||
|----------|---------|-------------|
|
||||
| 1 | JDK | `Objects`, `Optional`, `String`, `Math`, `Collections`, `Files`, `Path` |
|
||||
| 2 | Apache Commons | `StringUtils`, `CollectionUtils`, `Validate`, `IOUtils` |
|
||||
| 3 | Guava | `Preconditions`, `Strings`, `Iterables`, `Lists`, `Maps` |
|
||||
|
||||
## Logging
|
||||
|
||||
`@Slf4j` + SLF4J facade; parameterized `log.info("User: {}", id)`; no `System.out.println`; no logs in tests; main
|
||||
code at warn/error only. Never log secrets (security — P0).
|
||||
|
||||
## Test Patterns
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| BDD comments `// GIVEN / // WHEN / // THEN` | REQ |
|
||||
| `@DisplayName` on methods, not class | REQ |
|
||||
| No Javadoc in tests | REQ |
|
||||
| `.as("description")` on EVERY AssertJ assertion | REQ |
|
||||
| Concrete: `isEqualTo(y)` / `hasSize(n)` over weak `isNotNull()` / `isNotEmpty()` / `>=` | REQ |
|
||||
| No `if` in tests (assert precondition first, then unconditional assert) | VIOL |
|
||||
| `allSatisfy()` over `forEach`; `extracting().contains(tuple())` for collections | REQ |
|
||||
|
||||
```java
|
||||
// Weak — avoid
|
||||
assertThat(result).isNotNull();
|
||||
// Specific
|
||||
assertThat(result).as("created user").isEqualTo(expected);
|
||||
assertThat(list).as("result size").hasSize(3);
|
||||
```
|
||||
|
||||
## Spring Boot Patterns
|
||||
|
||||
`@Transactional` on service; `ResponseEntity<T>` in controllers; `@Valid` on request bodies;
|
||||
profile-specific `application-{profile}.yml`.
|
||||
|
||||
## Kotlin-Specific
|
||||
|
||||
`data class` for DTOs; extension functions for utilities; `?.let {}` over null checks; `when` over `if-else` chains;
|
||||
`Duration` conversions explicit.
|
||||
|
||||
## Common Violations
|
||||
|
||||
| # | Violation | Fix |
|
||||
|---|-----------|-----|
|
||||
| 1 | Missing Entity suffix | Add `Entity` to JPA entities |
|
||||
| 2 | Field injection | Constructor injection |
|
||||
| 3 | Loop instead of Stream | Convert to Stream API |
|
||||
| 4 | `System.out.println` | Use `@Slf4j` |
|
||||
| 5 | Missing `.as()` in test | Add description |
|
||||
| 6 | `isNotNull()` assertion | Specific value assertion |
|
||||
| 7 | `if` in test | Assert precondition first |
|
||||
| 8 | Writing utility that exists | Check JDK/Commons/Guava |
|
||||
| 9 | Logs in tests | Remove all logging |
|
||||
| 10 | Floating/`@1.0.0` dependency | Pin exact `X.Y.Z` |
|
||||
|
||||
## Search Locations (reuse-first)
|
||||
|
||||
`**/util/`, `**/common/`, `**/shared/`, `**/core/`, `**/helper/`.
|
||||
|
||||
## Tools
|
||||
|
||||
Maven/Gradle, Spring Boot, JUnit 5, AssertJ, Mockito, Lombok, WireMock, Testcontainers.
|
||||
@@ -0,0 +1,177 @@
|
||||
# Python Standards Reference
|
||||
|
||||
GENERIC modern-Python guidance (type hints, docstrings, imports, exceptions, comprehensions, testing). The project's
|
||||
own rules in `.codex/rules/*` + `.codex/convention/*` are authoritative — where this guidance conflicts with a
|
||||
project rule, the **project rule WINS**. Cite the project rule # when enforcing.
|
||||
|
||||
## Tech-Specific Checks (priority dimensions)
|
||||
|
||||
| Category | Checks |
|
||||
|----------|--------|
|
||||
| Type hints | Function signatures, return types, generics, `X \| None` over `Optional[X]` |
|
||||
| Exceptions | Specific exception types, `raise X from e`, context managers, no bare `except:` |
|
||||
| Async | `asyncio` patterns, event-loop handling, no blocking calls in async paths |
|
||||
| Reuse | stdlib (`itertools`, `functools`, `pathlib`) + existing project modules before new code |
|
||||
| Security | SQL parameterization, input validation; never log secrets (report only if CRITICAL/P0) |
|
||||
| Style | PEP8, docstrings, comprehensions, no `print()` in prod |
|
||||
|
||||
## File Patterns
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Source | `*.py` |
|
||||
| Tests | `test_*.py`, `*_test.py`, `**/tests/*.py` |
|
||||
| Config | `pyproject.toml`, `requirements*.txt` (`setup.py`/`setup.cfg` legacy) |
|
||||
| Types | `py.typed`, `*.pyi` |
|
||||
|
||||
## Type Hints
|
||||
|
||||
| Location | Requirement |
|
||||
|----------|-------------|
|
||||
| Function parameters | All params typed |
|
||||
| Function returns | Return type annotated |
|
||||
| Class attributes | Typed in `__init__` or class body |
|
||||
|
||||
> **Python 3.10+:** `X | Y` over `Union[X, Y]`, `list[X]` over `List[X]`.
|
||||
|
||||
```python
|
||||
# Fully typed
|
||||
def process_user(user_id: int, options: dict[str, Any] | None = None) -> User:
|
||||
...
|
||||
```
|
||||
|
||||
## Docstrings (Google style)
|
||||
|
||||
Module + public class + public function docstrings required; private (`_*`) optional.
|
||||
|
||||
```python
|
||||
def fetch_user(user_id: int, include_profile: bool = False) -> User | None:
|
||||
"""Fetch user by ID.
|
||||
|
||||
Args:
|
||||
user_id: Unique identifier.
|
||||
include_profile: Whether to include full profile data.
|
||||
|
||||
Returns:
|
||||
User if found, None otherwise.
|
||||
|
||||
Raises:
|
||||
DatabaseError: If the database connection fails.
|
||||
"""
|
||||
```
|
||||
|
||||
## Naming
|
||||
|
||||
| Type | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| Modules | snake_case | `user_service.py` |
|
||||
| Classes | PascalCase | `UserService` |
|
||||
| Functions | snake_case | `get_user_by_id` |
|
||||
| Constants | UPPER_SNAKE | `MAX_RETRIES` |
|
||||
| Private | `_prefix` | `_internal_method` |
|
||||
|
||||
## Imports (isort order)
|
||||
|
||||
1. Standard library 2. Third-party 3. Local. Absolute imports, one per line, no wildcards.
|
||||
|
||||
```python
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.models import User
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
| Pattern | When |
|
||||
|---------|------|
|
||||
| `@dataclass` | Simple data containers |
|
||||
| `@dataclass(frozen=True)` | Immutable values |
|
||||
| `pydantic.BaseModel` | Validation needed (API inputs) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Specific catch (no bare `except:`) | REQ |
|
||||
| Chain exceptions `raise X from e` | REQ |
|
||||
| Context managers for resources | REQ |
|
||||
|
||||
```python
|
||||
try:
|
||||
user = fetch_user(user_id)
|
||||
except UserNotFoundError:
|
||||
logger.warning("User %s not found", user_id)
|
||||
raise
|
||||
except DatabaseError as e:
|
||||
raise ServiceError("Database unavailable") from e
|
||||
```
|
||||
|
||||
## Testing (pytest)
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| pytest over unittest | PREF |
|
||||
| Fixtures + `conftest.py` for shared setup | REQ |
|
||||
| Fakes over mocks (capture state, assert at end) | REQ |
|
||||
| Parametrize via HELPER functions, not always `@pytest.mark.parametrize` | per project convention |
|
||||
| No `if` in test bodies (assert precondition, then unconditional assert) | VIOL |
|
||||
| Concrete equality (`== expected`) over weak `is not None` / `>=` checks | REQ |
|
||||
|
||||
```python
|
||||
def test_get_user_returns_user_when_exists(self, user_service, sample_user):
|
||||
# GIVEN
|
||||
user_id = sample_user.id
|
||||
# WHEN
|
||||
result = user_service.get_user(user_id)
|
||||
# THEN — full-object equality
|
||||
assert result == sample_user
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| `logging` module, logger per module | REQ |
|
||||
| No `print()` in prod | VIOL |
|
||||
| Lazy formatting `log.info("User: %s", id)` | PREF |
|
||||
| NEVER log secrets/tokens | VIOL (security — P0) |
|
||||
|
||||
> Tests emit NO logs; main code logs at warn/error only.
|
||||
|
||||
## Style & Tooling
|
||||
|
||||
Black (format), Ruff (lint), isort (imports), mypy (types). Prefer comprehensions for simple transforms;
|
||||
generator expressions for large/lazy iteration.
|
||||
|
||||
## Common Violations
|
||||
|
||||
| # | Violation | Fix |
|
||||
|---|-----------|-----|
|
||||
| 1 | Missing type hints | Add param + return types |
|
||||
| 2 | No docstring on public API | Add Google-style docstring |
|
||||
| 3 | Bare `except:` | Catch specific exceptions |
|
||||
| 4 | `print()` in prod | Use logging |
|
||||
| 5 | Wildcard import | Import specific names |
|
||||
| 6 | Missing `from e` in reraise | Chain exceptions |
|
||||
| 7 | Mutable default arg | Use `None` + conditional |
|
||||
| 8 | `Union[X, Y]` on 3.10+ | Use `X | Y` |
|
||||
| 9 | Logged/committed secret | Env + validation only (P0) |
|
||||
| 10 | Floating/`@1.0.0` dependency | Pin exact `X.Y.Z` |
|
||||
| 11 | Reinventing stdlib/existing module | Reuse-first: grep + import |
|
||||
|
||||
## Search Locations (reuse-first)
|
||||
|
||||
`**/app/`, `**/adapters/`, `**/domain/`, `**/utils/`, `**/common/`, `**/lib/`, `**/helpers/`, `**/tests/`.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
Pin every dependency to an exact `pkg==X.Y.Z` in `requirements*.txt` / `pyproject.toml`. Shared packages must pin
|
||||
identically across modules. The canonical pin table (if the project has one) is authoritative — bump in lockstep.
|
||||
|
||||
## Tools
|
||||
|
||||
pip / Poetry / uv (per project), pytest, respx/httpx-mock, testcontainers, mypy, Black, Ruff, isort.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Merged Report Layout (superreview Phase 4 — {PROJECT_NAME})
|
||||
|
||||
Output: `.codex/reports/{TIMESTAMP}_superreview/REPORT.md`. ONE consolidated, validated, P0->P3-sorted report.
|
||||
|
||||
```markdown
|
||||
# Super Review Report — {PROJECT_NAME}
|
||||
|
||||
**Generated:** {TIMESTAMP}
|
||||
**Mode:** {MODE} (branch: {BRANCH})
|
||||
**Scope:** {concrete scope — commit range | branch-vs-main | folder | working-tree vs HEAD | full project}
|
||||
**Focus:** {resolved focus — user directive, else default ordering; security only if P0}
|
||||
**Files Reviewed:** {COUNT}
|
||||
**Search tool used:** {grepai | Grep/Glob/Bash fallback}
|
||||
**Agents run (targeted fan-out):** {AGENT_LIST}
|
||||
|
||||
> Findings section below is MANDATORY-sorted by priority P0 -> P3 (highest severity first).
|
||||
|
||||
## Summary Severity Matrix
|
||||
|
||||
| Priority | Meaning | Count |
|
||||
|----------|---------|-------|
|
||||
| P0 | Architecture blockers + CRITICAL security + validator-restored misses | {N} |
|
||||
| P1 | Confirmed correctness + architecture/boundary | {N} |
|
||||
| P2 | Reuse misses + over-complexity + version-pin errors | {N} |
|
||||
| P3 | Business-requirements nits + minor over-complexity + warnings | {N} |
|
||||
|
||||
## Merged Prioritized Findings (sorted P0 -> P3, highest severity first)
|
||||
|
||||
| ID | Priority | Source | File:Line | Category | Severity | Title | Suggestion |
|
||||
|----|----------|--------|-----------|----------|----------|-------|------------|
|
||||
| P0-1 | P0 | {agent} | path:42-45 | boundary | blocker | ... | ... |
|
||||
|
||||
## Boundary & Architecture
|
||||
|
||||
| File:Line | Invariant | Issue | Fix |
|
||||
|-----------|-----------|-------|-----|
|
||||
|
||||
## Reuse / Duplicates
|
||||
|
||||
| New Code | Existing | Similarity | Action | Note |
|
||||
|----------|----------|------------|--------|------|
|
||||
|
||||
## Over-Complexity / Over-Engineering
|
||||
|
||||
| File:Line | What | Rule | Simpler shape |
|
||||
|-----------|------|------|---------------|
|
||||
| ... | speculative abstraction / gold-plating / premature generalization / collapsible dup | best-practices#N \| avoid#N | delete layer / inline one-caller / collapse dup / reuse existing |
|
||||
|
||||
## Dropped in Validation (false-positive / already-fixed / de-dup)
|
||||
|
||||
| Title | Reason |
|
||||
|-------|--------|
|
||||
|
||||
## VERDICT
|
||||
|
||||
**{APPROVED | CONDITIONAL | REWORK}**
|
||||
|
||||
## Stats
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| P0 / P1 / P2 / P3 | {a} / {b} / {c} / {d} |
|
||||
| Over-complexity findings | {OC} |
|
||||
| Candidate findings (pre-validation) | {N} |
|
||||
| Confirmed by validation | {N} |
|
||||
| Dropped by validation | {N} |
|
||||
| Agents spawned | {N} |
|
||||
| Files reviewed | {COUNT} |
|
||||
|
||||
## Recommendations / Next steps
|
||||
|
||||
> **superreview is READ-ONLY — it does not apply fixes.** It only reports. Act on the findings as below.
|
||||
|
||||
- **To FIX the findings:** start a NEW session (English), turn on **Manager mode (`++m`)**, and DELEGATE the fixes
|
||||
to the domain-owner agents. Address **P0/P1 first, then P2/P3**.
|
||||
- **To reduce over-complexity:** {OC} over-complexity / missed-reuse / duplication findings.
|
||||
{if {OC} > 0: **run the built-in `/simplify` skill** — it reviews the changed code for reuse / simplification /
|
||||
efficiency and APPLIES the cleanups. Run it in a fix-session, then re-run superreview to confirm.}
|
||||
{if {OC} == 0: `/simplify` is OPTIONAL — no over-complexity was flagged.}
|
||||
`/simplify` is a BUILT-IN Codex skill (NOT this skill, NOT a plugin); if it is unavailable, skip it.
|
||||
- **Optional:** `/code-review` (built-in) for a focused correctness diff pass.
|
||||
|
||||
> These are RECOMMENDATIONS only. superreview does NOT invoke `/simplify`, does NOT call any other skill, and does
|
||||
> NOT edit code — acting on them is the user's next session.
|
||||
```
|
||||
|
||||
## Severity / reuse legend
|
||||
|
||||
- **Priority:** P0 (blocker — fix first) -> P3 (nice-to-have).
|
||||
- **Severity:** blocker (outage/breach/data-loss) > critical (significant bug/perf/boundary) > major (maintainability) > minor (style).
|
||||
- **Reuse:** REUSE (import existing 90-100%) | EXTEND (add params to existing 70-89%) | CONSIDER (evaluate 50-69%) | KEEP_NEW (<50%, justified).
|
||||
- **Verdict:** REWORK if any P0; CONDITIONAL if any P1/P2 (no P0); APPROVED if only P3 / none.
|
||||
@@ -0,0 +1,132 @@
|
||||
# TypeScript / Node / React Standards Reference
|
||||
|
||||
Standards for TypeScript, Node.js and React projects. The project's own rules in `.codex/rules/*` +
|
||||
`.codex/convention/*` are authoritative — where this guidance conflicts, the **project rule WINS**. Cite the rule #.
|
||||
|
||||
## Tech-Specific Checks (priority dimensions)
|
||||
|
||||
| Category | Checks |
|
||||
|----------|--------|
|
||||
| Async | Promise handling, unhandled rejections, correct async/await, no floating promises |
|
||||
| Types | Strict null checks, type guards, generics; no `any` (use `unknown` + narrowing) |
|
||||
| Validation | Input sanitization, schema validation (Zod/Joi) at boundaries |
|
||||
| Reuse | existing hooks/components/utils before new ones (check `common/`, `shared/`) |
|
||||
| Security | XSS prevention, CSRF, no injected HTML (report only if CRITICAL/P0) |
|
||||
| Imports | ESM vs CJS consistency, barrel exports, no circular deps |
|
||||
|
||||
## File Patterns
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Components | `*.tsx`, `*.jsx` |
|
||||
| Logic | `*.ts`, `*.js` |
|
||||
| Styles | `*.styled.ts`, `**/styles.ts`, `*.css`, `*.scss` |
|
||||
| Tests | `*.test.tsx`, `*.spec.ts`, `**/__tests__/*` |
|
||||
| Config | `package.json`, `tsconfig.json`, `.eslintrc*` |
|
||||
|
||||
## Component Patterns (React)
|
||||
|
||||
Functional components only (no class components). Arrow functions for components. Structure order: types -> declaration
|
||||
-> hooks -> handlers -> render helpers -> JSX.
|
||||
|
||||
```tsx
|
||||
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
|
||||
return <div>{user.name}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
| Hook | Common mistake |
|
||||
|------|----------------|
|
||||
| `useState` | Over-using for derived state |
|
||||
| `useEffect` | Missing cleanup / deps array |
|
||||
| `useMemo`/`useCallback` | Premature optimization |
|
||||
| `useRef` | Using for state |
|
||||
|
||||
Custom hooks: `use*` prefix, extract reusable logic, return object for >2 values. **Check existing hooks first**
|
||||
(`hooks/`, `use*.ts`, grepai_search) before creating.
|
||||
|
||||
## TypeScript Type Safety
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Explicit prop/return types | REQ |
|
||||
| No `any` | VIOLATION |
|
||||
| `unknown` over `any` + narrowing | PREF |
|
||||
| Interface for objects, type for unions | PREF |
|
||||
|
||||
```tsx
|
||||
interface UserCardProps { user: User; onEdit?: (id: string) => void; }
|
||||
|
||||
type ApiResponse<T> =
|
||||
| { status: 'success'; data: T }
|
||||
| { status: 'error'; error: string };
|
||||
```
|
||||
|
||||
## Styling (if styled-components / theme)
|
||||
|
||||
Use theme tokens; no hardcoded colors/spacing/fonts. Colocate styles; extend base components; check
|
||||
`components/common/` before adding a new styled component.
|
||||
|
||||
## State Management
|
||||
|
||||
| Scope | Solution |
|
||||
|-------|----------|
|
||||
| Component | `useState` |
|
||||
| Subtree | Context + `useReducer` |
|
||||
| Global | Redux/Zustand/Jotai |
|
||||
|
||||
Prop drilling >3 levels -> Context or state management.
|
||||
|
||||
## Testing (Jest + React Testing Library)
|
||||
|
||||
| Rule | Verdict |
|
||||
|------|---------|
|
||||
| Test behavior, not implementation | REQ |
|
||||
| Query priority: `getByRole` > `getByLabelText` > `getByText` > `getByTestId` | PREF |
|
||||
| `screen` over destructure; `userEvent` over `fireEvent` | PREF |
|
||||
| GIVEN/WHEN/THEN structure | REQ |
|
||||
|
||||
```tsx
|
||||
it('renders user name', () => {
|
||||
// GIVEN
|
||||
const user = { id: '1', name: 'John' };
|
||||
// WHEN
|
||||
render(<UserCard user={user} />);
|
||||
// THEN
|
||||
expect(screen.getByText('John')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
`React.memo` / `useMemo` / `useCallback` AS NEEDED — profile first, optimize second. `React.lazy()` + Suspense for
|
||||
route-level code splitting.
|
||||
|
||||
## Common Violations
|
||||
|
||||
| # | Violation | Fix |
|
||||
|---|-----------|-----|
|
||||
| 1 | `any` type | `unknown` or specific type |
|
||||
| 2 | Class component | Convert to functional |
|
||||
| 3 | Missing types | Add explicit types |
|
||||
| 4 | Hardcoded colors | Theme tokens |
|
||||
| 5 | Duplicate styled component | Check Common/, extend existing |
|
||||
| 6 | Missing useEffect cleanup | Return cleanup function |
|
||||
| 7 | Prop drilling >3 levels | Use Context |
|
||||
| 8 | Testing implementation | Test behavior/output |
|
||||
| 9 | Floating promise / unhandled rejection | await / `.catch` |
|
||||
| 10 | Floating/`@1.0.0` dependency | Pin exact `X.Y.Z` |
|
||||
|
||||
## Search Locations (reuse-first)
|
||||
|
||||
`**/components/common/`, `**/components/shared/`, `**/hooks/`, `**/utils/`, `**/helpers/`, `**/types/`, `**/theme/`.
|
||||
|
||||
## Import Order
|
||||
|
||||
1. React 2. External libraries 3. Internal (absolute) 4. Relative 5. Styles/assets.
|
||||
|
||||
## Tools
|
||||
|
||||
npm/yarn/pnpm, TypeScript, ESLint, Prettier, Jest, React Testing Library, Vite/webpack, Storybook.
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/bin/bash
|
||||
# brewcode:superreview generator script
|
||||
# Adapted from setup.sh copy_review_skill(): copies the emit templates into the TARGET project
|
||||
# (.codex/skills/superreview/) substituting SCALAR placeholders, then validates that NO setup-time
|
||||
# {PLACEHOLDER} remains. Multi-row BLOCK placeholders are filled by the AI via Edit (see SKILL.md Phase 3).
|
||||
#
|
||||
# Usage: generate.sh <mode>
|
||||
# scan - Report target tech stack, agents, rules, source/test dirs (Phase 1)
|
||||
# emit - Copy + scalar-substitute templates into <cwd>/.codex/skills/superreview/ (Phase 2)
|
||||
# validate - Fail if any unresolved setup-time {PLACEHOLDER} remains (Phase 4)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MODE="${1:-emit}"
|
||||
|
||||
# Self-location: scripts/generate.sh -> skills/superreview/scripts -> skills/superreview
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
REFS="$SKILL_DIR/references"
|
||||
|
||||
# Target is the current working directory (the repo being reviewed)
|
||||
TARGET=".codex/skills/superreview"
|
||||
TARGET_REFS="$TARGET/references"
|
||||
|
||||
validate_templates() {
|
||||
if [ ! -f "$REFS/SKILL.md.template" ]; then
|
||||
echo "❌ Emit template not found: $REFS/SKILL.md.template"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── scan: report what the emitted skill must be wired to ────────────────────────
|
||||
scan_target() {
|
||||
echo "=== superreview: target scan ==="
|
||||
echo ""
|
||||
echo "--- Build files ---"
|
||||
find . -maxdepth 3 -type f \( \
|
||||
-name "package.json" -o -name "pom.xml" -o -name "build.gradle" -o \
|
||||
-name "build.gradle.kts" -o -name "requirements*.txt" -o -name "pyproject.toml" -o \
|
||||
-name "Pipfile" -o -name "Cargo.toml" -o -name "go.mod" -o -name "composer.json" \
|
||||
\) 2>/dev/null | sort || echo "(none found)"
|
||||
|
||||
echo ""
|
||||
echo "--- Project agents (.codex/agents/) ---"
|
||||
find .codex/agents -type f -name "*.md" 2>/dev/null | sort || echo "(none)"
|
||||
|
||||
echo ""
|
||||
echo "--- Rules (.codex/rules/) ---"
|
||||
find .codex/rules -type f -name "*.md" 2>/dev/null | sort || echo "(none)"
|
||||
|
||||
echo ""
|
||||
echo "--- Conventions (.codex/convention/) ---"
|
||||
find .codex/convention -type f -name "*.md" 2>/dev/null | sort || echo "(none)"
|
||||
|
||||
echo ""
|
||||
echo "--- Source / service dirs (top level) ---"
|
||||
find . -maxdepth 2 -type d \( -name "src" -o -name "app" -o -name "lib" -o \
|
||||
-name "pkg" -o -name "internal" -o -name "cmd" \) 2>/dev/null | sort || echo "(none)"
|
||||
|
||||
echo ""
|
||||
echo "--- Test dirs ---"
|
||||
find . -type d \( -name "test" -o -name "tests" -o -name "__tests__" \) 2>/dev/null | head -20 || echo "(none)"
|
||||
|
||||
echo ""
|
||||
echo "--- AGENTS.md ---"
|
||||
test -f ./AGENTS.md && echo "✅ AGENTS.md" || echo "⚠️ no AGENTS.md"
|
||||
}
|
||||
|
||||
# ── emit: copy + scalar-substitute templates into the target ────────────────────
|
||||
emit_skill() {
|
||||
echo "=== superreview: emit ==="
|
||||
validate_templates
|
||||
|
||||
mkdir -p "$TARGET_REFS"
|
||||
|
||||
# Scalar values (single-line ONLY — sed processes line-by-line; a newline truncates the substitution).
|
||||
PROJECT_NAME="${PROJECT_NAME:-this project}"
|
||||
STACK_LABEL="${STACK_LABEL:-the project stack}"
|
||||
STACK_REF="${STACK_REF:-python.md}"
|
||||
SOURCE_GLOB="${SOURCE_GLOB:-*}"
|
||||
PATHSPEC_GLOBS="${PATHSPEC_GLOBS:-'*' 'Dockerfile*' 'docker-compose.yml' '.github/workflows/*.yml'}"
|
||||
ARBITER_AGENT="${ARBITER_AGENT:-general-purpose}"
|
||||
VALIDATOR_AGENT="${VALIDATOR_AGENT:-general-purpose}"
|
||||
GENERATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
_sep=$'\x01'
|
||||
|
||||
# Sanitize every scalar before it lands on a sed RHS: escape backslash FIRST, then ampersand
|
||||
# (& is the whole-match backreference in sed replacements). Order matters.
|
||||
for _var in PROJECT_NAME STACK_LABEL STACK_REF SOURCE_GLOB PATHSPEC_GLOBS ARBITER_AGENT VALIDATOR_AGENT GENERATED_AT; do
|
||||
v="${!_var}"; v="${v//\\/\\\\}"; v="${v//&/\\&}"; printf -v "$_var" '%s' "$v"
|
||||
done
|
||||
|
||||
_subst() {
|
||||
# $1 = source template, $2 = destination
|
||||
sed \
|
||||
-e "s${_sep}{PROJECT_NAME}${_sep}${PROJECT_NAME}${_sep}g" \
|
||||
-e "s${_sep}{STACK_LABEL}${_sep}${STACK_LABEL}${_sep}g" \
|
||||
-e "s${_sep}{STACK_REF}${_sep}${STACK_REF}${_sep}g" \
|
||||
-e "s${_sep}{SOURCE_GLOB}${_sep}${SOURCE_GLOB}${_sep}g" \
|
||||
-e "s${_sep}{PATHSPEC_GLOBS}${_sep}${PATHSPEC_GLOBS}${_sep}g" \
|
||||
-e "s${_sep}{ARBITER_AGENT}${_sep}${ARBITER_AGENT}${_sep}g" \
|
||||
-e "s${_sep}{VALIDATOR_AGENT}${_sep}${VALIDATOR_AGENT}${_sep}g" \
|
||||
-e "s${_sep}{GENERATED_AT}${_sep}${GENERATED_AT}${_sep}g" \
|
||||
"$1" > "$2"
|
||||
}
|
||||
|
||||
_subst "$REFS/SKILL.md.template" "$TARGET/SKILL.md"
|
||||
echo "✅ $TARGET/SKILL.md"
|
||||
|
||||
_subst "$REFS/agent-prompt.md" "$TARGET_REFS/agent-prompt.md"
|
||||
echo "✅ $TARGET_REFS/agent-prompt.md"
|
||||
|
||||
_subst "$REFS/report-template.md" "$TARGET_REFS/report-template.md"
|
||||
echo "✅ $TARGET_REFS/report-template.md"
|
||||
|
||||
if [ -f "$REFS/$STACK_REF" ]; then
|
||||
cp "$REFS/$STACK_REF" "$TARGET_REFS/$STACK_REF"
|
||||
echo "✅ $TARGET_REFS/$STACK_REF"
|
||||
else
|
||||
echo "⚠️ stack reference not found: $REFS/$STACK_REF (emitted without per-stack doc)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Next: AI fills BLOCK placeholders via Edit (SKILL.md Phase 3), then run: generate.sh validate"
|
||||
}
|
||||
|
||||
# ── validate: no setup-time {PLACEHOLDER} may remain ────────────────────────────
|
||||
validate_emit() {
|
||||
echo "=== superreview: validate ==="
|
||||
|
||||
if [ ! -f "$TARGET/SKILL.md" ]; then
|
||||
echo "❌ emitted skill missing: $TARGET/SKILL.md — run 'generate.sh emit' first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Runtime tokens the emitted skill legitimately keeps (resolved at REVIEW time, not GENERATION time).
|
||||
_runtime='MODE|BRANCH|SCOPE|FILES|COUNT|TIMESTAMP|FOCUS|FILE_LIST|AGENT_LIST|CANDIDATES|MERGED|PATHSPEC|MAIN|SHA|FOLDER|GROUP|AGENT|N|OC|REPORT_DIR'
|
||||
|
||||
_errors=0
|
||||
for f in "$TARGET/SKILL.md" "$TARGET_REFS/agent-prompt.md" "$TARGET_REFS/report-template.md"; do
|
||||
[ -f "$f" ] || continue
|
||||
_unresolved=$(grep -oE '\{[A-Z_]+\}' "$f" | sort -u | grep -vE "^\{(${_runtime})\}$" || true)
|
||||
if [ -n "$_unresolved" ]; then
|
||||
echo "❌ unresolved setup-time placeholders in $f:"
|
||||
echo "$_unresolved"
|
||||
_errors=$((_errors+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Agent-reference allowlist: every agent named in the emitted SKILL.md must be a
|
||||
# project-local agent (.codex/agents/*.toml) or a real built-in (Explore|Plan|general-purpose).
|
||||
_builtins="Explore Plan general-purpose"
|
||||
_local_agents=""
|
||||
for _af in .codex/agents/*.toml; do
|
||||
[ -f "$_af" ] || continue
|
||||
_an="$(basename "$_af" .md)"
|
||||
_local_agents="$_local_agents $_an"
|
||||
done
|
||||
|
||||
# (a) explicit task_role="NAME" / task_role=NAME references
|
||||
_referenced=$(grep -oE 'task_role=("?)[A-Za-z0-9_-]+' "$TARGET/SKILL.md" \
|
||||
| sed -E 's/^task_role=("?)//' | sort -u || true)
|
||||
if [ -n "$_referenced" ]; then
|
||||
while IFS= read -r _name; do
|
||||
[ -n "$_name" ] || continue
|
||||
case " $_builtins $_local_agents " in
|
||||
*" $_name "*) : ;;
|
||||
*)
|
||||
echo "❌ unknown agent referenced in SKILL.md: $_name (not in target .codex/agents/ nor built-in Explore|Plan|general-purpose)"
|
||||
_errors=$((_errors+1))
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
$_referenced
|
||||
EOF
|
||||
fi
|
||||
|
||||
# (b) guard the known leak: bare reviewer/architect used as a built-in/fallback agent.
|
||||
for _bare in reviewer architect; do
|
||||
case " $_local_agents " in
|
||||
*" $_bare "*) continue ;; # legitimately exists in target -> allowed
|
||||
esac
|
||||
_leak=$(grep -nE "(built-in[^A-Za-z]+\`?${_bare}\`?|fallback[^A-Za-z]+\`?${_bare}\`?|on \`${_bare}\`)" "$TARGET/SKILL.md" || true)
|
||||
if [ -n "$_leak" ]; then
|
||||
echo "❌ bare \`${_bare}\` used as a built-in/fallback agent in SKILL.md (not in target .codex/agents/ nor built-in Explore|Plan|general-purpose):"
|
||||
echo "$_leak"
|
||||
_errors=$((_errors+1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$_errors" -eq 0 ]; then
|
||||
echo "✅ no unresolved setup-time placeholders"
|
||||
fi
|
||||
exit "$_errors"
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
scan) scan_target ;;
|
||||
emit) emit_skill ;;
|
||||
validate) validate_emit ;;
|
||||
*)
|
||||
echo "Usage: generate.sh <scan|emit|validate>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# Teams
|
||||
|
||||
Create and manage dynamic teams of domain-specific agents with a tracking framework. Analyzes your project, proposes 5-20 specialized agents, creates them, and sets up performance tracking.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
$brewcode:teams create my-project
|
||||
```
|
||||
|
||||
Analyzes the project, proposes agent variants (minimal/balanced/maximum), creates agents in `.codex/agents/`, and sets up a tracking framework.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Invocation | Description |
|
||||
|------|-----------|-------------|
|
||||
| Create | `$brewcode:teams create <name> [prompt]` | Analyze project, propose team, create agents + tracking framework |
|
||||
| Status | `$brewcode:teams status <name>` | Read-only report: agent health, success rates, issues, insights |
|
||||
| Update | `$brewcode:teams update <name>` | Analyze performance, tune or replace underperformers |
|
||||
| Cleanup | `$brewcode:teams cleanup <name>` | Archive old tracking data, remove inactive agents |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Create a team
|
||||
$brewcode:teams create backend
|
||||
|
||||
# Create with a guiding prompt
|
||||
$brewcode:teams create api-team "Focus on REST API, auth, and database layers"
|
||||
|
||||
# Check performance
|
||||
$brewcode:teams status backend
|
||||
|
||||
# Tune agents based on tracking data
|
||||
$brewcode:teams update backend
|
||||
|
||||
# Clean up after a long project phase
|
||||
$brewcode:teams cleanup backend
|
||||
```
|
||||
|
||||
### Common Mistakes
|
||||
|
||||
```bash
|
||||
# BAD: No team name
|
||||
$brewcode:teams create
|
||||
# -> Team name is required
|
||||
|
||||
# BAD: Update on team with no tracking data
|
||||
$brewcode:teams update new-team
|
||||
# -> All agents classified as Inactive -- run some tasks first
|
||||
|
||||
# BAD: Status on non-existent team
|
||||
$brewcode:teams status ghost-team
|
||||
# -> Error: "Team not found. Run $brewcode:teams create ghost-team"
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
After `$brewcode:teams create my-team`:
|
||||
|
||||
```
|
||||
.codex/
|
||||
agents/
|
||||
agent-one.md # Created agents (5-20 depending on variant)
|
||||
agent-two.md
|
||||
teams/
|
||||
my-team/
|
||||
team.md # Roster: agent list, domains, missions, status
|
||||
trace.jsonl # Unified log: tasks, issues, insights (append-only JSONL)
|
||||
```
|
||||
|
||||
## How Agents Work
|
||||
|
||||
Created agents follow the **sub-agent task Acceptance Protocol** -- they self-select tasks based on domain fit, record acceptance/refusal in `trace.jsonl` via `trace-ops.sh`, and log issues and insights as they work.
|
||||
|
||||
| Health | Criteria |
|
||||
|--------|----------|
|
||||
| Green | >70% success rate, active |
|
||||
| Yellow | 30-70% success or many refusals |
|
||||
| Red | <30% success or inactive |
|
||||
|
||||
The `update` mode uses this data to tune agent instructions, replace underperformers, or remove inactive agents.
|
||||
|
||||
## CREATE Flow
|
||||
|
||||
```
|
||||
$brewcode:teams create my-project
|
||||
|
|
||||
v
|
||||
[C1] Project Analysis --- 3-5 Explore agents in parallel
|
||||
|
|
||||
v
|
||||
[C2] Team Proposal ------ 3 variants + user confirmation
|
||||
|
|
||||
v
|
||||
[C2.5] Model Selection -- high-reasoning model / balanced model / fast model / mixed
|
||||
|
|
||||
v
|
||||
[C3] Agent Creation ----- agent-creator x N (batches of 3-4)
|
||||
|
|
||||
v
|
||||
[C4] Framework Setup ---- team.md + trace.jsonl + verification
|
||||
|
|
||||
v
|
||||
[C5] Quorum Review ------ 3 domain expert reviewers in parallel
|
||||
|
|
||||
v
|
||||
[C6] Consensus Filter --- 2/3 quorum, skip minor issues
|
||||
|
|
||||
v
|
||||
[C7] Verification ------- cross-check confirmed findings vs actual files
|
||||
|
|
||||
v
|
||||
[C8] Fix ---------------- agent-creator fixes critical + important
|
||||
|
|
||||
v
|
||||
[C9] Re-verify ---------- check fixes, retry if regression (max 2 cycles)
|
||||
|
|
||||
v
|
||||
[E1] AGENTS.md Update --- optional, user-confirmed
|
||||
[E2] Final Status ------- always runs STATUS
|
||||
```
|
||||
|
||||
## Review and Fix Pipeline (C5-C9)
|
||||
|
||||
After agent creation, a quality pipeline validates the team:
|
||||
|
||||
1. **Quorum Review (C5)** — 3 independent reviewer agents (domain experts matching the team) analyze every created agent in parallel: instruction quality, domain accuracy, tool selection, triggers, model fit
|
||||
2. **Consensus Filter (C6)** — issues confirmed by 2/3 reviewers pass quorum. Minor (cosmetic) issues are logged but skipped. Only critical and important proceed
|
||||
3. **Verification (C7)** — verification agent cross-checks each finding against actual agent files. False positives filtered. Severity: critical (broken) / important (degraded) / minor (cosmetic)
|
||||
4. **Fix (C8)** — agent-creator fixes critical and important issues. Minor skipped
|
||||
5. **Re-verify (C9)** — verification agent re-checks every fix. Regression goes back to Fix (max 2 cycles)
|
||||
|
||||
> Skip with `--skip-review`. Run separately: `$brewcode:teams update <name> --review`
|
||||
|
||||
## sub-agent task Acceptance Protocol
|
||||
|
||||
Each agent follows a 3-step self-selection before accepting a task:
|
||||
|
||||
| # | Check | Question | If No |
|
||||
|---|-------|----------|-------|
|
||||
| 1 | Domain | Is this my domain? | Refuse, suggest colleague |
|
||||
| 2 | Duplicate | Already done? | Refuse, link result |
|
||||
| 3 | Best fit | Colleague better suited? | Refuse, redirect |
|
||||
|
||||
**Accept flow:** All 3 checks pass -> accept task -> execute -> log to trace.jsonl -> complete/fail
|
||||
|
||||
**Refuse flow:** Any check fails -> log refusal reason to trace.jsonl -> suggest alternative agent
|
||||
|
||||
## Dynamic Agent Resolution
|
||||
|
||||
When other skills (convention, superreview, e2e) spawn agents, they check for team agents first:
|
||||
|
||||
| Priority | Source | Example |
|
||||
|----------|--------|---------|
|
||||
| 1 | Team agent | `.codex/agents/backend-api-expert.toml` (from teams) |
|
||||
| 2 | Project agent | `.codex/agents/custom-agent.toml` (manually created) |
|
||||
| 3 | Plugin agent | `brewcode:developer`, `brewcode:tester` |
|
||||
| 4 | System agent | `Explore`, `Plan` |
|
||||
|
||||
> If a team agent refuses a task (sub-agent task Acceptance Protocol), the skill re-delegates to the next priority level. Max 2 retries before falling back to plugin agents.
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | Relationship |
|
||||
|-------|-------------|
|
||||
| `$brewcode:superreview` | Generate a project-tailored deep-review skill |
|
||||
| `$brewcode:rules` | Extract team insights into project rules |
|
||||
|
||||
## Documentation
|
||||
|
||||
Full docs: [teams](https://doc-claude.brewcode.app/brewcode/skills/teams/)
|
||||
@@ -0,0 +1,393 @@
|
||||
---
|
||||
name: teams
|
||||
description: "Creates and manages dynamic teams of domain agents. Triggers: create team, agent team, team status, cleanup team."
|
||||
---
|
||||
|
||||
# Codex team coordination
|
||||
|
||||
Use collaboration agents only when the user or project instructions explicitly request a team. Split work into bounded independent tasks, keep one owner per file or surface, exchange evidence through collaboration messages, and synthesize results in the parent session. Do not invent unsupported agent parameters or create persistent team configuration unless requested.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
<instructions>
|
||||
|
||||
# Teams
|
||||
|
||||
Manage dynamic teams of domain-specific agents with tracking framework.
|
||||
|
||||
**Arguments:** `<arguments>`
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Parse Arguments
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/detect-mode.sh" "<arguments>" && echo "OK" || echo "FAILED"
|
||||
```
|
||||
|
||||
Output: `MODE:`, `TEAM_NAME:`, `PROMPT:` (optional). Store all three.
|
||||
|
||||
---
|
||||
|
||||
## Universal Prelude (every mode)
|
||||
|
||||
### Step 0: Init + Validate + Confirm
|
||||
|
||||
1. Output: `Mode: {MODE}, Team: {TEAM_NAME}`
|
||||
|
||||
2. Load environment:
|
||||
|
||||
| Action | Command / Path |
|
||||
|--------|----------------|
|
||||
| Read agent template | `<skill-directory>/references/agent-template.md` |
|
||||
| Read framework templates | `<skill-directory>/references/framework-files.md` |
|
||||
| Check team dir | `.codex/teams/{TEAM_NAME}/` -- exists? |
|
||||
| Check existing agents | `.codex/agents/` -- list all |
|
||||
| If team.md exists | Read, show current roster |
|
||||
| If trace.jsonl exists | Show entry counts via `trace-ops.sh read` |
|
||||
|
||||
3. If team exists, verify:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/verify-team.sh" "TEAM_NAME_HERE" && echo "PASS" || echo "FAIL"
|
||||
```
|
||||
|
||||
4. Formulate action plan for current mode.
|
||||
|
||||
5. **ASK** using request_user_input: "Here's my plan: {plan}. Continue?"
|
||||
Options: "Yes, continue" | "No, I want changes" | "Cancel"
|
||||
- "changes" -> request_user_input for details, revise plan
|
||||
- "Cancel" -> **STOP**
|
||||
|
||||
---
|
||||
|
||||
## Mode: CREATE (9 phases)
|
||||
|
||||
### C1: Project Analysis
|
||||
|
||||
Spawn 3-5 Explore agents in ONE message via sub-agent collaboration tools:
|
||||
|
||||
| # | Focus |
|
||||
|---|-------|
|
||||
| 1 | Code structure: modules, packages, domains, architectural layers |
|
||||
| 2 | Existing agents (`.codex/agents/`, `brewcode/agents/`, `~/.codex/agents/`) + Codex infrastructure |
|
||||
| 3 | Tech stack: build files, frameworks, dependencies, languages |
|
||||
| 4 | CI/CD, testing, deploy, infrastructure |
|
||||
| 5 (optional) | Domain boundaries: business logic, API, data layer, UI |
|
||||
|
||||
All via `Codex delegation brief (task_role="Explore")`. Consolidate into single analysis document.
|
||||
|
||||
### C2: Team Proposal (interactive)
|
||||
|
||||
Based on analysis + PROMPT (if provided), propose 3 variants via request_user_input:
|
||||
|
||||
```
|
||||
Minimal (5 agents):
|
||||
| Agent | Domain | Mission |
|
||||
|
||||
Balanced (10-12 agents) -- Recommended:
|
||||
| Agent | Domain | Mission |
|
||||
|
||||
Maximum (15-20 agents):
|
||||
| Agent | Domain | Mission |
|
||||
```
|
||||
|
||||
Options: "Minimal (5)" | "Balanced (recommended)" | "Maximum (15-20)" | "Custom -- I'll specify"
|
||||
|
||||
If "Custom" -- second request_user_input for free input. Final confirmation of agent list before proceeding.
|
||||
|
||||
### C2.5: Model Selection (request_user_input)
|
||||
|
||||
"Default model for domain agents: high-reasoning model (most reliable)."
|
||||
|
||||
| Model | Best for | Cost |
|
||||
|-------|----------|------|
|
||||
| high-reasoning model | Complex domains, architecture, critical logic | High |
|
||||
| balanced model | Standard domains, CRUD, testing, utilities | Medium |
|
||||
| fast model | Simple utility agents, formatting, validation | Low |
|
||||
|
||||
Options: "high-reasoning model (recommended)" | "balanced model" | "fast model" | "Mixed -- I'll choose per agent"
|
||||
|
||||
If "Mixed" -- ask model per agent in C3. Store as `DEFAULT_MODEL` (default: high-reasoning model).
|
||||
|
||||
### C3: Agent Creation (agent-creator x N)
|
||||
|
||||
1. Read `<skill-directory>/references/agent-template.md`
|
||||
2. For each agent, spawn `Codex delegation brief (task_role="brewcode:agent-creator")` with: placement=`.codex/agents/`, reasoning_tier=DEFAULT_MODEL (or per-agent), context=template + mission + domain + project analysis + colleague list. Agent `description` <= 100 chars (optimal ~80), single line, role + 2-3 triggers, no `<example>` blocks.
|
||||
3. Batch 3-4 agents in parallel per message
|
||||
4. After each batch, optimize:
|
||||
```
|
||||
Invoke `$brewtools:text-optimize` with arguments `-l .codex/agents/{agent-name}.toml`
|
||||
```
|
||||
|
||||
### C4: Framework Setup + Verification
|
||||
|
||||
1. Create team directory:
|
||||
```bash
|
||||
mkdir -p ".codex/teams/TEAM_NAME_HERE" && echo "OK" || echo "FAILED"
|
||||
```
|
||||
|
||||
2. Write from `<skill-directory>/references/framework-files.md` templates: `team.md` (fill with real agent data), `touch trace.jsonl`
|
||||
|
||||
3. Verify:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/verify-team.sh" "TEAM_NAME_HERE" && echo "PASS" || echo "FAIL"
|
||||
```
|
||||
> **STOP if FAIL** -- fix missing files before continuing.
|
||||
|
||||
4. request_user_input: final report + suggest `$brewcode:teams status {TEAM_NAME}`
|
||||
|
||||
### C5: Quorum Review
|
||||
|
||||
Spawn 3 reviewer agents in ONE message via sub-agent collaboration tools:
|
||||
|
||||
| # | Focus |
|
||||
|---|-------|
|
||||
| 1 | Instruction quality: clarity, imperative form, completeness, word budget |
|
||||
| 2 | Domain accuracy: correct scope, tool selection, model fit, description triggers |
|
||||
| 3 | Architecture: consistency across agents, no domain overlaps, proper sub-agent task Acceptance Protocol |
|
||||
|
||||
Each reads ALL agent files in `.codex/agents/` and outputs:
|
||||
```
|
||||
FILE: .codex/agents/{name}.toml
|
||||
SEVERITY: critical/important/minor
|
||||
ISSUE: description
|
||||
FIX: suggested fix
|
||||
```
|
||||
|
||||
### C6: Consensus Filter
|
||||
|
||||
**Quorum threshold: 2/3 agreement = confirmed.** Match criteria: same file + same area (+/- 5 lines or same section) + same category (instruction/domain/architecture/trigger).
|
||||
|
||||
| Outcome | Action |
|
||||
|---------|--------|
|
||||
| 2/3+ confirm | Mark **confirmed**, keep severity from highest reporter |
|
||||
| 1/3 only | Log as **unconfirmed**, skip |
|
||||
| Minor severity (all reporters) | Log but skip fix |
|
||||
|
||||
### C7: Verification
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="brewcode:reviewer", message="
|
||||
Verify these findings against actual agent files. For each:
|
||||
1. Read the agent file
|
||||
2. Check if the issue actually exists
|
||||
3. Mark: VERIFIED or FALSE_POSITIVE
|
||||
{confirmed_findings}
|
||||
")
|
||||
```
|
||||
|
||||
Filter out false positives. Final list = verified critical + important issues.
|
||||
|
||||
### C8: Fix
|
||||
|
||||
For each verified critical/important issue:
|
||||
```
|
||||
Codex delegation brief (task_role="brewcode:agent-creator", message="
|
||||
Fix this issue in {agent_file}:
|
||||
ISSUE: {description}
|
||||
FIX: {suggested_fix}
|
||||
SEVERITY: {severity}
|
||||
Read the file, apply the fix, validate.
|
||||
")
|
||||
```
|
||||
Batch: up to 3 parallel per message. Minor issues skipped.
|
||||
|
||||
### C9: Re-verify
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="brewcode:reviewer", message="
|
||||
Re-verify these fixes. For each:
|
||||
1. Read the fixed agent file
|
||||
2. Check original issue is resolved
|
||||
3. Check no regression introduced
|
||||
Mark: FIXED or REGRESSION
|
||||
{fixes_applied}
|
||||
")
|
||||
```
|
||||
|
||||
| Outcome | Action |
|
||||
|---------|--------|
|
||||
| All FIXED | Pipeline complete, proceed to Epilogue |
|
||||
| REGRESSION found | Return to C8 for that file (max 2 cycles) |
|
||||
| Still failing after 2 cycles | Log as unresolved, proceed to Epilogue |
|
||||
|
||||
> To skip review pipeline: add `--skip-review` to create arguments.
|
||||
> To run review on existing team: `$brewcode:teams update {TEAM_NAME} --review`
|
||||
|
||||
---
|
||||
|
||||
## Mode: STATUS (read-only)
|
||||
|
||||
No modifications. Read + report only.
|
||||
|
||||
1. Read `.codex/teams/{TEAM_NAME}/team.md`
|
||||
2. Read trace data:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/trace-ops.sh" read ".codex/teams/{TEAM_NAME}" && echo "OK" || echo "FAILED"
|
||||
```
|
||||
Parse JSONL: group by `src` (agent) and `k` (kind). Compute per-agent stats from `k=track` (took/refused/completed/failed counts), issues from `k=issue`, insights from `k=insight`.
|
||||
|
||||
**Output:**
|
||||
```markdown
|
||||
# Team Status: {TEAM_NAME}
|
||||
|
||||
## Summary
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Agents | {N} |
|
||||
| Tasks tracked | {N} |
|
||||
| Success rate | {%} |
|
||||
| Open issues | {N} (high: {N}, critical: {N}) |
|
||||
| Insights | {N} |
|
||||
| Last activity | {date} |
|
||||
|
||||
## Per Agent
|
||||
| Agent | Tasks | Success | Refused | Issues | Insights | Health |
|
||||
|-------|-------|---------|---------|--------|----------|--------|
|
||||
|
||||
## Recommendations
|
||||
```
|
||||
|
||||
Health:
|
||||
|
||||
| Label | Criteria |
|
||||
|-------|----------|
|
||||
| Healthy | >70% success, active |
|
||||
| Needs tuning | 30-70% success or many refusals |
|
||||
| Underperforming/Inactive | <30% success or inactive |
|
||||
|
||||
Recommendations: underperformers -> suggest `$brewcode:teams update`; >200 trace rows -> suggest cleanup; 0 activity -> suggest review.
|
||||
|
||||
No request_user_input -- purely informational.
|
||||
|
||||
---
|
||||
|
||||
## Mode: UPDATE (self-reflection)
|
||||
|
||||
### U1: Load & Parse
|
||||
|
||||
```bash
|
||||
CURSOR=$(bash "<skill-directory>/scripts/trace-ops.sh" cursor ".codex/teams/{TEAM_NAME}")
|
||||
bash "<skill-directory>/scripts/trace-ops.sh" read ".codex/teams/{TEAM_NAME}" --since "$CURSOR" && echo "OK" || echo "FAILED"
|
||||
```
|
||||
|
||||
If cursor empty: all entries returned. If team not found -> **STOP**. If cursor exists and <10 post-cursor entries: expand to last 30 days.
|
||||
|
||||
### U2: Analyze Performance
|
||||
|
||||
Filter post-cursor trace: `k=track` for task stats, `k=issue` for problems, `k=insight` for patterns.
|
||||
|
||||
| Status | Criteria | Action |
|
||||
|--------|----------|--------|
|
||||
| Healthy | >70% success, active | No changes |
|
||||
| Needs tuning | 30-70% success or many refusals | Update instructions |
|
||||
| Underperforming | <30% success | AskUser: update or delete+create new |
|
||||
| Inactive | 0 records | AskUser: delete or keep |
|
||||
|
||||
### U3: Present & Confirm
|
||||
|
||||
**ASK** using request_user_input with analysis table and proposed actions (Update/Delete/No changes per agent).
|
||||
Options: "Apply all" | "Let me choose" | "Show detailed analysis"
|
||||
|
||||
If "Let me choose" -> request_user_input per agent. If "Show detailed" -> output full stats, then re-ask.
|
||||
|
||||
### U4: Apply Changes
|
||||
|
||||
| Agent Status | Action |
|
||||
|--------------|--------|
|
||||
| Needs tuning | `Codex delegation brief (task_role="brewcode:agent-creator")` update mode with tracking/issues/insights data |
|
||||
| Underperforming (update) | Same as tuning |
|
||||
| Underperforming (replace) | Delete agent file + create new via agent-creator |
|
||||
| Inactive (delete) | Remove `.codex/agents/{name}.toml` + update team.md status to `removed` |
|
||||
|
||||
Immutable traits (Name, Base Role) -> delete + create new. Mutable traits (Character, Instructions) -> update during tuning.
|
||||
|
||||
Update `team.md` with current state and `Last update` date.
|
||||
|
||||
Set cursor:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/trace-ops.sh" cursor ".codex/teams/{TEAM_NAME}" set "$(date -u +%Y-%m-%dT%H:%M:%SZ)" && echo "✅" || echo "❌ FAILED"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mode: CLEANUP
|
||||
|
||||
Read `<skill-directory>/references/cleanup-flow.md` and execute step by step:
|
||||
|
||||
1. Overview scan -> show trace.jsonl entry counts by kind
|
||||
2. request_user_input: what to clean (all / trace data / agents / step-by-step)
|
||||
3. Trace cleanup (if selected) -- request_user_input with archive options
|
||||
4. Agents review (if selected) -- request_user_input per agent if needed
|
||||
5. Summary report
|
||||
|
||||
Archive: entries appended to `.codex/teams/{TEAM_NAME}/trace-archive.jsonl`. Cursor reset after cleanup.
|
||||
|
||||
---
|
||||
|
||||
## Universal Epilogue (every mode)
|
||||
|
||||
### Step E1: Update AGENTS.md (conditional)
|
||||
|
||||
Only for modes that change team composition (CREATE, UPDATE with removals, CLEANUP with agent removal):
|
||||
|
||||
**ASK** using request_user_input: "Update team info in AGENTS.md?"
|
||||
Options: "Yes, in project AGENTS.md" | "Yes, in .codex/AGENTS.local.md" | "No, skip"
|
||||
|
||||
Format to write:
|
||||
```markdown
|
||||
## Teams
|
||||
|
||||
Team: {TEAM_NAME} | Agents: {N} | Status: active
|
||||
|
||||
| Agent | Domain | Mission |
|
||||
|-------|--------|---------|
|
||||
|
||||
Protocol: agents self-select tasks, trace in `.codex/teams/{TEAM_NAME}/trace.jsonl`.
|
||||
Manage: `$brewcode:teams [status|update|cleanup]`
|
||||
```
|
||||
|
||||
### Step E2: Final Status
|
||||
|
||||
Always run STATUS mode logic after all changes: read team.md + trace.jsonl, compute stats, output Team Status table.
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# teams [{MODE}]
|
||||
|
||||
## Detection
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Arguments | `{raw args}` |
|
||||
| Mode | `{MODE}` |
|
||||
| Team | `{TEAM_NAME}` |
|
||||
| Prompt | `{PROMPT or none}` |
|
||||
|
||||
## Results
|
||||
{Mode-specific output}
|
||||
|
||||
## Next Steps
|
||||
- {recommendations}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| Team not found (STATUS/UPDATE/CLEANUP) | "Team '{TEAM_NAME}' not found. Run `$brewcode:teams create {TEAM_NAME}`." **STOP** |
|
||||
| Team already exists (CREATE) | Show roster, request_user_input: "Team exists. Update instead?" |
|
||||
| verify-team.sh FAIL | Show missing items, attempt fix, re-verify |
|
||||
| No agents created (C3 failure) | Retry failed agents once, then report |
|
||||
| 0 trace entries (UPDATE) | Classify all agents as Inactive |
|
||||
|
||||
</instructions>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Teams"
|
||||
short_description: "Creates and manages dynamic teams of domain agents. Triggers: cr"
|
||||
default_prompt: "Use $brewcode:teams for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Native Codex agent template
|
||||
|
||||
Create a TOML file under `.codex/agents/` with `name`, `description`, and `developer_instructions`. The instructions define mission, domain, scope, task acceptance, self-check, and colleague handoff. Delegate through Codex collaboration with `task_name` and `message` only. Do not add Markdown frontmatter, tool allowlists, or legacy model aliases.
|
||||
@@ -0,0 +1,125 @@
|
||||
# Cleanup Flow
|
||||
|
||||
## Overview
|
||||
|
||||
Interactive cleanup of team trace data and agents. Every destructive action requires user confirmation via request_user_input.
|
||||
|
||||
## Order of Operations
|
||||
|
||||
1. Overview scan — show sizes
|
||||
2. Trace cleanup (if selected)
|
||||
3. Agents review (if selected)
|
||||
4. Summary report
|
||||
|
||||
## Step 1: Overview Scan
|
||||
|
||||
Read `trace.jsonl` via `trace-ops.sh read`, calculate entry counts by kind.
|
||||
|
||||
```
|
||||
request_user_input:
|
||||
question: |
|
||||
Cleanup for team {TEAM_NAME}:
|
||||
|
||||
| Data | Entries | Size |
|
||||
| trace.jsonl (track) | {N} entries | — |
|
||||
| trace.jsonl (issue) | {N} entries | — |
|
||||
| trace.jsonl (insight) | {N} entries | — |
|
||||
| Total | {N} entries | {KB} |
|
||||
| Agents | {N} agents | — |
|
||||
|
||||
What to clean?
|
||||
options:
|
||||
- "All — full cleanup"
|
||||
- "Trace data only"
|
||||
- "Agents review only"
|
||||
- "Let me choose step by step"
|
||||
```
|
||||
|
||||
## Step 2: Trace Cleanup
|
||||
|
||||
```
|
||||
request_user_input:
|
||||
question: |
|
||||
trace.jsonl: {N} entries
|
||||
Oldest: {date}, Newest: {date}
|
||||
By kind: track={N}, issue={N}, insight={N}
|
||||
|
||||
Options:
|
||||
options:
|
||||
- "Archive all → trace-archive.jsonl, start fresh"
|
||||
- "Keep last 30 days, archive rest"
|
||||
- "Keep last 50 entries, archive rest"
|
||||
- "Keep only issues + insights, archive track entries"
|
||||
- "Skip"
|
||||
```
|
||||
|
||||
**Archive logic:**
|
||||
|
||||
- Read current `trace.jsonl`
|
||||
- Split into keep/archive based on selection
|
||||
- Append archived entries to `trace-archive.jsonl` (create if not exists)
|
||||
- Rewrite `trace.jsonl` with kept entries only
|
||||
- Reset `trace.cursor` via `trace-ops.sh cursor <dir> set ""`
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
# Example: archive all, start fresh
|
||||
cat ".codex/teams/{TEAM}/trace.jsonl" >> ".codex/teams/{TEAM}/trace-archive.jsonl" && \
|
||||
printf '' > ".codex/teams/{TEAM}/trace.jsonl" && \
|
||||
bash "$<plugin-root>/skills/teams/scripts/trace-ops.sh" cursor ".codex/teams/{TEAM}" set "" && \
|
||||
echo "✅ Archived" || echo "❌ FAILED"
|
||||
```
|
||||
|
||||
## Step 3: Agents Review
|
||||
|
||||
Show inactive/problematic agents:
|
||||
|
||||
```
|
||||
request_user_input:
|
||||
question: |
|
||||
Inactive agents (0 tasks or last activity >30 days):
|
||||
| Agent | Last activity | Tasks total |
|
||||
| ... | ... | ... |
|
||||
|
||||
Action?
|
||||
options:
|
||||
- "Delete all inactive"
|
||||
- "Let me choose per agent"
|
||||
- "Keep all"
|
||||
```
|
||||
|
||||
If "per agent" — loop request_user_input for each:
|
||||
|
||||
```
|
||||
request_user_input:
|
||||
question: "Agent {name}: {domain}, last active {date}, {N} tasks total. Delete?"
|
||||
options: ["Delete", "Keep"]
|
||||
```
|
||||
|
||||
On delete:
|
||||
|
||||
1. Remove `.codex/agents/{name}.toml`
|
||||
2. Update team.md: set status to `removed`
|
||||
3. Record via trace-ops.sh: `bash "$<plugin-root>/skills/teams/scripts/trace-ops.sh" add ".codex/teams/{TEAM}" "$SID" "system" "track" "completed" "removed {name}: cleanup"`
|
||||
|
||||
## Step 4: Summary
|
||||
|
||||
Output report:
|
||||
|
||||
```
|
||||
# Cleanup Summary: {TEAM_NAME}
|
||||
|
||||
| Action | Details |
|
||||
|--------|---------|
|
||||
| Trace entries archived | {N} |
|
||||
| Trace entries kept | {N} |
|
||||
| Agents removed | {list or "none"} |
|
||||
| Archive file | trace-archive.jsonl |
|
||||
| Cursor | reset |
|
||||
```
|
||||
|
||||
## Archive File Format
|
||||
|
||||
Archive files live in `.codex/teams/{TEAM_NAME}/` alongside `trace.jsonl`.
|
||||
|
||||
`trace-archive.jsonl` — same JSONL format as `trace.jsonl`. Entries appended on each cleanup. Multiple cleanups accumulate in the same archive file.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Framework Files Reference
|
||||
|
||||
Templates for `.codex/teams/{TEAM_NAME}/` directory. All placeholders (`{TEAM_NAME}`, `{DATE}`, `{N}`, `{CWD}`) replaced at creation time. DATE format: `YYYY-MM-DD`.
|
||||
|
||||
team.md uses apply_patch. trace.jsonl is **append-only** via Bash (`trace-ops.sh add`).
|
||||
|
||||
---
|
||||
|
||||
## 1. team.md
|
||||
|
||||
```markdown
|
||||
# Team: {TEAM_NAME}
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Created | {DATE} |
|
||||
| Last update | {DATE} |
|
||||
| Agents | {N} |
|
||||
| Project | {CWD} |
|
||||
|
||||
## Agents
|
||||
|
||||
| Agent | Domain | Mission | Status | Updated |
|
||||
|-------|--------|---------|--------|---------|
|
||||
```
|
||||
|
||||
Status values: `active`, `inactive`, `updating`, `removed`
|
||||
|
||||
---
|
||||
|
||||
## 2. trace.jsonl
|
||||
|
||||
Empty file at creation. Agents append via `trace-ops.sh add`.
|
||||
|
||||
Format: JSONL — one JSON object per line:
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `ts` | auto | ISO8601 UTC timestamp |
|
||||
| `sid` | yes | Session ID, 8 chars |
|
||||
| `src` | yes | Agent name |
|
||||
| `k` | yes | `track` / `issue` / `insight` |
|
||||
| `s` | track | `took` / `refused` / `completed` / `failed` |
|
||||
| `sev` | issue | `low` / `medium` / `high` / `critical` |
|
||||
| `cat` | insight | `pattern` / `architecture` / `performance` / `security` / `convention` / `debt` |
|
||||
| `txt` | yes | Text, max 100 chars (auto-truncated by trace-ops.sh) |
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
ARGS="${1:-}"
|
||||
TEAMS_DIR=".codex/teams"
|
||||
|
||||
validate_name() {
|
||||
case "$1" in
|
||||
*[!a-zA-Z0-9_-]*) printf 'ERROR:invalid team name (alphanumeric, dash, underscore only)\n'; exit 1 ;;
|
||||
"") printf 'ERROR:empty team name\n'; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Parse first word (shell expansion, no sed regex injection)
|
||||
FIRST=""
|
||||
REST=""
|
||||
if [ -n "$ARGS" ]; then
|
||||
TRIMMED=$(printf '%s' "$ARGS" | sed 's/^[[:space:]]*//')
|
||||
FIRST=$(printf '%s' "$TRIMMED" | cut -d' ' -f1)
|
||||
REST="${TRIMMED#"$FIRST"}"
|
||||
REST=$(printf '%s' "$REST" | sed 's/^[[:space:]]*//')
|
||||
fi
|
||||
|
||||
is_keyword() {
|
||||
case "$1" in
|
||||
create|update|status|cleanup) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Extract second word and remainder from REST (shell expansion, no sed)
|
||||
second_word() { printf '%s' "$REST" | cut -d' ' -f1; }
|
||||
after_second() {
|
||||
_sw=$(second_word)
|
||||
_r="${REST#"$_sw"}"
|
||||
printf '%s' "$_r" | sed 's/^[[:space:]]*//'
|
||||
}
|
||||
|
||||
MODE=""
|
||||
TEAM_NAME=""
|
||||
PROMPT=""
|
||||
|
||||
if [ -z "$FIRST" ]; then
|
||||
if [ -d "$TEAMS_DIR" ] && [ "$(find "$TEAMS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)" ]; then
|
||||
MODE="status"
|
||||
TEAM_NAME=$(find "$TEAMS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
|
||||
TEAM_NAME=$(basename "$TEAM_NAME")
|
||||
else
|
||||
MODE="create"
|
||||
TEAM_NAME="default"
|
||||
fi
|
||||
elif is_keyword "$FIRST"; then
|
||||
MODE="$FIRST"
|
||||
if [ -n "$REST" ]; then
|
||||
TEAM_NAME=$(second_word)
|
||||
if [ "$MODE" = "create" ]; then
|
||||
PROMPT=$(after_second)
|
||||
fi
|
||||
else
|
||||
TEAM_NAME="default"
|
||||
fi
|
||||
else
|
||||
TEAM_NAME="$FIRST"
|
||||
if [ -d "$TEAMS_DIR/$TEAM_NAME" ]; then
|
||||
MODE="status"
|
||||
else
|
||||
MODE="create"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate team name (skip for "default")
|
||||
if [ "$TEAM_NAME" != "default" ]; then
|
||||
validate_name "$TEAM_NAME"
|
||||
fi
|
||||
|
||||
printf 'MODE:%s\n' "$MODE"
|
||||
printf 'TEAM_NAME:%s\n' "$TEAM_NAME"
|
||||
[ -n "$PROMPT" ] && printf 'PROMPT:%s\n' "$PROMPT"
|
||||
|
||||
exit 0
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
USAGE="Usage: trace-ops.sh <add|read|cursor|migrate> <team_dir> [args...]"
|
||||
|
||||
die() { printf '%s\n' "$*" >&2; exit 1; }
|
||||
|
||||
escape_json() {
|
||||
printf '%s' "$1" | tr '\n\r' ' ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/ /\\t/g'
|
||||
}
|
||||
|
||||
truncate_text() {
|
||||
_t="$1"
|
||||
if [ "${#_t}" -gt 100 ]; then
|
||||
_t="$(printf '%.100s' "$_t")"
|
||||
fi
|
||||
printf '%s' "$_t"
|
||||
}
|
||||
|
||||
cmd_add() {
|
||||
[ $# -ge 6 ] || die "Usage: trace-ops.sh add <team_dir> <sid> <agent> <kind> <qualifier> <text>"
|
||||
_dir="$1"; _sid="$2"; _agent="$3"; _kind="$4"; _qual="$5"
|
||||
shift 5; _text="$*"
|
||||
|
||||
case "$_kind" in
|
||||
track)
|
||||
case "$_qual" in
|
||||
took|refused|completed|failed) : ;;
|
||||
*) die "Invalid status: $_qual (expected took|refused|completed|failed)" ;;
|
||||
esac ;;
|
||||
issue)
|
||||
case "$_qual" in
|
||||
low|medium|high|critical) : ;;
|
||||
*) die "Invalid severity: $_qual (expected low|medium|high|critical)" ;;
|
||||
esac ;;
|
||||
insight)
|
||||
case "$_qual" in
|
||||
pattern|architecture|performance|security|convention|debt) : ;;
|
||||
*) die "Invalid category: $_qual (expected pattern|architecture|performance|security|convention|debt)" ;;
|
||||
esac ;;
|
||||
*) die "Invalid kind: $_kind (expected track|issue|insight)" ;;
|
||||
esac
|
||||
|
||||
_ts="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
_text="$(truncate_text "$_text")"
|
||||
_text_esc="$(escape_json "$_text")"
|
||||
_agent_esc="$(escape_json "$_agent")"
|
||||
_sid_esc="$(escape_json "$_sid")"
|
||||
|
||||
case "$_kind" in
|
||||
track) _qfield="\"s\":\"$_qual\"" ;;
|
||||
issue) _qfield="\"sev\":\"$_qual\"" ;;
|
||||
insight) _qfield="\"cat\":\"$_qual\"" ;;
|
||||
esac
|
||||
|
||||
_line="$(printf '{"ts":"%s","sid":"%s","src":"%s","k":"%s",%s,"txt":"%s"}' \
|
||||
"$_ts" "$_sid_esc" "$_agent_esc" "$_kind" "$_qfield" "$_text_esc")"
|
||||
|
||||
printf '%s\n' "$_line" >> "$_dir/trace.jsonl"
|
||||
printf '%s\n' "$_line"
|
||||
}
|
||||
|
||||
cmd_read() {
|
||||
[ $# -ge 1 ] || die "Usage: trace-ops.sh read <team_dir> [--since <ts>] [--sid <sid>] [--kind <k>]"
|
||||
_dir="$1"; shift
|
||||
_since=""; _sid=""; _kind=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--since) _since="$2"; shift 2 ;;
|
||||
--sid) _sid="$2"; shift 2 ;;
|
||||
--kind) _kind="$2"; shift 2 ;;
|
||||
*) die "Unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
_file="$_dir/trace.jsonl"
|
||||
[ -f "$_file" ] || return 0
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
_filter="."
|
||||
[ -n "$_since" ] && _filter="$_filter | select(.ts >= \"$_since\")"
|
||||
[ -n "$_sid" ] && _filter="$_filter | select(.sid == \"$_sid\")"
|
||||
[ -n "$_kind" ] && _filter="$_filter | select(.k == \"$_kind\")"
|
||||
jq -c "$_filter" "$_file"
|
||||
else
|
||||
_result="$(cat "$_file")"
|
||||
if [ -n "$_since" ]; then
|
||||
_result="$(printf '%s\n' "$_result" | while IFS= read -r _ln; do
|
||||
_lts="$(printf '%s' "$_ln" | sed -n 's/.*"ts":"\([^"]*\)".*/\1/p')"
|
||||
case "$(printf '%s\n%s' "$_since" "$_lts" | sort | head -1)" in
|
||||
"$_since") printf '%s\n' "$_ln" ;;
|
||||
esac
|
||||
done)"
|
||||
fi
|
||||
[ -n "$_sid" ] && _result="$(printf '%s\n' "$_result" | grep -F "\"sid\":\"$_sid\"" || true)"
|
||||
[ -n "$_kind" ] && _result="$(printf '%s\n' "$_result" | grep -F "\"k\":\"$_kind\"" || true)"
|
||||
[ -n "$_result" ] && printf '%s\n' "$_result"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_cursor() {
|
||||
[ $# -ge 1 ] || die "Usage: trace-ops.sh cursor <team_dir> [set <ts>]"
|
||||
_dir="$1"; shift
|
||||
_cfile="$_dir/trace.cursor"
|
||||
|
||||
if [ $# -ge 2 ] && [ "$1" = "set" ]; then
|
||||
printf '%s\n' "$2" > "$_cfile"
|
||||
elif [ -f "$_cfile" ]; then
|
||||
cat "$_cfile"
|
||||
fi
|
||||
}
|
||||
|
||||
parse_md_rows() {
|
||||
_mdfile="$1"
|
||||
[ -f "$_mdfile" ] || return 0
|
||||
_skip_header=1
|
||||
sed -n '/^|/p' "$_mdfile" | grep -v '^[| -]*$' | while IFS='|' read -r _ _c1 _c2 _c3 _c4 _c5 _; do
|
||||
if [ "$_skip_header" -eq 1 ]; then _skip_header=0; continue; fi
|
||||
_c1="$(printf '%s' "$_c1" | sed 's/^ *//;s/ *$//')"
|
||||
_c2="$(printf '%s' "$_c2" | sed 's/^ *//;s/ *$//')"
|
||||
_c3="$(printf '%s' "$_c3" | sed 's/^ *//;s/ *$//')"
|
||||
_c4="$(printf '%s' "$_c4" | sed 's/^ *//;s/ *$//')"
|
||||
_c5="$(printf '%s' "${_c5:-}" | sed 's/^ *//;s/ *$//')"
|
||||
printf '%s\t%s\t%s\t%s\t%s\n' "$_c1" "$_c2" "$_c3" "$_c4" "$_c5"
|
||||
done
|
||||
}
|
||||
|
||||
to_lower() {
|
||||
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
cmd_migrate() {
|
||||
[ $# -ge 1 ] || die "Usage: trace-ops.sh migrate <team_dir>"
|
||||
_dir="$1"
|
||||
_out="$_dir/trace.jsonl"
|
||||
_total_track=0; _total_issue=0; _total_insight=0
|
||||
|
||||
if [ -f "$_dir/tracking.md" ]; then
|
||||
parse_md_rows "$_dir/tracking.md" | while IFS="$(printf '\t')" read -r _date _agent _task _status _comment; do
|
||||
[ -n "$_task" ] || continue
|
||||
_suffix=""; [ -n "$_comment" ] && _suffix=" — $_comment" || true
|
||||
_txt="$(truncate_text "$_task$_suffix")"
|
||||
_txt_esc="$(escape_json "$_txt")"
|
||||
_agent_esc="$(escape_json "$_agent")"
|
||||
_s="$(to_lower "$_status")"
|
||||
case "$_s" in
|
||||
took|refused|completed|failed) : ;;
|
||||
*) _s="took" ;;
|
||||
esac
|
||||
_ts="${_date:-1970-01-01}T00:00:00Z"
|
||||
printf '{"ts":"%s","sid":"migrated","src":"%s","k":"track","s":"%s","txt":"%s"}\n' \
|
||||
"$_ts" "$_agent_esc" "$_s" "$_txt_esc"
|
||||
done >> "$_out"
|
||||
_total_track="$(parse_md_rows "$_dir/tracking.md" | grep -c . || true)"
|
||||
mv "$_dir/tracking.md" "$_dir/tracking.md.bak"
|
||||
fi
|
||||
|
||||
if [ -f "$_dir/issues.md" ]; then
|
||||
parse_md_rows "$_dir/issues.md" | while IFS="$(printf '\t')" read -r _date _agent _desc _sev _; do
|
||||
[ -n "$_desc" ] || continue
|
||||
_txt="$(truncate_text "$_desc")"
|
||||
_txt_esc="$(escape_json "$_txt")"
|
||||
_agent_esc="$(escape_json "$_agent")"
|
||||
_sv="$(to_lower "$_sev")"
|
||||
case "$_sv" in
|
||||
low|medium|high|critical) : ;;
|
||||
*) _sv="medium" ;;
|
||||
esac
|
||||
_ts="${_date:-1970-01-01}T00:00:00Z"
|
||||
printf '{"ts":"%s","sid":"migrated","src":"%s","k":"issue","sev":"%s","txt":"%s"}\n' \
|
||||
"$_ts" "$_agent_esc" "$_sv" "$_txt_esc"
|
||||
done >> "$_out"
|
||||
_total_issue="$(parse_md_rows "$_dir/issues.md" | grep -c . || true)"
|
||||
mv "$_dir/issues.md" "$_dir/issues.md.bak"
|
||||
fi
|
||||
|
||||
if [ -f "$_dir/insights.md" ]; then
|
||||
parse_md_rows "$_dir/insights.md" | while IFS="$(printf '\t')" read -r _date _agent _insight _cat _; do
|
||||
[ -n "$_insight" ] || continue
|
||||
_txt="$(truncate_text "$_insight")"
|
||||
_txt_esc="$(escape_json "$_txt")"
|
||||
_agent_esc="$(escape_json "$_agent")"
|
||||
_ct="$(to_lower "$_cat")"
|
||||
case "$_ct" in
|
||||
pattern|architecture|performance|security|convention|debt) : ;;
|
||||
*) _ct="pattern" ;;
|
||||
esac
|
||||
_ts="${_date:-1970-01-01}T00:00:00Z"
|
||||
printf '{"ts":"%s","sid":"migrated","src":"%s","k":"insight","cat":"%s","txt":"%s"}\n' \
|
||||
"$_ts" "$_agent_esc" "$_ct" "$_txt_esc"
|
||||
done >> "$_out"
|
||||
_total_insight="$(parse_md_rows "$_dir/insights.md" | grep -c . || true)"
|
||||
mv "$_dir/insights.md" "$_dir/insights.md.bak"
|
||||
fi
|
||||
|
||||
printf 'Migrated: tracking=%s issues=%s insights=%s\n' \
|
||||
"$_total_track" "$_total_issue" "$_total_insight"
|
||||
}
|
||||
|
||||
[ $# -ge 2 ] || die "$USAGE"
|
||||
CMD="$1"; TEAM_DIR="$2"; shift 2
|
||||
|
||||
case "$CMD" in
|
||||
add) cmd_add "$TEAM_DIR" "$@" ;;
|
||||
read) cmd_read "$TEAM_DIR" "$@" ;;
|
||||
cursor) cmd_cursor "$TEAM_DIR" "$@" ;;
|
||||
migrate) cmd_migrate "$TEAM_DIR" "$@" ;;
|
||||
*) die "$USAGE" ;;
|
||||
esac
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
TEAM_NAME="${1:-}"
|
||||
if [ -z "$TEAM_NAME" ]; then
|
||||
echo "Usage: verify-team.sh <team-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TEAM_DIR=".codex/teams/$TEAM_NAME"
|
||||
FAIL=0
|
||||
|
||||
check() {
|
||||
label="$1"
|
||||
path="$2"
|
||||
printf "CHECK: %s ... " "$label"
|
||||
if [ -e "$path" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "MISSING"
|
||||
FAIL=1
|
||||
fi
|
||||
}
|
||||
|
||||
check "teams dir" "$TEAM_DIR"
|
||||
check "team.md" "$TEAM_DIR/team.md"
|
||||
check "trace.jsonl" "$TEAM_DIR/trace.jsonl"
|
||||
|
||||
if [ ! -f "$TEAM_DIR/trace.jsonl" ]; then
|
||||
for old_file in tracking.md issues.md insights.md; do
|
||||
if [ -f "$TEAM_DIR/$old_file" ]; then
|
||||
echo "MIGRATE: old $old_file found without trace.jsonl. Run: trace-ops.sh migrate $TEAM_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -f "$TEAM_DIR/team.md" ]; then
|
||||
in_agents=0
|
||||
past_header=0
|
||||
found_agents=0
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
"## Agents"*) in_agents=1; past_header=0; continue ;;
|
||||
"## "*) [ "$in_agents" -eq 1 ] && break ;;
|
||||
esac
|
||||
[ "$in_agents" -eq 0 ] && continue
|
||||
case "$line" in
|
||||
"|"*"---|"*) past_header=1; continue ;;
|
||||
"|"*)
|
||||
[ "$past_header" -eq 0 ] && continue
|
||||
found_agents=1
|
||||
agent=$(printf '%s' "$line" | cut -d'|' -f2 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed 's/`//g')
|
||||
[ -z "$agent" ] && continue
|
||||
printf "CHECK: agent %s ... " "$agent"
|
||||
if [ -f ".codex/agents/${agent}.toml" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "MISSING"
|
||||
FAIL=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < "$TEAM_DIR/team.md"
|
||||
if [ "$in_agents" -eq 1 ] && [ "$found_agents" -eq 0 ]; then
|
||||
echo "WARN: no agents found in table"
|
||||
fi
|
||||
if [ "$in_agents" -eq 0 ]; then
|
||||
echo "WARN: no ## Agents section in team.md"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "VERIFY: PASS"
|
||||
exit 0
|
||||
else
|
||||
echo "VERIFY: FAIL"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "brewdoc",
|
||||
"version": "4.0.6+codex.20260713124026",
|
||||
"description": "Codex-native documentation synchronization and Markdown-to-PDF workflows.",
|
||||
"author": { "name": "Maksim Kochetkov", "email": "apmatypa88@gmail.com", "url": "https://github.com/kochetkov-ma" },
|
||||
"homepage": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"repository": "https://github.com/kochetkov-ma/claude-brewcode.git",
|
||||
"license": "MIT",
|
||||
"keywords": ["codex", "documentation", "sync", "pdf"],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Brewdoc",
|
||||
"shortDescription": "Codex documentation workflows",
|
||||
"longDescription": "Reusable Codex workflows for controlled documentation synchronization and verified Markdown-to-PDF conversion.",
|
||||
"developerName": "Maksim Kochetkov",
|
||||
"category": "Productivity",
|
||||
"capabilities": ["Read", "Write", "Interactive"],
|
||||
"websiteURL": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"defaultPrompt": ["Use Brewdoc to synchronize this documentation."]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hooks": {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# MD to PDF
|
||||
|
||||
Converts Markdown files to professional PDF documents. Supports two rendering engines: **reportlab** (lightweight, pure Python) and **weasyprint** (full HTML/CSS pipeline with syntax highlighting).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
$brewdoc:md-to-pdf docs/report.md
|
||||
```
|
||||
|
||||
Converts `docs/report.md` to `docs/report.pdf` using your saved engine preference (or prompts you to choose one on first run).
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Trigger | What it does |
|
||||
|------|---------|--------------|
|
||||
| Convert | `<file.md>` | Converts the Markdown file to PDF |
|
||||
| Convert+Prompt | `<file.md> "instructions"` | Applies LLM preprocessing to the Markdown content, then converts |
|
||||
| Styles | `styles` | Interactive configuration for page size, colors, code theme, footer |
|
||||
| Test | `test` | Converts a bundled test file to `/tmp/` to verify the setup works |
|
||||
| Help | no args or `help` | Prints usage reference |
|
||||
|
||||
## Engines
|
||||
|
||||
| Feature | reportlab | weasyprint |
|
||||
|---------|-----------|------------|
|
||||
| Install | `python3 -m pip install reportlab==4.4.10` | `pip` + `brew` system deps |
|
||||
| Quality | Good | Excellent |
|
||||
| Speed | Fast | Moderate |
|
||||
| Images | Basic | Full |
|
||||
| CSS Styling | No | Yes |
|
||||
| Code Highlighting | No | Yes (Pygments) |
|
||||
|
||||
**Default:** `reportlab`. Override per-invocation with `--engine`, or save a preference via the first-run prompt (stored in `.codex/md-to-pdf.config.json`).
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Usage
|
||||
|
||||
**Simple conversion:**
|
||||
```
|
||||
$brewdoc:md-to-pdf README.md
|
||||
```
|
||||
|
||||
**Specify engine explicitly:**
|
||||
```
|
||||
$brewdoc:md-to-pdf README.md --engine weasyprint
|
||||
```
|
||||
|
||||
**Preprocess before converting -- remove a section, rewrite headings, restructure:**
|
||||
```
|
||||
$brewdoc:md-to-pdf docs/api.md "Remove the Changelog section and make all headings one level smaller"
|
||||
```
|
||||
|
||||
**Configure page layout and colors interactively:**
|
||||
```
|
||||
$brewdoc:md-to-pdf styles
|
||||
```
|
||||
|
||||
**Verify installation with the bundled test document:**
|
||||
```
|
||||
$brewdoc:md-to-pdf test
|
||||
```
|
||||
|
||||
### Common Mistakes
|
||||
|
||||
**Non-existent file:**
|
||||
```
|
||||
$brewdoc:md-to-pdf missing-file.md
|
||||
```
|
||||
The skill reads the file first and stops with an error if it does not exist. Verify the path before invoking.
|
||||
|
||||
**Using weasyprint features with reportlab:**
|
||||
```
|
||||
$brewdoc:md-to-pdf doc.md --engine reportlab
|
||||
```
|
||||
If you need CSS styling, syntax highlighting, or full image support, use `--engine weasyprint` instead. Reportlab does not support these features.
|
||||
|
||||
**Forgetting quotes around the preprocessing prompt:**
|
||||
```
|
||||
$brewdoc:md-to-pdf doc.md remove the changelog
|
||||
```
|
||||
The preprocessing prompt must be the last argument wrapped in double quotes: `"remove the changelog"`.
|
||||
|
||||
## Output
|
||||
|
||||
The PDF is written to the same directory as the input file, with the same name and a `.pdf` extension. In test mode, output goes to `/tmp/md-to-pdf-test-<engine>.pdf`.
|
||||
|
||||
After conversion, a result report is printed:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Source | Absolute path to the input Markdown file |
|
||||
| Output | Absolute path to the generated PDF |
|
||||
| Pages | Total page count |
|
||||
| Size | File size of the PDF |
|
||||
| Engine | Which engine was used (`reportlab` or `weasyprint`) |
|
||||
| Preprocessing | Summary of the prompt applied, or `none` |
|
||||
|
||||
## Tips
|
||||
|
||||
- **Choosing an engine:** Start with `reportlab` for speed and zero system dependencies. Switch to `weasyprint` when you need syntax-highlighted code blocks, CSS-based styling, or high-fidelity image rendering.
|
||||
- **Style customization:** Run `$brewdoc:md-to-pdf styles` to configure page size (A4, Letter, Legal), color scheme, code theme, and footer format. Settings are saved to `.codex/md-to-pdf.config.json` and reused automatically.
|
||||
- **Preprocessing use cases:** The `"prompt"` argument is useful for preparing documents before PDF generation -- strip draft sections, translate headings, flatten structure, or redact sensitive content without modifying the original file.
|
||||
- **Test before sharing:** Run `$brewdoc:md-to-pdf test` after installing a new engine or changing styles to confirm everything renders correctly.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full docs: [md-to-pdf](https://doc-claude.brewcode.app/brewdoc/md-to-pdf/)
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
name: md-to-pdf
|
||||
description: "Convert Markdown to PDF via reportlab or weasyprint engines. Triggers - pdf, md to pdf, markdown to pdf, generate pdf."
|
||||
---
|
||||
|
||||
# Markdown to PDF
|
||||
|
||||
Convert a local Markdown file with `scripts/md_to_pdf.py`. Check dependencies with `scripts/check_deps.sh`, choose a bundled style from `styles/`, and write only to the requested output path. Store project preferences in `.codex/md-to-pdf.config.json` only after confirmation. Render and inspect the result before reporting completion.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
# MD to PDF
|
||||
|
||||
Converts Markdown files to professional PDF using one of two rendering engines.
|
||||
|
||||
## Step 0: Parse Arguments
|
||||
|
||||
Parse `<arguments>` to determine mode and components.
|
||||
|
||||
| Component | Required | Description |
|
||||
|-----------|:--------:|-------------|
|
||||
| `md_file` | per mode | Path to `.md` file |
|
||||
| `--engine` | No | `reportlab` or `weasyprint` (overrides saved config) |
|
||||
| `custom_prompt` | No | Last argument in double quotes = LLM preprocessing instructions |
|
||||
|
||||
**Mode detection rules:**
|
||||
|
||||
| Condition | Mode |
|
||||
|-----------|------|
|
||||
| Empty or `help` | HELP |
|
||||
| `styles` or `config` | STYLES |
|
||||
| `test` | TEST |
|
||||
| Path to `.md` file + quoted string at end | CONVERT+PROMPT |
|
||||
| Path to `.md` file (no quoted string) | CONVERT |
|
||||
|
||||
Extract `--engine <name>` from anywhere in arguments if present. Remove it before further parsing.
|
||||
|
||||
## Step 1: Dependency Check
|
||||
|
||||
Determine the target engine (from `--engine` flag, saved config, or default `reportlab`).
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/check_deps.sh" check ENGINE_NAME 2>&1; echo "EXIT_CODE=$?"
|
||||
```
|
||||
Replace `ENGINE_NAME` with the target engine.
|
||||
|
||||
**If output contains `MISSING_PIP` or `MISSING_SYSTEM`:**
|
||||
|
||||
Use `request_user_input` presenting the engine comparison table:
|
||||
|
||||
| Feature | reportlab | weasyprint |
|
||||
|---------|-----------|------------|
|
||||
| Install | pip only | pip + brew |
|
||||
| Quality | Good | Excellent |
|
||||
| Speed | Fast | Moderate |
|
||||
| Images | Basic | Full |
|
||||
| CSS Styling | No | Yes |
|
||||
| Code highlight | No | Yes (Pygments) |
|
||||
|
||||
Options:
|
||||
- "Install ENGINE_NAME dependencies"
|
||||
- "Switch to OTHER_ENGINE" (if the other engine is available)
|
||||
- "Cancel"
|
||||
|
||||
If user chooses install, **EXECUTE** using shell:
|
||||
```bash
|
||||
bash "<skill-directory>/scripts/check_deps.sh" install ENGINE_NAME 2>&1 && echo "---INSTALL_OK---" || echo "---INSTALL_FAILED---"
|
||||
```
|
||||
|
||||
> **STOP if INSTALL_FAILED** -- report error and exit.
|
||||
|
||||
If user cancels -- STOP.
|
||||
|
||||
## Step 2: Engine Selection (first run only)
|
||||
|
||||
Check for saved config in order:
|
||||
1. Project: `.codex/md-to-pdf.config.json`
|
||||
2. Global: `~/.codex/md-to-pdf.config.json`
|
||||
|
||||
If `--engine` flag was provided -- use it (skip config lookup).
|
||||
|
||||
If no saved preference and no `--engine` flag -- use `request_user_input` with the engine comparison table from Step 1. Save the choice:
|
||||
|
||||
```json
|
||||
{
|
||||
"engine": "reportlab",
|
||||
"pygments_theme": "github"
|
||||
}
|
||||
```
|
||||
|
||||
Write to project config `.codex/md-to-pdf.config.json` (create `.codex/` dir if needed).
|
||||
|
||||
## Step 3: Mode Execution
|
||||
|
||||
### HELP Mode
|
||||
|
||||
Print formatted usage:
|
||||
|
||||
```
|
||||
MD to PDF Converter
|
||||
|
||||
Usage:
|
||||
$brewdoc:md-to-pdf <file.md> Convert with saved engine/style
|
||||
$brewdoc:md-to-pdf <file.md> --engine weasyprint Convert with specific engine
|
||||
$brewdoc:md-to-pdf <file.md> "remove section X" Preprocess MD then convert
|
||||
$brewdoc:md-to-pdf styles Configure page/color/font
|
||||
$brewdoc:md-to-pdf test Convert bundled test file
|
||||
$brewdoc:md-to-pdf help Show this help
|
||||
|
||||
Engines:
|
||||
reportlab -- Pure Python, fast, no system deps (python3 -m pip install reportlab==4.4.10)
|
||||
weasyprint -- HTML/CSS pipeline, best quality (pip + brew deps)
|
||||
```
|
||||
|
||||
EXIT after printing.
|
||||
|
||||
### CONVERT Mode
|
||||
|
||||
1. Read the input MD file with filesystem reader. If not found -- STOP with error.
|
||||
2. Determine output path: same directory, same name, `.pdf` extension.
|
||||
3. Build the config path argument (if project or global config exists, add `--config CONFIG_PATH`).
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
python3 "<skill-directory>/scripts/md_to_pdf.py" "INPUT_PATH" "OUTPUT_PATH" --engine ENGINE --quiet 2>&1 && echo "---CONVERT_OK---" || echo "---CONVERT_FAILED---"
|
||||
```
|
||||
Replace `INPUT_PATH`, `OUTPUT_PATH`, `ENGINE` with actual values. Add `--config CONFIG_PATH` if a style config JSON exists. Add `--pygments-theme THEME` for weasyprint if configured.
|
||||
|
||||
> **STOP if CONVERT_FAILED** -- read error output, attempt fix, retry once. If still failing -- report error.
|
||||
|
||||
4. Parse structured output lines: `STATUS`, `OUTPUT`, `PAGES`, `SIZE`, `ENGINE`.
|
||||
|
||||
### CONVERT+PROMPT Mode
|
||||
|
||||
1. Read the input MD file with filesystem reader.
|
||||
2. Apply LLM transformations per the `custom_prompt` instructions (delete sections, rewrite headings, restructure, etc.).
|
||||
3. Write modified content to temp file: `{original_dir}/.tmp_{original_name}.md`
|
||||
4. Run the converter on the temp file (same command as CONVERT mode, using temp file as input, original name for output).
|
||||
5. Delete the temp file.
|
||||
|
||||
**EXECUTE** using shell:
|
||||
```bash
|
||||
rm -f "TEMP_FILE_PATH"
|
||||
```
|
||||
|
||||
6. Proceed to Step 4 with `preprocessing: true`.
|
||||
|
||||
### STYLES Mode
|
||||
|
||||
Run interactive configuration via `request_user_input` dialogs:
|
||||
|
||||
**Question 1 -- Page size:**
|
||||
Options: `A4` (default), `Letter`, `Legal`
|
||||
|
||||
**Question 2 -- Color scheme:**
|
||||
Options: `Default blue` (primary #1a3a5c), `Dark` (primary #2d3748), `Custom` (ask for hex values)
|
||||
|
||||
**Question 3 -- Code theme (weasyprint only):**
|
||||
Options: `github` (default), `monokai`, `friendly`, `solarized-dark`, `solarized-light`
|
||||
|
||||
**Question 4 -- Footer format:**
|
||||
Options: `Page {page} of {total}` (default), `{page}/{total}`, `Disabled`
|
||||
|
||||
Build JSON config matching `styles/default.json` structure, overriding changed values. Write to `.codex/md-to-pdf.config.json`.
|
||||
|
||||
Report saved settings table and EXIT.
|
||||
|
||||
### TEST Mode
|
||||
|
||||
1. Use bundled test file at `<skill-directory>/test/test-all-elements.md` as INPUT_PATH.
|
||||
2. Determine output path: `/tmp/md-to-pdf-test-ENGINE.pdf`
|
||||
3. Run converter (same command as CONVERT mode, using test file as input, `/tmp/` output).
|
||||
4. Proceed to Step 4.
|
||||
|
||||
## Step 4: Report Results
|
||||
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| Source | absolute path to input MD |
|
||||
| Output | absolute path to output PDF |
|
||||
| Pages | from `PAGES=` in script output |
|
||||
| Size | from `SIZE=` in script output |
|
||||
| Engine | `reportlab` or `weasyprint` |
|
||||
| Preprocessing | custom_prompt summary (if used) or `none` |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Md To Pdf"
|
||||
short_description: "Convert Markdown to PDF via reportlab or weasyprint engines. Tri"
|
||||
default_prompt: "Use $brewdoc:md-to-pdf for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPORTLAB_VERSION=4.4.10
|
||||
WEASYPRINT_VERSION=68.1
|
||||
MARKDOWN_VERSION=3.10.2
|
||||
PYGMENTS_VERSION=2.19.2
|
||||
PANGO_VERSION=1.57.1
|
||||
CAIRO_VERSION=1.18.4
|
||||
GDK_PIXBUF_VERSION=2.44.6
|
||||
LIBFFI_VERSION=3.6.0
|
||||
|
||||
usage() { echo "usage: check_deps.sh <check|install|status> [reportlab|weasyprint]"; }
|
||||
python_version() { python3 -c 'import sys; raise SystemExit(sys.version_info < (3, 8))'; }
|
||||
package_version() { python3 -c 'from importlib.metadata import version; print(version("'"$1"'"))' 2>/dev/null; }
|
||||
check_package() { test "$(package_version "$1")" = "$2"; }
|
||||
brew_version() { brew list --versions "$1" 2>/dev/null | awk '{print $NF}'; }
|
||||
check_formula() { test "$(brew_version "$1")" = "$2"; }
|
||||
|
||||
check_engine() {
|
||||
local engine="$1"
|
||||
command -v python3 >/dev/null && python_version || { echo "MISSING_PYTHON|requires Python >=3.8"; return 1; }
|
||||
case "$engine" in
|
||||
reportlab)
|
||||
check_package reportlab "$REPORTLAB_VERSION" || { echo "MISSING_OR_WRONG_PIP|reportlab==$REPORTLAB_VERSION"; return 1; }
|
||||
;;
|
||||
weasyprint)
|
||||
check_package weasyprint "$WEASYPRINT_VERSION" || { echo "MISSING_OR_WRONG_PIP|weasyprint==$WEASYPRINT_VERSION"; return 1; }
|
||||
check_package markdown "$MARKDOWN_VERSION" || { echo "MISSING_OR_WRONG_PIP|markdown==$MARKDOWN_VERSION"; return 1; }
|
||||
check_package pygments "$PYGMENTS_VERSION" || { echo "MISSING_OR_WRONG_PIP|pygments==$PYGMENTS_VERSION"; return 1; }
|
||||
if test "$(uname -s)" = Darwin; then
|
||||
check_formula pango "$PANGO_VERSION" || { echo "MISSING_OR_WRONG_SYSTEM|pango==$PANGO_VERSION"; return 1; }
|
||||
check_formula cairo "$CAIRO_VERSION" || { echo "MISSING_OR_WRONG_SYSTEM|cairo==$CAIRO_VERSION"; return 1; }
|
||||
check_formula gdk-pixbuf "$GDK_PIXBUF_VERSION" || { echo "MISSING_OR_WRONG_SYSTEM|gdk-pixbuf==$GDK_PIXBUF_VERSION"; return 1; }
|
||||
check_formula libffi "$LIBFFI_VERSION" || { echo "MISSING_OR_WRONG_SYSTEM|libffi==$LIBFFI_VERSION"; return 1; }
|
||||
fi
|
||||
;;
|
||||
*) usage >&2; return 2 ;;
|
||||
esac
|
||||
echo OK
|
||||
}
|
||||
|
||||
install_engine() {
|
||||
local engine="$1"
|
||||
command -v python3 >/dev/null || { echo "Python >=3.8 is required" >&2; return 1; }
|
||||
case "$engine" in
|
||||
reportlab)
|
||||
python3 -m pip install "reportlab==$REPORTLAB_VERSION"
|
||||
;;
|
||||
weasyprint)
|
||||
if test "$(uname -s)" = Darwin; then
|
||||
for pin in "pango==$PANGO_VERSION" "cairo==$CAIRO_VERSION" "gdk-pixbuf==$GDK_PIXBUF_VERSION" "libffi==$LIBFFI_VERSION"; do
|
||||
formula="${pin%%==*}"; version="${pin##*==}"
|
||||
check_formula "$formula" "$version" || { echo "Install exact system dependency $pin through an approved pinned package source, then retry." >&2; return 1; }
|
||||
done
|
||||
fi
|
||||
python3 -m pip install "weasyprint==$WEASYPRINT_VERSION" "markdown==$MARKDOWN_VERSION" "pygments==$PYGMENTS_VERSION"
|
||||
;;
|
||||
*) usage >&2; return 2 ;;
|
||||
esac
|
||||
check_engine "$engine"
|
||||
}
|
||||
|
||||
command="${1:-}"; engine="${2:-}"
|
||||
case "$command" in
|
||||
check) test -n "$engine" || { usage >&2; exit 2; }; check_engine "$engine" ;;
|
||||
install) test -n "$engine" || { usage >&2; exit 2; }; install_engine "$engine" ;;
|
||||
status) check_engine reportlab || true; check_engine weasyprint || true ;;
|
||||
help|-h|--help) usage ;;
|
||||
*) usage >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -0,0 +1,848 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Universal Markdown to PDF converter with dual engine support.
|
||||
|
||||
Engines:
|
||||
- reportlab (default) -- pure Python, no system deps
|
||||
- weasyprint -- HTML/CSS pipeline, better fidelity
|
||||
|
||||
Usage:
|
||||
python3 md_to_pdf.py <input.md> [output.pdf] [options]
|
||||
python3 md_to_pdf.py input.md --engine weasyprint --style custom.css
|
||||
python3 md_to_pdf.py input.md output.pdf --config my_config.json --quiet
|
||||
|
||||
Dependencies:
|
||||
reportlab engine: python3 -m pip install reportlab==4.4.10
|
||||
weasyprint engine: python3 -m pip install weasyprint==68.1 markdown==3.10.2 pygments==2.19.2
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
import platform
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
DEFAULT_CONFIG_PATH = SCRIPT_DIR / ".." / "styles" / "default.json"
|
||||
DEFAULT_CSS_PATH = SCRIPT_DIR / ".." / "styles" / "default.css"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def parse_args(argv=None):
|
||||
p = argparse.ArgumentParser(
|
||||
description="Convert Markdown to PDF (reportlab or weasyprint).",
|
||||
)
|
||||
p.add_argument("input", help="Path to the Markdown file")
|
||||
p.add_argument("output", nargs="?", default=None, help="Output PDF path (default: <input>.pdf)")
|
||||
p.add_argument("--engine", choices=["reportlab", "weasyprint"], default="reportlab",
|
||||
help="Rendering engine (default: reportlab)")
|
||||
p.add_argument("--config", default=None, help="JSON style config overrides")
|
||||
p.add_argument("--style", default=None, help="CSS file (weasyprint only)")
|
||||
p.add_argument("--pygments-theme", default="github", help="Code theme (weasyprint only, default: github)")
|
||||
p.add_argument("--quiet", action="store_true", help="Suppress progress output")
|
||||
args = p.parse_args(argv)
|
||||
if args.output is None:
|
||||
args.output = str(Path(args.input).with_suffix(".pdf"))
|
||||
return args
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config loading (deep merge)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _deep_merge(base: dict, override: dict) -> dict:
|
||||
result = deepcopy(base)
|
||||
for key, val in override.items():
|
||||
if key in result and isinstance(result[key], dict) and isinstance(val, dict):
|
||||
result[key] = _deep_merge(result[key], val)
|
||||
else:
|
||||
result[key] = deepcopy(val)
|
||||
return result
|
||||
|
||||
|
||||
def load_config(config_path=None) -> dict:
|
||||
"""Load config from JSON, falling back to defaults."""
|
||||
defaults = {}
|
||||
if DEFAULT_CONFIG_PATH.exists():
|
||||
defaults = json.loads(DEFAULT_CONFIG_PATH.read_text(encoding="utf-8"))
|
||||
if config_path:
|
||||
user_cfg = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
||||
return _deep_merge(defaults, user_cfg)
|
||||
return defaults
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-platform font detection (reportlab)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FONT_SEARCH = {
|
||||
"Darwin": [
|
||||
("/System/Library/Fonts/Supplemental/PTSans.ttc", {
|
||||
"body": ("PTSans", 0), "bold": ("PTSans-Bold", 7),
|
||||
"italic": ("PTSans-Italic", 1), "boldItalic": ("PTSans-BoldItalic", 6),
|
||||
}),
|
||||
("/System/Library/Fonts/Supplemental/Arial.ttf", {
|
||||
"body": ("Arial", None), "bold": ("Arial-Bold", None),
|
||||
"italic": ("Arial-Italic", None), "boldItalic": ("Arial-BoldItalic", None),
|
||||
}),
|
||||
],
|
||||
"Linux": [
|
||||
("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", {
|
||||
"body": ("DejaVuSans", None), "bold": ("DejaVuSans-Bold", None),
|
||||
"italic": ("DejaVuSans-Oblique", None), "boldItalic": ("DejaVuSans-BoldOblique", None),
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
# Linux bold/italic companion files
|
||||
_LINUX_COMPANIONS = {
|
||||
"bold": "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"italic": "/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf",
|
||||
"boldItalic": "/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf",
|
||||
}
|
||||
|
||||
|
||||
def detect_fonts() -> dict:
|
||||
"""Detect available fonts, return name mapping dict.
|
||||
|
||||
Returns:
|
||||
{"body": "FontName", "bold": "FontName-Bold",
|
||||
"italic": "FontName-Italic", "boldItalic": "FontName-BoldItalic",
|
||||
"family": "FontName", "_source": "path_or_builtin",
|
||||
"_entries": [(name, path, subfontIndex|None), ...]}
|
||||
"""
|
||||
system = platform.system()
|
||||
candidates = _FONT_SEARCH.get(system, []) + _FONT_SEARCH.get("Linux", [])
|
||||
|
||||
for font_path, mapping in candidates:
|
||||
if not os.path.exists(font_path):
|
||||
continue
|
||||
|
||||
entries = []
|
||||
names = {}
|
||||
for role, (name, idx) in mapping.items():
|
||||
if system == "Linux" and role != "body":
|
||||
companion = _LINUX_COMPANIONS.get(role)
|
||||
if companion and os.path.exists(companion):
|
||||
entries.append((name, companion, None))
|
||||
else:
|
||||
entries.append((name, font_path, idx))
|
||||
else:
|
||||
entries.append((name, font_path, idx))
|
||||
names[role] = name
|
||||
|
||||
names["family"] = names["body"]
|
||||
names["_source"] = font_path
|
||||
names["_entries"] = entries
|
||||
return names
|
||||
|
||||
return {
|
||||
"body": "Helvetica", "bold": "Helvetica-Bold",
|
||||
"italic": "Helvetica-Oblique", "boldItalic": "Helvetica-BoldOblique",
|
||||
"family": "Helvetica", "_source": "builtin", "_entries": [],
|
||||
}
|
||||
|
||||
|
||||
def register_detected_fonts(font_info: dict):
|
||||
"""Register detected fonts with reportlab (lazy import)."""
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
for name, path, idx in font_info.get("_entries", []):
|
||||
kwargs = {"subfontIndex": idx} if idx is not None else {}
|
||||
pdfmetrics.registerFont(TTFont(name, path, **kwargs))
|
||||
|
||||
if font_info["_entries"]:
|
||||
pdfmetrics.registerFontFamily(
|
||||
font_info["family"],
|
||||
normal=font_info["body"],
|
||||
bold=font_info["bold"],
|
||||
italic=font_info["italic"],
|
||||
boldItalic=font_info["boldItalic"],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Structured output
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def print_status(output_path: str, page_count: int, engine: str, quiet: bool):
|
||||
size_bytes = Path(output_path).stat().st_size
|
||||
size_kb = f"{size_bytes / 1024:.0f}KB"
|
||||
lines = [
|
||||
f"STATUS=OK",
|
||||
f"OUTPUT={output_path}",
|
||||
f"PAGES={page_count}",
|
||||
f"SIZE={size_kb}",
|
||||
f"ENGINE={engine}",
|
||||
]
|
||||
if not quiet:
|
||||
for ln in lines:
|
||||
print(ln)
|
||||
|
||||
|
||||
def print_failure(message: str):
|
||||
print("STATUS=FAILED", file=sys.stdout)
|
||||
print(message, file=sys.stderr)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WeasyPrint engine
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def convert_weasyprint(input_path: str, output_path: str, config: dict,
|
||||
css_path=None, pygments_theme="github", quiet=False):
|
||||
"""Convert MD -> HTML -> CSS -> PDF via weasyprint."""
|
||||
try:
|
||||
import markdown
|
||||
import weasyprint
|
||||
from pygments.formatters import HtmlFormatter
|
||||
except ImportError as exc:
|
||||
print_failure(f"Missing dependency for weasyprint engine: {exc}\n"
|
||||
f"Install with: python3 -m pip install weasyprint==68.1 markdown==3.10.2 pygments==2.19.2")
|
||||
sys.exit(1)
|
||||
|
||||
md_text = Path(input_path).read_text(encoding="utf-8")
|
||||
|
||||
extensions = [
|
||||
"tables", "fenced_code", "codehilite", "footnotes",
|
||||
"toc", "attr_list", "def_list", "admonition", "sane_lists", "smarty",
|
||||
]
|
||||
extension_configs = {
|
||||
"codehilite": {"css_class": "highlight", "guess_lang": True},
|
||||
}
|
||||
html_body = markdown.markdown(md_text, extensions=extensions,
|
||||
extension_configs=extension_configs)
|
||||
|
||||
# Resolve CSS
|
||||
css_file = Path(css_path) if css_path else DEFAULT_CSS_PATH
|
||||
css_link = ""
|
||||
if css_file.exists():
|
||||
css_link = f'<link rel="stylesheet" href="file://{css_file.resolve()}">'
|
||||
|
||||
# Pygments inline CSS
|
||||
try:
|
||||
pygments_css = HtmlFormatter(style=pygments_theme).get_style_defs(".highlight")
|
||||
except Exception:
|
||||
pygments_css = HtmlFormatter(style="default").get_style_defs(".highlight")
|
||||
|
||||
html_doc = f"""<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="utf-8">
|
||||
{css_link}
|
||||
<style>{pygments_css}</style>
|
||||
</head><body>
|
||||
{html_body}
|
||||
</body></html>"""
|
||||
|
||||
doc = weasyprint.HTML(string=html_doc, base_url=str(Path(input_path).parent)).render()
|
||||
doc.write_pdf(output_path)
|
||||
|
||||
page_count = len(doc.pages)
|
||||
print_status(output_path, page_count, "weasyprint", quiet)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _rl_colors(config: dict):
|
||||
"""Build reportlab color objects from config."""
|
||||
from reportlab.lib import colors as rlc
|
||||
c = config.get("colors", {})
|
||||
return {
|
||||
"primary": rlc.HexColor(c.get("primary", "#1a3a5c")),
|
||||
"secondary": rlc.HexColor(c.get("secondary", "#2c5282")),
|
||||
"text": rlc.HexColor(c.get("text", "#1a1a1a")),
|
||||
"code_bg": rlc.HexColor(c.get("code_bg", "#f0f0f0")),
|
||||
"header_bg": rlc.HexColor(c.get("header_bg", "#1a3a5c")),
|
||||
"header_fg": rlc.HexColor(c.get("header_fg", "#ffffff")),
|
||||
"quote_bg": rlc.HexColor(c.get("quote_bg", "#f5f5f5")),
|
||||
"border": rlc.HexColor(c.get("border", "#3182ce")),
|
||||
"light_bg": rlc.HexColor(c.get("light_bg", "#f0f4f8")),
|
||||
"white": rlc.white,
|
||||
}
|
||||
|
||||
|
||||
def safe_xml(text: str, code_font: str = "Courier-Bold") -> str:
|
||||
"""Escape XML-unsafe chars, apply inline Markdown markup.
|
||||
|
||||
Handles: **bold**, *italic*, ***bold italic***, `inline code`, [links](url).
|
||||
Args:
|
||||
code_font: font face for inline `code` spans (default: Courier-Bold).
|
||||
"""
|
||||
text = text.replace("&", "&")
|
||||
text = text.replace("<", "<").replace(">", ">")
|
||||
|
||||
# Links [text](url) -- must come before bold/italic to avoid mangling
|
||||
# Internal anchors (#...) become plain bold text; external URLs become <a> tags
|
||||
def _link_repl(m):
|
||||
link_text, url = m.group(1), m.group(2)
|
||||
if url.startswith("#"):
|
||||
return f"<b>{link_text}</b>"
|
||||
return f'<a href="{url}" color="blue"><u>{link_text}</u></a>'
|
||||
|
||||
text = re.sub(r"\[([^\]]+?)\]\(([^)]+?)\)", _link_repl, text)
|
||||
|
||||
# Bold+italic (***), then bold (**), then italic (*)
|
||||
text = re.sub(r"\*\*\*(.+?)\*\*\*", r"<b><i>\1</i></b>", text)
|
||||
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
|
||||
text = re.sub(r"\*(.+?)\*", r"<i>\1</i>", text)
|
||||
|
||||
# Inline code -- monospace bold in red
|
||||
text = re.sub(r"`(.+?)`", rf'<font face="{code_font}" color="#c53030">\1</font>', text)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def parse_md_table(lines: list) -> list:
|
||||
"""Parse markdown table lines into a list of rows (list of cell strings)."""
|
||||
rows = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line.startswith("|"):
|
||||
continue
|
||||
cells = [c.strip() for c in line.split("|")]
|
||||
if cells and cells[0] == "":
|
||||
cells = cells[1:]
|
||||
if cells and cells[-1] == "":
|
||||
cells = cells[:-1]
|
||||
if all(re.match(r"^[-:]+$", c) for c in cells):
|
||||
continue
|
||||
rows.append(cells)
|
||||
return rows
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- styles
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_styles(font_info: dict, config: dict):
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT
|
||||
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
|
||||
|
||||
clr = _rl_colors(config)
|
||||
typo = config.get("typography", {})
|
||||
body_sz = typo.get("body_size", 9)
|
||||
f = font_info
|
||||
|
||||
ss = StyleSheet1()
|
||||
|
||||
ss.add(ParagraphStyle(
|
||||
name="Normal", fontName=f["body"], fontSize=body_sz,
|
||||
leading=body_sz * 1.35, textColor=clr["text"], spaceAfter=4,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="H1", fontName=f["bold"], fontSize=typo.get("h1_size", 18),
|
||||
leading=22, textColor=clr["primary"], alignment=TA_CENTER,
|
||||
spaceAfter=6, spaceBefore=0,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="H2", fontName=f["bold"], fontSize=typo.get("h2_size", 14),
|
||||
leading=18, textColor=clr["primary"], alignment=TA_LEFT,
|
||||
spaceAfter=6, spaceBefore=14,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="H3", fontName=f["bold"], fontSize=typo.get("h3_size", 12),
|
||||
leading=15, textColor=clr["secondary"], alignment=TA_LEFT,
|
||||
spaceAfter=4, spaceBefore=10,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="H4", fontName=f["bold"], fontSize=typo.get("h4_size", 10),
|
||||
leading=13, textColor=clr["primary"], alignment=TA_LEFT,
|
||||
spaceAfter=4, spaceBefore=8,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="Blockquote", fontName=f["italic"], fontSize=body_sz - 0.5,
|
||||
leading=11, textColor=clr["text"], leftIndent=14,
|
||||
spaceAfter=4, spaceBefore=2, backColor=clr["quote_bg"],
|
||||
borderPadding=(4, 6, 4, 6),
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="TableCell", fontName=f["body"], fontSize=8,
|
||||
leading=10, textColor=clr["text"],
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="TableCellBold", fontName=f["bold"], fontSize=8,
|
||||
leading=10, textColor=clr["text"],
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="TableHeaderCell", fontName=f["bold"], fontSize=8,
|
||||
leading=10, textColor=clr["header_fg"],
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="Subtitle", fontName=f["italic"], fontSize=10,
|
||||
leading=13, textColor=clr["text"], alignment=TA_CENTER, spaceAfter=10,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="BulletItem", fontName=f["body"], fontSize=body_sz,
|
||||
leading=body_sz * 1.35, textColor=clr["text"],
|
||||
leftIndent=16, bulletIndent=6, spaceAfter=3,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="NumberedItem", fontName=f["body"], fontSize=body_sz,
|
||||
leading=body_sz * 1.35, textColor=clr["text"],
|
||||
leftIndent=20, firstLineIndent=-14, spaceAfter=3,
|
||||
))
|
||||
ss.add(ParagraphStyle(
|
||||
name="CodeBlock", fontName="Courier", fontSize=config.get("code", {}).get("font_size", 7.5),
|
||||
leading=10, textColor=clr["text"], leftIndent=6,
|
||||
spaceAfter=2, spaceBefore=2,
|
||||
))
|
||||
|
||||
return ss
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- table builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_table(rows: list, styles, font_info: dict, clr: dict, available_width: float):
|
||||
"""Build a reportlab Table from parsed MD rows with auto column widths."""
|
||||
from reportlab.lib import colors as rlc
|
||||
from reportlab.platypus import Table, TableStyle, Paragraph
|
||||
|
||||
if not rows:
|
||||
return None
|
||||
|
||||
num_cols = max(len(r) for r in rows)
|
||||
for row in rows:
|
||||
while len(row) < num_cols:
|
||||
row.append("")
|
||||
|
||||
header_style = styles["TableHeaderCell"]
|
||||
cell_style = styles["TableCell"]
|
||||
|
||||
data = []
|
||||
for ri, row in enumerate(rows):
|
||||
prow = []
|
||||
for cell in row:
|
||||
cell_text = safe_xml(cell)
|
||||
st = header_style if ri == 0 else cell_style
|
||||
prow.append(Paragraph(cell_text, st))
|
||||
data.append(prow)
|
||||
|
||||
# Auto column widths based on content length
|
||||
col_weights = [0.0] * num_cols
|
||||
for row in rows:
|
||||
for ci, cell in enumerate(row):
|
||||
col_weights[ci] = max(col_weights[ci], len(cell))
|
||||
|
||||
total_weight = sum(col_weights) or 1
|
||||
col_ratios = [max(min(w / total_weight, 0.60), 0.05) for w in col_weights]
|
||||
ratio_sum = sum(col_ratios)
|
||||
col_widths = [(r / ratio_sum) * available_width for r in col_ratios]
|
||||
|
||||
table = Table(data, colWidths=col_widths, repeatRows=1)
|
||||
table.setStyle(TableStyle([
|
||||
("BACKGROUND", (0, 0), (-1, 0), clr["header_bg"]),
|
||||
("TEXTCOLOR", (0, 0), (-1, 0), clr["header_fg"]),
|
||||
("FONTNAME", (0, 0), (-1, 0), font_info["bold"]),
|
||||
("FONTSIZE", (0, 0), (-1, 0), 8),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 3),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 3),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 4),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 4),
|
||||
("GRID", (0, 0), (-1, -1), 0.5, rlc.HexColor("#c0c0c0")),
|
||||
("ROWBACKGROUNDS", (0, 1), (-1, -1), [rlc.white, clr["light_bg"]]),
|
||||
]))
|
||||
return table
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- blockquote builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_blockquote(text: str, styles, font_info: dict, clr: dict, available_width: float):
|
||||
"""Build a blockquote as a table with a left blue border."""
|
||||
from reportlab.platypus import Table, TableStyle, Paragraph
|
||||
|
||||
cell_text = safe_xml(text)
|
||||
para = Paragraph(cell_text, styles["Blockquote"])
|
||||
|
||||
data = [[" ", para]]
|
||||
col_widths = [3, available_width - 10]
|
||||
t = Table(data, colWidths=col_widths)
|
||||
t.setStyle(TableStyle([
|
||||
("BACKGROUND", (0, 0), (0, 0), clr["border"]),
|
||||
("BACKGROUND", (1, 0), (1, 0), clr["quote_bg"]),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 4),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
|
||||
("LEFTPADDING", (0, 0), (0, 0), 0),
|
||||
("RIGHTPADDING", (0, 0), (0, 0), 0),
|
||||
("LEFTPADDING", (1, 0), (1, 0), 6),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
]))
|
||||
return t
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- code block builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_code_block(text: str, styles, clr: dict, available_width: float):
|
||||
"""Build a code block with gray background."""
|
||||
from reportlab.lib import colors as rlc
|
||||
from reportlab.platypus import Table, TableStyle, Paragraph
|
||||
|
||||
text = text.replace("&", "&")
|
||||
text = text.replace("<", "<").replace(">", ">")
|
||||
text = text.replace("\n", "<br/>")
|
||||
para = Paragraph(text, styles["CodeBlock"])
|
||||
|
||||
data = [[para]]
|
||||
t = Table(data, colWidths=[available_width])
|
||||
t.setStyle(TableStyle([
|
||||
("BACKGROUND", (0, 0), (0, 0), clr["code_bg"]),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 8),
|
||||
("BOX", (0, 0), (-1, -1), 0.5, rlc.HexColor("#d0d0d0")),
|
||||
]))
|
||||
return t
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- image support
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _try_build_image(alt: str, src: str, available_width: float):
|
||||
"""Attempt to build a reportlab Image flowable. Returns None if not possible."""
|
||||
from reportlab.platypus import Image
|
||||
|
||||
if src.startswith("http://") or src.startswith("https://"):
|
||||
return None
|
||||
img_path = Path(src)
|
||||
if not img_path.exists():
|
||||
return None
|
||||
try:
|
||||
img = Image(str(img_path))
|
||||
iw, ih = img.drawWidth, img.drawHeight
|
||||
if iw > available_width:
|
||||
ratio = available_width / iw
|
||||
img.drawWidth = available_width
|
||||
img.drawHeight = ih * ratio
|
||||
return img
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- numbered canvas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_numbered_canvas_class(font_name: str, footer_fmt: str):
|
||||
"""Create a NumberedCanvas class bound to the given font and format string."""
|
||||
from reportlab.lib import colors as rlc
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
|
||||
class NumberedCanvas(Canvas):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Canvas.__init__(self, *args, **kwargs)
|
||||
self._saved_page_states = []
|
||||
|
||||
def showPage(self):
|
||||
self._saved_page_states.append(dict(self.__dict__))
|
||||
self._startPage()
|
||||
|
||||
def save(self):
|
||||
num_pages = len(self._saved_page_states)
|
||||
for state in self._saved_page_states:
|
||||
self.__dict__.update(state)
|
||||
self._draw_footer(num_pages)
|
||||
Canvas.showPage(self)
|
||||
Canvas.save(self)
|
||||
|
||||
def _draw_footer(self, page_count):
|
||||
self.saveState()
|
||||
fn = font_name if font_name != "Helvetica" else "Helvetica"
|
||||
self.setFont(fn, 8)
|
||||
self.setFillColor(rlc.HexColor("#888888"))
|
||||
text = footer_fmt.format(page=self._pageNumber, total=page_count)
|
||||
self.drawCentredString(A4[0] / 2, 25, text)
|
||||
self.restoreState()
|
||||
|
||||
return NumberedCanvas
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- markdown to story (flowables)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def md_to_story(md_text: str, styles, font_info: dict, clr: dict,
|
||||
available_width: float) -> list:
|
||||
"""Parse markdown text and return a list of reportlab flowables."""
|
||||
from reportlab.lib import colors as rlc
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
from reportlab.platypus.flowables import HRFlowable
|
||||
|
||||
lines = md_text.split("\n")
|
||||
story = []
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
|
||||
if not stripped:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Fenced code block
|
||||
if stripped.startswith("```"):
|
||||
code_lines = []
|
||||
i += 1
|
||||
while i < len(lines):
|
||||
if lines[i].strip().startswith("```"):
|
||||
i += 1
|
||||
break
|
||||
code_lines.append(lines[i].rstrip())
|
||||
i += 1
|
||||
story.append(build_code_block("\n".join(code_lines), styles, clr, available_width))
|
||||
story.append(Spacer(1, 4))
|
||||
continue
|
||||
|
||||
# Image 
|
||||
img_match = re.match(r"^!\[([^\]]*)\]\(([^)]+)\)$", stripped)
|
||||
if img_match:
|
||||
alt, src = img_match.group(1), img_match.group(2)
|
||||
img = _try_build_image(alt, src, available_width)
|
||||
if img:
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(img)
|
||||
story.append(Spacer(1, 4))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Horizontal rule
|
||||
if stripped in ("---", "***", "___"):
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(HRFlowable(
|
||||
width="100%", thickness=0.5,
|
||||
color=rlc.HexColor("#cccccc"), spaceAfter=4, spaceBefore=4,
|
||||
))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# H1
|
||||
if stripped.startswith("# ") and not stripped.startswith("## "):
|
||||
story.append(Spacer(1, 20))
|
||||
story.append(Paragraph(safe_xml(stripped[2:].strip()), styles["H1"]))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# H2
|
||||
if stripped.startswith("## ") and not stripped.startswith("### "):
|
||||
story.append(Paragraph(safe_xml(stripped[3:].strip()), styles["H2"]))
|
||||
story.append(HRFlowable(
|
||||
width="100%", thickness=0.8,
|
||||
color=clr["primary"], spaceAfter=6, spaceBefore=1,
|
||||
))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# H3
|
||||
if stripped.startswith("### ") and not stripped.startswith("#### "):
|
||||
story.append(Paragraph(safe_xml(stripped[4:].strip()), styles["H3"]))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# H4
|
||||
if stripped.startswith("#### "):
|
||||
story.append(Paragraph(safe_xml(stripped[5:].strip()), styles["H4"]))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Blockquote
|
||||
if stripped.startswith("> ") or stripped == ">":
|
||||
quote_lines = []
|
||||
while i < len(lines):
|
||||
s = lines[i].strip()
|
||||
if s.startswith("> "):
|
||||
quote_lines.append(s[2:])
|
||||
elif s == ">":
|
||||
quote_lines.append("")
|
||||
else:
|
||||
break
|
||||
i += 1
|
||||
story.append(build_blockquote(" ".join(quote_lines), styles, font_info, clr, available_width))
|
||||
story.append(Spacer(1, 4))
|
||||
continue
|
||||
|
||||
# Table
|
||||
if stripped.startswith("|"):
|
||||
table_lines = []
|
||||
while i < len(lines):
|
||||
s = lines[i].strip()
|
||||
if s.startswith("|"):
|
||||
table_lines.append(s)
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
rows = parse_md_table(table_lines)
|
||||
if rows:
|
||||
t = build_table(rows, styles, font_info, clr, available_width)
|
||||
if t:
|
||||
story.append(t)
|
||||
story.append(Spacer(1, 6))
|
||||
continue
|
||||
|
||||
# Checkbox items
|
||||
if stripped.startswith("- [ ] ") or stripped.startswith("- [x] ") or stripped.startswith("- [X] "):
|
||||
text = stripped[6:].strip()
|
||||
marker = "\u2610 " if stripped.startswith("- [ ]") else "\u2611 "
|
||||
story.append(Paragraph(marker + safe_xml(text), styles["BulletItem"]))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Numbered list
|
||||
m_num = re.match(r"^(\d+)\.\s+(.+)$", stripped)
|
||||
if m_num:
|
||||
story.append(Paragraph(
|
||||
f"<b>{m_num.group(1)}.</b> " + safe_xml(m_num.group(2)),
|
||||
styles["NumberedItem"],
|
||||
))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Bullet list
|
||||
if stripped.startswith("- ") or stripped.startswith("* "):
|
||||
story.append(Paragraph(
|
||||
"\u2022 " + safe_xml(stripped[2:].strip()),
|
||||
styles["BulletItem"],
|
||||
))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Bold-colon lines (**Label:** value) -- render with bold styling
|
||||
if stripped.startswith("**") and ":" in stripped:
|
||||
text = safe_xml(stripped)
|
||||
story.append(Paragraph(text, styles["Normal"]))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Plain text
|
||||
story.append(Paragraph(safe_xml(stripped), styles["Normal"]))
|
||||
i += 1
|
||||
|
||||
return story
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- document builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_document(output_path: str, config: dict):
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
|
||||
|
||||
page_width, page_height = A4
|
||||
margins = config.get("page", {}).get("margins", {})
|
||||
left = margins.get("left", 25) * mm
|
||||
right = margins.get("right", 20) * mm
|
||||
top = margins.get("top", 25) * mm
|
||||
bottom = margins.get("bottom", 25) * mm
|
||||
|
||||
doc = BaseDocTemplate(
|
||||
output_path, pagesize=A4,
|
||||
leftMargin=left, rightMargin=right,
|
||||
topMargin=top, bottomMargin=bottom,
|
||||
)
|
||||
|
||||
frame = Frame(
|
||||
left, bottom,
|
||||
page_width - left - right,
|
||||
page_height - top - bottom,
|
||||
id="main_frame",
|
||||
)
|
||||
|
||||
page_template = PageTemplate(
|
||||
id="main", frames=[frame],
|
||||
onPage=lambda canvas, doc: None,
|
||||
)
|
||||
doc.addPageTemplates([page_template])
|
||||
|
||||
available_width = page_width - left - right
|
||||
return doc, available_width
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reportlab engine -- main conversion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def convert_reportlab(input_path: str, output_path: str, config: dict, quiet=False):
|
||||
"""Convert MD -> PDF via reportlab."""
|
||||
try:
|
||||
from reportlab.platypus import Paragraph # noqa: verify import
|
||||
except ImportError:
|
||||
print_failure("reportlab is not installed.\nInstall with: python3 -m pip install reportlab==4.4.10")
|
||||
sys.exit(1)
|
||||
|
||||
font_info = detect_fonts()
|
||||
register_detected_fonts(font_info)
|
||||
|
||||
clr = _rl_colors(config)
|
||||
styles = build_styles(font_info, config)
|
||||
doc, available_width = build_document(output_path, config)
|
||||
|
||||
md_text = Path(input_path).read_text(encoding="utf-8")
|
||||
story = md_to_story(md_text, styles, font_info, clr, available_width)
|
||||
|
||||
footer_cfg = config.get("footer", {})
|
||||
footer_fmt = footer_cfg.get("format", "Page {page} of {total}")
|
||||
canvas_cls = _make_numbered_canvas_class(font_info["body"], footer_fmt)
|
||||
|
||||
if footer_cfg.get("enabled", True):
|
||||
doc.build(story, canvasmaker=canvas_cls)
|
||||
else:
|
||||
doc.build(story)
|
||||
|
||||
page_count = canvas_cls.__dict__.get("_page_count", 0)
|
||||
# Fallback: read page count from built doc
|
||||
if page_count == 0:
|
||||
try:
|
||||
page_count = doc.page
|
||||
except Exception:
|
||||
page_count = 0
|
||||
|
||||
print_status(output_path, page_count, "reportlab", quiet)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
if not os.path.isfile(args.input):
|
||||
print_failure(f"File not found: {args.input}")
|
||||
sys.exit(1)
|
||||
|
||||
config = load_config(args.config)
|
||||
|
||||
try:
|
||||
if args.engine == "weasyprint":
|
||||
convert_weasyprint(args.input, args.output, config,
|
||||
css_path=args.style,
|
||||
pygments_theme=args.pygments_theme,
|
||||
quiet=args.quiet)
|
||||
else:
|
||||
convert_reportlab(args.input, args.output, config, quiet=args.quiet)
|
||||
except Exception as exc:
|
||||
print_failure(str(exc))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,127 @@
|
||||
/* === Page Setup === */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 20mm 25mm 25mm;
|
||||
@bottom-center {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-size: 7pt;
|
||||
color: #888888;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Base Typography === */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", "Noto Sans CJK SC", sans-serif;
|
||||
font-size: 9pt;
|
||||
line-height: 1.4;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
/* === Headings === */
|
||||
h1, h2, h3, h4, h5, h6 { margin-top: 12pt; margin-bottom: 4pt; page-break-after: avoid; }
|
||||
h1 { font-size: 20pt; color: #1a3a5c; text-align: center; margin-top: 18pt; margin-bottom: 10pt; }
|
||||
h2 { font-size: 15pt; color: #1a3a5c; border-bottom: 1px solid #c0c0c0; padding-bottom: 3pt; }
|
||||
h3 { font-size: 12pt; color: #2c5282; }
|
||||
h4 { font-size: 10pt; color: #2c5282; }
|
||||
h5 { font-size: 9pt; color: #4a6fa5; }
|
||||
h6 { font-size: 9pt; color: #6b8cba; font-style: italic; }
|
||||
|
||||
/* === Paragraphs & Links === */
|
||||
p { margin: 4pt 0; }
|
||||
a { color: #2b6cb0; text-decoration: underline; }
|
||||
@media print { a { text-decoration: none; } }
|
||||
|
||||
/* === Tables === */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 8pt 0;
|
||||
font-size: 8.5pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
th, td { border: 0.5px solid #c0c0c0; padding: 3px 4px; text-align: left; }
|
||||
th { background-color: #1a3a5c; color: #ffffff; font-weight: bold; }
|
||||
tr:nth-child(even) { background-color: #f0f4f8; }
|
||||
tr:nth-child(odd) { background-color: #ffffff; }
|
||||
|
||||
/* === Code Blocks === */
|
||||
pre { page-break-inside: avoid; margin: 6pt 0; }
|
||||
pre code {
|
||||
display: block;
|
||||
background: #f0f0f0;
|
||||
padding: 6px 8px;
|
||||
border: 0.5px solid #d0d0d0;
|
||||
border-radius: 2px;
|
||||
font-family: "SFMono-Regular", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
|
||||
font-size: 8pt;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
color: inherit;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* === Inline Code === */
|
||||
code {
|
||||
background: #f0f0f0;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
font-family: "SFMono-Regular", Menlo, Consolas, monospace;
|
||||
font-size: 8.5pt;
|
||||
color: #c53030;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* === Blockquotes === */
|
||||
blockquote {
|
||||
border-left: 3px solid #3182ce;
|
||||
background: #f5f5f5;
|
||||
padding: 6px 10px;
|
||||
margin: 6pt 0;
|
||||
font-style: italic;
|
||||
color: #4a4a4a;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
blockquote p { margin: 2pt 0; }
|
||||
|
||||
/* === Lists === */
|
||||
ul, ol { margin: 4pt 0; padding-left: 18pt; }
|
||||
ul ul, ol ol, ul ol, ol ul { padding-left: 14pt; }
|
||||
ul ul ul, ol ol ol, ul ul ol, ol ol ul,
|
||||
ul ol ul, ol ul ol, ul ol ol, ol ul ul { padding-left: 12pt; }
|
||||
li { margin-bottom: 2pt; }
|
||||
|
||||
/* === sub-agent task Lists === */
|
||||
li input[type="checkbox"] { margin-right: 4px; vertical-align: middle; }
|
||||
li.task-list-item { list-style-type: none; margin-left: -18pt; }
|
||||
|
||||
/* === Horizontal Rules === */
|
||||
hr { border: none; border-top: 0.5px solid #cccccc; margin: 10pt 0; }
|
||||
|
||||
/* === Images === */
|
||||
img { max-width: 100%; height: auto; }
|
||||
|
||||
/* === Pygments Syntax Highlighting === */
|
||||
.highlight .k, .highlight .kn, .highlight .kd,
|
||||
.highlight .kc, .highlight .kr, .highlight .kt { color: #d73a49; font-weight: bold; }
|
||||
|
||||
.highlight .s, .highlight .s1, .highlight .s2, .highlight .sb,
|
||||
.highlight .sc, .highlight .sd, .highlight .se, .highlight .sh,
|
||||
.highlight .sx, .highlight .si, .highlight .sr { color: #032f62; }
|
||||
|
||||
.highlight .n, .highlight .na, .highlight .nc, .highlight .nd,
|
||||
.highlight .ne, .highlight .ni, .highlight .nl, .highlight .nn,
|
||||
.highlight .nt, .highlight .nv, .highlight .p { color: #24292e; }
|
||||
|
||||
.highlight .c, .highlight .c1, .highlight .cm, .highlight .ch,
|
||||
.highlight .cs, .highlight .cp, .highlight .cpf { color: #6a737d; font-style: italic; }
|
||||
|
||||
.highlight .o, .highlight .ow { color: #d73a49; }
|
||||
.highlight .nb, .highlight .bp { color: #005cc5; }
|
||||
.highlight .nf, .highlight .fm { color: #6f42c1; }
|
||||
|
||||
.highlight .mi, .highlight .mf, .highlight .mh,
|
||||
.highlight .mo, .highlight .mb, .highlight .il { color: #005cc5; }
|
||||
|
||||
.highlight .w { color: transparent; }
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"page": {
|
||||
"size": "A4",
|
||||
"margins": { "top": 25, "right": 20, "bottom": 25, "left": 25 }
|
||||
},
|
||||
"fonts": {
|
||||
"body": "auto",
|
||||
"heading": "auto",
|
||||
"code": "monospace"
|
||||
},
|
||||
"colors": {
|
||||
"primary": "#1a3a5c",
|
||||
"secondary": "#2c5282",
|
||||
"text": "#1a1a1a",
|
||||
"code_bg": "#f0f0f0",
|
||||
"header_bg": "#1a3a5c",
|
||||
"header_fg": "#ffffff",
|
||||
"quote_bg": "#f5f5f5",
|
||||
"border": "#3182ce",
|
||||
"light_bg": "#f0f4f8"
|
||||
},
|
||||
"footer": {
|
||||
"enabled": true,
|
||||
"format": "Page {page} of {total}"
|
||||
},
|
||||
"code": {
|
||||
"theme": "github",
|
||||
"font_size": 8
|
||||
},
|
||||
"typography": {
|
||||
"body_size": 9,
|
||||
"h1_size": 18,
|
||||
"h2_size": 14,
|
||||
"h3_size": 12,
|
||||
"h4_size": 10,
|
||||
"line_height": 1.4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
# Markdown-to-PDF Converter Test File
|
||||
|
||||
This document exercises every Markdown element to verify correct PDF rendering.
|
||||
|
||||
---
|
||||
|
||||
## 1. Headers
|
||||
|
||||
# Header Level 1 — Primary Title
|
||||
|
||||
## Header Level 2 — Section Title
|
||||
|
||||
### Header Level 3 — Subsection
|
||||
|
||||
#### Header Level 4 — Sub-subsection
|
||||
|
||||
##### Header Level 5 — Minor Heading
|
||||
|
||||
###### Header Level 6 — Smallest Heading
|
||||
|
||||
---
|
||||
|
||||
## 2. Text Formatting
|
||||
|
||||
This paragraph contains **bold text**, *italic text*, ***bold italic text***, ~~strikethrough text~~, and `inline code`.
|
||||
|
||||
Normal text resumes here to verify that formatting terminates correctly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Links and Images
|
||||
|
||||
- External link: [OpenAI](https://openai.com)
|
||||
- Internal anchor: [Jump to Headers](#1-headers)
|
||||
- Auto-linked URL: https://example.com
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 4. Lists
|
||||
|
||||
### Unordered List
|
||||
|
||||
- First unordered item
|
||||
- Second unordered item
|
||||
- Third unordered item
|
||||
|
||||
### Ordered List
|
||||
|
||||
1. First ordered item
|
||||
2. Second ordered item
|
||||
3. Third ordered item
|
||||
|
||||
### Nested List (3 Levels)
|
||||
|
||||
- Level 1 — Item A
|
||||
- Level 2 — Item A.1
|
||||
- Level 3 — Item A.1.a
|
||||
- Level 3 — Item A.1.b
|
||||
- Level 2 — Item A.2
|
||||
- Level 1 — Item B
|
||||
1. Level 2 — Ordered B.1
|
||||
2. Level 2 — Ordered B.2
|
||||
- Level 3 — Mixed B.2.a
|
||||
|
||||
### sub-agent task Lists
|
||||
|
||||
- [x] Completed task
|
||||
- [x] Another completed task
|
||||
- [ ] Pending task
|
||||
- [ ] Another pending task
|
||||
|
||||
---
|
||||
|
||||
## 5. Tables
|
||||
|
||||
### Simple Table
|
||||
|
||||
| Name | Role | Status |
|
||||
|------------|----------------|----------|
|
||||
| Alice | Developer | Active |
|
||||
| Bob | Designer | On Leave |
|
||||
| Charlie | Project Lead | Active |
|
||||
| Diana | QA Engineer | Active |
|
||||
|
||||
### Wide Table (6+ Columns)
|
||||
|
||||
| ID | Name | Department | Location | Start Date | Salary | Rating |
|
||||
|-----|---------|------------|-------------|------------|----------|--------|
|
||||
| 001 | Alice | Engineering| New York | 2022-01-15 | $120,000 | A |
|
||||
| 002 | Bob | Design | San Francisco| 2021-06-01 | $110,000 | B+ |
|
||||
| 003 | Charlie | Management | London | 2020-03-20 | $140,000 | A+ |
|
||||
| 004 | Diana | QA | Berlin | 2023-09-10 | $95,000 | A |
|
||||
|
||||
### Table with Formatting
|
||||
|
||||
| Feature | Syntax | Supported |
|
||||
|----------------|---------------------|-----------|
|
||||
| **Bold** | `**text**` | Yes |
|
||||
| *Italic* | `*text*` | Yes |
|
||||
| `Inline Code` | `` `code` `` | Yes |
|
||||
| ~~Strikethrough~~ | `~~text~~` | Yes |
|
||||
|
||||
---
|
||||
|
||||
## 6. Code Blocks
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
def read_config(path: str) -> Optional[dict]:
|
||||
config_file = Path(path)
|
||||
if not config_file.exists():
|
||||
return None
|
||||
with config_file.open("r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
async function fetchUsers(apiUrl) {
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### Bash
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INPUT_DIR="${1:-.}"
|
||||
OUTPUT_DIR="${2:-./output}"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
for file in "$INPUT_DIR"/*.md; do
|
||||
echo "Converting: $(basename "$file")"
|
||||
pandoc "$file" -o "$OUTPUT_DIR/$(basename "${file%.md}.pdf")"
|
||||
done
|
||||
```
|
||||
|
||||
### SQL
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
COUNT(o.id) AS order_count,
|
||||
COALESCE(SUM(o.total), 0) AS total_spent
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON o.user_id = u.id
|
||||
WHERE u.created_at >= '2025-01-01'
|
||||
GROUP BY u.id, u.username
|
||||
HAVING COUNT(o.id) > 0
|
||||
ORDER BY total_spent DESC
|
||||
```
|
||||
|
||||
### JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "markdown-converter",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"puppeteer": "^22.0.0",
|
||||
"marked": "^12.0.0",
|
||||
"highlight.js": "^11.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"convert": "node src/convert.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func listMarkdownFiles(dir string) ([]string, error) {
|
||||
matches, err := filepath.Glob(filepath.Join(dir, "*.md"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glob failed: %w", err)
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
```
|
||||
|
||||
### YAML
|
||||
|
||||
```yaml
|
||||
converter:
|
||||
input_format: markdown
|
||||
output_format: pdf
|
||||
options:
|
||||
page_size: A4
|
||||
margin:
|
||||
top: 20mm
|
||||
bottom: 20mm
|
||||
left: 15mm
|
||||
right: 15mm
|
||||
font_family: "Noto Sans"
|
||||
syntax_highlighting: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Blockquotes
|
||||
|
||||
### Simple Blockquote
|
||||
|
||||
> This is a simple blockquote. It should render with a left border and subtle background.
|
||||
|
||||
### Nested Blockquote
|
||||
|
||||
> This is the outer blockquote.
|
||||
>
|
||||
> > This is a nested inner blockquote. It should be visually indented further.
|
||||
|
||||
### Blockquote with Formatting
|
||||
|
||||
> **Important:** This blockquote contains **bold text**, `inline code`, and a link to [example.com](https://example.com).
|
||||
>
|
||||
> It also spans multiple lines to test paragraph handling within quotes.
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
___
|
||||
|
||||
## 8. Horizontal Rules
|
||||
|
||||
The three horizontal rules above use `---`, `***`, and `___` respectively. They should all render identically.
|
||||
|
||||
---
|
||||
|
||||
## 9. Footnotes
|
||||
|
||||
Markdown-to-PDF conversion requires careful handling of layout[^1] and typography[^2].
|
||||
|
||||
[^1]: Layout includes margins, page breaks, headers, and footers.
|
||||
[^2]: Typography covers font selection, line height, letter spacing, and ligatures.
|
||||
|
||||
---
|
||||
|
||||
## 10. Definition Lists
|
||||
|
||||
Term 1 — Markdown
|
||||
: A lightweight markup language for creating formatted text using a plain-text editor.
|
||||
|
||||
Term 2 — PDF
|
||||
: Portable Document Format, a file format developed by Adobe for presenting documents independent of software or hardware.
|
||||
|
||||
---
|
||||
|
||||
## 11. Multilingual Text
|
||||
|
||||
### English
|
||||
|
||||
The Markdown-to-PDF converter must handle a variety of text encodings and scripts. Proper font fallback is essential for rendering characters outside the Latin alphabet. This section verifies that each language displays correctly in the generated PDF. Missing glyphs indicate a font configuration issue.
|
||||
|
||||
### Russian
|
||||
|
||||
Кириллический текст для проверки отображения шрифтов. Поддержка различных языков является важной функцией конвертера. Каждый символ должен корректно отображаться в итоговом PDF-документе. Проверка переносов строк и интервалов также необходима.
|
||||
|
||||
### Chinese
|
||||
|
||||
中文测试文本。这是一个用于测试PDF转换器多语言支持的段落。正确的字体回退机制对于渲染非拉丁字母字符至关重要。每个字符都应在生成的PDF中正确显示。
|
||||
|
||||
### Japanese
|
||||
|
||||
日本語テスト。PDFコンバーターのテストです。フォントフォールバックが正しく機能することを確認します。各文字が正しくレンダリングされる必要があります。
|
||||
|
||||
---
|
||||
|
||||
## 12. Long Paragraph for Line Wrapping
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. This sentence follows the Latin filler to verify that mixed content and long continuous paragraphs wrap correctly at page boundaries without clipping or overlapping adjacent elements.
|
||||
|
||||
---
|
||||
|
||||
## 13. Special Characters
|
||||
|
||||
### Arrows
|
||||
|
||||
Left arrow: <- Right arrow: -> Bidirectional: <-> Double right: =>
|
||||
|
||||
### Math Symbols
|
||||
|
||||
Less than or equal: <= Greater than or equal: >= Not equal: != Plus-minus: +/- Multiplication: x
|
||||
|
||||
### Unicode Symbols and Arrows
|
||||
|
||||
Arrows: → ← ↔ ↑ ↓ ⇒ ⇐ ⇔
|
||||
|
||||
Math: ≤ ≥ ≠ ± × ÷ ∞ ∑ ∏ √ ∂ ∫
|
||||
|
||||
Check/Cross: ✓ ✗ ✔ ✘
|
||||
|
||||
Stars: ★ ☆ ✦ ✧
|
||||
|
||||
Miscellaneous: ◆ ◇ ● ○ ■ □ ▲ △
|
||||
|
||||
### Emoji
|
||||
|
||||
Documents: 📄 📝 📋 📂
|
||||
|
||||
Tools: 🔧 🔨 ⚙️ 🛠️
|
||||
|
||||
Status: ✅ ❌ ⚠️ ℹ️
|
||||
|
||||
Other: 🚀 💡 🎯 🔍
|
||||
|
||||
---
|
||||
|
||||
*End of test file.*
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "brewtools",
|
||||
"version": "4.0.6+codex.20260713124026",
|
||||
"description": "Codex-native text utilities plus user-invoked Manager, task-board, and terse-hook workflows.",
|
||||
"author": { "name": "Maksim Kochetkov", "email": "apmatypa88@gmail.com", "url": "https://github.com/kochetkov-ma" },
|
||||
"homepage": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"repository": "https://github.com/kochetkov-ma/claude-brewcode.git",
|
||||
"license": "MIT",
|
||||
"keywords": ["codex", "utilities", "manager", "task-board", "text"],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Brewtools",
|
||||
"shortDescription": "Codex utility workflows",
|
||||
"longDescription": "Reusable Codex text optimization and humanization, plus explicitly invoked Manager, task-board, and terse-hook configuration.",
|
||||
"developerName": "Maksim Kochetkov",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": ["Read", "Write", "Interactive"],
|
||||
"websiteURL": "https://github.com/kochetkov-ma/claude-brewcode",
|
||||
"defaultPrompt": ["Use Brewtools to inspect and improve this workflow."]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${PLUGIN_ROOT}/hooks/session-start.mjs\"",
|
||||
"timeout": 2,
|
||||
"statusMessage": "Loading Brewtools manager state"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${PLUGIN_ROOT}/hooks/manager-prompt.mjs\"",
|
||||
"timeout": 3,
|
||||
"statusMessage": "Checking Brewtools codewords"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export async function readInput() {
|
||||
const chunks = [];
|
||||
for await (const chunk of process.stdin) chunks.push(chunk);
|
||||
const raw = Buffer.concat(chunks).toString('utf8');
|
||||
const input = JSON.parse(raw);
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('hook input must be an object');
|
||||
if (process.env.NODE_ENV === 'test' && process.env.CODEX_HOOK_TEST_DELAY_MS) {
|
||||
const delay = Number(process.env.CODEX_HOOK_TEST_DELAY_MS);
|
||||
if (Number.isFinite(delay) && delay > 0) await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
export function respond(value = {}) {
|
||||
process.stdout.write(`${JSON.stringify(value)}\n`);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const MODES = new Set(['full', 'planmode', 'architect', 'review-regression', 'review-double']);
|
||||
|
||||
function readText(file) {
|
||||
try { return fs.readFileSync(file, 'utf8').trim(); } catch { return ''; }
|
||||
}
|
||||
|
||||
function readJson(file) {
|
||||
try {
|
||||
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
||||
} catch { return {}; }
|
||||
}
|
||||
|
||||
export function state(cwd) {
|
||||
const value = readJson(path.join(cwd, '.codex', 'brewtools', 'manager', 'state.json'));
|
||||
return {
|
||||
hard: value.hard === true,
|
||||
level: value.level === 'strict' ? 'strict' : 'balanced'
|
||||
};
|
||||
}
|
||||
|
||||
export function prompt(mode, cwd, pluginRoot) {
|
||||
if (!MODES.has(mode)) return '';
|
||||
const candidates = [
|
||||
path.join(cwd, '.codex', 'brewtools', 'manager', 'prompts', `${mode}.md`),
|
||||
path.join(os.homedir(), '.codex', 'manager', 'prompts', `${mode}.md`),
|
||||
path.join(pluginRoot || '', 'skills', 'manager', 'references', `${mode}.md`),
|
||||
path.join(pluginRoot || '', '.codex', 'skills', 'manager', 'references', `${mode}.md`)
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const value = readText(candidate);
|
||||
if (value) return value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readInput, respond } from './lib/io.mjs';
|
||||
import { prompt, state } from './lib/manager.mjs';
|
||||
|
||||
try {
|
||||
const input = await readInput();
|
||||
if (input.hook_event_name !== 'UserPromptSubmit') {
|
||||
respond({});
|
||||
} else {
|
||||
const text = typeof input.prompt === 'string' ? input.prompt : '';
|
||||
const modes = [];
|
||||
if (/(?<![\w+])\+\+m(?![\w])/i.test(text)) modes.push(input.permission_mode === 'plan' ? 'planmode' : 'full');
|
||||
if (/(?<![\w+])\+\+a(?![\w])/i.test(text)) modes.push('architect');
|
||||
if (/(?<![\w+])\+\+rr(?![\w])/i.test(text)) modes.push('review-regression');
|
||||
else if (/(?<![\w+])\+\+r(?![\w])/i.test(text)) modes.push('review-double');
|
||||
if (modes.length === 0 && state(input.cwd || process.cwd()).hard) modes.push('full');
|
||||
|
||||
const pluginRoot = process.env.PLUGIN_ROOT || '';
|
||||
const blocks = modes.map(mode => prompt(mode, input.cwd || process.cwd(), pluginRoot)).filter(Boolean);
|
||||
if (blocks.length === 0) {
|
||||
respond({});
|
||||
} else {
|
||||
respond({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'UserPromptSubmit',
|
||||
additionalContext: blocks.join('\n\n---\n\n').slice(0, 9000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
respond({});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readInput, respond } from './lib/io.mjs';
|
||||
import { state } from './lib/manager.mjs';
|
||||
|
||||
try {
|
||||
const input = await readInput();
|
||||
if (input.hook_event_name !== 'SessionStart') {
|
||||
respond({});
|
||||
} else {
|
||||
const current = state(input.cwd || process.cwd());
|
||||
if (!current.hard) {
|
||||
respond({});
|
||||
} else {
|
||||
respond({
|
||||
systemMessage: `Brewtools manager prompt mode is active (${current.level})`,
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: 'Manager prompt mode is active. Codex PreToolUse cannot distinguish parent and sub-agent calls, so this package does not claim a hard enforcement boundary.'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
respond({});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Manager for Codex
|
||||
|
||||
Ambient manager, architecture, and review prompt guidance. This variant provides no hard security wall and no parent-only enforcement.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: manager
|
||||
description: "Configures Manager and review prompt modes. Explicit user invocation only."
|
||||
---
|
||||
|
||||
# Ambient manager prompt mode
|
||||
|
||||
This skill configures ambient prompt guidance only. It does not create, claim, or enforce a hard security wall.
|
||||
|
||||
## Intent and scope
|
||||
|
||||
Resolve `status`, `on`, `off`, `level`, `edit`, or `reset`, then choose project state at `.codex/brewtools/manager/state.json` or personal prompt overrides under `~/.codex/manager/`. Obtain confirmation before global writes.
|
||||
|
||||
## Behavior
|
||||
|
||||
- `++m`: manager guidance, using the plan-aware reference in plan mode.
|
||||
- `++a`: architecture-first guidance.
|
||||
- `++rr`: anti-regression review guidance.
|
||||
- `++r`: two-pass review guidance.
|
||||
- `on` / `off`: enable or disable ambient prompt state only.
|
||||
- `level`: set balanced or strict prompt wording; it does not change sandbox or authorization.
|
||||
- `edit` / `reset`: update or remove prompt overrides after showing the diff.
|
||||
- `status`: show hook registration, state source, level, override paths, and the no-security-wall limitation.
|
||||
|
||||
The plugin uses `SessionStart` and `UserPromptSubmit` hooks. Preserve unrelated hook entries and review changed definitions with `/hooks`.
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Manager"
|
||||
short_description: "Manager prompt modes; user invoked"
|
||||
default_prompt: "Use $brewtools:manager only when the user explicitly requests Manager configuration."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -0,0 +1 @@
|
||||
Start from system boundaries, data flow, ownership, failure modes, and compatibility constraints before choosing an implementation.
|
||||
@@ -0,0 +1,11 @@
|
||||
# [ROLE: MANAGER]
|
||||
|
||||
The user's ++M codeword authorizes foreground delegation for this task. Orchestrate the work to a verified outcome while preserving repository instructions, user scope, unrelated changes, and external safety gates.
|
||||
|
||||
1. Inspect the applicable AGENTS.md files, current task state, and the minimum repository evidence needed to understand the request.
|
||||
2. Use update_plan for the session execution plan. If the project requires a durable board, synchronize it through its task-tracker workflow before implementation and again at completion.
|
||||
3. Map dependencies and split only independent, bounded workstreams. Parallelize useful read-only or non-overlapping work; keep dependent work sequential.
|
||||
4. When delegation is useful, select the matching project expert from .codex/agents before built-in or global agents. If the collaboration surface cannot select a custom type, name the expert explicitly and include its developer instructions in the brief without claiming the type was instantiated.
|
||||
5. Use spawn_agent, send_message, followup_task, and wait_agent for foreground collaboration. Give each agent concrete scope, expected evidence, allowed mutation surface, and validation duties.
|
||||
6. Review every delegated result before using it. Reconcile conflicts against authoritative project files and run validation proportional to risk.
|
||||
7. Lead the final handoff with the outcome, changed surfaces, exact validation, and any genuine remaining risk.
|
||||
@@ -0,0 +1,19 @@
|
||||
# [ROLE: MANAGER]
|
||||
|
||||
The user's ++M codeword authorizes foreground delegation for this task. Orchestrate the work to a verified outcome while preserving repository instructions, user scope, unrelated changes, and external safety gates.
|
||||
|
||||
1. Inspect the applicable AGENTS.md files, current task state, and the minimum repository evidence needed to understand the request.
|
||||
2. Use update_plan for the session execution plan. If the project requires a durable board, synchronize it through its task-tracker workflow before implementation and again at completion.
|
||||
3. Map dependencies and split only independent, bounded workstreams. Parallelize useful read-only or non-overlapping work; keep dependent work sequential.
|
||||
4. When delegation is useful, select the matching project expert from .codex/agents before built-in or global agents. If the collaboration surface cannot select a custom type, name the expert explicitly and include its developer instructions in the brief without claiming the type was instantiated.
|
||||
5. Use spawn_agent, send_message, followup_task, and wait_agent for foreground collaboration. Give each agent concrete scope, expected evidence, allowed mutation surface, and validation duties.
|
||||
6. Review every delegated result before using it. Reconcile conflicts against authoritative project files and run validation proportional to risk.
|
||||
7. Lead the final handoff with the outcome, changed surfaces, exact validation, and any genuine remaining risk.
|
||||
|
||||
# [ADDON: PLAN MODE]
|
||||
|
||||
Stay read-only. Explore enough to remove implementation ambiguity, but do not edit files, install packages, change configuration, or trigger external side effects.
|
||||
|
||||
Produce a complete English implementation plan covering scope, non-goals, current behavior, target behavior, affected files, ordered steps, agent ownership, tests, validation, rollout or migration, rollback where relevant, and explicit unresolved decisions. Ask only questions whose answers materially change the plan.
|
||||
|
||||
The future implementation prompt must begin with Step 0: re-assume [ROLE: MANAGER], re-read applicable AGENTS.md files, synchronize the required task board, instantiate the plan with update_plan, and route bounded work to project experts before implementation.
|
||||
@@ -0,0 +1 @@
|
||||
Perform two passes: first correctness and safety, then maintainability, clarity, and missing validation. Keep findings evidence-based.
|
||||
@@ -0,0 +1 @@
|
||||
Review for behavioral regressions first. Compare old and new contracts, exercise negative paths, and require evidence for compatibility claims.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Codex task board initializer
|
||||
|
||||
Creates one canonical task board under `.codex/features/`, plus a native TOML task-tracker agent and Codex skill.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: task-board-init
|
||||
description: "Creates a Codex file-based task board. Explicit user invocation only."
|
||||
---
|
||||
|
||||
# Codex task-board initializer
|
||||
|
||||
Create exactly one Codex-owned file board; never create or mirror it under another assistant namespace.
|
||||
|
||||
## P0: resolve target and directive
|
||||
|
||||
1. Resolve the target repository, language, release marker style, exclusions, and whether optional AGENTS.md optimization is requested.
|
||||
|
||||
## P1: analyze the repository
|
||||
|
||||
2. Analyze repository domains, documentation, release conventions, and current task artifacts using bounded Codex collaboration when explicitly authorized.
|
||||
|
||||
## P2-P4: generate native board components
|
||||
|
||||
3. Generate a native task-tracker TOML at `.codex/agents/task-tracker.toml` from the Codex template in `references/02-task-tracker-agent.md`.
|
||||
4. Generate the task-board skill at `.codex/skills/task-board/SKILL.md` from `references/03-task-board-skill.md`.
|
||||
5. Create the single canonical board under `.codex/features/`: `board.md`, `INDEX.md`, `TRACKER.md`, `TASK_TEMPLATE.md`, and `backlog/`, `todo/`, `progress/`, `closed/`, `specs/`.
|
||||
6. Add Codex task rules under `.codex/rules/` only if that rule layer is active in the target repository. Sweep documentation links without creating duplicate boards.
|
||||
|
||||
## P5: verify and report
|
||||
|
||||
7. Verify paths, TOML, skill frontmatter, folder/status invariants, board counts, link integrity, and idempotence.
|
||||
|
||||
## P5.5: optional AGENTS.md optimization
|
||||
|
||||
8. Optimize `AGENTS.md` only behind the separate explicit gate and preserve project-specific constraints.
|
||||
|
||||
Do not create a migration-card file automatically and do not create a duplicate board under another assistant namespace.
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Task Board Init"
|
||||
short_description: "Codex task-board setup; user invoked"
|
||||
default_prompt: "Use $brewtools:task-board-init only when the user explicitly requests board setup."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -0,0 +1,83 @@
|
||||
# 01 -- Step 1: Multi-agent repo analysis
|
||||
|
||||
[DICT: DOM=domain id segment, EXCL=source-path exclusions, REL=release style, FINDINGS=integrated result object]
|
||||
|
||||
Goal: inspect the TARGET repo and produce a FINDINGS object, then CONFIRM it with the user before any generation.
|
||||
|
||||
## Spawn (parallel -- one message, multiple sub-agent calls)
|
||||
|
||||
Spawn these in a SINGLE message so they run concurrently. Use `subagent_type` shown; fall back to `general-purpose` if an agent is unavailable.
|
||||
|
||||
### Agent A -- domains + release style (`brewcode:architect`)
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="brewcode:architect", message="
|
||||
Analyze the repo at TARGET=<abs path>. You are scoping a file-based Kanban id scheme. Return ONLY this block, no prose:
|
||||
|
||||
DOMAINS:
|
||||
- 6-12 SHORT UPPER-KEBAB segments naming the repo's functional areas (the first kebab segment after an id prefix). Derive from top-level source dirs, module names, package names, bounded contexts, major features. Example shape (brewpage): HTML, KV, JSON, FILES, SITE, SEO, ABUSE, PREVIEW, DEDUP, SWEEP. Yours must reflect THIS repo.
|
||||
|
||||
RELEASE_STYLE: one of
|
||||
vtag -- repo ships via semver tags vX.Y.Z (look: `git tag` has vN.N.N, CI on tag, AGENTS.md release flow mentions tags)
|
||||
sha -- repo closes work by bare commit SHA / merge, no version tags
|
||||
none -- no discernible release ritual
|
||||
Cite the evidence (1 line: tags found / CI trigger / AGENTS.md section).
|
||||
|
||||
Evidence: bullet the files/commands you used (git tag -l | head, package.json/build files, AGENTS.md release section).
|
||||
")
|
||||
```
|
||||
|
||||
### Agent B -- exclusions + doc inventory (`Explore`)
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="Explore", message="
|
||||
Explore the repo at TARGET=<abs path>. Return ONLY this block, no prose:
|
||||
|
||||
EXCLUSIONS:
|
||||
- The top-level SOURCE / build / test dirs a docs-only curator agent must NEVER write to. Include things like src/, app/, backend/, frontend/, lib/, e2e-tests/, tests/, docs/, and any language/build dirs. List the ACTUAL dirs present in this repo.
|
||||
|
||||
LANG:
|
||||
- The dominant human language of the repo's existing docs/READMEs (English unless clearly otherwise).
|
||||
|
||||
DOCS:
|
||||
- Every existing file that tracks backlog / features / tasks / roadmap / TODO. Search: TODO.md, ROADMAP.md, BACKLOG.md, FEATURES*, .codex/features/**, docs/**/*todo*, *task*, *backlog*, any 'planned'/'wishlist' lists. For each: path + one-line what-it-holds + rough item count. This is the migration inventory for the doc sweep.
|
||||
")
|
||||
```
|
||||
|
||||
## Integrate
|
||||
|
||||
Merge A + B into FINDINGS:
|
||||
```
|
||||
DOMAINS = <A.DOMAINS>
|
||||
EXCLUSIONS = <B.EXCLUSIONS>
|
||||
REL_STYLE = <A.RELEASE_STYLE> # vtag | sha | none
|
||||
LANG = <B.LANG>
|
||||
DOCS = <B.DOCS>
|
||||
```
|
||||
|
||||
## Confirm with the user (request_user_input -- MANDATORY before generating)
|
||||
|
||||
Present FINDINGS compactly, then ask. Confirm DOMAINS and EXCLUSIONS especially -- these parametrize every emitted artifact.
|
||||
|
||||
> **Confirm the task-board setup for `<repo name>`:**
|
||||
>
|
||||
> **Domains** (id segments): `D1, D2, D3, ...`
|
||||
> **Exclusions** (never written by task-tracker): `src/, tests/, docs/, ...`
|
||||
> **Release style:** `vtag` (vX.Y.Z) | `sha` | `none` -- *<evidence line>*
|
||||
> **Doc language:** `English`
|
||||
> **Docs to migrate:** `N files` -- *<one-line list>*
|
||||
>
|
||||
> Options:
|
||||
> 1. Looks right -- generate
|
||||
> 2. Edit domains (give the list)
|
||||
> 3. Edit exclusions (give the list)
|
||||
> 4. Change release style
|
||||
> 5. Change language
|
||||
|
||||
Apply edits, re-show if substantial, proceed only on explicit "generate". If the user gives a custom domain/exclusion list, use it verbatim (UPPER-KEBAB the domains).
|
||||
|
||||
> **Empty DOMAINS edge:** if analysis yields no domains, do NOT proceed with an empty `{{DOMAINS}}` (it would produce broken ids like `T--SLUG`); ask the user to name at least one domain via request_user_input, or fall back to a single `CORE` domain.
|
||||
|
||||
## Output contract handed to P2-P4
|
||||
|
||||
A confirmed FINDINGS object with: `DOMAINS` (UPPER-KEBAB list), `EXCLUSIONS` (dir list), `REL_STYLE` (enum), `LANG` (string), `DOCS` (inventory for sweep).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Native task-tracker agent template
|
||||
|
||||
Write `TARGET/.codex/agents/task-tracker.toml` with exactly these TOML keys:
|
||||
|
||||
`name = "task-tracker"`
|
||||
|
||||
`description` identifies board view, add, transition, close, and grooming triggers.
|
||||
|
||||
`developer_instructions` owns only `.codex/features/**`. It enforces folder equals status, updates `board.md` in the same change as every transition, keeps stable upper-kebab ids, requires a file for progress tasks, records the configured close marker, and never touches application code. It reads `.codex/features/TRACKER.md` and the active task rule before mutation.
|
||||
|
||||
Substitute the analyzed domains, exclusions, release-marker policy, and artifact language. Validate the result with Python `tomllib`.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Native task-board skill template
|
||||
|
||||
Write `TARGET/.codex/skills/task-board/SKILL.md` with frontmatter keys `name` and `description` only. The workflow supports view, add, move, backlog, groom, and close against the single canonical `.codex/features/board.md`.
|
||||
|
||||
Every transition moves or creates the task file, updates frontmatter, and synchronizes board tables, counts, and current focus in the same patch. Bulk work may use the native task-tracker agent through Codex collaboration with `task_name` and `message` only. Validate with the Codex skill quick validator.
|
||||
@@ -0,0 +1,37 @@
|
||||
# 04 -- Step 4a: `tasks.md` rule template
|
||||
|
||||
Substitute `{{DOMAINS}}`, `{{LANG}}`, `{{CLOSE_MARKER_SHORT}}` (same map as ref 03), `{{FIRST_DOMAIN}}`. Write the body below to `TARGET/.codex/rules/tasks.md`.
|
||||
|
||||
> This rule lives ONLY in `.codex/rules/tasks.md`. Do NOT add it to AGENTS.md. The `paths:` frontmatter scopes it so it auto-loads when the agent touches `.codex/features/**`.
|
||||
|
||||
---
|
||||
|
||||
```markdown
|
||||
---
|
||||
paths:
|
||||
- ".codex/features/**"
|
||||
---
|
||||
|
||||
[DICT: GROOM=backlog triage, FM=frontmatter, TT=task-tracker agent]
|
||||
|
||||
# sub-agent task tracker rules
|
||||
|
||||
Canonical task LIST: `.codex/features/board.md`. sub-agent task files: `.codex/features/{backlog,todo,progress,closed}/`. New file = copy `.codex/features/TASK_TEMPLATE.md`.
|
||||
|
||||
| # | Rule |
|
||||
|---|------|
|
||||
| 1 | `board.md` = canonical LIST + status. Update in SAME change as ANY transition -- a lagging board = wrong board |
|
||||
| 2 | Folder == `status:` FM. File lives in `backlog/`\|`todo/`\|`progress/`\|`closed/`; FM `status:` MUST match folder. On move -> change BOTH |
|
||||
| 3 | Lifecycle: `backlog -> todo -> progress -> closed` (trash/merge only from `backlog`). sub-agent task in `progress/` MUST have a file from `TASK_TEMPLATE.md` |
|
||||
| 4 | IDs = UPPER-KEBAB, never change. Prefix: `T-` (feature) \| `BUG-` (defect) \| `M-` (maintenance/refactor) \| `EPIC-` (umbrella) |
|
||||
| 5 | First kebab segment = a repo domain { {{DOMAINS}} }. e.g. `T-{{FIRST_DOMAIN}}-SLUG`, `BUG-{{FIRST_DOMAIN}}-SLUG`, `M-{{FIRST_DOMAIN}}-SLUG` |
|
||||
| 6 | Required FM fields: `id, title, status, priority, owner, created, updated` |
|
||||
| 7 | `backlog/` = ungated inbox. GROOM loop: promote -> `todo`, merge dupes, or trash. !=leave groomed items behind |
|
||||
| 8 | **At the START of ANY task, run the `task-tracker` agent in ISOLATION (a spawned subagent via sub-agent task, NOT inlined) to claim/sync the board** -- it bookends every task: claim `todo->progress` at start; reconcile `board.md` + INDEX at end |
|
||||
| 9 | This repo has NO root `TODO.md` -- NEVER invent one. The board lives ONLY under `.codex/features/` |
|
||||
| 10 | {{LANG}} only. Closing: record {{CLOSE_MARKER_SHORT}} in `## Notes` |
|
||||
| 11 | After closing tasks, COMMIT the `.codex/features/**` change -- closure !=done until committed |
|
||||
| 12 | Non-trivial board work (GROOM pass, bulk transitions, hand-edits) -> delegate to `task-tracker` agent; !=hand-edit ad-hoc |
|
||||
```
|
||||
|
||||
> Note on rule 8: this is the EXTRA rule beyond the brewpage etalon -- it mandates running `task-tracker` as a spawned, isolated subagent at the start of any task (never inlined into the main session). Keep it phrased as a hard requirement.
|
||||
@@ -0,0 +1,261 @@
|
||||
# 05 -- Step 4b: `.codex/features/**` file templates
|
||||
|
||||
Write each block below to its path under `TARGET/.codex/features/`. Substitute `{{REPO_NAME}}`, `{{DOMAINS}}`, `{{FIRST_DOMAIN}}`, `{{LANG}}`, `{{CLOSE_MARKER_SHORT}}` (ref 03 map), `{{TODAY}}` (ISO date).
|
||||
|
||||
The `board.md` here is the EMPTY skeleton (counts 0). The Step-4c doc sweep fills it from the migrated docs.
|
||||
|
||||
---
|
||||
|
||||
## `board.md`
|
||||
|
||||
```markdown
|
||||
# {{REPO_NAME}} sub-agent task Board
|
||||
|
||||
> Canonical task list + status. Procedure: [`TRACKER.md`](TRACKER.md). New-task template:
|
||||
> [`TASK_TEMPLATE.md`](TASK_TEMPLATE.md). Ungroomed inbox: [`backlog/`](backlog/).
|
||||
> Root `TODO.md` does NOT exist -- this board is the only tracker.
|
||||
|
||||
## Overall status
|
||||
|
||||
- **Live:** (set on first close)
|
||||
- **Counts:** backlog `0` | todo `0` | progress `0` | closed `0` | specs `0`.
|
||||
- **Current focus:**
|
||||
1. (none yet -- add tasks via `/task-board` or the `task-tracker` agent)
|
||||
|
||||
## Progress (WIP)
|
||||
|
||||
| id | title | prio | owner | file |
|
||||
|----|-------|------|-------|------|
|
||||
|
||||
## Todo (queued)
|
||||
|
||||
| id | title | prio | owner | file |
|
||||
|----|-------|------|-------|------|
|
||||
|
||||
## Backlog (ungroomed)
|
||||
|
||||
`0` items -- see [`backlog/`](backlog/). Procedure: [`TRACKER.md`](TRACKER.md) grooming section.
|
||||
|
||||
## Closed (recent)
|
||||
|
||||
| id | title | closed in | file |
|
||||
|----|-------|-----------|------|
|
||||
|
||||
## Feature specs
|
||||
|
||||
| id | title | file |
|
||||
|----|-------|------|
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `TRACKER.md`
|
||||
|
||||
```markdown
|
||||
# TRACKER -- {{REPO_NAME}} task/feature tracker procedure
|
||||
|
||||
> Canonical procedure for the `.codex/features/` task board. The board (`board.md`)
|
||||
> is the single source of truth for the task LIST + status. A task file (when present)
|
||||
> is the source of truth for that task's DETAIL. Read this before touching any task.
|
||||
|
||||
[DICT: WIP=work in progress, GROOM=backlog triage]
|
||||
|
||||
## 1. What this is
|
||||
|
||||
A lightweight, file-based Kanban for {{REPO_NAME}}. No external tool. Everything lives in
|
||||
`.codex/features/` and is versioned with the repo. It is the canonical task tracker for the
|
||||
project. There is NO root `TODO.md` -- never create one.
|
||||
|
||||
## 2. Layout
|
||||
|
||||
```
|
||||
.codex/features/
|
||||
board.md <- DASHBOARD: overall status + index table of EVERY task (canonical list)
|
||||
TRACKER.md <- this procedure
|
||||
TASK_TEMPLATE.md <- copy this to create a new task file
|
||||
INDEX.md <- maps the control files
|
||||
backlog/ <- INBOX: ungroomed junk/ideas/dumps; not yet real tasks (README.md permanent)
|
||||
todo/ <- accepted tasks, queued, not started (file optional here)
|
||||
progress/ <- WIP; a task file is MANDATORY here
|
||||
closed/ <- done/shipped (file optional; keep notable ones)
|
||||
specs/ <- per-task implementation specs (linked from task links:)
|
||||
```
|
||||
|
||||
Folder name == task status. A task file always lives in the folder matching its status.
|
||||
|
||||
## 3. Lifecycle (state machine)
|
||||
|
||||
```
|
||||
groom (promote) pick up ship
|
||||
backlog ------------------> todo --------> progress --------> closed
|
||||
| \ ^ |
|
||||
| \ groom (trash) | re-queue | blocked/parked
|
||||
| -----> [deleted] +---------------+
|
||||
|
|
||||
+--> groom (merge into existing task)
|
||||
```
|
||||
|
||||
| Transition | Action |
|
||||
|------------|--------|
|
||||
| backlog -> todo | groom: a real, scoped task. Give it an id, create a task file (or board row), place under `todo/`. |
|
||||
| backlog -> deleted | groom: noise / done / out of scope. Delete the backlog file. Note nothing. |
|
||||
| backlog -> merge | groom: duplicates/extends an existing task. Fold notes in, delete the backlog file. |
|
||||
| todo -> progress | pick up: MOVE the file into `progress/` (create from template if table-only), set `status: progress`, set `owner`, set `updated`. |
|
||||
| progress -> closed | ship: MOVE the file into `closed/`, set `status: closed`, add a one-line outcome + {{CLOSE_MARKER_SHORT}}. |
|
||||
| progress -> todo | re-queue/park: MOVE back, set `status: todo`, note why parked. |
|
||||
|
||||
Always update `board.md` in the SAME change as any transition. The board lags reality = the board is wrong.
|
||||
|
||||
## 4. sub-agent task file format
|
||||
|
||||
Copy `TASK_TEMPLATE.md`. Frontmatter is required; body sections recommended. {{LANG}} only.
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: T-{{FIRST_DOMAIN}}-SLUG
|
||||
title: One-line task title
|
||||
status: progress # backlog | todo | progress | closed (MUST match folder)
|
||||
priority: P1 # P1 (now) | P2 (soon) | P3 (nice-to-have)
|
||||
owner: developer # agent name or person; empty in todo/backlog
|
||||
created: {{TODAY}}
|
||||
updated: {{TODAY}}
|
||||
tags: []
|
||||
links: []
|
||||
---
|
||||
|
||||
## Context
|
||||
Why this exists, what problem it solves.
|
||||
|
||||
## Acceptance
|
||||
- [ ] concrete, checkable outcome
|
||||
|
||||
## Notes
|
||||
Running log: decisions, blockers, links to PRs/commits/reports.
|
||||
```
|
||||
|
||||
Invariants:
|
||||
- `status` frontmatter MUST equal the folder. On any move, change both.
|
||||
- A task in `progress/` MUST have a file. In `todo/`/`backlog/` a file is optional.
|
||||
- Closing a task: keep `updated` current and record {{CLOSE_MARKER_SHORT}} in `## Notes`.
|
||||
|
||||
## 5. ID convention
|
||||
|
||||
Id = UPPER-KEBAB, short, stable. Once minted it never changes (filename stem + board key).
|
||||
|
||||
Format: `<PREFIX>-<DOMAIN>-<SLUG>`.
|
||||
|
||||
| Prefix | Use |
|
||||
|--------|-----|
|
||||
| `T-*` | feature / product task |
|
||||
| `BUG-*` | defect |
|
||||
| `M-*` | maintenance / refactor / tech-debt |
|
||||
| `EPIC-*` | umbrella over several tasks |
|
||||
|
||||
First kebab segment after the prefix = a repo domain, one of: {{DOMAINS}}.
|
||||
Examples: `T-{{FIRST_DOMAIN}}-SLUG`, `BUG-{{FIRST_DOMAIN}}-SLUG`, `M-{{FIRST_DOMAIN}}-SLUG`.
|
||||
|
||||
## 6. The board (`board.md`)
|
||||
|
||||
`board.md` is the canonical LIST. It holds:
|
||||
1. **Overall status** -- release line, headline counts (backlog/todo/progress/closed), current focus (1-3 lines).
|
||||
2. **Progress table** -- every WIP task.
|
||||
3. **Todo table** -- every queued task (incl. rows with no file yet).
|
||||
4. **Backlog** -- count + pointer to `backlog/` (do not enumerate noise here).
|
||||
5. **Closed (recent)** -- last N notable closes; older ones live as files in `closed/` only.
|
||||
|
||||
Table columns: `id | title | priority | owner | file`. The `file` cell links the task file or says `--` when table-only.
|
||||
|
||||
Rule: if a task exists anywhere (file or row), it is on the board. Edited by hand on every transition. Keep it terse.
|
||||
|
||||
## 7. Backlog grooming (do this periodically)
|
||||
|
||||
`backlog/` is the dumping ground -- raw ideas, pasted logs, "look into X later". Drop anything there fast as a `*.md`; do not gate it.
|
||||
|
||||
Groom on a cadence (start of a session, or when backlog > ~10 items):
|
||||
1. Read each `backlog/*.md`.
|
||||
2. Decide its fate per section 3: **promote**, **merge**, or **trash**.
|
||||
3. Never leave a groomed item in `backlog/`.
|
||||
4. Log nothing for trashed junk; promoted items carry context in the new task file.
|
||||
|
||||
The `task-tracker` agent and the `task-board` skill both know this loop -- invoke them to run a groom pass.
|
||||
|
||||
## 8. Working procedure (per session)
|
||||
|
||||
1. Open `board.md` -> read overall status + progress table.
|
||||
2. (Optional) groom `backlog/` per section 7.
|
||||
3. Pick a `todo` task (respect priority). Move it to `progress/`, set owner, update board.
|
||||
4. Do the work. Keep `## Notes` current.
|
||||
5. On done: ship, move the file to `closed/`, record {{CLOSE_MARKER_SHORT}}, update board counts + focus.
|
||||
6. If new work surfaces mid-task, drop it in `backlog/` (do not derail).
|
||||
|
||||
## 9. Ownership & related rules
|
||||
|
||||
- See `.codex/rules/tasks.md` for task-authoring conventions (incl. running `task-tracker` at task start).
|
||||
- When you start/finish/park a task, follow sections 3 + 8 and keep the board in sync.
|
||||
- Non-trivial board work -> delegate to the `task-tracker` agent.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `TASK_TEMPLATE.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: T-{{FIRST_DOMAIN}}-REPLACE-ME
|
||||
title: One-line task title
|
||||
status: todo
|
||||
priority: P2
|
||||
owner:
|
||||
created: {{TODAY}}
|
||||
updated: {{TODAY}}
|
||||
tags: []
|
||||
links: []
|
||||
---
|
||||
|
||||
## Context
|
||||
Why this task exists and what problem it solves.
|
||||
|
||||
## Acceptance
|
||||
- [ ] concrete, checkable outcome
|
||||
|
||||
## Notes
|
||||
Running log: decisions, blockers, PR/commit/report links.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `INDEX.md`
|
||||
|
||||
```markdown
|
||||
# Features -- control-file index
|
||||
|
||||
> `board.md` is the **canonical** task list + status. This index just maps the control
|
||||
> surfaces; it never duplicates the board.
|
||||
|
||||
## Control files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| [`board.md`](board.md) | Canonical task LIST + status (dashboard: overall status, progress/todo/backlog/closed/specs tables). Every task = a board row. |
|
||||
| [`TRACKER.md`](TRACKER.md) | The procedure: layout, lifecycle state machine, task-file format, id convention, grooming loop. |
|
||||
| [`TASK_TEMPLATE.md`](TASK_TEMPLATE.md) | Copy this to create a new task file. |
|
||||
| [`INDEX.md`](INDEX.md) | This file. |
|
||||
|
||||
## Folders (folder name == task `status:`)
|
||||
|
||||
| Folder | Holds |
|
||||
|--------|-------|
|
||||
| [`backlog/`](backlog/) | Ungroomed inbox -- raw ideas/dumps; groomed into `todo/` or trashed. |
|
||||
| [`todo/`](todo/) | Accepted, queued, not started. |
|
||||
| [`progress/`](progress/) | WIP -- a task file is MANDATORY here. |
|
||||
| [`closed/`](closed/) | Done / shipped. |
|
||||
| [`specs/`](specs/) | Per-task implementation/design specs, linked from a task's `links:`. Not a status folder. |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `backlog/README.md`
|
||||
|
||||
```markdown
|
||||
Ungroomed inbox. Drop raw ideas as *.md; task-tracker grooms into todo/ or trashes. See ../TRACKER.md.
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# 06 -- Step 4c: multi-agent doc sweep
|
||||
|
||||
[DICT: DOCS=migration inventory from Step 1, FEAT=TARGET/.codex/features, EXCL=source-path exclusions]
|
||||
|
||||
Goal: consolidate every existing backlog/feature/task doc found in Step 1 (`DOCS`) into the new board: dedup, trash cruft, migrate ready/done items into `closed/`, format `backlog/`, then author the real `board.md` (filling the skeleton from 4b).
|
||||
|
||||
> Sweep subagents write ONLY under `TARGET/.codex/features/**`. They must NOT edit any EXCLUSIONS dir. Reading source docs to extract tasks is fine; modifying source is not.
|
||||
|
||||
## Spawn (parallel -- one message, multiple sub-agent calls)
|
||||
|
||||
Partition the `DOCS` inventory across N subagents (1 if small, 2-3 if many docs / large). Each gets a slice + the same contract. Use `general-purpose` (it must Read source docs and Write under `.codex/features/`).
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="general-purpose", message="
|
||||
TARGET=<abs path>. You are migrating legacy task/backlog docs into a new file-based Kanban at TARGET/.codex/features/.
|
||||
You may WRITE only under TARGET/.codex/features/**. NEVER edit these source dirs: <EXCLUSIONS>.
|
||||
Procedure + format: read TARGET/.codex/features/TRACKER.md and TARGET/.codex/rules/tasks.md FIRST and follow them exactly.
|
||||
Id domains allowed: <DOMAINS>. Language: <LANG>. Closing marker style: <CLOSE_MARKER_SHORT>.
|
||||
|
||||
Your slice of legacy docs: <subset of DOCS with paths>.
|
||||
|
||||
For each legacy item:
|
||||
1. Classify: open/ready task | in-progress | done/shipped | duplicate | noise/obsolete.
|
||||
2. open + scoped -> create TARGET/.codex/features/todo/<ID>.md from TASK_TEMPLATE.md (or a board row if thin).
|
||||
3. clearly in-progress -> create under progress/<ID>.md (progress REQUIRES a file).
|
||||
4. done/shipped -> create under closed/<ID>.md, record the closing marker in ## Notes.
|
||||
5. raw/unclear idea -> drop a TARGET/.codex/features/backlog/<slug>.md (ungated, no id yet).
|
||||
6. duplicate -> fold into the existing task's ## Notes, do not create a second.
|
||||
7. noise/obsolete -> skip (do NOT create anything).
|
||||
Mint UPPER-KEBAB ids: <PREFIX>-<DOMAIN>-<SLUG>, domain from the allowed list. Ensure unique (Glob .codex/features/**/<ID>.md).
|
||||
Do NOT author board.md (the orchestrator does that after merging all slices). Do NOT delete the legacy source docs.
|
||||
|
||||
Return ONLY a manifest: a table of every file you created (path | id | status-folder | one-line title) + a count of items skipped as noise + any duplicates folded.
|
||||
")
|
||||
```
|
||||
|
||||
## Integrate -- author the real `board.md` (orchestrator, main session)
|
||||
|
||||
After all sweep subagents return their manifests:
|
||||
|
||||
1. `Glob` `TARGET/.codex/features/{todo,progress,closed,specs}/*.md` to get the true file set (do not trust manifests blindly -- verify on disk).
|
||||
2. Read each file's FM (`id`, `title`, `priority`, `owner`, `status`) -- delegate this read to a single `Explore` subagent if there are many files.
|
||||
3. Rewrite `TARGET/.codex/features/board.md` (Edit/Write) from the 4b skeleton:
|
||||
- Counts: real `backlog | todo | progress | closed | specs`.
|
||||
- Progress / Todo / Closed (recent) tables: one row per file (`id | title | prio | owner | file`).
|
||||
- Current focus: top 1-3 P1 items in progress/todo.
|
||||
- Backlog count: number of ungroomed `backlog/*.md` (minus `README.md`).
|
||||
4. Sanity: every file under a status folder appears as a board row; folder == its FM `status`.
|
||||
|
||||
## Legacy-source disposition
|
||||
|
||||
Do NOT delete or rewrite the original legacy docs (e.g. a root `TODO.md`) in this skill -- that risks touching EXCLUSIONS or losing history. Instead, in the P5 report, LIST the legacy docs that were migrated and recommend the user remove/redirect them (or delegate that cleanup to the new `task-tracker` agent in a follow-up). The new board is now canonical; the rule (`tasks.md` rule 9) forbids a root `TODO.md` going forward.
|
||||
|
||||
## Output contract handed to P5
|
||||
|
||||
- A populated `board.md` with real counts + tables.
|
||||
- Files under `todo/ progress/ closed/ backlog/` reflecting the migration.
|
||||
- A migration summary: N migrated (by status), N folded duplicates, N skipped as noise, list of legacy source docs to retire.
|
||||
@@ -0,0 +1,214 @@
|
||||
# 07 -- Step 5 (optional): AGENTS.md optimization
|
||||
|
||||
[DICT: CMD=root AGENTS.md, LOCAL=AGENTS.local.md, MOD=module/subproject, MODCMD=module-level AGENTS.md, RULES=.codex/rules/*.md, BUDGET=line budget, TO=brewtools:text-optimize skill, DIR=free-text prompt directive]
|
||||
|
||||
OPTIONAL, opt-in phase. PROPOSE-ONLY: every change is gated behind request_user_input; never restructure without explicit approval. Runs AFTER the board is scaffolded + verified (P5), only if the user opted in at P0. Replaces the legacy "do NOT touch AGENTS.md" stance: this phase is the SANCTIONED, gated way to touch it.
|
||||
|
||||
> **Verified lazy-loading mechanic (source: developers.openai.com/codex/guides/agents-md, fetched 2026-06-14).** Bake this into every proposal rationale:
|
||||
> - Root CMD + all ancestor AGENTS.md/AGENTS.local.md: **loaded in full AT LAUNCH**, every session, regardless of length.
|
||||
> - Subdirectory (nested) AGENTS.md: **NOT loaded at launch -- loaded ON-DEMAND when Codex reads a file in that subtree.**
|
||||
> - `@path` imports: **EAGER -- expanded into context at launch.** They help organization but do NOT reduce root context.
|
||||
> - `.codex/rules/*.md` with `paths:` FM: on-demand when matching files are touched; without `paths:`: at launch.
|
||||
> CONSEQUENCE: to shrink always-on context, push MOD detail into a NESTED MODCMD. NEVER use `@import` for that goal (eager = no savings). This is the justification stated to the user in the module-split proposal.
|
||||
|
||||
---
|
||||
|
||||
## Inputs
|
||||
|
||||
From P0:
|
||||
- `TARGET` (abs path).
|
||||
- `DIR` = the free-text prompt directive parsed from `<arguments>` (may be empty). Tunes which sub-steps run / how aggressive they are. See "Directive influence" below.
|
||||
- `OPTIN` = whether the user opted into this phase (asked at P0).
|
||||
|
||||
If `OPTIN` is false -> SKIP this entire file; do nothing.
|
||||
|
||||
---
|
||||
|
||||
## Directive influence (DIR)
|
||||
|
||||
`DIR` is free text. Match case-insensitive substrings to toggle sub-steps. Default = all sub-steps ENABLED (still each individually AskUser-gated). DIR only flips which sub-steps are OFFERED; it never bypasses a gate.
|
||||
|
||||
| DIR hint (substring) | Effect |
|
||||
|----------------------|--------|
|
||||
| `skip module split`, `no module`, `no nested` | disable 5d (module split) |
|
||||
| `skip local`, `no local`, `keep secrets inline` | disable 5b (local-only extraction) -- but STILL warn if hard secrets found |
|
||||
| `skip dedup`, `no dedup`, `skip rules` | disable 5e (rules dedup/compress) |
|
||||
| `also dedupe rules`, `dedup rules` | force-enable 5e even if other hints narrow scope |
|
||||
| `budget N`, `max N lines`, `target N` | override BUDGET_OPTIMAL=N (and OVER=N*1.5 rounded) |
|
||||
| `aggressive`, `max compress`, `deep` | pass `-d` (or `-x` only if `max`/`extreme`/`atomic` present) to TO in 5e/5f |
|
||||
| `report only`, `dry run`, `propose only` | run 5a detection + present a full plan, but make NO edits even if approved -- emit plan as the deliverable |
|
||||
| anything else | record as free-form intent; apply best-effort to phrasing of proposals, do NOT invent new behaviors |
|
||||
|
||||
If DIR is ambiguous or conflicts (e.g. `skip rules` + `also dedupe rules`), surface the conflict in the P5.5 AskUser intro and let the user pick.
|
||||
|
||||
---
|
||||
|
||||
## 5a. Detect (read-only scan, NO edits)
|
||||
|
||||
Locate the root CMD: prefer `TARGET/AGENTS.md`, else `TARGET/.codex/AGENTS.md`. If neither exists -> there is nothing to optimize; report "no root AGENTS.md found" and SKIP the rest (do NOT create a root AGENTS.md -- out of scope for this phase).
|
||||
|
||||
**EXECUTE** using shell (read-only):
|
||||
```bash
|
||||
CMD=""
|
||||
for c in "$TARGET/AGENTS.md" "$TARGET/.codex/AGENTS.md"; do test -f "$c" && CMD="$c" && break; done
|
||||
test -n "$CMD" && wc -l < "$CMD" | tr -d ' ' && echo "CMD=$CMD" || echo "NO_CMD"
|
||||
ls "$TARGET/.codex/rules/"*.md 2>/dev/null || echo "NO_RULES"
|
||||
test -f "$TARGET/AGENTS.local.md" && echo "LOCAL_EXISTS" || echo "LOCAL_ABSENT"
|
||||
```
|
||||
|
||||
Then `Read` the CMD (and each `RULES` file) into context. Produce a DETECTION object (no writes):
|
||||
|
||||
```
|
||||
CMD_PATH = <abs>
|
||||
CMD_LINES = <int> # current line count
|
||||
BUDGET_OPTIMAL = 200 # or DIR override
|
||||
BUDGET_OVER = 300 # or DIR override (optimal*1.5)
|
||||
OVER = CMD_LINES > BUDGET_OVER # bool
|
||||
LOCAL_ITEMS = [ {line, snippet, kind} ... ] # see 5b heuristics
|
||||
MODULES = [ {dir, why, has_own_cmd} ... ] # see 5d detection
|
||||
RULES = [ {path, lines} ... ]
|
||||
DUP_SPANS = [ {a, b, overlap_summary} ... ] # cross-file dup/overlap, see 5e
|
||||
```
|
||||
|
||||
Report CMD_LINES vs BUDGET_OPTIMAL/BUDGET_OVER up front (current vs target), regardless of whether any proposal follows.
|
||||
|
||||
### Module detection (for MODULES)
|
||||
A repo subtree is a MOD candidate if it has its own manifest / build / package boundary. Signals (any): `package.json`, `pom.xml`/`build.gradle`, `pyproject.toml`/`setup.py`, `go.mod`, `Cargo.toml`, `Makefile`, a `src/` of its own, or a workspace member (npm/pnpm/yarn workspaces, gradle subproject, cargo workspace, go work). Skip dirs in EXCLUSIONS only if EXCLUSIONS means "don't WRITE" -- here writing a MODCMD inside a source module IS allowed (a MODCMD is doc, not source). Record `has_own_cmd` = whether `<dir>/AGENTS.md` already exists.
|
||||
|
||||
### Local-only detection heuristics (for LOCAL_ITEMS)
|
||||
Scan CMD lines for items that should NOT be in a team-shared, committed file:
|
||||
| kind | heuristic |
|
||||
|------|-----------|
|
||||
| secret | `password`, `passwd`, `secret`, `token`, `api[_-]?key`, `bearer`, `BEGIN .*PRIVATE KEY`, AWS-style `AKIA[0-9A-Z]{16}`, long base64/hex blobs assigned to a var |
|
||||
| abs machine path | absolute paths under `/Users/<name>/`, `/home/<name>/`, `C:\Users\`, `/opt/<host-specific>` -- machine/user-specific, not repo-relative |
|
||||
| host/user config | personal localhost ports/URLs, `localhost:<port>` sandbox URLs, `~/.ssh`, hostnames, personal emails, "my " sandbox/test data |
|
||||
NEVER print full secret values back to the user in the proposal -- mask (`sk-...AB12`). Flag line numbers + masked snippet + kind.
|
||||
|
||||
---
|
||||
|
||||
## 5b. PROPOSE: extract local-only items -> AGENTS.local.md (gated)
|
||||
|
||||
If `DIR` disabled 5b: skip, BUT if any `kind=secret` was found, still emit a one-line warning ("hard secrets detected in committed AGENTS.md; consider re-running without skip-local").
|
||||
|
||||
If LOCAL_ITEMS non-empty, request_user_input:
|
||||
|
||||
> **Found N local-only items in committed AGENTS.md** (secrets / machine paths / host config). These leak into every teammate's context and (for secrets) into git. Propose: move them to `AGENTS.local.md` (gitignored, loaded only for you), leaving CMD clean.
|
||||
> - Move all N to AGENTS.local.md (create it + add to .gitignore)
|
||||
> - Let me pick which to move
|
||||
> - Leave as-is (do not touch)
|
||||
|
||||
On approval:
|
||||
1. If `AGENTS.local.md` absent -> create it at `TARGET/AGENTS.local.md` with a header `# Local-only (gitignored) -- machine/user-specific, not committed`.
|
||||
2. Append the approved items (verbatim values) under topical headings.
|
||||
3. Remove them from CMD (Edit, not Write; bottom-up by line number).
|
||||
4. Ensure `AGENTS.local.md` is gitignored:
|
||||
```bash
|
||||
grep -qxF "AGENTS.local.md" "$TARGET/.gitignore" 2>/dev/null || echo "(needs .gitignore entry)"
|
||||
```
|
||||
If missing, propose adding `AGENTS.local.md` to `.gitignore` (one more AskUser line, or include in the same approval). Do NOT silently edit .gitignore without the user's yes.
|
||||
> If a secret was already committed, note to the user that gitignoring does NOT purge history -- they should rotate the secret. Do not attempt history rewrite.
|
||||
|
||||
---
|
||||
|
||||
## 5c. PROPOSE: line-budget decomposition (gated, only if OVER)
|
||||
|
||||
Report always: `AGENTS.md is <CMD_LINES> lines (optimal ~<BUDGET_OPTIMAL>, over at <BUDGET_OVER>).`
|
||||
|
||||
If NOT OVER: state it's within budget; offer optional tidy (markup pass 5g) but propose no decomposition.
|
||||
|
||||
If OVER: assemble a concrete decomposition PLAN combining 5d (module split), 5e (rules dedup), 5f (compress), then AskUser ONCE with the whole plan before applying any of it:
|
||||
|
||||
> **AGENTS.md is <CMD_LINES> lines (over the <BUDGET_OVER> ceiling; optimal ~<BUDGET_OPTIMAL>).** Proposed decomposition to get back under budget:
|
||||
> 1. Move detail for modules `<M1, M2, ...>` into per-module AGENTS.md (loaded on-demand, shrinks always-on context). Root keeps a 2-line module index. [est -X lines]
|
||||
> 2. Move topic blocks `<...>` into path-scoped `.codex/rules/*.md` (load only when matching files are touched). [est -Y lines]
|
||||
> 3. Dedup overlap with existing rules `<...>`; delete duplicated spans. [est -Z lines]
|
||||
> 4. Deep-compress the remainder via brewtools:text-optimize. [est -W lines]
|
||||
> Projected: <CMD_LINES> -> ~<TARGET_LINES>.
|
||||
> - Apply the full plan
|
||||
> - Apply only steps I pick
|
||||
> - Skip decomposition (leave AGENTS.md as-is)
|
||||
|
||||
Apply ONLY approved steps. Each sub-step (5d/5e/5f) below still narrates what it does, but execution is gated by THIS approval (do not re-ask per sub-step unless the user chose "only steps I pick", then confirm the subset).
|
||||
|
||||
---
|
||||
|
||||
## 5d. Module split -> nested module AGENTS.md (part of 5c plan; disabled if DIR says skip)
|
||||
|
||||
For each approved MOD in MODULES:
|
||||
1. Gather the CMD content that is module-specific (build/test cmds, layout, conventions for that subtree).
|
||||
2. Write/extend `<MOD.dir>/AGENTS.md` (a NESTED file -- this is what gives on-demand loading). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets).
|
||||
3. In the ROOT CMD, REPLACE the moved block with a MAX-COMPRESSED index: a couple of lines, e.g.:
|
||||
```
|
||||
## Modules (each has its own AGENTS.md, loaded on-demand when you work in it)
|
||||
| Module | Path | Owns |
|
||||
|--------|------|------|
|
||||
| api | services/api/ | handlers, OpenAPI, db migrations |
|
||||
| web | apps/web/ | UI, build, e2e |
|
||||
```
|
||||
Keep ONLY the index in root; the detail lives in the MODCMD.
|
||||
> Rationale to state in the proposal: nested AGENTS.md loads ONLY when Codex touches that subtree, so module detail leaves the always-on root context. Do NOT use `@import` here -- imports are eager and would not save context.
|
||||
> Do NOT move CROSS-cutting / repo-wide rules into a single module; those stay in root or go to a `.codex/rules/*.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5e. Rules dedup + compress (disabled if DIR says skip; forced if DIR says also dedupe rules)
|
||||
|
||||
1. From DUP_SPANS, identify content duplicated or overlapping across `.codex/rules/*.md` and between rules and CMD.
|
||||
2. PROPOSE (folded into 5c plan, or its own AskUser if 5c not triggered): single-source each fact. Keep repo-wide invariants in CMD or an unscoped rule; keep path-specific guidance in a `paths:`-scoped rule. Delete the duplicate copies.
|
||||
3. Apply approved dedup via Edit.
|
||||
> Single-source-of-truth: a fact in two places drifts; pick the correct home (path-scoped rule for path-specific, CMD/unscoped rule for global).
|
||||
|
||||
---
|
||||
|
||||
## 5f. Delegate compression to brewtools:text-optimize
|
||||
|
||||
After structural moves (5b/5d/5e), the remaining CMD + touched MODCMD + RULES should be token-compressed by the dedicated skill, NOT hand-compressed here.
|
||||
|
||||
- Recommend/invoke: `brewtools:text-optimize` auto-detects `AGENTS.md` and `.codex/rules/*.md` as LLM-only files and selects DEEP mode (DICT header + symbol substitution + verification rounds).
|
||||
- Default invocation (after this phase's edits are approved + applied):
|
||||
```
|
||||
$brewtools:text-optimize AGENTS.md
|
||||
$brewtools:text-optimize .codex/rules/
|
||||
```
|
||||
Multiple files in one call run in parallel: `$brewtools:text-optimize AGENTS.md, <MOD>/AGENTS.md`.
|
||||
- DIR `aggressive`/`deep` -> pass `-d`; DIR `max`/`extreme`/`atomic` -> pass `-x` (max mode, 2 mandatory verify rounds). Otherwise let auto-detect pick deep for these files.
|
||||
- text-optimize has its OWN AskUser/verification; do not duplicate it here. This phase's job is to RECOMMEND/INVOKE it on the touched files, then re-count lines for the final report.
|
||||
> Do NOT inline-reimplement compression. Single source of compression logic = text-optimize.
|
||||
|
||||
---
|
||||
|
||||
## 5g. Markup / structure pass (all touched docs)
|
||||
|
||||
For every doc this phase wrote or edited (CMD, MODCMD, AGENTS.local.md, touched RULES): ensure headers form a clean hierarchy, prose -> tables/bullets where it compresses, code fences valid, consistent terminology. This is light and folds into text-optimize's structural rules; do not double-apply if 5f ran on the same file.
|
||||
|
||||
---
|
||||
|
||||
## 5h. Re-count + report (this phase's slice of P5 report)
|
||||
|
||||
After all approved edits + any text-optimize run:
|
||||
```bash
|
||||
wc -l < "$CMD_PATH" | tr -d ' '
|
||||
```
|
||||
Report:
|
||||
- CMD line count: BEFORE -> AFTER (vs optimal/over).
|
||||
- Local-only items moved: count + that AGENTS.local.md was created/updated + gitignore status.
|
||||
- Modules split: list of MODCMD written + that root now indexes them.
|
||||
- Rules dedup: spans removed.
|
||||
- text-optimize: whether invoked, mode, resulting reduction.
|
||||
- A one-line flag if any secret was found that predates this run (rotate + history note).
|
||||
|
||||
Set `CMD_DECOMPOSED = true` iff 5d wrote/updated at least one MODCMD -- this flag is consumed by the task-tracker agent template addition (ref 02) so the generated agent knows the target's AGENTS.md is decomposed/lazy-loaded.
|
||||
|
||||
---
|
||||
|
||||
## Gates (this phase)
|
||||
|
||||
| Condition | Response |
|
||||
|-----------|----------|
|
||||
| OPTIN false | SKIP phase entirely |
|
||||
| No root AGENTS.md | report + SKIP (do NOT create a root AGENTS.md) |
|
||||
| User declines a proposal | make NO edit for that sub-step; proceed to next |
|
||||
| `report only` / dry-run DIR | detection + full plan only; ZERO edits even if "approve" |
|
||||
| Secret found | mask in output; on move, warn that gitignore != history purge; never echo full value |
|
||||
| DIR conflict | surface in AskUser intro; user decides |
|
||||
| Edits | Edit (not Write), bottom-up by line number; never clobber an existing MODCMD |
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Text Humanizer
|
||||
|
||||
Universal, context-aware humanizer. It removes AI surface artifacts (chat scaffolding, fake tickets, unicode, trivial docs that restate a name) and fits the text to its register -- code, technical docs, published articles, or reddit/chat. It does NOT claim to detect AI authorship.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
$brewtools:text-human [path|commit|folder|text] [custom instructions]
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Phase 0 -- greedy flow detection
|
||||
At the start the skill picks exactly ONE flow from context and announces it (`Flow: <name> -- <why>`), then lazy-loads only that flow file plus the pattern sections it needs.
|
||||
|
||||
| Flow | Domain | Inject stage |
|
||||
|------|--------|--------------|
|
||||
| code | source, comments, docstrings, JavaDoc/JSDoc/KDoc | OFF (formal contract) |
|
||||
| docs | README, guides, PR/commit, changelogs | ON, restrained (terse for PR/commit) |
|
||||
| social | reddit, forum, slack, discord, chat | ON, casual |
|
||||
| article | formal essay, published blog, long-form | ON, burstiness + stance |
|
||||
| mixed | commit / folder dispatcher | per-file routing |
|
||||
|
||||
Detection priority: explicit intent keywords (RU+EN) -> path/extension -> content sniff.
|
||||
|
||||
### Two-pass model
|
||||
- PASS 1 STRIP: remove validated AI tells. HIGH-tier acts on single hits; MED density-signals act only on clusters; behavior-changing items (hallucinated refs, fake tickets) are surfaced for review, never auto-edited.
|
||||
- PASS 2 INJECT (gated): apply human style for the domain. HARD-OFF for code and API docs. Never injects typos, errors, or fabricated references.
|
||||
|
||||
## Arguments
|
||||
|
||||
The first token is parsed as scope when it resolves to a path or a 7+ hex git hash; the rest is a custom prompt. Otherwise the whole input is treated as a prompt (the text to humanize may be inline). The custom prompt both selects/overrides the flow and adds rules. No args -> it asks what to humanize.
|
||||
|
||||
| Input shape | Result |
|
||||
|-------------|--------|
|
||||
| File path | code/docs/article flow on one file, direct |
|
||||
| Commit hash | mixed flow, all changed files |
|
||||
| Folder | mixed flow, parallel blocks |
|
||||
| Free text | flow inferred from the text + intent |
|
||||
| Path + prompt | scope + custom rules |
|
||||
| No args | interactive fallback |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Code -- strip AI tells, normalize unicode, inject OFF
|
||||
$brewtools:text-human src/main/java/com/example/OrderService.java
|
||||
|
||||
# JavaDoc / API docs -- clean-only sub-profile
|
||||
$brewtools:text-human clean the javadoc in PaymentApi.java
|
||||
|
||||
# Commit -- mixed flow routes each file to its domain
|
||||
$brewtools:text-human 3be67487
|
||||
|
||||
# Reddit reply -- casual injection, sparse emoji, lowercase
|
||||
$brewtools:text-human review this reddit reply: <text>
|
||||
|
||||
# Blog post -- burstiness + real stance
|
||||
$brewtools:text-human humanize this blog post: <text>
|
||||
|
||||
# Custom prompt overrides defaults
|
||||
$brewtools:text-human src/ only strip AI artifacts, no inject
|
||||
$brewtools:text-human 3be67487 also drop all @author tags
|
||||
```
|
||||
|
||||
## What it strips (PASS 1)
|
||||
|
||||
| Category | Example | Tier |
|
||||
|----------|---------|------|
|
||||
| Chat scaffolding | "Certainly!", "I hope this helps", "Here's the rewritten..." | HIGH |
|
||||
| AI self-attribution | `// AI-generated`, `# Codex suggestion`, bot trailers | HIGH |
|
||||
| Prompt residue | `// Replace with your...` placeholder narration | HIGH |
|
||||
| Unicode in code/text | em-dash, arrows, smart quotes -> ASCII | HIGH |
|
||||
| Trivial doc/comment | `// Loop through users`, `@param userId The user ID` | density |
|
||||
| Excess-vocab cluster | delve, leverage, seamless, landscape (co-occurring) | MED cluster only |
|
||||
| Promotional/template prose | "In today's fast-paced world", "plays a significant role" | HIGH/MED |
|
||||
|
||||
## What it surfaces (never auto-edits)
|
||||
|
||||
Hallucinated package/method/URL refs, fabricated tickets, try/except-everything, empty catch-all, placeholder TODO logic, duplicated abstractions, happy-path-only tests, CI gaming. These change meaning -- they are reported, not changed.
|
||||
|
||||
## What it keeps
|
||||
|
||||
WHY comments, public API docs, real project tickets (INTELDEV-12345, JIRA-456), `@throws` with conditions, structural SQL/YAML banners, complex algorithm explanations, BDD comments.
|
||||
|
||||
## Injection (PASS 2, per flow)
|
||||
|
||||
| Signal | reddit | chat | docs | commit/PR | article | code/API |
|
||||
|--------|--------|------|------|-----------|---------|----------|
|
||||
| Burstiness | high | high | med | low | high | OFF |
|
||||
| Contractions | high | high | OK | low | high | FORBIDDEN |
|
||||
| Stance | high | med | none | grounded-why | high | FORBIDDEN |
|
||||
| Emoji | rare | incidental | no | no | no | no |
|
||||
| Inject stage | ON | ON | ON | ON terse | ON | OFF |
|
||||
|
||||
## Output
|
||||
|
||||
A **Humanization Report**: flow, scope, files/blocks, fast model/balanced model split, per-file strip/inject counts, a surfaced-for-review section, and totals. Files are edited in place -- use git to revert.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full docs: [text-human](https://doc-claude.brewcode.app/brewtools/skills/text-human/)
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: text-human
|
||||
description: "Humanizes code, docs, articles, reddit/chat, javadoc -- strips AI artifacts, fixes unicode, injects context-fit human style. Triggers - humanize, ai artifacts, unicode fix, article, reddit, javadoc, text."
|
||||
---
|
||||
|
||||
# Humanize text
|
||||
|
||||
Edit the supplied text or repository artifact in place only when authorized. Preserve technical meaning, identifiers, citations, and house style while removing repetitive phrasing, filler, and synthetic narration. Return a focused diff and do not delegate unless explicitly requested.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
# Text Humanizer
|
||||
|
||||
Universal, context-aware humanizer. Works on source code, comments, docstrings, technical docs, commits/PRs, published articles, and reddit/chat text. It picks ONE flow from context, lazy-loads only that flow plus the relevant pattern sections, and runs a two-pass model: STRIP validated AI tells, then a gated INJECT of human style fit for the domain.
|
||||
|
||||
Position: removes AI surface artifacts and fits register -- it does NOT claim to detect authorship.
|
||||
|
||||
## Two-pass model (applies to every flow)
|
||||
|
||||
- PASS 1 -- STRIP: remove validated AI tells per `@reference/ai-patterns.md`. Only HIGH-tier universal-strip acts on single instances. MED density-signals act ONLY when several co-occur. Behavior-changing items (hallucinated refs, fake tickets, try/except-everything) are SURFACED for review, never auto-edited.
|
||||
- PASS 2 -- INJECT (gated): apply `@reference/human-patterns.md` for the flow's domain. HARD-OFF for code / API / formal-contract. GLOBAL GUARD: never inject typos, errors, or fabricated references in any flow.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 -- Greedy flow detection (do this FIRST)
|
||||
|
||||
Before any processing, parse the argument, pick exactly ONE flow, and ANNOUNCE it:
|
||||
|
||||
`Flow: <name> -- <one-line why>`
|
||||
|
||||
Then greedy-load ONLY the chosen flow file plus the pattern sections it needs (lazy -- not everything).
|
||||
|
||||
### Argument parsing (universal)
|
||||
Accept all of: path, commit hash, folder, free-text prompt, path+prompt, no args.
|
||||
|
||||
1. Take the first token. If it resolves to an existing path OR matches a 7+ hex git hash -> that is `scope`, the rest is `customPrompt`.
|
||||
2. Otherwise the WHOLE input is a `customPrompt` (the text to humanize may be inline, or it may describe intent). Flow is detected from the prompt + any inline content.
|
||||
3. `customPrompt` both selects/overrides the flow AND adds custom rules (highest priority on conflict).
|
||||
4. No args at all -> request_user_input fallback is allowed ONLY here ("What to humanize?" -> commit / file / folder / paste text). Prefer inferring whenever possible.
|
||||
|
||||
### Detection signals (priority order)
|
||||
1. Explicit intent keywords in the prompt (RU+EN):
|
||||
- reddit / forum / slack / discord / chat / чат / форум -> social
|
||||
- javadoc / jsdoc / kdoc / docstring / "api doc" / апи док -> code (CLEAN-ONLY sub-profile)
|
||||
- pr / pull request / commit / changelog / readme / docs / guide / коммит / документация -> docs
|
||||
- article / blog / essay / post / статья / эссе -> article
|
||||
- commit hash, or folder of mixed files -> mixed
|
||||
2. Path / extension:
|
||||
- `.java/.kt/.py/.ts/.tsx/.js/.jsx/.go/.rs/.cpp/...` -> code
|
||||
- `.md/.mdx/.rst` -> docs; sniff content: long-form essay/blog -> article
|
||||
- 7+ hex git hash -> mixed
|
||||
- folder -> mixed
|
||||
3. Content sniff:
|
||||
- short fragmented lines / no caps -> social
|
||||
- structured prose paragraphs with a thesis -> article
|
||||
- imperative + code blocks -> docs
|
||||
|
||||
### Flow -> file
|
||||
| Flow | Load | Domain |
|
||||
|------|------|--------|
|
||||
| code | `@reference/flows/code.md` | source, comments, docstrings, JavaDoc/JSDoc/KDoc (inject OFF) |
|
||||
| docs | `@reference/flows/docs.md` | README, docs, guides, PR/commit (inject restrained) |
|
||||
| social | `@reference/flows/social.md` | reddit, forum, slack, discord, chat |
|
||||
| article | `@reference/flows/article.md` | formal essay, published blog, long-form |
|
||||
| mixed | `@reference/flows/mixed.md` | commit / folder dispatcher -> routes each file to its flow |
|
||||
|
||||
Pattern files (load the sections the flow needs): `@reference/ai-patterns.md`, `@reference/human-patterns.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 -- Execute the flow
|
||||
|
||||
- Single file or inline text -> apply the chosen flow's rules directly, no sub-agent task delegation.
|
||||
- mixed (commit / folder) -> follow `@reference/flows/mixed.md`: block split, fast model/balanced model classification, parallel sub-agent task launch, JSON aggregation. Each file is routed to its correct flow's rules.
|
||||
|
||||
Custom prompt, when present, is prepended to direct processing and to every sub-agent sub-agent task prompt:
|
||||
```
|
||||
CUSTOM INSTRUCTIONS (highest priority, override defaults):
|
||||
<customPrompt>
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output -- Humanization Report
|
||||
|
||||
```
|
||||
## Humanization Report
|
||||
|
||||
Flow: <name>
|
||||
|
||||
### Summary
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Scope | <file|commit|folder|text> |
|
||||
| Files / blocks | N / M |
|
||||
| fast model / balanced model | X / Y |
|
||||
|
||||
### Results
|
||||
[per-file or per-block: stripped, injected, surfaced]
|
||||
|
||||
### Surfaced for review (NOT auto-applied)
|
||||
[file:line -- issue] e.g. hallucinated ref, fabricated ticket, try/except-everything
|
||||
|
||||
### Totals
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| AI tells stripped | X |
|
||||
| Human edits injected | Y |
|
||||
| Items surfaced | Z |
|
||||
| Unicode normalized | W |
|
||||
```
|
||||
|
||||
Files are edited in place. No backups -- use git to revert.
|
||||
|
||||
## Error handling
|
||||
| Error | Action |
|
||||
|-------|--------|
|
||||
| Agent timeout | Continue with other blocks |
|
||||
| File read error | Skip, note in report |
|
||||
| Binary file | Skip, note in report |
|
||||
| No changes | Report "No humanization required" |
|
||||
|
||||
## Examples
|
||||
```bash
|
||||
/text-human src/main/java/OrderService.java # code flow, single file
|
||||
/text-human 3be67487 # mixed flow, commit
|
||||
/text-human src/main/java/services/ # mixed flow, folder
|
||||
/text-human review this reddit reply: <text> # social flow, inline text
|
||||
/text-human humanize this blog post: <text> # article flow
|
||||
/text-human clean the javadoc in PaymentApi.java # code flow, CLEAN-ONLY
|
||||
/text-human 3be67487 also drop all @author tags # mixed + custom rule
|
||||
/text-human src/ only strip AI artifacts, no inject # custom prompt overrides
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Text Human"
|
||||
short_description: "Humanizes code, docs, articles, reddit/chat, javadoc -- strips A"
|
||||
default_prompt: "Use $brewtools:text-human for this task."
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,101 @@
|
||||
# AI Tells -- Strip / Surface (PASS 1)
|
||||
|
||||
Condensed from validated catalog. Tiers:
|
||||
- HIGH = mechanical artifact, ~zero human FP, single hit actionable.
|
||||
- MED = real LLM-skew but present in human writing, act ONLY on co-occurring density (cluster), never on one instance.
|
||||
- weak = high-FP folklore, tiebreaker inside an already-flagged passage only. Listed in section 6 as DROPPED -- do not flag standalone.
|
||||
|
||||
Action key: strip = remove safely. rewrite = reword to neutral/concrete. flag = surface to human, NEVER auto-edit (behavior-changing).
|
||||
|
||||
Core rule: never act on a single MED/weak surface feature. Detectors hit 61% FP on non-native English and flag the US Constitution. Position as "removes AI surface artifacts", not "detects AI authorship".
|
||||
|
||||
---
|
||||
|
||||
## 1. Universal-strip (HIGH, safe all flows)
|
||||
|
||||
| Pattern | Action | Scope | Src |
|
||||
|---|---|---|---|
|
||||
| Chat scaffolding: "Certainly!", "Absolutely!", "Great question!", "Sure, here's", "I hope this helps", "Here's the rewritten/revised" | strip | prose+chat | DeGPT |
|
||||
| Self-reference: "As an AI language model", "As a large language model", knowledge-cutoff/refusal phrasing | strip | all | Wikipedia |
|
||||
| Broken model markup: `oaicite`, `contentReference`, invalid DOIs, stray tokens | strip | all | Wikipedia |
|
||||
| AI self-attribution comment: `// AI-generated`, `// Added by AI`, `# Codex suggestion`, `# TODO (AI)` | strip | code | Gatech |
|
||||
| Co-author/bot trailer, generator banner: `Co-Authored-By: <bot>` | strip | code/commit | Gatech |
|
||||
| Prompt/instruction residue as comment: `# Remember to paste...`, `// Replace with your...` placeholder narration | strip | code | Netcraft |
|
||||
| Unicode in code/text: em-dash, smart quotes, arrows in comments/strings | normalize-ASCII | all | Gatech |
|
||||
|
||||
## 2. Prose tells
|
||||
|
||||
| Pattern | Action | Tier | Src |
|
||||
|---|---|---|---|
|
||||
| Opener "In today's fast-paced world / rapidly evolving landscape" | rewrite | HIGH | GPTZero (107x) |
|
||||
| High-ratio phrases: "plays a significant role in shaping" (182x), "aims to explore" (50x), "notable works include" (120x) | rewrite | HIGH | GPTZero |
|
||||
| "It's important to note / worth noting / worth mentioning" | strip/rewrite | MED-HIGH | Wikipedia |
|
||||
| Trailing "-ing" significance tail: "...reshaping industries, highlighting their significance" | rewrite | MED-HIGH | Wikipedia |
|
||||
| Copula avoidance: "serves as / stands as a testament" for is/are | rewrite | MED | Wikipedia |
|
||||
| "Despite its... challenges" / "Challenges and Future" rigid template | rewrite | MED-HIGH | Wikipedia |
|
||||
| Bold lead-in label: `**Key point:** ...` | strip-bold | MED | Wikipedia |
|
||||
|
||||
## 3. Code tells
|
||||
|
||||
| Pattern | Action | Tier | Src |
|
||||
|---|---|---|---|
|
||||
| Comment restates the line below, zero added info: `// Loop through users` over a `for`, `// Initialize the list`, `// Check if null` | strip | HIGH on density | Symantec |
|
||||
| Comment on nearly every line / line-by-line narration | strip-redundant | HIGH density | Infosecurity |
|
||||
| Tutorial framing: "Here we...", "Now we...", "First, we...", "Note that..." | strip/rewrite | MED-HIGH | Sohail |
|
||||
| Emoji in comments/debug: `// 🔍 Search`, `print("✅ Done")` | strip | HIGH | Netcraft |
|
||||
| Docstring/JavaDoc restates signature: `@param userId The user ID` = reworded identifier | strip-redundant | MED | Kuryuliya |
|
||||
| Print/log narrating trivial steps: `print("Starting loop...")` | strip | MED | Netcraft |
|
||||
|
||||
> Strip docstring lines ONLY when text == reworded identifier. Never blanket-strip -- high-level JavaDoc is where AI is good.
|
||||
|
||||
## 4. Density-signals (MED -- act ONLY when several co-occur)
|
||||
|
||||
Never flag one. Weight by count; act when cluster crosses threshold.
|
||||
|
||||
| Pattern | Action | Src |
|
||||
|---|---|---|
|
||||
| Excess-vocabulary cluster: delve, underscore, showcase, meticulous, tapestry, intricate, pivotal, testament, resonate + crucial/comprehensive/notably | rewrite | PMC |
|
||||
| Spatial-metaphor set: landscape, realm, ecosystem, navigate, foster | rewrite | Dead Language Society |
|
||||
| Corporate verbs: leverage, harness, utilize, robust, seamless, streamline, embark | rewrite | Decrypt |
|
||||
| Promotional tone on neutral topic: "nestled in the heart of", "rich cultural heritage" | rewrite | Wikipedia |
|
||||
| Transition stacking: "Furthermore, Moreover, Additionally" (esp. moreover) | rewrite | Wikipedia |
|
||||
| "plays a crucial/vital/significant role" | rewrite | Wikipedia |
|
||||
| "a wide range / myriad of / treasure trove of / diverse array" | rewrite | hyacinth.ai |
|
||||
| Empty hedge "both approaches have merit / it depends" with no stance | rewrite | DeGPT |
|
||||
| Negative parallelism "It's not X, it's Y" / "not just X, but Y" -- density only, common in humans | rewrite | Wikipedia |
|
||||
| Low burstiness / metronomic cadence (sentences 18-24 words, uniform paragraphs) | flag-rewrite | duey.ai |
|
||||
| Section-banner overuse `# ===== HELPERS =====` in trivial files | strip | Diatom |
|
||||
|
||||
## 5. Surface-for-review (behavior-changing -- NEVER auto-strip)
|
||||
|
||||
Flag to human; silently editing changes meaning. Fabricated refs are BUGS, not cosmetics.
|
||||
|
||||
| Pattern | Tier | Src |
|
||||
|---|---|---|
|
||||
| Hallucinated package/method/URL refs: `import superjson_utils`, fake github URLs | HIGH | CACM (>half generated URLs hallucinated) |
|
||||
| Fabricated issue/ticket refs: `// Fixes BUG-001` -- only strip if ID resolves to nothing, else surface | MED | R2 |
|
||||
| Try/except wrapping everything | MED | Sohail |
|
||||
| Repeated defensive checks: `if (arr && arr.length>0)` repeated | MED | R2 |
|
||||
| Empty/generic catch-all: `catch (Exception e) {}` | MED | Diatom |
|
||||
| Placeholder TODO logic in otherwise-complete code | MED | R2 |
|
||||
| Ceremonial naming: `currentLoggedInUserAuthTokenValue` | MED | Kuryuliya |
|
||||
| Unnecessary helper/abstraction layers, duplicated utilities | MED-HIGH | GitHub Blog |
|
||||
| Inconsistent naming drift in one unit: `userData`->`user_data`->`data` | MED | R2 |
|
||||
| Tests asserting only happy path, CI gaming (`npm test \|\| true`, deleted tests) | HIGH | GitHub Blog |
|
||||
|
||||
## 6. DROPPED / weak-only (do NOT flag standalone)
|
||||
|
||||
>50% FP, mislabels non-native writers. At most a faint tiebreaker inside an already-flagged passage.
|
||||
|
||||
| Pattern | Why dropped |
|
||||
|---|---|
|
||||
| Em-dash overuse | folklore; heavy in essayists; suppressed in GPT-5.1 |
|
||||
| Rule of three / tricolon | standard human device |
|
||||
| "Not only...but also" | common in humans, esp. listicles |
|
||||
| Smart/curly quotes | auto-inserted by Word/Docs/CMS -- tool artifact |
|
||||
| Clean grammar, standalone "In conclusion" | classic FP (Constitution, ESL) |
|
||||
| Single vocab word (one "delve") | corpus signal is statistical over millions |
|
||||
| Title Case headings | also house style |
|
||||
| "Picture this / Imagine this" | standard human hook |
|
||||
| Sterile/"too clean" code | formatters confound; AI code "usually looks fantastic" |
|
||||
| 3+ blank lines, trailing whitespace | formatters collapse; safe cosmetic but weak tell |
|
||||
@@ -0,0 +1,21 @@
|
||||
# Flow: article
|
||||
|
||||
Formal essay / published blog / long-form prose.
|
||||
|
||||
## Stance
|
||||
- PASS 1 STRIP: ON, includes burstiness flattening.
|
||||
- PASS 2 INJECT: ON. Levers are burstiness + real stance + concrete specifics.
|
||||
|
||||
## PASS 1 -- strip (from @reference/ai-patterns.md)
|
||||
- Universal-strip sec 1: chat scaffolding, self-reference, broken markup; normalize unicode (note: em-dash/smart-quote overuse alone is a DROPPED weak tell -- normalize cosmetically, do not treat as proof).
|
||||
- Prose tells sec 2: rewrite "In today's fast-paced world", high-ratio phrases ("plays a significant role in shaping"), "It's important to note", "-ing" significance tails, copula avoidance ("serves as a testament"), rigid "Despite its challenges" templates; strip `**Key point:**` lead-ins.
|
||||
- Density-signals sec 4 (cluster only): excess-vocabulary cluster (delve/underscore/showcase/meticulous/tapestry), spatial metaphors, corporate verbs, promotional tone on neutral topics, transition stacking, "a wide range of", empty "it depends" hedge, negative parallelism, low/metronomic burstiness.
|
||||
|
||||
## PASS 2 -- inject (from @reference/human-patterns.md, "formal essay / published blog")
|
||||
ADD: high burstiness (mix sentence lengths by choice; read-aloud test); a real thesis / stated position -- don't hedge "it depends" without specifics; concrete specifics over abstraction; contractions, idiom, less-predictable word choice.
|
||||
AVOID: symmetric both-sides + "ultimately it depends", uniform rhythm, abstraction-stacking, "Moreover/Furthermore" chains.
|
||||
|
||||
## Hard-guards
|
||||
- NO injected typos / grammar errors -- loses authority instantly.
|
||||
- Never fabricate references, quotes, or citations. Omitting beats inventing.
|
||||
- Stance must be grounded, not invented persona.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Flow: code
|
||||
|
||||
Source code + inline comments + docstrings/JavaDoc/JSDoc/KDoc.
|
||||
|
||||
## Stance
|
||||
- PASS 1 STRIP: ON, heavy.
|
||||
- PASS 2 INJECT: HARD-OFF. Code and API docs are formal-contract -- only strip AI tells and normalize unicode/formatting. Never inject personality, contractions, stance, or burstiness.
|
||||
|
||||
## Sub-profile: API-docs / JavaDoc / docstring CLEAN-ONLY
|
||||
Triggered by javadoc/jsdoc/kdoc/docstring/"api doc" intent OR detecting `/** */`, `@param`/`@return`/`@throws`, KDoc, docstrings, OpenAPI descriptions.
|
||||
- Run strip-only. Inject stage fully disabled.
|
||||
- Keep high-level JavaDoc -- AI is good here; do NOT blanket-strip.
|
||||
- Strip a `@param`/`@return` line ONLY when its text == reworded identifier (`@param userId The user ID`).
|
||||
- Keep `@throws` with real conditions, contract notes, null-handling, boundaries.
|
||||
|
||||
## Load language reference (lazy)
|
||||
Read only the matching file:
|
||||
- `*.java`, `*.kt`, `*.groovy` -> `@reference/java.md`
|
||||
- `*.ts`, `*.tsx`, `*.js`, `*.jsx` -> `@reference/typescript.md`
|
||||
- `*.py` -> `@reference/python.md`
|
||||
- other languages (`*.go`, `*.rs`, `*.cpp`, ...) -> apply the universal code rules below; no dedicated reference.
|
||||
|
||||
## PASS 1 -- strip (from @reference/ai-patterns.md)
|
||||
Apply universal-strip (sec 1) on single instances:
|
||||
- AI self-attribution comments, bot trailers, prompt residue.
|
||||
- Unicode in code -> normalize to ASCII: em-dash -> `--`, arrows -> `->`/`<-`/`=>`, smart quotes -> `"`/`'`, bullets -> `-`.
|
||||
Apply code tells (sec 3):
|
||||
- Strip comment that restates the line below (zero added info) -- on density.
|
||||
- Strip line-by-line narration, tutorial framing ("Here we...", "Now we...").
|
||||
- Strip emoji in comments/debug output.
|
||||
- Strip docstring line only when it reworded the identifier.
|
||||
Density-signals (sec 4): act ONLY when several co-occur (e.g. banner overuse in a trivial file).
|
||||
|
||||
## Keep (WHY over WHAT)
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| `// Initialize the list` | `// Retry 3x due to flaky external API` |
|
||||
| `// Loop through items` | `// Uses UTC to match database timezone` |
|
||||
| `// Check if null` | `// Thread-safe: synchronized on class lock` |
|
||||
| Stale `// TODO: refactor this` | `// HACK: workaround for JDK-12345` |
|
||||
|
||||
Preserve BDD comments: `// GIVEN`, `// WHEN`, `// THEN`, `// AND`.
|
||||
|
||||
## Issue references
|
||||
Keep real project tickets (INTELDEV-XXXXX, JIRA-XXXXX, GH-XXX). Strip generic AI-invented (BUG-001, FIX-123, ISSUE-42) only when the ID resolves to nothing -- otherwise SURFACE for review.
|
||||
|
||||
## Surface-for-review -- NEVER auto-edit (from ai-patterns sec 5)
|
||||
Report, do not change: hallucinated package/method/URL refs, fabricated tickets, try/except-everything, empty catch-all, placeholder TODO logic, duplicated abstractions, naming drift, happy-path-only tests, CI gaming.
|
||||
|
||||
## Formatting (safe cosmetic)
|
||||
`/* single line */` -> `// single line`; 3+ blank lines -> max 2; trim trailing whitespace; mixed tabs/spaces -> spaces. These are weak tells -- cosmetic only.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Flow: docs
|
||||
|
||||
Technical prose: README, docs, guides, PR/commit messages, changelogs.
|
||||
|
||||
## Stance
|
||||
- PASS 1 STRIP: ON.
|
||||
- PASS 2 INJECT: ON, RESTRAINED. Imperative voice, concrete examples, terse. No marketing, no fake stance.
|
||||
|
||||
## PASS 1 -- strip (from @reference/ai-patterns.md)
|
||||
- Universal-strip sec 1: chat scaffolding, self-reference, broken markup; normalize unicode to ASCII.
|
||||
- Prose tells sec 2: rewrite "In today's fast-paced world", high-ratio phrases, "It's important to note", rigid "Despite its challenges" templates; strip `**Key point:**` bold lead-ins.
|
||||
- Density-signals sec 4: rewrite ONLY on cluster -- corporate verbs (leverage/utilize/seamless), spatial metaphors (landscape/realm/ecosystem), transition stacking (Furthermore/Moreover), "plays a crucial role", "a wide range of". Single instance = leave it.
|
||||
|
||||
## PASS 2 -- inject (from @reference/human-patterns.md, "technical docs / README")
|
||||
ADD: clear imperative ("Click Submit", "Run the build"); concrete runnable examples over abstraction; restrained conversational tone, contractions OK, second person; varied sentence lengths.
|
||||
AVOID: marketing/buzzwords, false-simplicity (`simply`, `it's easy`, `that simple`), `please` in instructions, slang, cutesy tone.
|
||||
GUARD: NO injected typos (a typo in a command/path breaks correctness). No fake stance.
|
||||
|
||||
## Sub-profile: PR / commit (terse)
|
||||
Triggered by pr/pull request/commit/changelog intent or commit-message content.
|
||||
ADD: terse subject (<=50 chars, 72 max), blank line, body ~72 cols; body = what + why (motivation, contrast with prior), not how; PR = short "What changed / Why / How to test".
|
||||
AVOID: fluff, "fixes stuff", restating the diff, padding, two-page templates.
|
||||
GUARD: no injected errors; NO fake tickets (fabricating IDs is worse than omitting); terseness IS the human signal; stance only as grounded "why".
|
||||
|
||||
## Surface-for-review
|
||||
Hallucinated URLs/refs and fabricated tickets -> surface, never silently insert or delete.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Flow: mixed (dispatcher)
|
||||
|
||||
Git commit OR folder with heterogeneous files. Split into blocks, route each file to its correct flow's rules, run parallel sub-agents, aggregate.
|
||||
|
||||
## When this flow is chosen
|
||||
- Scope is a 7+ hex git commit hash.
|
||||
- Scope is a folder containing more than one file type.
|
||||
Single file -> do NOT use mixed; pick the file's own flow and process directly.
|
||||
|
||||
## Phase 1 -- scope analysis
|
||||
|
||||
### Commit mode
|
||||
Process all text files from the commit. No extension filtering at listing time:
|
||||
`git diff --name-only <hash>^..<hash>`
|
||||
|
||||
### File inclusion
|
||||
| Include | Exclude |
|
||||
|---------|---------|
|
||||
| Source (`*.java`, `*.kt`, `*.py`, `*.ts`, `*.js`, `*.go`, `*.rs`, ...) | Binary (`*.class`, `*.pyc`, `*.exe`) |
|
||||
| Config (`*.xml`, `*.yaml`, `*.yml`, `*.json`, `*.toml`) | Images (`*.png`, `*.jpg`, `*.gif`, `*.ico`) |
|
||||
| Docs (`*.md`, `*.mdx`, `*.rst`, `*.txt`) | Archives (`*.zip`, `*.tar`, `*.gz`) |
|
||||
| Build (`pom.xml`, `package.json`, `pyproject.toml`) | Generated (`target/`, `build/`, `dist/`, `node_modules/`, `__pycache__/`) |
|
||||
| SQL (`*.sql`, `*.ddl`) | Lock files, IDE files |
|
||||
|
||||
### Path mode
|
||||
`find <path> -type f \( -name "*.java" -o -name "*.py" -o -name "*.ts" -o -name "*.js" -o -name "*.md" \) | grep -v -E "(target/|node_modules/|\.git/|build/|dist/|__pycache__/)" || true`
|
||||
|
||||
### Block count
|
||||
| Files | Lines | Blocks |
|
||||
|-------|-------|--------|
|
||||
| 1-2 | <200 | 1 (direct) |
|
||||
| 3-5 | <500 | 3 |
|
||||
| 6-10 | 500-1500 | 5 |
|
||||
| 11-20 | 1500-3000 | 7 |
|
||||
| 21+ | 3000+ | 10 |
|
||||
|
||||
## Phase 2 -- classify each file to a flow + model
|
||||
Per file, pick the sub-flow (its rules apply inside the block):
|
||||
- code extensions, docstrings, JavaDoc -> code flow (`@reference/flows/code.md`)
|
||||
- README/docs/guide/changelog/PR/commit text -> docs flow (`@reference/flows/docs.md`)
|
||||
- long-form essay/blog `.md`/`.mdx` -> article flow (`@reference/flows/article.md`)
|
||||
- chat/forum dumps -> social flow (`@reference/flows/social.md`)
|
||||
|
||||
Model split:
|
||||
- fast model (simple): config (`*.properties`, `*.yaml`, `*.toml`, `*.ini`), data (`*.json`, `*.csv`), text (`*.txt`, `*.md`), single-statement SQL, files <50 lines no logic.
|
||||
- balanced model (complex): source with logic, tests, complex SQL (CTEs/windows/JOINs), config classes.
|
||||
|
||||
## Phase 3 -- block formation
|
||||
Group by type and complexity (avoid mixing fast model/balanced model in one block), balance line count, keep related files together (same package/dir). Data-file block (YAML/JSON/CSV with comments) -> fast model for unicode fixes.
|
||||
|
||||
## Phase 4 -- parallel execution
|
||||
Launch ALL sub-agent calls in a single message for true parallelism. Each block prompt states its files, the sub-flow each file uses, the two-pass rules, and requests JSON.
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role="developer", reasoning_tier="fast model", message="> <plugin-root> is in your context (pre-task.mjs).\n[CUSTOM_INSTRUCTIONS_IF_ANY]\nBlock 1 files: [...]. Per file apply its flow rules from $<plugin-root>/skills/text-human/reference/flows/<flow>.md plus ai-patterns.md / human-patterns.md. Two-pass: STRIP then gated INJECT. Return JSON.")
|
||||
Codex delegation brief (task_role="developer", reasoning_tier="balanced model", message="> <plugin-root> is in your context (pre-task.mjs).\n[CUSTOM_INSTRUCTIONS_IF_ANY]\nBlock 2 files: [...]. Same rules. Return JSON.")
|
||||
```
|
||||
|
||||
If a custom prompt was provided, prepend to EVERY sub-agent prompt after the context line:
|
||||
```
|
||||
CUSTOM INSTRUCTIONS (highest priority, override defaults):
|
||||
<user prompt text>
|
||||
---
|
||||
```
|
||||
|
||||
JSON output per agent:
|
||||
```
|
||||
{
|
||||
"files_processed": N,
|
||||
"changes": [{"file": "path", "flow": "code|docs|article|social", "stripped": N, "injected": N, "surfaced": N}],
|
||||
"surfaced_for_review": [{"file": "path", "line": N, "issue": "..."}]
|
||||
}
|
||||
```
|
||||
|
||||
## Phase 5 -- aggregation
|
||||
Collect JSON from all agents -> merge stats -> unified Humanization Report (see SKILL.md report format). Surface every `surfaced_for_review` item; never auto-applied.
|
||||
|
||||
## Error handling
|
||||
| Error | Action |
|
||||
|-------|--------|
|
||||
| Agent timeout | Continue with other blocks |
|
||||
| File read error | Skip, note in report |
|
||||
| Binary file | Skip, note in report |
|
||||
| No changes | Report "No humanization required" |
|
||||
@@ -0,0 +1,27 @@
|
||||
# Flow: social
|
||||
|
||||
reddit / forum / slack / discord / chat.
|
||||
|
||||
## Stance
|
||||
- PASS 1 STRIP: ON (chat residue).
|
||||
- PASS 2 INJECT: ON, casual. Strongest humanization budget -- but emoji density is itself a tell.
|
||||
|
||||
## PASS 1 -- strip (from @reference/ai-patterns.md)
|
||||
- Universal-strip sec 1: chat scaffolding ("Certainly!", "Great question!", "I hope this helps", "Here's the rewritten..."), self-reference, broken markup.
|
||||
- Normalize stray unicode arrows/em-dash in plain claims.
|
||||
- Prose tells sec 2 + density sec 4: rewrite essay-tone openers, corporate verbs, balanced both-sides hedge -- these read fake in chat.
|
||||
|
||||
## PASS 2 -- inject (from @reference/human-patterns.md)
|
||||
### reddit / forum
|
||||
ADD: lowercase sentence starts; `imo`/`tbh`; hedges `imo/ymmv/afaik` on real claims; anecdote-led; parenthetical asides; self-deprecation; high burstiness, fragments OK.
|
||||
AVOID: essay intros/conclusions, "In conclusion", balanced both-sides, marketing polish, uniform sentences.
|
||||
|
||||
### slack / discord / chat
|
||||
ADD: fragments; no capitalization; one thought per line; emoji sparingly+naturally (reaction not decoration); heavy contractions; clipped ("on it", "lgtm", "wfm").
|
||||
AVOID: greetings/sign-offs, full paragraphs, numbered lists for a quick reply, emoji on every line.
|
||||
|
||||
## Hard-guards
|
||||
- Emoji density IS the tell -- over-injection reads bot-like. Sparse + natural only.
|
||||
- Don't manufacture slang the poster wouldn't use (mismatched slang reads faker than formality).
|
||||
- Typos incidental only, NEVER manufactured. Let absence-of-capitalization carry casualness.
|
||||
- Never fabricate references.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Human-Injection Patterns by Domain (PASS 2)
|
||||
|
||||
Gated, separate from strip. Classify register FIRST -- misclassification is the main failure mode. Human-ness = burstiness (sentence-length variance) + concreteness (specific detail) + stance (real opinion) + register fit. NOT injected errors.
|
||||
|
||||
---
|
||||
|
||||
## GLOBAL HARD-GUARDS (apply before any domain rule)
|
||||
|
||||
1. NEVER inject typos / misspellings / grammar errors. A typo in a command/path/identifier breaks correctness. Tolerable only as incidental in casual social -- never manufactured. FORBIDDEN in code, docs, API, commit/PR, formal.
|
||||
2. API / formal / legal = STRIP-ONLY, inject DISABLED. Detect `/** */`, `@param`/`@return`/`@throws`, KDoc, docstrings, OpenAPI descriptions, legal text -> turn off inject, run strip-only.
|
||||
3. NEVER fabricate references -- tickets, URLs, package names, citations. Omitting beats inventing.
|
||||
4. Tune by weight, not on/off. Burstiness HIGH for essay/reddit, LOW for commit/API. Emoji incidental-only in chat. Hedges reddit-only, on real claims.
|
||||
|
||||
---
|
||||
|
||||
## reddit / forum
|
||||
ADD: lowercase sentence starts; `imo`/`tbh` (lowercase=casual); hedges `imo/ymmv/afaik` on real claims; anecdote-led; parenthetical asides; self-deprecation; high burstiness, fragments OK.
|
||||
AVOID: essay intros/conclusions, "In conclusion", balanced both-sides, marketing polish, uniform sentences.
|
||||
GUARD: don't manufacture slang the poster wouldn't use. Typos incidental only.
|
||||
|
||||
## slack / discord / chat
|
||||
ADD: fragments; no capitalization; one thought per line; emoji sparingly+naturally (reaction not decoration); heavy contractions; clipped ("on it", "lgtm", "wfm").
|
||||
AVOID: greetings/sign-offs, full paragraphs, numbered lists for a quick reply, emoji on every line.
|
||||
GUARD: emoji density IS the tell -- over-injection reads bot-like. No fabricated typos.
|
||||
|
||||
## technical docs / README
|
||||
ADD: clear imperative ("Click Submit", "Run the build"); concrete runnable examples over abstraction; conversational-but-restrained, contractions OK, second person; varied sentence lengths.
|
||||
AVOID: marketing/buzzwords, false-simplicity (`simply`, `it's easy`, `that simple`), `please` in instructions, slang, cutesy tone.
|
||||
GUARD: NO injected typos (breaks commands). No fake stance. Humanize via tone + concrete examples only.
|
||||
|
||||
## commit / PR
|
||||
ADD: terse subject (<=50 chars, 72 max), blank line, body ~72 cols; body = what + why (motivation, contrast with prior), not how; real ticket refs in footer; PR = short "What changed / Why / How to test", answerable <1 min.
|
||||
AVOID: fluff, "fixes stuff", restating the diff, padding, two-page templates.
|
||||
GUARD: no injected errors; NO fake tickets; no personality -- terseness IS the human signal; stance only as grounded "why".
|
||||
|
||||
## formal essay / published blog
|
||||
ADD: high burstiness (mix lengths by choice; read-aloud test); a real thesis / stated position -- don't hedge "it depends" without specifics; concrete specifics over abstraction; contractions, idiom, less-predictable word choice.
|
||||
AVOID: symmetric both-sides + "ultimately it depends", uniform rhythm, abstraction-stacking, "Moreover/Furthermore" chains.
|
||||
GUARD: NO injected typos/grammar errors (loses authority). Burstiness + stance are the levers.
|
||||
|
||||
## JavaDoc / API docs -- CLEAN-ONLY (inject DISABLED)
|
||||
ADD (precision, not personality): third-person imperative ("Gets the foo", "Returns the bar" -- NOT "Get the foo"); explicit contract (null-handling, boundaries, ranges, corner cases); `@param`/`@return` lowercase, no trailing period; `@throws` conditional ("if the file could not be found"); lean, precise.
|
||||
AVOID: explaining common terms, restating trivial impl, marketing, narrative voice, opinion.
|
||||
GUARD (strongest): NEVER inject informality, contractions, stance, asides, burstiness-for-flavor, lowercase, typos. On API-doc context -> DISABLE inject, run strip-only.
|
||||
|
||||
---
|
||||
|
||||
## Per-domain weight matrix
|
||||
|
||||
| Signal | reddit | chat | tech-docs | commit/PR | essay/blog | API/formal |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Burstiness | high | high | med | low | high | OFF |
|
||||
| Contractions | high | high | OK | low | high | FORBIDDEN |
|
||||
| Stance/opinion | high | med | none | grounded-why | high | FORBIDDEN |
|
||||
| Emoji | rare | incidental | no | no | no | no |
|
||||
| Hedges (imo/ymmv) | yes (real claims) | rare | no | no | no | no |
|
||||
| Concrete specifics | high | med | high (examples) | high (why) | high | high (contract) |
|
||||
| Inject stage | ON | ON | ON | ON (terse) | ON | OFF |
|
||||
@@ -0,0 +1,216 @@
|
||||
# Java/Kotlin Reference
|
||||
|
||||
> Language-specific rules for text-human skill
|
||||
|
||||
## File Classification
|
||||
|
||||
### fast model (Simple)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Resources | `logback*.xml`, `pom.xml`, `build.gradle` |
|
||||
| Small DTOs | `*.java` <50 lines, no logic |
|
||||
| Properties | `application.properties`, `bootstrap.yml` |
|
||||
|
||||
### balanced model (Complex)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Business logic | `*.java`, `*.kt` with logic |
|
||||
| Tests | `*Test.java`, `*Spec.kt`, `*IT.java` |
|
||||
| Config classes | `*Configuration.java`, `*Config.java` |
|
||||
| Controllers | `*Controller.java`, `*RestController.java` |
|
||||
| Services | `*Service.java`, `*ServiceImpl.java` |
|
||||
| Repositories | `*Repository.java`, `*Dao.java` |
|
||||
| Mapper XML | MyBatis/JOOQ dynamic SQL |
|
||||
|
||||
### Classification Logic
|
||||
|
||||
| Extension | Condition | Result |
|
||||
|-----------|-----------|--------|
|
||||
| java, kt | Contains `@Test`, `@Configuration`, `@Service`, `@Repository` | COMPLEX |
|
||||
| java, kt | <50 lines, no nested classes | SIMPLE |
|
||||
| java, kt | Otherwise | COMPLEX |
|
||||
| groovy | Spock tests, Gradle scripts | COMPLEX |
|
||||
|
||||
---
|
||||
|
||||
## JavaDoc Cleanup
|
||||
|
||||
Remove JavaDoc from: private methods, test files, obvious classes. Keep logic description, remove trivial @param/@return.
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| All private/package-private methods | Public API docs with non-obvious behavior |
|
||||
| All test files (`*Test.java`, `*Spec.kt`, test helpers) | @DisplayName on test methods |
|
||||
| Obvious DTOs/Entities (name describes purpose) | Complex business logic explanation |
|
||||
| All @param (restates param name) | `@throws` with specific conditions |
|
||||
| All @return (restates method name) | Non-obvious side effects |
|
||||
| Class doc when class name is self-explanatory | External API contracts |
|
||||
|
||||
### Logic vs Parameters Rule
|
||||
|
||||
**NEVER convert JavaDoc `/** */` to inline `//` comment.** Two rules:
|
||||
|
||||
1. **JavaDoc is unnecessary** (private method, obvious class) → DELETE entirely, no replacement
|
||||
2. **JavaDoc has useful description but trivial @param/@return** → strip @param/@return, keep description as single-line `/** ... */`
|
||||
|
||||
```java
|
||||
// BEFORE - useful description + trivial @param/@return:
|
||||
/**
|
||||
* Converts USD to target currency. Returns unchanged if USD.
|
||||
* @param amount the amount
|
||||
* @param currency the currency
|
||||
* @return converted amount
|
||||
*/
|
||||
|
||||
// AFTER - keep description as single-line JavaDoc, strip @param/@return:
|
||||
/** Converts USD to target currency. Returns unchanged if USD. */
|
||||
BigDecimal convertCurrency(BigDecimal amount, CurrencyCode currency) { }
|
||||
|
||||
// KEEP @param - explains non-obvious behavior:
|
||||
/**
|
||||
* @param hasExtraStops true=include ALL loads, false/null=only WITHOUT extra stops
|
||||
*/
|
||||
```
|
||||
|
||||
### Private Methods
|
||||
|
||||
```java
|
||||
// REMOVE - private methods don't need JavaDoc:
|
||||
/** Validates the filter. */
|
||||
private boolean isValidFilter(Filter f) { }
|
||||
|
||||
/** Builds full condition. */
|
||||
private Condition buildFullCondition() { }
|
||||
|
||||
/** Rounds value to scale. @param value the value @param scale the scale */
|
||||
private Double roundToScale(double value, int scale) { }
|
||||
```
|
||||
|
||||
### Obvious DTOs/Entities
|
||||
|
||||
```java
|
||||
// REMOVE - class name is self-explanatory:
|
||||
/** Filter DTO for rates history queries. */
|
||||
@Value @Builder
|
||||
public class RatesHistoryFilter { }
|
||||
|
||||
/** Entity for rate history data. */
|
||||
@Value @Builder
|
||||
public class HistoryRateEntity { }
|
||||
|
||||
// KEEP - adds non-obvious context:
|
||||
/** Cached for 1 hour. Thread-safe via copy-on-write. */
|
||||
public class CompanyCache { }
|
||||
```
|
||||
|
||||
### Test Files
|
||||
|
||||
Remove all JavaDoc from: test classes (`*Test.java`, `*Spec.kt`), test helpers (`*Data.java`, `*Requests.java`, `*Fixtures.java`), any file in `src/test/`.
|
||||
|
||||
```java
|
||||
// REMOVE - test class:
|
||||
/** Test class for LoadsHistoryRepository. */
|
||||
class LoadsHistoryRepositoryTest {
|
||||
/** Tests filtering by company ID. */
|
||||
@Test void testFilterByCompanyId() { }
|
||||
}
|
||||
|
||||
// REMOVE - test helper classes:
|
||||
/** Expected data for rates stats tests. */
|
||||
@UtilityClass
|
||||
public class RatesStatsExpectedData {
|
||||
/** Factory method for expected stats. */
|
||||
public static Stats expected() { }
|
||||
}
|
||||
|
||||
// CORRECT - no JavaDoc, clear method names:
|
||||
class LoadsHistoryRepositoryTest {
|
||||
@Test
|
||||
@DisplayName("Should filter loads by company ID")
|
||||
void filterByCompanyId() {
|
||||
// GIVEN
|
||||
// WHEN
|
||||
// THEN
|
||||
}
|
||||
}
|
||||
|
||||
@UtilityClass
|
||||
public class RatesStatsExpectedData {
|
||||
public static Stats expected() { }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comments
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| `// Initialize the list` | `// Retry 3x due to flaky external API` |
|
||||
| `// Loop through items` | `// Uses UTC to match database timezone` |
|
||||
| `// Check if null` | `// Thread-safe: synchronized on class lock` |
|
||||
| Stale `// TODO: refactor this` | `// HACK: workaround for JDK-12345` |
|
||||
|
||||
Preserve all BDD comments: `// GIVEN`, `// WHEN`, `// THEN`, `// AND`
|
||||
|
||||
---
|
||||
|
||||
## Issue References
|
||||
|
||||
```java
|
||||
// REMOVE - AI-invented numbers:
|
||||
// BUG-001 fix: ...
|
||||
// FIX-123: ...
|
||||
// ISSUE-42: ...
|
||||
|
||||
// KEEP - real ClickUp/Jira tickets:
|
||||
// INTELDEV-19207: ...
|
||||
// Workaround for JIRA-12345
|
||||
```
|
||||
|
||||
Keep project-specific ticket patterns (INTELDEV-XXXXX, JIRA-XXXXX). Remove generic patterns (BUG-001, FIX-123).
|
||||
|
||||
---
|
||||
|
||||
## Pre-Completion Checklist
|
||||
|
||||
| Check | Rule |
|
||||
|-------|------|
|
||||
| [ ] | Private methods: no JavaDoc on private/protected/package-private methods |
|
||||
| [ ] | Test files: no JavaDoc on `*Test.java`, `*Spec.kt`, or any file in `src/test/` |
|
||||
| [ ] | Test helpers: no JavaDoc on `*Data.java`, `*Requests.java`, `*Fixtures.java` |
|
||||
| [ ] | Obvious DTOs: no JavaDoc if class name is self-explanatory |
|
||||
| [ ] | Trivial @param: no `@param id the id` or similar restating param name |
|
||||
| [ ] | Trivial @return: no `@return` that restates method name |
|
||||
| [ ] | Lombok: no docs on `@Value`, `@Data`, `@Builder` classes unless non-obvious |
|
||||
|
||||
### Scan Pattern
|
||||
|
||||
```
|
||||
# Semantic search queries for grepai_search:
|
||||
grepai_search("private methods with javadoc")
|
||||
grepai_search("trivial param documentation")
|
||||
grepai_search("test helper classes")
|
||||
grepai_search("DTO classes with javadoc")
|
||||
|
||||
# Fallback grep patterns:
|
||||
grep -n "^\s*/\*\*" <file> // Find all JavaDoc
|
||||
grep -n "private.*{" <file> // Find private methods
|
||||
```
|
||||
|
||||
### Files to Double-Check
|
||||
|
||||
Repository classes (private helper methods), Service classes (internal methods), Test helper classes (not ending in `Test`), DTOs/Entities (redundant class-level JavaDoc).
|
||||
|
||||
---
|
||||
|
||||
## File Inclusion
|
||||
|
||||
| Include | Exclude |
|
||||
|---------|---------|
|
||||
| `*.java`, `*.kt`, `*.groovy` | `*.class`, `*.jar`, `*.war` |
|
||||
| `pom.xml`, `build.gradle`, `build.gradle.kts` | `target/`, `build/`, `.gradle/` |
|
||||
| `*.xml` (Spring, MyBatis) | Generated sources |
|
||||
| `*.properties`, `*.yaml`, `*.yml` | IDE files (`.idea/`, `*.iml`) |
|
||||
@@ -0,0 +1,241 @@
|
||||
# Python Reference
|
||||
|
||||
> Language-specific rules for text-human skill
|
||||
|
||||
## File Classification
|
||||
|
||||
### fast model (Simple)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Config | `*.toml`, `pyproject.toml`, `setup.cfg` |
|
||||
| Requirements | `requirements*.txt`, `constraints.txt` |
|
||||
| Constants | `constants.py`, `config.py` (pure values) |
|
||||
| Init files | `__init__.py` (imports only) |
|
||||
| Type stubs | `*.pyi` |
|
||||
|
||||
### balanced model (Complex)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Business logic | `*.py` with classes/functions |
|
||||
| Tests | `test_*.py`, `*_test.py`, `conftest.py` |
|
||||
| CLI | `cli.py`, `__main__.py` |
|
||||
| APIs | `*_api.py`, `routes.py`, `views.py` |
|
||||
| Models | `models.py`, `schemas.py` |
|
||||
| Services | `*_service.py`, `services/*.py` |
|
||||
|
||||
### Classification Logic
|
||||
|
||||
| Extension | Condition | Result |
|
||||
|-----------|-----------|--------|
|
||||
| py | Contains `def test_`, `@pytest`, `class Test` | COMPLEX |
|
||||
| py | <30 lines, only imports/constants | SIMPLE |
|
||||
| py | Has classes with methods | COMPLEX |
|
||||
| py | Otherwise | COMPLEX |
|
||||
| pyi | Type stubs | SIMPLE |
|
||||
|
||||
---
|
||||
|
||||
## Docstring Cleanup
|
||||
|
||||
Remove docstrings from: private methods, test files, obvious functions. Keep public API docs.
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| Private methods (`_method`, `__method`) | Public API |
|
||||
| Test files (`test_*.py`) | Complex algorithms |
|
||||
| Obvious functions (name = purpose) | Non-obvious side effects |
|
||||
| Trivial Args restating name | Raises with conditions |
|
||||
| Trivial Returns restating function | Examples with edge cases |
|
||||
| Class docstring when name is clear | Type explanations for Any/Union |
|
||||
|
||||
### Docstring Styles
|
||||
|
||||
Handle all common styles (Google, NumPy, Sphinx):
|
||||
|
||||
```python
|
||||
# REMOVE - trivial Google style:
|
||||
def get_user(user_id: int) -> User:
|
||||
"""Get user by ID.
|
||||
|
||||
Args:
|
||||
user_id: The user ID.
|
||||
|
||||
Returns:
|
||||
The user object.
|
||||
"""
|
||||
|
||||
# CORRECT - no docstring needed:
|
||||
def get_user(user_id: int) -> User:
|
||||
...
|
||||
|
||||
# KEEP - adds context:
|
||||
def get_user(user_id: int) -> User:
|
||||
"""Fetches from cache first, falls back to DB. Returns None if not found."""
|
||||
```
|
||||
|
||||
### Private Methods
|
||||
|
||||
```python
|
||||
# REMOVE - private methods don't need docstrings:
|
||||
def _validate_input(self, data: dict) -> bool:
|
||||
"""Validate the input data."""
|
||||
|
||||
def __calculate_hash(self, value: str) -> int:
|
||||
"""Calculate hash for value."""
|
||||
|
||||
# CORRECT - no docstrings:
|
||||
def _validate_input(self, data: dict) -> bool:
|
||||
...
|
||||
|
||||
def __calculate_hash(self, value: str) -> int:
|
||||
...
|
||||
```
|
||||
|
||||
### Classes
|
||||
|
||||
```python
|
||||
# REMOVE - obvious class:
|
||||
class UserRepository:
|
||||
"""Repository for user operations."""
|
||||
|
||||
class OrderService:
|
||||
"""Service for order management."""
|
||||
|
||||
# CORRECT - no docstring:
|
||||
class UserRepository:
|
||||
...
|
||||
|
||||
# KEEP - non-obvious behavior:
|
||||
class RateLimiter:
|
||||
"""Token bucket algorithm. Thread-safe. Tokens refill every 100ms."""
|
||||
```
|
||||
|
||||
### Dunder Methods
|
||||
|
||||
```python
|
||||
# REMOVE - dunder methods are well-known:
|
||||
def __init__(self, name: str):
|
||||
"""Initialize with name."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return string representation."""
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return length."""
|
||||
|
||||
# CORRECT - no docstrings on dunders:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
# EXCEPTION - keep if non-standard behavior:
|
||||
def __eq__(self, other) -> bool:
|
||||
"""Compares by ID only, ignores other fields."""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comments
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| `# Initialize variable` | `# Workaround for Python 3.9 bug` |
|
||||
| `# Loop through items` | `# Must be eager (not lazy) for thread safety` |
|
||||
| `# Check if None` | `# noqa: E501 - URL cannot be split` |
|
||||
| `# TODO: refactor` | `# HACK: see https://bugs.python.org/12345` |
|
||||
|
||||
### Type Comments
|
||||
|
||||
```python
|
||||
# REMOVE - use type hints instead:
|
||||
x = [] # type: List[int]
|
||||
y = None # type: Optional[str]
|
||||
|
||||
# CORRECT - inline type hints:
|
||||
x: list[int] = []
|
||||
y: str | None = None
|
||||
|
||||
# KEEP - when type hints not possible:
|
||||
# type: ignore[arg-type] # mypy false positive
|
||||
```
|
||||
|
||||
### Noqa Comments
|
||||
|
||||
| Action | Example |
|
||||
|--------|---------|
|
||||
| KEEP | `# noqa: E501 - long URL` (with reason) |
|
||||
| KEEP | `# type: ignore[override]` (mypy) |
|
||||
| REMOVE | `# noqa` without code or reason |
|
||||
| REVIEW | Multiple noqa in one file (code smell) |
|
||||
|
||||
---
|
||||
|
||||
## Test Files
|
||||
|
||||
Remove all docstrings from: test files, fixtures, conftest.py.
|
||||
|
||||
```python
|
||||
# REMOVE - test file:
|
||||
class TestUserService:
|
||||
"""Tests for UserService."""
|
||||
|
||||
def test_create_user(self):
|
||||
"""Test creating a user."""
|
||||
|
||||
# CORRECT - no docstrings:
|
||||
class TestUserService:
|
||||
def test_create_user(self):
|
||||
# Arrange
|
||||
# Act
|
||||
# Assert
|
||||
...
|
||||
|
||||
# REMOVE - fixtures:
|
||||
@pytest.fixture
|
||||
def mock_user():
|
||||
"""Create mock user for tests."""
|
||||
|
||||
# CORRECT:
|
||||
@pytest.fixture
|
||||
def mock_user():
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Completion Checklist
|
||||
|
||||
| Check | Rule |
|
||||
|-------|------|
|
||||
| [ ] | No docstrings on private methods (`_method`) |
|
||||
| [ ] | No docstrings on dunder methods (unless non-standard) |
|
||||
| [ ] | No docstrings on test files |
|
||||
| [ ] | No trivial Args/Returns in docstrings |
|
||||
| [ ] | No type comments when type hints work |
|
||||
| [ ] | `# noqa` comments have explanations |
|
||||
|
||||
### Scan Pattern
|
||||
|
||||
```
|
||||
# Semantic search:
|
||||
grepai_search("private methods with docstrings")
|
||||
grepai_search("trivial docstring parameters")
|
||||
grepai_search("test file documentation")
|
||||
|
||||
# Fallback grep:
|
||||
grep -n '"""' <file> # Find docstrings
|
||||
grep -n "def _" <file> # Find private methods
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Inclusion
|
||||
|
||||
| Include | Exclude |
|
||||
|---------|---------|
|
||||
| `*.py`, `*.pyi` | `__pycache__/` |
|
||||
| `*.toml`, `*.cfg`, `*.ini` | `*.pyc`, `*.pyo` |
|
||||
| `*.txt` (requirements) | `.venv/`, `venv/`, `.env/` |
|
||||
| `*.md`, `*.rst` | `dist/`, `build/`, `*.egg-info/` |
|
||||
| `Makefile`, `Dockerfile` | `.tox/`, `.pytest_cache/` |
|
||||
@@ -0,0 +1,206 @@
|
||||
# TypeScript/JavaScript/React Reference
|
||||
|
||||
> Language-specific rules for text-human skill
|
||||
|
||||
## File Classification
|
||||
|
||||
### fast model (Simple)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Config | `*.json`, `tsconfig.json`, `package.json`, `.eslintrc.*` |
|
||||
| Constants | `constants.ts`, `config.ts` (pure exports) |
|
||||
| Types only | `*.d.ts`, `types.ts`, `interfaces.ts` |
|
||||
| Styles | `*.css`, `*.scss`, `*.less` |
|
||||
|
||||
### balanced model (Complex)
|
||||
|
||||
| Type | Patterns |
|
||||
|------|----------|
|
||||
| Components | `*.tsx`, `*.jsx` with logic |
|
||||
| Hooks | `use*.ts`, `use*.tsx` |
|
||||
| Services | `*Service.ts`, `*Api.ts` |
|
||||
| Tests | `*.test.ts`, `*.spec.ts`, `*.test.tsx` |
|
||||
| Utilities | `utils/*.ts` with business logic |
|
||||
| State | `*Slice.ts`, `*Store.ts`, `*Context.tsx` |
|
||||
|
||||
### Classification Logic
|
||||
|
||||
| Extension | Condition | Result |
|
||||
|-----------|-----------|--------|
|
||||
| ts, tsx | Contains `describe(`, `it(`, `test(` | COMPLEX |
|
||||
| ts, tsx | <30 lines, only types/interfaces | SIMPLE |
|
||||
| ts, tsx | React component with hooks/effects | COMPLEX |
|
||||
| js, jsx | Otherwise | COMPLEX |
|
||||
| json | Pure data | SIMPLE (often skip) |
|
||||
|
||||
---
|
||||
|
||||
## JSDoc Cleanup
|
||||
|
||||
Remove JSDoc from: private functions, test files, obvious components. Keep public API docs.
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| Internal/private functions | Exported public API |
|
||||
| Test files (`*.test.ts`, `*.spec.ts`) | Complex utility functions |
|
||||
| Obvious components (name = purpose) | Non-obvious side effects |
|
||||
| Trivial `@param` restating name | `@throws`, `@deprecated` |
|
||||
| Trivial `@returns` restating function name | `@example` with usage |
|
||||
| `@type` when TypeScript infers | Generic type explanations |
|
||||
|
||||
### JSDoc vs TypeScript
|
||||
|
||||
Prefer TypeScript types over JSDoc when both exist:
|
||||
|
||||
```typescript
|
||||
// REMOVE - redundant JSDoc with TS types:
|
||||
/**
|
||||
* @param {string} name - The user name
|
||||
* @param {number} age - The user age
|
||||
* @returns {User} The created user
|
||||
*/
|
||||
function createUser(name: string, age: number): User { }
|
||||
|
||||
// KEEP - only TS types:
|
||||
function createUser(name: string, age: number): User { }
|
||||
|
||||
// KEEP - adds context beyond types:
|
||||
/** Creates user and sends welcome email. Throws if email fails. */
|
||||
function createUser(name: string, age: number): User { }
|
||||
```
|
||||
|
||||
### React Components
|
||||
|
||||
```tsx
|
||||
// REMOVE - obvious component:
|
||||
/** Button component that renders a button. */
|
||||
const Button: FC<ButtonProps> = ({ onClick, children }) => { }
|
||||
|
||||
/** User profile card component. */
|
||||
const UserCard: FC<UserCardProps> = ({ user }) => { }
|
||||
|
||||
// KEEP - non-obvious behavior:
|
||||
/** Debounces input by 300ms. Calls onChange only after user stops typing. */
|
||||
const DebouncedInput: FC<InputProps> = ({ onChange }) => { }
|
||||
|
||||
// KEEP - complex props explanation:
|
||||
/**
|
||||
* @param renderItem - Custom renderer, receives (item, index, isLast)
|
||||
* @param onEndReached - Called when scroll reaches 80% of list
|
||||
*/
|
||||
const VirtualList: FC<ListProps> = ({ renderItem, onEndReached }) => { }
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
```tsx
|
||||
// REMOVE - obvious hook:
|
||||
/** Hook that manages loading state. */
|
||||
const useLoading = () => { }
|
||||
|
||||
/** Hook for user data. */
|
||||
const useUser = (id: string) => { }
|
||||
|
||||
// KEEP - non-obvious behavior:
|
||||
/** Fetches on mount + refetches every 30s. Pauses when tab inactive. */
|
||||
const usePolling = (url: string, interval = 30000) => { }
|
||||
|
||||
/** Returns memoized callback. Deps auto-tracked via proxy. */
|
||||
const useAutoCallback = <T extends Function>(fn: T): T => { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comments
|
||||
|
||||
| Remove | Keep |
|
||||
|--------|------|
|
||||
| `// Initialize state` | `// Workaround for React 18 strict mode` |
|
||||
| `// Map over items` | `// Must be sync for Safari compatibility` |
|
||||
| `// Check if null` | `// Intentionally not memoized - cheap to compute` |
|
||||
| `// TODO: fix later` | `// HACK: workaround for webpack/webpack#12345` |
|
||||
|
||||
### ESLint/TSLint Comments
|
||||
|
||||
| Action | Example |
|
||||
|--------|---------|
|
||||
| KEEP | `// eslint-disable-next-line @typescript-eslint/no-explicit-any` |
|
||||
| KEEP | `/* eslint-disable */` at file top with reason |
|
||||
| REMOVE | `// eslint-disable` without explanation |
|
||||
| REVIEW | Multiple disables in one file (code smell) |
|
||||
|
||||
### Preserve
|
||||
|
||||
- `// @ts-expect-error` with explanation
|
||||
- `// @ts-ignore` (but prefer @ts-expect-error)
|
||||
- Region comments `// #region`, `// #endregion`
|
||||
|
||||
---
|
||||
|
||||
## Test Files
|
||||
|
||||
Remove all JSDoc from: test files, test utilities, mocks, fixtures.
|
||||
|
||||
```typescript
|
||||
// REMOVE - test file:
|
||||
/** Tests for UserService. */
|
||||
describe('UserService', () => {
|
||||
/** Should create user with valid data. */
|
||||
it('creates user', () => { })
|
||||
})
|
||||
|
||||
// CORRECT - no JSDoc, clear descriptions:
|
||||
describe('UserService', () => {
|
||||
it('creates user with valid data', () => {
|
||||
// Arrange
|
||||
// Act
|
||||
// Assert
|
||||
})
|
||||
})
|
||||
|
||||
// REMOVE - test utilities:
|
||||
/** Creates mock user for tests. */
|
||||
export const createMockUser = () => { }
|
||||
|
||||
// CORRECT:
|
||||
export const createMockUser = () => { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Completion Checklist
|
||||
|
||||
| Check | Rule |
|
||||
|-------|------|
|
||||
| [ ] | No JSDoc on internal/unexported functions |
|
||||
| [ ] | No JSDoc on test files (`*.test.ts`, `*.spec.ts`) |
|
||||
| [ ] | No redundant JSDoc when TS types exist |
|
||||
| [ ] | No JSDoc on obvious React components |
|
||||
| [ ] | ESLint disables have explanations |
|
||||
| [ ] | No `@param`/`@returns` restating obvious info |
|
||||
|
||||
### Scan Pattern
|
||||
|
||||
```
|
||||
# Semantic search:
|
||||
grepai_search("jsdoc on private functions")
|
||||
grepai_search("redundant type annotations")
|
||||
grepai_search("test file documentation")
|
||||
|
||||
# Fallback grep:
|
||||
grep -n "/\*\*" <file> // Find JSDoc
|
||||
grep -n "^const.*=" <file> // Find functions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Inclusion
|
||||
|
||||
| Include | Exclude |
|
||||
|---------|---------|
|
||||
| `*.ts`, `*.tsx`, `*.js`, `*.jsx` | `*.d.ts` (generated) |
|
||||
| `*.mjs`, `*.cjs` | `node_modules/` |
|
||||
| `*.json` (with comments) | `dist/`, `build/`, `.next/` |
|
||||
| `*.md`, `*.mdx` | `coverage/` |
|
||||
| Config files | Lock files (`*.lock`, `*-lock.json`) |
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025-2026 Maxim Kochetkov (kochetkov-ma)
|
||||
https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# Text Optimizer
|
||||
|
||||
Optimizes text files for LLM token efficiency with 4 compression modes — from light cleanup to deep dictionary-encoded compression for LLM-only documents. Applies 40+ validated rules for current Codex models, supports smart auto-detection of optimal mode, and verifies no information loss. Works on single files, multiple files in parallel, or entire directories.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
$brewtools:text-optimize AGENTS.md
|
||||
```
|
||||
|
||||
Auto-detects optimal mode for the file (deep for AGENTS.md, standard for README.md), applies all standard transformations, and prints a report with token savings.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Flag | Best For | What Changes |
|
||||
|------|------|----------|--------------|
|
||||
| **Light** | `-l` | Critical files, production prompts | Filler removal, tone fixes, reference checks — structure untouched |
|
||||
| **Medium** | _(default)_ | General docs, agents, skills | Tables, bullets, merged sections, full rule set |
|
||||
| **Standard** | `-s` | README, docs, user-facing content | 30-50% compression preserving human readability. Filler removal, paragraph→bullets, prose→tables. 1 verification round |
|
||||
| **Deep** | `-d` | AGENTS.md, system prompts, agent/skill defs | 2-3x compression for LLM-only consumption. Dictionary encoding, symbol substitutions, abbreviation tables. 2 verification rounds |
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Usage
|
||||
|
||||
```bash
|
||||
# Single file, medium mode (default)
|
||||
$brewtools:text-optimize AGENTS.md
|
||||
|
||||
# Light mode on a production agent -- safe, minimal changes
|
||||
$brewtools:text-optimize -l .codex/agents/reviewer.toml
|
||||
|
||||
# Deep mode on a verbose prompt you want compressed
|
||||
$brewtools:text-optimize -d prompts/analysis-prompt.md
|
||||
|
||||
# Multiple files processed in parallel
|
||||
$brewtools:text-optimize agents/planner.md, agents/executor.md, AGENTS.md
|
||||
|
||||
# All markdown files in a directory
|
||||
$brewtools:text-optimize -d agents/
|
||||
|
||||
# Standard mode — 30-50% compression, stays human-readable
|
||||
$brewtools:text-optimize -s docs/getting-started.md
|
||||
|
||||
# Deep mode — max compression with dictionary encoding for LLM consumption
|
||||
$brewtools:text-optimize -d AGENTS.md
|
||||
|
||||
# Auto-detect: AGENTS.md → deep, README.md → standard
|
||||
$brewtools:text-optimize AGENTS.md
|
||||
$brewtools:text-optimize README.md
|
||||
|
||||
# Prompt hint overrides auto-detect
|
||||
$brewtools:text-optimize "super compress" verbose-doc.md
|
||||
```
|
||||
|
||||
### Common Mistakes
|
||||
|
||||
```bash
|
||||
# Running deep mode on production files without reviewing the diff
|
||||
$brewtools:text-optimize -d AGENTS.md
|
||||
# Deep mode aggressively rephrases and merges sections.
|
||||
# ALWAYS review the diff before accepting deep mode changes.
|
||||
|
||||
# Optimizing generated or third-party files you don't control
|
||||
$brewtools:text-optimize node_modules/some-lib/README.md
|
||||
# Only optimize files YOU maintain. External files get overwritten on update.
|
||||
|
||||
# Using deep mode on files with precise technical references
|
||||
$brewtools:text-optimize -d API-REFERENCE.md
|
||||
# Deep mode may rephrase domain terms or merge sections that need to stay separate.
|
||||
# Use light (-l) or medium for reference documentation.
|
||||
|
||||
# Using deep mode on user-facing documentation
|
||||
$brewtools:text-optimize -d README.md
|
||||
# Deep mode uses dictionary encoding and symbols not readable by humans.
|
||||
# Use standard (-s) for docs that humans will read.
|
||||
|
||||
# Expecting deep mode output to be human-readable
|
||||
# Deep mode is designed for LLM consumption only (AGENTS.md, system prompts).
|
||||
# The output uses DICT headers, symbols (→, !=, ∵), and abbreviations.
|
||||
```
|
||||
|
||||
## Auto-Detection
|
||||
|
||||
When no mode flag is provided, the optimizer analyzes the file path and content to select the best mode:
|
||||
|
||||
| File Pattern | Auto-Selected Mode |
|
||||
|--------------|-------------------|
|
||||
| `AGENTS.md`, `.codex/rules/*.md` | Deep |
|
||||
| `.codex/agents/*.toml`, `.codex/skills/**/SKILL.md` | Deep |
|
||||
| `KNOWLEDGE.*`, system prompts | Deep |
|
||||
| `README.md`, `docs/**` | Standard |
|
||||
| API references, user-facing docs | Standard |
|
||||
| Unknown / mixed | Asks user |
|
||||
|
||||
Prompt text can also hint at the mode: "compress for LLM" → deep, "safe compress" → standard, "super compress" → deep.
|
||||
|
||||
## Verification
|
||||
|
||||
Standard and deep modes include automatic verification to prevent information loss.
|
||||
|
||||
| Mode | Rounds | Pass Threshold |
|
||||
|------|--------|----------------|
|
||||
| Standard | 1 | All facts preserved |
|
||||
| Deep | 2 | >= 95% semantic match |
|
||||
|
||||
The report includes a semantic match percentage and lists any facts that were lost or distorted during compression.
|
||||
|
||||
## What It Does
|
||||
|
||||
- Converts verbose prose to dense tables (up to 3x more token-efficient)
|
||||
- Removes filler words and passive constructions
|
||||
- Restructures numbered lists to bullets where order does not matter
|
||||
- Converts multi-line code blocks to inline code when a single expression suffices
|
||||
- Merges redundant or overlapping sections
|
||||
- Applies positive framing ("do Y" instead of "don't do X")
|
||||
- Verifies all file paths (R.1), URLs (R.2), and circular references (R.3)
|
||||
- Uses standard abbreviations in tables only (full words in prose)
|
||||
|
||||
## Output
|
||||
|
||||
Each file produces an optimization report containing:
|
||||
|
||||
| Section | Contents |
|
||||
|---------|----------|
|
||||
| Metrics | Lines and tokens -- before, after, percent reduction |
|
||||
| Rules Applied | Which rule IDs were used and what changed |
|
||||
| Issues Found | Broken references, redundancies, structural problems -- and how they were fixed |
|
||||
| Cross-Reference Check | Verification status for file paths, URLs, circular refs |
|
||||
| Semantic Match | Compression ratio and semantic match % (standard/deep modes) |
|
||||
|
||||
Files are modified in-place. The report is printed to the conversation.
|
||||
|
||||
## Tips
|
||||
|
||||
- **Start with light mode** (`-l`) on important files to preview what changes look like before committing to deeper optimization.
|
||||
- **Review deep mode diffs carefully** -- aggressive rephrasing can alter meaning in domain-specific content.
|
||||
- **Run without arguments** to optimize all standard locations at once: `AGENTS.md`, `.codex/agents/*.toml`, `.codex/skills/**/SKILL.md`.
|
||||
- **Parallel processing** kicks in automatically when you pass multiple files or a directory -- no extra flags needed.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full docs: [text-optimize](https://doc-claude.brewcode.app/brewtools/skills/text-optimize/)
|
||||
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: text-optimize
|
||||
description: "Optimizes text/docs for LLM token efficiency. Triggers - optimize, reduce tokens, compress, deep compress."
|
||||
---
|
||||
|
||||
# Optimize text for tokens
|
||||
|
||||
Compress the requested text while preserving every load-bearing constraint, identifier, example, and safety rule. Measure before and after size, explain material removals, and write only to the requested Codex-owned artifact path. Do not create Markdown agent definitions or unsupported agent calls.
|
||||
|
||||
## Complete native workflow
|
||||
|
||||
Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve `<skill-directory>`, `<plugin-root>`, `<project-root>`, and `<arguments>` before running commands.
|
||||
|
||||
|
||||
# Text & File Optimizer
|
||||
|
||||
## Step 0: Load Rules
|
||||
|
||||
> **REQUIRED:** Read `references/rules-review.md` before ANY optimization.
|
||||
> If file not found -> ERROR + STOP. Do not proceed without rules reference.
|
||||
|
||||
## Modes
|
||||
|
||||
Parse `<arguments>`: `-l`/`--light` | `-s`/`--standard` | `-d`/`--deep` | `-x`/`--max` | no flag -> medium (default) or auto-detect.
|
||||
|
||||
| Mode | Flag | Target | Compression | Human-readable | Verification |
|
||||
|------|------|--------|-------------|----------------|--------------|
|
||||
| Light | `-l`, `--light` | Any | Minimal | Yes | None |
|
||||
| Medium | _(default)_ | Any | Moderate | Yes | None |
|
||||
| Standard | `-s`, `--standard` | Docs, README | 30-50% | Yes | 1 round |
|
||||
| Deep | `-d`, `--deep` | AGENTS.md, system prompts, agent/skill defs, KNOWLEDGE | 2-3x | No (LLM-only) | 1-2 rounds |
|
||||
| Max | `-x`, `--max` | AGENTS.md, system prompts, KNOWLEDGE | 3-4x | No (LLM-only) | 2 (mandatory) |
|
||||
|
||||
## Smart Auto-Detection
|
||||
|
||||
When no flag provided AND input suggests compression (not just optimization):
|
||||
|
||||
1. Parse file path + content header
|
||||
2. Classify:
|
||||
- LLM-only files (`AGENTS.md`, `.codex/rules/*.md`, `.codex/agents/*.toml`, `.codex/skills/**/SKILL.md`, `KNOWLEDGE.*`, system prompts) → deep candidate
|
||||
- `README.md`, `docs/`, API references, user-facing docs → standard candidate
|
||||
- Unknown / mixed → ask user via request_user_input
|
||||
3. If confident → tell user: "Selected mode: {mode} for {file} because {reason}"
|
||||
4. If ambiguous → request_user_input with mode options
|
||||
5. User can override via flags regardless of auto-detection
|
||||
6. **Max is opt-in only** — NEVER auto-selected without an explicit `-x`/`--max` flag or an explicit maximum/extreme compress hint
|
||||
|
||||
### Context Hints from Prompt Text
|
||||
|
||||
| Hint | Mode |
|
||||
|------|------|
|
||||
| "compress for AGENTS.md / for context / for prompt / for LLM" | deep |
|
||||
| "deep compress / deep encode / super compress / maximum" | deep |
|
||||
| "compress / slim / tighten" (generic) | standard |
|
||||
| "safe compress / human readable" | standard |
|
||||
| "max compress / extreme / maximum density / atomic" | max |
|
||||
| Explicit target (e.g., "reduce by 70%") | adjust aggressiveness |
|
||||
|
||||
## Rule ID Quick Reference
|
||||
|
||||
| Category | Rule IDs | Scope |
|
||||
|----------|----------|-------|
|
||||
| Codex behavior | C.1-C.8 | Literal following, avoid "think", positive framing, match style, descriptive instructions, overengineering, avoid ALL-CAPS, prompt format |
|
||||
| Token efficiency | T.1-T.8, T.10 | Tables, bullets, one-liners, inline code, abbreviations, filler, comma lists, arrows, strip whitespace |
|
||||
| Structure | S.1-S.8 | XML tags, imperative, single source, context/motivation, blockquotes, progressive disclosure, consistent terminology, ref depth |
|
||||
| Reference integrity | R.1-R.3 | Verify file paths, check URLs, linearize circular refs |
|
||||
| Perception | P.1-P.6 | Examples near rules, hierarchy, bold keywords, standard symbols, instruction order, default over options |
|
||||
| LLM Comprehension | L.1-L.7 | Critical info position, documents-first, conciseness, quote-first, add WHY, reiterate constraint, prompt repetition |
|
||||
|
||||
### ID-to-Rule Mapping
|
||||
|
||||
| ID | Rule | ID | Rule |
|
||||
|----|------|----|------|
|
||||
| C.1 | Literal instruction following | C.2 | Avoid "think" word |
|
||||
| C.3 | Positive framing (do Y not don't X) | C.4 | Match prompt style to output |
|
||||
| C.5 | Descriptive over emphatic instructions | C.6 | Overengineering prevention |
|
||||
| T.1 | Tables over prose (multi-column) | T.2 | Bullets over numbered (~5-10%) |
|
||||
| T.3 | One-liners for rules | T.4 | Inline code over blocks |
|
||||
| T.5 | Standard abbreviations (tables only) | T.6 | Remove filler words |
|
||||
| T.7 | Comma-separated inline lists | T.8 | Arrows for flow notation |
|
||||
| S.1 | XML tags for sections | S.2 | Imperative form |
|
||||
| S.3 | Single source of truth | S.4 | Add context/motivation |
|
||||
| S.5 | Blockquotes for critical | S.6 | Progressive disclosure |
|
||||
| R.1 | Verify file paths | R.2 | Check URLs |
|
||||
| R.3 | Linearize circular refs | P.1 | Examples near rules |
|
||||
| P.2 | Hierarchy via headers (max 3-4) | P.3 | Bold for keywords (max 2-3/100 lines) |
|
||||
| P.4 | Standard symbols (→ + / ✅❌⚠️) | | |
|
||||
| S.7 | Consistent terminology | S.8 | One-level reference depth |
|
||||
| P.5 | Instruction order (anchoring) | P.6 | Default over options |
|
||||
| C.7 | Avoid ALL-CAPS emphasis (4.x) | C.8 | Prompt format → output format |
|
||||
| T.10 | Strip whitespace from code | | |
|
||||
| L.1 | Critical info at START or END | L.2 | Documents first, query last |
|
||||
| L.3 | Explicitly request conciseness | L.4 | Quote-first grounding |
|
||||
| L.5 | Add WHY to instructions | L.6 | Reiterate constraint at END |
|
||||
| L.7 | Prompt repetition (non-reasoning) | | |
|
||||
|
||||
## Mode-to-Rules Mapping
|
||||
|
||||
| Mode | Applies | Notes |
|
||||
|------|---------|-------|
|
||||
| Light | C.1-C.8, T.6, R.1-R.3, P.1-P.4, L.1-L.7 | Text cleanup only — no restructuring |
|
||||
| Medium | All rules (C + T + S + R + P + L) | Balanced transformations |
|
||||
| Standard | All rules (C + T + S + R + P + L) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round |
|
||||
| Deep | All rules (C + T + S + R + P + L) + `references/deep-compression.md` | DICT header, symbol substitutions, 1-2 verification rounds (conditional) |
|
||||
| Max | All rules (C + T + S + R + P + L) + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `$brewtools:text-optimize` | Optimize ALL: `AGENTS.md`, `.codex/agents/*.toml`, `.codex/skills/**/SKILL.md` |
|
||||
| `$brewtools:text-optimize file.md` | Single file (medium mode) |
|
||||
| `$brewtools:text-optimize -l file.md` | Light mode — text cleanup only, structure untouched |
|
||||
| `$brewtools:text-optimize -d file.md` | Deep mode — max compression, review diff after |
|
||||
| `$brewtools:text-optimize path1.md, path2.md` | Multiple files — parallel processing |
|
||||
| `$brewtools:text-optimize -d agents/` | Directory — all `.md` files with specified mode |
|
||||
| `$brewtools:text-optimize -s README.md` | Standard mode — 30-50% compression, human-readable |
|
||||
| `$brewtools:text-optimize -d AGENTS.md` | Deep mode — dictionary compression, LLM-only output |
|
||||
| `$brewtools:text-optimize -x AGENTS.md` | Max mode — atomic fact-lines + ASCII operators, LLM-only, 2-round verify |
|
||||
| `$brewtools:text-optimize AGENTS.md` | Auto-detect → selects deep for AGENTS.md |
|
||||
| `$brewtools:text-optimize README.md` | Auto-detect → selects standard for README |
|
||||
| `$brewtools:text-optimize "super compress" file.md` | Prompt hint → deep mode |
|
||||
|
||||
## File Processing
|
||||
|
||||
### Input Parsing
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| No args | Optimize ALL: `AGENTS.md`, `.codex/agents/*.toml`, `.codex/skills/**/SKILL.md` |
|
||||
| Single path | Process directly |
|
||||
| `path1, path2` | Parallel processing |
|
||||
|
||||
### 2-Phase Execution
|
||||
|
||||
> **Orchestration:** Phase 1+2 are executed by the SKILL in the main conversation (manager level). The text-optimizer agent handles single-file optimization only — it cannot spawn sub-agents.
|
||||
|
||||
**Phase 1: Analysis** — Parallel `Explore` agents
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role: "Explore", prompt: "Analyze {file}: structure, dependencies, cross-refs, redundancies")
|
||||
```
|
||||
|
||||
**Phase 2: Optimization** — Parallel text-optimizer agents
|
||||
|
||||
> **Context:** <plugin-root> is available in your context (injected by pre-task.mjs hook). Use it to access plugin resources.
|
||||
|
||||
```
|
||||
Codex delegation brief (task_role: "text-optimizer", prompt: "FIRST: Read $<plugin-root>/skills/text-optimize/references/rules-review.md for validation rules. FOR STANDARD MODE: Also read $<plugin-root>/skills/text-optimize/references/standard-compression.md. FOR DEEP MODE: Also read $<plugin-root>/skills/text-optimize/references/deep-compression.md. FOR MAX MODE: Also read $<plugin-root>/skills/text-optimize/references/max-compression.md and apply atomic fact-line decomposition + ASCII operator dialect + guardrails; run 2 verification rounds. THEN optimize {file} using {mode} mode. Apply transformations, verify refs, output report with metrics.")
|
||||
```
|
||||
|
||||
> **Spawn parallel:** For multiple files, spawn ALL agents in ONE message for speed.
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
### Before
|
||||
- [ ] Read entire text
|
||||
- [ ] Identify type (prompt, docs, agent, skill)
|
||||
- [ ] Note critical info and cross-references
|
||||
|
||||
### During — Apply by Mode
|
||||
|
||||
| Check | Light | Med | Std | Deep | Max |
|
||||
|-------|-------|-----|-----|------|-----|
|
||||
| C.1-C.8 (Codex behavior) | Yes | Yes | Yes | Yes | Yes |
|
||||
| T.6 (filler removal) | Yes | Yes | Yes | Yes | Yes |
|
||||
| T.1-T.5, T.7-T.8 (token compression) | - | Yes | Yes | Yes | Yes |
|
||||
| S.1-S.8 (structure/clarity) | - | Yes | Yes | Yes | Yes |
|
||||
| R.1-R.3 (reference integrity) | Yes | Yes | Yes | Yes | Yes |
|
||||
| P.1-P.4 (LLM perception) | Yes | Yes | Yes | Yes | Yes |
|
||||
| L.1-L.7 (LLM comprehension) | Yes | Yes | Yes | Yes | Yes |
|
||||
| Standard compression ref | - | - | Yes | - | - |
|
||||
| Deep compression ref + DICT | - | - | - | Yes | Yes |
|
||||
| Aggressive rephrasing | - | - | - | Yes | Yes |
|
||||
| Max compression ref (atomic fact-lines) | - | - | - | - | Yes |
|
||||
| Guardrails C1-C4 (scope, punctuation, signal/token) | - | - | - | - | Yes |
|
||||
| Verification round(s) | - | - | 1 | 2 | 2 |
|
||||
| No information loss | Yes | Yes | Yes | Yes | Yes |
|
||||
|
||||
## Deep Mode Pipeline
|
||||
|
||||
### Phase 1: Compress
|
||||
- Load `references/deep-compression.md` for symbol/abbreviation tables
|
||||
- Scan text for terms occurring 3+ times → build DICT header
|
||||
- Apply symbol substitutions, filler removal, structural compression
|
||||
- Apply existing rules (C, T, S, R, P) in addition to deep techniques
|
||||
|
||||
### Phase 2: Verify Round 1
|
||||
- Spawn verification agent with ORIGINAL + COMPRESSED text
|
||||
- Agent reads both, lists all lost/distorted facts
|
||||
- Calculate semantic match %
|
||||
- If >= 95% → done
|
||||
- If < 95% → return loss list for patching
|
||||
|
||||
### Phase 3: Patch + Verify Round 2
|
||||
- Apply patches for missing facts
|
||||
- Re-verify
|
||||
- If still < 95% → warn user with loss list
|
||||
- Output final result + statistics
|
||||
|
||||
## Max Mode Pipeline
|
||||
|
||||
### Phase 1: Compress
|
||||
- Apply all Deep techniques (DICT header, symbol substitutions, structural compression)
|
||||
- Load `references/max-compression.md` for atomic fact-line decomposition, ASCII operator dialect, format-aware tables
|
||||
- Respect guardrails C1-C4: optimize for signal/token (not raw token count); preserve scope qualifiers; ~20% deletion ceiling — never strip punctuation; consistent terminology throughout
|
||||
|
||||
### Phase 2: Verify Round 1
|
||||
- Spawn verification agent with ORIGINAL + COMPRESSED text
|
||||
- Calculate semantic match %
|
||||
- Gate at >= 95% → proceed; if < 95% → return loss list
|
||||
|
||||
### Phase 3: Patch + Verify Round 2 (MANDATORY)
|
||||
- Apply patches for missing/distorted facts
|
||||
- Re-verify — Round 2 is mandatory, NEVER skip
|
||||
- If still < 95% → warn user with explicit loss list
|
||||
- Output final result + statistics
|
||||
|
||||
## Standard Mode Pipeline
|
||||
|
||||
### Phase 1: Compress
|
||||
- Load `references/standard-compression.md`
|
||||
- Remove filler words/constructions
|
||||
- Merge repeated ideas
|
||||
- Convert paragraphs to bullets/tables where appropriate
|
||||
- Apply existing rules (C, T, S, R, P)
|
||||
|
||||
### Phase 2: Verify
|
||||
- Compare compressed vs original
|
||||
- List any lost facts → patch
|
||||
- One round only
|
||||
|
||||
## Iron Rules (All Modes)
|
||||
|
||||
| Rule | Detail |
|
||||
|------|--------|
|
||||
| Preserve | Names, numbers, dates, URLs, file paths, versions, ports, sizes |
|
||||
| Preserve | Negative rule semantics (`!=` notation in deep mode) |
|
||||
| Preserve | At least one example per rule with examples |
|
||||
| Preserve | Scope qualifiers ("every section, not just the first") — high-reasoning model 4.8 literalism (Max/Deep) |
|
||||
| Deep only | DICT header at document start |
|
||||
| Max only | Atomic fact-lines, ASCII operators over unicode glyphs, 2 mandatory verification rounds |
|
||||
| Output | Statistics: original (chars/words/~tokens), compressed (chars/words/~tokens), ratio, semantic match % |
|
||||
|
||||
### After
|
||||
- [ ] All facts preserved
|
||||
- [ ] Logic consistent
|
||||
- [ ] References valid (R.1-R.3)
|
||||
- [ ] Tokens reduced
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## Optimization Report: [filename]
|
||||
|
||||
| Metric | Before | After | Change |
|
||||
|--------|--------|-------|--------|
|
||||
| Lines | X | Y | -Z% |
|
||||
| Tokens | ~X | ~Y | -Z% |
|
||||
|
||||
### Rules Applied
|
||||
- [Rule IDs]: [Description of changes]
|
||||
|
||||
### Issues Found & Fixed
|
||||
- [Issue]: [Resolution]
|
||||
|
||||
### Cross-Reference Verification
|
||||
- [x] All file refs valid (R.1)
|
||||
- [x] All URLs checked (R.2)
|
||||
- [x] No circular refs (R.3)
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Avoid | Why |
|
||||
|-------|-----|
|
||||
| Remove all examples | Hurts generalization (P.1) |
|
||||
| Over-abbreviate | Reduces readability (T.5 caveat) |
|
||||
| Generic compression | Domain terms matter |
|
||||
| Over-aggressive language | high-reasoning model 4.5 overtriggers (C.5) |
|
||||
| Flatten hierarchy | Loses structure (P.2) |
|
||||
| "Don't do X" framing | Less effective than "Do Y" (C.3) |
|
||||
| Overengineer prompts | high-reasoning model 4.5 follows literally (C.6) |
|
||||
| Overload single prompts | Divided attention, hallucinations (S.3) |
|
||||
| Over-focus on wording | Structure > word choice (T.1) |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user