ima-claude 2.25.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -17
- package/dist/cli.js +381 -13
- package/package.json +2 -2
- package/platforms/codex/adapter.ts +452 -0
- package/platforms/codex/hooks-translator.py +67 -0
- package/platforms/shared/detector.ts +2 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
- package/plugins/ima-claude/agents/reviewer.md +29 -1
- package/plugins/ima-claude/skills/ima-git/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +20 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
FP patterns, architecture guidance, and team standards for AI coding agents.
|
|
4
4
|
|
|
5
|
-
**Supports Claude Code, Junie CLI, Gemini CLI, and
|
|
5
|
+
**Supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI** — with an extensible adapter architecture ready for more platforms.
|
|
6
6
|
|
|
7
7
|
Built by [Independent Medical Alliance](https://imahealth.org) (formerly FLCCC)
|
|
8
8
|
|
|
@@ -38,9 +38,9 @@ claude plugin update ima-claude
|
|
|
38
38
|
|
|
39
39
|
Or use `/plugin` inside Claude Code to manage updates interactively via the **Installed** tab.
|
|
40
40
|
|
|
41
|
-
### Junie CLI / Gemini CLI / GitHub Copilot — Multi-Platform Installer
|
|
41
|
+
### Junie CLI / Gemini CLI / GitHub Copilot / Codex CLI — Multi-Platform Installer
|
|
42
42
|
|
|
43
|
-
For Junie, Gemini, GitHub Copilot, or any non-plugin platform, use the interactive CLI installer:
|
|
43
|
+
For Junie, Gemini, GitHub Copilot, OpenAI Codex CLI, or any non-plugin platform, use the interactive CLI installer:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
npx ima-claude install
|
|
@@ -48,7 +48,7 @@ npx ima-claude install
|
|
|
48
48
|
|
|
49
49
|
The installer auto-detects which platforms are available and walks you through installation:
|
|
50
50
|
|
|
51
|
-
1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), Gemini CLI (`~/.gemini`),
|
|
51
|
+
1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), Gemini CLI (`~/.gemini`), GitHub Copilot (`~/.copilot`), and OpenAI Codex CLI (`~/.codex`, or `$CODEX_HOME`)
|
|
52
52
|
2. **Shows preview** — lists all skills, agents, and platform-specific items to install
|
|
53
53
|
3. **Allows exclusions** — skip specific skills or agents you don't need
|
|
54
54
|
4. **Installs with feedback** — step-by-step progress for each item
|
|
@@ -59,20 +59,46 @@ You can also target a specific platform directly:
|
|
|
59
59
|
npx ima-claude install --target junie # Junie only
|
|
60
60
|
npx ima-claude install --target gemini # Gemini CLI only
|
|
61
61
|
npx ima-claude install --target gh-copilot # GitHub Copilot only
|
|
62
|
+
npx ima-claude install --target codex # OpenAI Codex CLI only
|
|
62
63
|
npx ima-claude install --target claude # Claude Code only (plugin recommended instead)
|
|
63
64
|
npx ima-claude detect # Show detected platforms
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
**Tip: Install into a single project instead of globally**
|
|
68
|
+
|
|
69
|
+
The installer resolves its target paths via `$HOME` (`~/.claude/...`). Override `$HOME` at invocation time to write skills, agents, hooks, and `settings.json` into the current repo's `./.claude/` instead:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
HOME=$(pwd) npx ima-claude install --target claude
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Produces:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
./.claude/skills/ # project-scoped skills
|
|
79
|
+
./.claude/agents/ # project-scoped agents
|
|
80
|
+
./.claude/hooks/ # Python hook scripts
|
|
81
|
+
./.claude/settings.json # hook configuration (absolute paths)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Claude Code already reads these as project-scoped config, so the skills/hooks load only when you're working inside that repo.
|
|
85
|
+
|
|
86
|
+
Caveats:
|
|
87
|
+
|
|
88
|
+
- The plugin marketplace flow (`/plugin install ima-claude`) remains the recommended path for most users. Use this override only when you want per-repo skill customization without touching your home install.
|
|
89
|
+
- `settings.json` records hook paths as absolute strings resolved at install time — if you rename or move the project, the hook entries break.
|
|
90
|
+
- `HOME=$(pwd)` also redirects npm's cache, so the install may drop a `.npm/` folder in the project root. Add it to `.gitignore` or clean up afterward.
|
|
91
|
+
|
|
66
92
|
**What's different per platform?**
|
|
67
93
|
|
|
68
|
-
| | Claude Code | Junie CLI | Gemini CLI | GitHub Copilot |
|
|
69
|
-
|
|
70
|
-
| **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` | Copied to `~/.copilot/skills/` |
|
|
71
|
-
| **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names | Strips `model` + `permissionMode`, maps tool names, renames to `.agent.md` |
|
|
72
|
-
| **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim | Translated events + tool names, translator shim, flattened config |
|
|
73
|
-
| **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` | Generated `copilot-instructions.md` |
|
|
94
|
+
| | Claude Code | Junie CLI | Gemini CLI | GitHub Copilot | Codex CLI |
|
|
95
|
+
|---|---|---|---|---|---|
|
|
96
|
+
| **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` | Copied to `~/.copilot/skills/` | Copied to `~/.codex/skills/` |
|
|
97
|
+
| **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names | Strips `model` + `permissionMode`, maps tool names, renames to `.agent.md` | Strips `model` + `permissionMode`, maps tool names; reference copies in `~/.codex/agents/` (Codex has no formal subagent dispatcher yet) |
|
|
98
|
+
| **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim | Translated events + tool names, translator shim, flattened config | Translated tool names only (events match Claude Code), translator shim, matcher-grouped config |
|
|
99
|
+
| **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` | Generated `copilot-instructions.md` | Generated `AGENTS.md` (Codex's native convention) |
|
|
74
100
|
|
|
75
|
-
Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini and GitHub Copilot have hooks but use different event/tool names — a translator shim normalizes input so all existing hooks work unmodified. Copilot additionally uses a flat hook config format with `bash` field and `version: 1` wrapper.
|
|
101
|
+
Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini and GitHub Copilot have hooks but use different event/tool names — a translator shim normalizes input so all existing hooks work unmodified. Copilot additionally uses a flat hook config format with `bash` field and `version: 1` wrapper. Codex CLI shares Claude Code's hook event names natively, so the translator only rewrites tool-name strings; MCP servers register via `[mcp_servers.<name>]` tables in `~/.codex/config.toml` (not auto-configured yet).
|
|
76
102
|
|
|
77
103
|
### Adding New Platforms
|
|
78
104
|
|
|
@@ -84,7 +110,8 @@ platforms/
|
|
|
84
110
|
├── claude/adapter.ts # Claude Code adapter
|
|
85
111
|
├── junie/adapter.ts # Junie CLI adapter
|
|
86
112
|
├── gemini/adapter.ts # Gemini CLI adapter
|
|
87
|
-
|
|
113
|
+
├── gh-copilot/adapter.ts # GitHub Copilot adapter
|
|
114
|
+
└── codex/adapter.ts # OpenAI Codex CLI adapter
|
|
88
115
|
```
|
|
89
116
|
|
|
90
117
|
See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface contract.
|
|
@@ -93,10 +120,10 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
|
|
|
93
120
|
|
|
94
121
|
## What's Included
|
|
95
122
|
|
|
96
|
-
- **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, Gemini CLI, and
|
|
97
|
-
- **
|
|
123
|
+
- **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI
|
|
124
|
+
- **63 Skills**: Foundational + FP implementation + domain expert + integration + meta-skills
|
|
98
125
|
- **6 Named Agents**: Explorer (haiku), Implementer (sonnet), Reviewer (sonnet), Tester (sonnet), WP Developer (sonnet), Memory (sonnet) — enforced constraints
|
|
99
|
-
- **
|
|
126
|
+
- **24 Hooks**: Automatic behavioral enforcement (security, memory, workflow, Serena, code quality) — translated to guidelines for platforms without hook support
|
|
100
127
|
- **Default Persona**: "The Practitioner" - 25-year veteran mindset, collaborative, plan-first
|
|
101
128
|
- **3-Tier Memory**: Vestige (neural decay) + Qdrant (permanent library) + Serena (project workbench)
|
|
102
129
|
- **IMA Workflow**: Brainstorm → Plan → Implement → Test → Review → Document (habit-driven, not tool-enforced)
|
|
@@ -106,7 +133,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
|
|
|
106
133
|
|
|
107
134
|
## Prerequisites
|
|
108
135
|
|
|
109
|
-
- [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), [Gemini CLI](https://github.com/google-gemini/gemini-cli),
|
|
136
|
+
- [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [GitHub Copilot](https://github.com/features/copilot), or [OpenAI Codex CLI](https://github.com/openai/codex) installed
|
|
110
137
|
|
|
111
138
|
## MCP Servers (Highly Recommended)
|
|
112
139
|
|
|
@@ -311,6 +338,7 @@ For in-scope hard reasoning (debugging, trade-offs within plan), agents invoke `
|
|
|
311
338
|
| `wp-ddev` | WP-CLI commands for DDEV WordPress environments |
|
|
312
339
|
| `wp-local` | WP-CLI commands for Flywheel Local WP |
|
|
313
340
|
| `jira-checkpoint` | Jira awareness checkpoints for team visibility |
|
|
341
|
+
| `ima-git` | IMA trunk-based git workflow (main/release/tag model, hotfix flow, deploy-gate push cadence, commit atomicity) |
|
|
314
342
|
| `phpunit-wp` | PHPUnit testing for WordPress plugins with FP principles |
|
|
315
343
|
| `rg` | Ripgrep usage patterns |
|
|
316
344
|
| `ima-forms-expert` | WordPress form components (IMA Forms) |
|
|
@@ -471,7 +499,7 @@ ima-claude follows a **Persona + Skills** architecture:
|
|
|
471
499
|
- **Platform adapters** - Shared `PlatformAdapter` interface; each platform implements detect, preview, install, and guideline generation
|
|
472
500
|
|
|
473
501
|
This makes ima-claude:
|
|
474
|
-
1. **Multi-platform** - Same skills and agents across Claude Code, Junie CLI, Gemini CLI, and
|
|
502
|
+
1. **Multi-platform** - Same skills and agents across Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI
|
|
475
503
|
2. **Fully standalone** - Complete system without dependencies
|
|
476
504
|
3. **Consistent** - Same mindset across all interactions
|
|
477
505
|
4. **Efficient** - Skills load on-demand based on context
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ var HOOKS_DIR = join(CLAUDE_DIR, "hooks");
|
|
|
11
11
|
var COMMANDS_DIR = join(CLAUDE_DIR, "commands");
|
|
12
12
|
var RULES_DIR = join(CLAUDE_DIR, "rules");
|
|
13
13
|
var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
14
|
-
var VERSION = "2.
|
|
14
|
+
var VERSION = "2.27.0";
|
|
15
15
|
var colors = {
|
|
16
16
|
reset: "\x1B[0m",
|
|
17
17
|
bright: "\x1B[1m",
|
|
@@ -111,6 +111,7 @@ var SKILLS_TO_INSTALL = [
|
|
|
111
111
|
"design-to-code",
|
|
112
112
|
"livecanvas",
|
|
113
113
|
"jira-checkpoint",
|
|
114
|
+
"ima-git",
|
|
114
115
|
// Testing skills
|
|
115
116
|
"unit-testing",
|
|
116
117
|
"phpunit-wp",
|
|
@@ -1424,12 +1425,379 @@ function mergeCopilotHooksConfig(configPath, newConfig) {
|
|
|
1424
1425
|
writeFileSync4(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1425
1426
|
}
|
|
1426
1427
|
|
|
1428
|
+
// platforms/codex/adapter.ts
|
|
1429
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
1430
|
+
import { homedir as homedir5 } from "os";
|
|
1431
|
+
import { existsSync as existsSync6, readdirSync as readdirSync6, statSync as statSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5, copyFileSync as copyFileSync5 } from "fs";
|
|
1432
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1433
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
1434
|
+
var __dirname4 = dirname4(__filename3);
|
|
1435
|
+
var CODEX_DIR = process.env.CODEX_HOME ?? join6(homedir5(), ".codex");
|
|
1436
|
+
var CODEX_SKILLS_DIR = join6(CODEX_DIR, "skills");
|
|
1437
|
+
var CODEX_AGENTS_DIR = join6(CODEX_DIR, "agents");
|
|
1438
|
+
var CODEX_HOOKS_DIR = join6(CODEX_DIR, "hooks");
|
|
1439
|
+
var CODEX_HOOKS_CONFIG = join6(CODEX_DIR, "hooks.json");
|
|
1440
|
+
var CODEX_GUIDELINES_FILE = join6(CODEX_DIR, "AGENTS.md");
|
|
1441
|
+
var CODEX_CONFIG_TOML = join6(CODEX_DIR, "config.toml");
|
|
1442
|
+
var TOOL_MAP3 = {
|
|
1443
|
+
Bash: "shell",
|
|
1444
|
+
Read: "read",
|
|
1445
|
+
Edit: "edit",
|
|
1446
|
+
Write: "write",
|
|
1447
|
+
Glob: "glob",
|
|
1448
|
+
Grep: "grep",
|
|
1449
|
+
LS: "list",
|
|
1450
|
+
WebSearch: "web_search",
|
|
1451
|
+
WebFetch: "fetch",
|
|
1452
|
+
ExitPlanMode: "ExitPlanMode"
|
|
1453
|
+
};
|
|
1454
|
+
var EVENT_MAP3 = {
|
|
1455
|
+
PreToolUse: "PreToolUse",
|
|
1456
|
+
PostToolUse: "PostToolUse",
|
|
1457
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
1458
|
+
SessionStart: "SessionStart"
|
|
1459
|
+
};
|
|
1460
|
+
function parseFrontmatter4(content) {
|
|
1461
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1462
|
+
if (!match) return { frontmatter: {}, body: content };
|
|
1463
|
+
const frontmatter = {};
|
|
1464
|
+
for (const line of match[1].split("\n")) {
|
|
1465
|
+
const colonIdx = line.indexOf(":");
|
|
1466
|
+
if (colonIdx === -1) continue;
|
|
1467
|
+
const key = line.slice(0, colonIdx).trim();
|
|
1468
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
1469
|
+
if (key) frontmatter[key] = value;
|
|
1470
|
+
}
|
|
1471
|
+
return { frontmatter, body: match[2] };
|
|
1472
|
+
}
|
|
1473
|
+
function serializeFrontmatter4(frontmatter, body) {
|
|
1474
|
+
const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
|
|
1475
|
+
return `---
|
|
1476
|
+
${lines.join("\n")}
|
|
1477
|
+
---
|
|
1478
|
+
${body}`;
|
|
1479
|
+
}
|
|
1480
|
+
function mapToolName3(claudeName) {
|
|
1481
|
+
return TOOL_MAP3[claudeName] ?? claudeName;
|
|
1482
|
+
}
|
|
1483
|
+
function transformAgentForCodex(content) {
|
|
1484
|
+
const { frontmatter, body } = parseFrontmatter4(content);
|
|
1485
|
+
const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
|
|
1486
|
+
if (kept.tools) {
|
|
1487
|
+
const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName3).join(", ");
|
|
1488
|
+
kept.tools = mapped;
|
|
1489
|
+
}
|
|
1490
|
+
return serializeFrontmatter4(kept, body);
|
|
1491
|
+
}
|
|
1492
|
+
function translateMatcher3(matcher) {
|
|
1493
|
+
return TOOL_MAP3[matcher] ?? matcher;
|
|
1494
|
+
}
|
|
1495
|
+
function translateHookCommand3(command2) {
|
|
1496
|
+
const translatorPath = join6(CODEX_HOOKS_DIR, "hooks-translator.py");
|
|
1497
|
+
const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
|
|
1498
|
+
if (!match) return command2;
|
|
1499
|
+
const scriptName = match[1];
|
|
1500
|
+
const trailingArgs = match[2] ?? "";
|
|
1501
|
+
return `python3 ${translatorPath} ${join6(CODEX_HOOKS_DIR, scriptName)}${trailingArgs}`;
|
|
1502
|
+
}
|
|
1503
|
+
function generateCodexHooksConfig() {
|
|
1504
|
+
const codexHooks = {};
|
|
1505
|
+
for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
|
|
1506
|
+
const codexEvent = EVENT_MAP3[claudeEvent] ?? claudeEvent;
|
|
1507
|
+
codexHooks[codexEvent] = hookEntries.map(
|
|
1508
|
+
(entry) => {
|
|
1509
|
+
const translated = {};
|
|
1510
|
+
if (entry.matcher) {
|
|
1511
|
+
translated.matcher = translateMatcher3(entry.matcher);
|
|
1512
|
+
}
|
|
1513
|
+
translated.hooks = entry.hooks.map((h) => ({
|
|
1514
|
+
type: h.type,
|
|
1515
|
+
command: translateHookCommand3(h.command)
|
|
1516
|
+
}));
|
|
1517
|
+
return translated;
|
|
1518
|
+
}
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
return { hooks: codexHooks };
|
|
1522
|
+
}
|
|
1523
|
+
function generateCodexAgentsMd() {
|
|
1524
|
+
return `# ima-claude: AI Coding Agent Guidelines
|
|
1525
|
+
|
|
1526
|
+
> Generated by ima-claude v${VERSION} for OpenAI Codex CLI.
|
|
1527
|
+
> Source: https://github.com/Soabirw/ima-claude
|
|
1528
|
+
|
|
1529
|
+
This file is auto-loaded by Codex at session start (\`AGENTS.md\` convention).
|
|
1530
|
+
Per-directory \`AGENTS.md\` files in your project tree merge on top of this one,
|
|
1531
|
+
with closer-to-cwd files taking precedence.
|
|
1532
|
+
|
|
1533
|
+
## Default Persona: The Practitioner
|
|
1534
|
+
|
|
1535
|
+
A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
|
|
1536
|
+
Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
|
|
1537
|
+
|
|
1538
|
+
**Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
|
|
1539
|
+
|
|
1540
|
+
---
|
|
1541
|
+
|
|
1542
|
+
## Memory Routing
|
|
1543
|
+
|
|
1544
|
+
| Store what | Where | Why |
|
|
1545
|
+
|---|---|---|
|
|
1546
|
+
| Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
|
|
1547
|
+
| Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
|
|
1548
|
+
| Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
|
|
1549
|
+
| Future reminders | Vestige \`intention\` | Surfaces at next session |
|
|
1550
|
+
|
|
1551
|
+
At session start, check memory before asking questions:
|
|
1552
|
+
- Vestige: search for user preferences and project context
|
|
1553
|
+
- Vestige: check for pending reminders/intentions
|
|
1554
|
+
- Serena: list memories if in a Serena-activated project
|
|
1555
|
+
|
|
1556
|
+
Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
|
|
1557
|
+
|
|
1558
|
+
After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
|
|
1559
|
+
|
|
1560
|
+
---
|
|
1561
|
+
|
|
1562
|
+
## Orchestrator Protocol
|
|
1563
|
+
|
|
1564
|
+
You are the Orchestrator. Plan and delegate. Do NOT implement directly.
|
|
1565
|
+
- Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
|
|
1566
|
+
- Trivial = single file, < 5 lines, no judgment calls
|
|
1567
|
+
|
|
1568
|
+
---
|
|
1569
|
+
|
|
1570
|
+
## Available Agents
|
|
1571
|
+
|
|
1572
|
+
Codex does not yet support a formal named-subagent dispatcher. Treat the agents
|
|
1573
|
+
listed below as documented delegation patterns: when the work matches, scope a
|
|
1574
|
+
session (or a per-directory \`AGENTS.md\` overlay) to that agent's persona.
|
|
1575
|
+
Reference copies of each agent's full system prompt live in \`~/.codex/agents/\`.
|
|
1576
|
+
|
|
1577
|
+
| Agent | Use For |
|
|
1578
|
+
|---|---|
|
|
1579
|
+
| \`explorer\` | File discovery, codebase exploration |
|
|
1580
|
+
| \`implementer\` | Feature dev, bug fixes, refactoring |
|
|
1581
|
+
| \`reviewer\` | Code review, security audit, FP checks |
|
|
1582
|
+
| \`tester\` | Test creation, TDD, debugging test failures |
|
|
1583
|
+
| \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
|
|
1584
|
+
| \`memory\` | Memory search, storage, consolidation |
|
|
1585
|
+
|
|
1586
|
+
---
|
|
1587
|
+
|
|
1588
|
+
## Code Navigation (Serena)
|
|
1589
|
+
|
|
1590
|
+
When Serena MCP is available, **prefer Serena over read/grep for code investigation.** 40-70% token savings.
|
|
1591
|
+
|
|
1592
|
+
| Instead of | Use |
|
|
1593
|
+
|---|---|
|
|
1594
|
+
| Read file to understand structure | Serena get_symbols_overview |
|
|
1595
|
+
| grep for class/function definition | Serena find_symbol |
|
|
1596
|
+
| grep for callers/references | Serena find_referencing_symbols |
|
|
1597
|
+
|
|
1598
|
+
Use the read tool only when you need the actual implementation body of a known, specific symbol.
|
|
1599
|
+
|
|
1600
|
+
---
|
|
1601
|
+
|
|
1602
|
+
## Complex Reasoning
|
|
1603
|
+
|
|
1604
|
+
Use sequential thinking before acting on:
|
|
1605
|
+
- Debugging / root cause analysis / "why is this failing"
|
|
1606
|
+
- Trade-off evaluation / "which approach"
|
|
1607
|
+
- Architectural decisions / design choices
|
|
1608
|
+
- Multi-step investigations where approach may change
|
|
1609
|
+
|
|
1610
|
+
---
|
|
1611
|
+
|
|
1612
|
+
## MCP Tool Routing
|
|
1613
|
+
|
|
1614
|
+
| Signal | Preferred Tool |
|
|
1615
|
+
|---|---|
|
|
1616
|
+
| "latest", "2025/2026", "what's new" | Tavily search |
|
|
1617
|
+
| Library/framework API question | Context7 |
|
|
1618
|
+
| URL content extraction | Tavily extract (use advanced for complex pages) |
|
|
1619
|
+
|
|
1620
|
+
Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
|
|
1621
|
+
Before external lookups: check Vestige memory first.
|
|
1622
|
+
|
|
1623
|
+
To register MCP servers in Codex, add \`[mcp_servers.<name>]\` tables to
|
|
1624
|
+
\`~/.codex/config.toml\`. STDIO transport is the documented default.
|
|
1625
|
+
|
|
1626
|
+
---
|
|
1627
|
+
|
|
1628
|
+
## Search Preference
|
|
1629
|
+
|
|
1630
|
+
Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
|
|
1631
|
+
|
|
1632
|
+
---
|
|
1633
|
+
|
|
1634
|
+
## Security
|
|
1635
|
+
|
|
1636
|
+
- Verify nonce usage and input sanitization in WordPress PHP code
|
|
1637
|
+
- Never concatenate user input directly into SQL \u2014 use parameterized queries
|
|
1638
|
+
- Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
|
|
1639
|
+
|
|
1640
|
+
---
|
|
1641
|
+
|
|
1642
|
+
## Code Style
|
|
1643
|
+
|
|
1644
|
+
- Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
|
|
1645
|
+
- In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
|
|
1646
|
+
- Prefer Bootstrap utility classes over custom CSS overrides
|
|
1647
|
+
- Run \`composer dump-autoload\` after creating new PHP files
|
|
1648
|
+
|
|
1649
|
+
---
|
|
1650
|
+
|
|
1651
|
+
## Documentation
|
|
1652
|
+
|
|
1653
|
+
Follow the three-tier documentation system:
|
|
1654
|
+
- **Active** \u2014 Living docs, kept current (README, API docs, architecture)
|
|
1655
|
+
- **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
|
|
1656
|
+
- **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
|
|
1657
|
+
`;
|
|
1658
|
+
}
|
|
1659
|
+
var CodexAdapter = class {
|
|
1660
|
+
name = "codex";
|
|
1661
|
+
displayName = "OpenAI Codex CLI";
|
|
1662
|
+
configDir = CODEX_DIR;
|
|
1663
|
+
detect() {
|
|
1664
|
+
return existsSync6(CODEX_DIR);
|
|
1665
|
+
}
|
|
1666
|
+
preview(sourceDir) {
|
|
1667
|
+
const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
|
|
1668
|
+
name: skill,
|
|
1669
|
+
category: "skill",
|
|
1670
|
+
destPath: join6(CODEX_SKILLS_DIR, skill),
|
|
1671
|
+
exists: existsSync6(join6(CODEX_SKILLS_DIR, skill))
|
|
1672
|
+
})).filter((item) => existsSync6(join6(sourceDir, "skills", item.name)));
|
|
1673
|
+
const agentsDir = join6(sourceDir, "agents");
|
|
1674
|
+
const agentItems = existsSync6(agentsDir) ? readdirSync6(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
|
|
1675
|
+
name: file.replace(/\.md$/, ""),
|
|
1676
|
+
category: "agent",
|
|
1677
|
+
destPath: join6(CODEX_AGENTS_DIR, file),
|
|
1678
|
+
exists: existsSync6(join6(CODEX_AGENTS_DIR, file))
|
|
1679
|
+
})) : [];
|
|
1680
|
+
const hookItems = HOOKS_TO_INSTALL.map((file) => ({
|
|
1681
|
+
name: file,
|
|
1682
|
+
category: "hook",
|
|
1683
|
+
destPath: join6(CODEX_HOOKS_DIR, file),
|
|
1684
|
+
exists: existsSync6(join6(CODEX_HOOKS_DIR, file))
|
|
1685
|
+
}));
|
|
1686
|
+
const translatorItem = {
|
|
1687
|
+
name: "hooks-translator.py",
|
|
1688
|
+
category: "hook",
|
|
1689
|
+
destPath: join6(CODEX_HOOKS_DIR, "hooks-translator.py"),
|
|
1690
|
+
exists: existsSync6(join6(CODEX_HOOKS_DIR, "hooks-translator.py"))
|
|
1691
|
+
};
|
|
1692
|
+
const guidelineItem = {
|
|
1693
|
+
name: "AGENTS.md",
|
|
1694
|
+
category: "guideline",
|
|
1695
|
+
destPath: CODEX_GUIDELINES_FILE,
|
|
1696
|
+
exists: existsSync6(CODEX_GUIDELINES_FILE)
|
|
1697
|
+
};
|
|
1698
|
+
return {
|
|
1699
|
+
platform: this.name,
|
|
1700
|
+
targetDir: CODEX_DIR,
|
|
1701
|
+
items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
installSkills(sourceDir, exclude) {
|
|
1705
|
+
ensureDir(CODEX_SKILLS_DIR);
|
|
1706
|
+
const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
|
|
1707
|
+
for (const skill of skills) {
|
|
1708
|
+
const src = join6(sourceDir, skill);
|
|
1709
|
+
if (existsSync6(src) && statSync6(src).isDirectory()) {
|
|
1710
|
+
copyDirRecursive(src, join6(CODEX_SKILLS_DIR, skill));
|
|
1711
|
+
log.step(`skill: ${skill}`);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
installAgents(sourceDir, exclude) {
|
|
1716
|
+
ensureDir(CODEX_AGENTS_DIR);
|
|
1717
|
+
const entries = readdirSync6(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
|
|
1718
|
+
for (const file of entries) {
|
|
1719
|
+
const content = readFileSync5(join6(sourceDir, file), "utf8");
|
|
1720
|
+
const transformed = transformAgentForCodex(content);
|
|
1721
|
+
writeFileSync5(join6(CODEX_AGENTS_DIR, file), transformed);
|
|
1722
|
+
log.step(`agent: ${file}`);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
installGuidelines(_pluginRoot) {
|
|
1726
|
+
ensureDir(CODEX_DIR);
|
|
1727
|
+
writeFileSync5(CODEX_GUIDELINES_FILE, generateCodexAgentsMd());
|
|
1728
|
+
log.step(`guidelines: ${CODEX_GUIDELINES_FILE}`);
|
|
1729
|
+
}
|
|
1730
|
+
installHooks(sourceDir, exclude) {
|
|
1731
|
+
ensureDir(CODEX_HOOKS_DIR);
|
|
1732
|
+
const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
|
|
1733
|
+
for (const file of hooks) {
|
|
1734
|
+
const src = join6(sourceDir, file);
|
|
1735
|
+
if (existsSync6(src)) {
|
|
1736
|
+
copyFileSync5(src, join6(CODEX_HOOKS_DIR, file));
|
|
1737
|
+
log.step(`hook: ${file}`);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const shimSrc = join6(__dirname4, "hooks-translator.py");
|
|
1741
|
+
if (!existsSync6(shimSrc)) {
|
|
1742
|
+
throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
|
|
1743
|
+
}
|
|
1744
|
+
copyFileSync5(shimSrc, join6(CODEX_HOOKS_DIR, "hooks-translator.py"));
|
|
1745
|
+
log.step("hook: hooks-translator.py (shim)");
|
|
1746
|
+
const hooksConfig = generateCodexHooksConfig();
|
|
1747
|
+
mergeCodexHooksConfig(CODEX_HOOKS_CONFIG, hooksConfig);
|
|
1748
|
+
log.step("hook: hooks.json (generated for Codex CLI)");
|
|
1749
|
+
}
|
|
1750
|
+
postInstall() {
|
|
1751
|
+
log.info("OpenAI Codex CLI install complete. Verify:");
|
|
1752
|
+
log.info(` Skills: ${CODEX_SKILLS_DIR}`);
|
|
1753
|
+
log.info(` Agents: ${CODEX_AGENTS_DIR}`);
|
|
1754
|
+
log.info(` Hooks: ${CODEX_HOOKS_DIR}`);
|
|
1755
|
+
log.info(` Hooks cfg: ${CODEX_HOOKS_CONFIG}`);
|
|
1756
|
+
log.info(` Guidelines: ${CODEX_GUIDELINES_FILE}`);
|
|
1757
|
+
log.info("");
|
|
1758
|
+
log.info("MCP servers are not auto-configured. To register them, add");
|
|
1759
|
+
log.info(` [mcp_servers.<name>] command = "..." args = [...]`);
|
|
1760
|
+
log.info(`tables to ${CODEX_CONFIG_TOML} (STDIO transport).`);
|
|
1761
|
+
log.info("");
|
|
1762
|
+
log.info("If a hook silently fails to fire, the Codex tool name may have changed \u2014");
|
|
1763
|
+
log.info("update TOOL_MAP in platforms/codex/adapter.ts and reinstall.");
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
function mergeCodexHooksConfig(configPath, newConfig) {
|
|
1767
|
+
let existing = {};
|
|
1768
|
+
if (existsSync6(configPath)) {
|
|
1769
|
+
try {
|
|
1770
|
+
const content = readFileSync5(configPath, "utf8");
|
|
1771
|
+
existing = JSON.parse(content);
|
|
1772
|
+
} catch {
|
|
1773
|
+
existing = {};
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
if (!existing.hooks) {
|
|
1777
|
+
existing.hooks = {};
|
|
1778
|
+
}
|
|
1779
|
+
const existingHooks = existing.hooks;
|
|
1780
|
+
const incomingHooks = newConfig.hooks;
|
|
1781
|
+
for (const [event, entries] of Object.entries(incomingHooks)) {
|
|
1782
|
+
if (!existingHooks[event]) {
|
|
1783
|
+
existingHooks[event] = entries;
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
const userEntries = existingHooks[event].filter(
|
|
1787
|
+
(entry) => !entry.hooks?.some((h) => h.command?.includes("hooks-translator.py"))
|
|
1788
|
+
);
|
|
1789
|
+
existingHooks[event] = [...userEntries, ...entries];
|
|
1790
|
+
}
|
|
1791
|
+
writeFileSync5(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1427
1794
|
// platforms/shared/detector.ts
|
|
1428
1795
|
var ADAPTERS = [
|
|
1429
1796
|
new ClaudeAdapter(),
|
|
1430
1797
|
new JunieAdapter(),
|
|
1431
1798
|
new GeminiAdapter(),
|
|
1432
|
-
new GhCopilotAdapter()
|
|
1799
|
+
new GhCopilotAdapter(),
|
|
1800
|
+
new CodexAdapter()
|
|
1433
1801
|
];
|
|
1434
1802
|
function detectPlatforms() {
|
|
1435
1803
|
return ADAPTERS.map((adapter) => {
|
|
@@ -1443,28 +1811,28 @@ function getAdapter(name) {
|
|
|
1443
1811
|
}
|
|
1444
1812
|
|
|
1445
1813
|
// platforms/shared/installer.ts
|
|
1446
|
-
import { join as
|
|
1814
|
+
import { join as join8 } from "path";
|
|
1447
1815
|
|
|
1448
1816
|
// platforms/shared/types.ts
|
|
1449
|
-
import { join as
|
|
1450
|
-
import { existsSync as
|
|
1451
|
-
import { fileURLToPath as
|
|
1817
|
+
import { join as join7, resolve, dirname as dirname5 } from "path";
|
|
1818
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1819
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1452
1820
|
function findPackageRoot() {
|
|
1453
1821
|
let dir;
|
|
1454
1822
|
try {
|
|
1455
|
-
dir =
|
|
1823
|
+
dir = dirname5(fileURLToPath4(import.meta.url));
|
|
1456
1824
|
} catch {
|
|
1457
1825
|
dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
|
|
1458
1826
|
}
|
|
1459
1827
|
for (let i = 0; i < 10; i++) {
|
|
1460
|
-
if (
|
|
1461
|
-
const parent =
|
|
1828
|
+
if (existsSync7(join7(dir, "package.json"))) return dir;
|
|
1829
|
+
const parent = join7(dir, "..");
|
|
1462
1830
|
if (parent === dir) break;
|
|
1463
1831
|
dir = parent;
|
|
1464
1832
|
}
|
|
1465
1833
|
return process.cwd();
|
|
1466
1834
|
}
|
|
1467
|
-
var PLUGIN_SOURCE =
|
|
1835
|
+
var PLUGIN_SOURCE = join7(
|
|
1468
1836
|
findPackageRoot(),
|
|
1469
1837
|
"plugins",
|
|
1470
1838
|
"ima-claude"
|
|
@@ -1580,9 +1948,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
|
|
|
1580
1948
|
return filter;
|
|
1581
1949
|
}
|
|
1582
1950
|
async function installForPlatform(adapter, options = {}) {
|
|
1583
|
-
const skillsSource =
|
|
1584
|
-
const agentsSource =
|
|
1585
|
-
const hooksSource =
|
|
1951
|
+
const skillsSource = join8(PLUGIN_SOURCE, "skills");
|
|
1952
|
+
const agentsSource = join8(PLUGIN_SOURCE, "agents");
|
|
1953
|
+
const hooksSource = join8(PLUGIN_SOURCE, "hooks");
|
|
1586
1954
|
const preview = adapter.preview(PLUGIN_SOURCE);
|
|
1587
1955
|
showPreview(adapter, preview.items);
|
|
1588
1956
|
const filter = await promptExclusions(preview.items);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima-claude",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, Gemini CLI, and
|
|
3
|
+
"version": "2.27.0",
|
|
4
|
+
"description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup",
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, copyFileSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
import type { PlatformAdapter, InstallItem, InstallPreview } from "../shared/types";
|
|
7
|
+
import { ensureDir, copyDirRecursive, log, SKILLS_TO_INSTALL, HOOKS_TO_INSTALL, HOOKS_CONFIG, VERSION } from "../../scripts/utils";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Honour CODEX_HOME if set; otherwise default to ~/.codex
|
|
13
|
+
const CODEX_DIR = process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
14
|
+
const CODEX_SKILLS_DIR = join(CODEX_DIR, "skills");
|
|
15
|
+
const CODEX_AGENTS_DIR = join(CODEX_DIR, "agents");
|
|
16
|
+
const CODEX_HOOKS_DIR = join(CODEX_DIR, "hooks");
|
|
17
|
+
const CODEX_HOOKS_CONFIG = join(CODEX_DIR, "hooks.json");
|
|
18
|
+
const CODEX_GUIDELINES_FILE = join(CODEX_DIR, "AGENTS.md");
|
|
19
|
+
const CODEX_CONFIG_TOML = join(CODEX_DIR, "config.toml");
|
|
20
|
+
|
|
21
|
+
// Claude Code → Codex CLI tool name mapping
|
|
22
|
+
// Based on developers.openai.com/codex docs. "shell" is well-attested; the rest
|
|
23
|
+
// are best-known canonical names. Update here if a Codex release renames any.
|
|
24
|
+
const TOOL_MAP: Record<string, string> = {
|
|
25
|
+
Bash: "shell",
|
|
26
|
+
Read: "read",
|
|
27
|
+
Edit: "edit",
|
|
28
|
+
Write: "write",
|
|
29
|
+
Glob: "glob",
|
|
30
|
+
Grep: "grep",
|
|
31
|
+
LS: "list",
|
|
32
|
+
WebSearch: "web_search",
|
|
33
|
+
WebFetch: "fetch",
|
|
34
|
+
ExitPlanMode: "ExitPlanMode",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Claude Code → Codex CLI hook event mapping
|
|
38
|
+
// Codex uses Claude Code's exact event names natively, so this is identity.
|
|
39
|
+
// Kept as a constant so future divergences are a single-point edit.
|
|
40
|
+
const EVENT_MAP: Record<string, string> = {
|
|
41
|
+
PreToolUse: "PreToolUse",
|
|
42
|
+
PostToolUse: "PostToolUse",
|
|
43
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
44
|
+
SessionStart: "SessionStart",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Simple single-line YAML parser — same shape as Junie/Gemini/Copilot adapters.
|
|
48
|
+
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
49
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
50
|
+
if (!match) return { frontmatter: {}, body: content };
|
|
51
|
+
|
|
52
|
+
const frontmatter: Record<string, string> = {};
|
|
53
|
+
for (const line of match[1].split("\n")) {
|
|
54
|
+
const colonIdx = line.indexOf(":");
|
|
55
|
+
if (colonIdx === -1) continue;
|
|
56
|
+
const key = line.slice(0, colonIdx).trim();
|
|
57
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
58
|
+
if (key) frontmatter[key] = value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { frontmatter, body: match[2] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function serializeFrontmatter(frontmatter: Record<string, string>, body: string): string {
|
|
65
|
+
const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
|
|
66
|
+
return `---\n${lines.join("\n")}\n---\n${body}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function mapToolName(claudeName: string): string {
|
|
70
|
+
return TOOL_MAP[claudeName] ?? claudeName;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function transformAgentForCodex(content: string): string {
|
|
74
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
75
|
+
|
|
76
|
+
// Drop permissionMode (Codex uses global approval_policy / sandbox_mode)
|
|
77
|
+
// Drop model (Codex uses its own model selection)
|
|
78
|
+
const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
|
|
79
|
+
|
|
80
|
+
// Map tool names in the tools field if present
|
|
81
|
+
if (kept.tools) {
|
|
82
|
+
const mapped = kept.tools
|
|
83
|
+
.split(",")
|
|
84
|
+
.map((t) => t.trim())
|
|
85
|
+
.map(mapToolName)
|
|
86
|
+
.join(", ");
|
|
87
|
+
kept.tools = mapped;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return serializeFrontmatter(kept, body);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function translateMatcher(matcher: string): string {
|
|
94
|
+
// MCP tool matchers pass through unchanged; only map built-in Claude tool names
|
|
95
|
+
return TOOL_MAP[matcher] ?? matcher;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function translateHookCommand(command: string): string {
|
|
99
|
+
// Rewrite hook commands to route through the translator shim
|
|
100
|
+
// Original: python3 ~/.claude/hooks/some_hook.py
|
|
101
|
+
// Codex: python3 ~/.codex/hooks/hooks-translator.py ~/.codex/hooks/some_hook.py
|
|
102
|
+
const translatorPath = join(CODEX_HOOKS_DIR, "hooks-translator.py");
|
|
103
|
+
|
|
104
|
+
const match = command.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
|
|
105
|
+
if (!match) return command;
|
|
106
|
+
|
|
107
|
+
const scriptName = match[1];
|
|
108
|
+
const trailingArgs = match[2] ?? "";
|
|
109
|
+
return `python3 ${translatorPath} ${join(CODEX_HOOKS_DIR, scriptName)}${trailingArgs}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function generateCodexHooksConfig(): Record<string, unknown> {
|
|
113
|
+
const codexHooks: Record<string, unknown[]> = {};
|
|
114
|
+
|
|
115
|
+
for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
|
|
116
|
+
const codexEvent = EVENT_MAP[claudeEvent] ?? claudeEvent;
|
|
117
|
+
|
|
118
|
+
codexHooks[codexEvent] = (hookEntries as Array<{ matcher?: string; hooks: Array<{ type: string; command: string }> }>).map(
|
|
119
|
+
(entry) => {
|
|
120
|
+
const translated: Record<string, unknown> = {};
|
|
121
|
+
|
|
122
|
+
if (entry.matcher) {
|
|
123
|
+
translated.matcher = translateMatcher(entry.matcher);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
translated.hooks = entry.hooks.map((h) => ({
|
|
127
|
+
type: h.type,
|
|
128
|
+
command: translateHookCommand(h.command),
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
return translated;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { hooks: codexHooks };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function generateCodexAgentsMd(): string {
|
|
140
|
+
return `# ima-claude: AI Coding Agent Guidelines
|
|
141
|
+
|
|
142
|
+
> Generated by ima-claude v${VERSION} for OpenAI Codex CLI.
|
|
143
|
+
> Source: https://github.com/Soabirw/ima-claude
|
|
144
|
+
|
|
145
|
+
This file is auto-loaded by Codex at session start (\`AGENTS.md\` convention).
|
|
146
|
+
Per-directory \`AGENTS.md\` files in your project tree merge on top of this one,
|
|
147
|
+
with closer-to-cwd files taking precedence.
|
|
148
|
+
|
|
149
|
+
## Default Persona: The Practitioner
|
|
150
|
+
|
|
151
|
+
A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
|
|
152
|
+
Uses "we" not "I" — collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
|
|
153
|
+
|
|
154
|
+
**Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Memory Routing
|
|
159
|
+
|
|
160
|
+
| Store what | Where | Why |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
|
|
163
|
+
| Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
|
|
164
|
+
| Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
|
|
165
|
+
| Future reminders | Vestige \`intention\` | Surfaces at next session |
|
|
166
|
+
|
|
167
|
+
At session start, check memory before asking questions:
|
|
168
|
+
- Vestige: search for user preferences and project context
|
|
169
|
+
- Vestige: check for pending reminders/intentions
|
|
170
|
+
- Serena: list memories if in a Serena-activated project
|
|
171
|
+
|
|
172
|
+
Auto-store: "I prefer..." → Vestige preference. "Let's go with X because..." → Vestige decision. "The reason this failed..." → Vestige bug.
|
|
173
|
+
|
|
174
|
+
After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Orchestrator Protocol
|
|
179
|
+
|
|
180
|
+
You are the Orchestrator. Plan and delegate. Do NOT implement directly.
|
|
181
|
+
- Non-trivial work → task-planner (decompose) → task-runner (delegate)
|
|
182
|
+
- Trivial = single file, < 5 lines, no judgment calls
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Available Agents
|
|
187
|
+
|
|
188
|
+
Codex does not yet support a formal named-subagent dispatcher. Treat the agents
|
|
189
|
+
listed below as documented delegation patterns: when the work matches, scope a
|
|
190
|
+
session (or a per-directory \`AGENTS.md\` overlay) to that agent's persona.
|
|
191
|
+
Reference copies of each agent's full system prompt live in \`~/.codex/agents/\`.
|
|
192
|
+
|
|
193
|
+
| Agent | Use For |
|
|
194
|
+
|---|---|
|
|
195
|
+
| \`explorer\` | File discovery, codebase exploration |
|
|
196
|
+
| \`implementer\` | Feature dev, bug fixes, refactoring |
|
|
197
|
+
| \`reviewer\` | Code review, security audit, FP checks |
|
|
198
|
+
| \`tester\` | Test creation, TDD, debugging test failures |
|
|
199
|
+
| \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
|
|
200
|
+
| \`memory\` | Memory search, storage, consolidation |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Code Navigation (Serena)
|
|
205
|
+
|
|
206
|
+
When Serena MCP is available, **prefer Serena over read/grep for code investigation.** 40-70% token savings.
|
|
207
|
+
|
|
208
|
+
| Instead of | Use |
|
|
209
|
+
|---|---|
|
|
210
|
+
| Read file to understand structure | Serena get_symbols_overview |
|
|
211
|
+
| grep for class/function definition | Serena find_symbol |
|
|
212
|
+
| grep for callers/references | Serena find_referencing_symbols |
|
|
213
|
+
|
|
214
|
+
Use the read tool only when you need the actual implementation body of a known, specific symbol.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Complex Reasoning
|
|
219
|
+
|
|
220
|
+
Use sequential thinking before acting on:
|
|
221
|
+
- Debugging / root cause analysis / "why is this failing"
|
|
222
|
+
- Trade-off evaluation / "which approach"
|
|
223
|
+
- Architectural decisions / design choices
|
|
224
|
+
- Multi-step investigations where approach may change
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## MCP Tool Routing
|
|
229
|
+
|
|
230
|
+
| Signal | Preferred Tool |
|
|
231
|
+
|---|---|
|
|
232
|
+
| "latest", "2025/2026", "what's new" | Tavily search |
|
|
233
|
+
| Library/framework API question | Context7 |
|
|
234
|
+
| URL content extraction | Tavily extract (use advanced for complex pages) |
|
|
235
|
+
|
|
236
|
+
Before web tools: check internal knowledge → Context7 → then Tavily.
|
|
237
|
+
Before external lookups: check Vestige memory first.
|
|
238
|
+
|
|
239
|
+
To register MCP servers in Codex, add \`[mcp_servers.<name>]\` tables to
|
|
240
|
+
\`~/.codex/config.toml\`. STDIO transport is the documented default.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Search Preference
|
|
245
|
+
|
|
246
|
+
Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Security
|
|
251
|
+
|
|
252
|
+
- Verify nonce usage and input sanitization in WordPress PHP code
|
|
253
|
+
- Never concatenate user input directly into SQL — use parameterized queries
|
|
254
|
+
- Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Code Style
|
|
259
|
+
|
|
260
|
+
- Don't create custom FP utility functions (pipe, compose, curry) — use language-native patterns or established libraries
|
|
261
|
+
- In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
|
|
262
|
+
- Prefer Bootstrap utility classes over custom CSS overrides
|
|
263
|
+
- Run \`composer dump-autoload\` after creating new PHP files
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Documentation
|
|
268
|
+
|
|
269
|
+
Follow the three-tier documentation system:
|
|
270
|
+
- **Active** — Living docs, kept current (README, API docs, architecture)
|
|
271
|
+
- **Archive** — Historical reference, rarely updated (decisions, post-mortems)
|
|
272
|
+
- **Transient** — Ephemeral, git-ignored (session notes, scratch)
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export class CodexAdapter implements PlatformAdapter {
|
|
277
|
+
readonly name = "codex";
|
|
278
|
+
readonly displayName = "OpenAI Codex CLI";
|
|
279
|
+
readonly configDir = CODEX_DIR;
|
|
280
|
+
|
|
281
|
+
detect(): boolean {
|
|
282
|
+
return existsSync(CODEX_DIR);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
preview(sourceDir: string): InstallPreview {
|
|
286
|
+
const skillItems: InstallItem[] = SKILLS_TO_INSTALL.map((skill) => ({
|
|
287
|
+
name: skill,
|
|
288
|
+
category: "skill" as const,
|
|
289
|
+
destPath: join(CODEX_SKILLS_DIR, skill),
|
|
290
|
+
exists: existsSync(join(CODEX_SKILLS_DIR, skill)),
|
|
291
|
+
})).filter((item) => existsSync(join(sourceDir, "skills", item.name)));
|
|
292
|
+
|
|
293
|
+
const agentsDir = join(sourceDir, "agents");
|
|
294
|
+
const agentItems: InstallItem[] = existsSync(agentsDir)
|
|
295
|
+
? readdirSync(agentsDir)
|
|
296
|
+
.filter((f) => f.endsWith(".md"))
|
|
297
|
+
.map((file) => ({
|
|
298
|
+
name: file.replace(/\.md$/, ""),
|
|
299
|
+
category: "agent" as const,
|
|
300
|
+
destPath: join(CODEX_AGENTS_DIR, file),
|
|
301
|
+
exists: existsSync(join(CODEX_AGENTS_DIR, file)),
|
|
302
|
+
}))
|
|
303
|
+
: [];
|
|
304
|
+
|
|
305
|
+
const hookItems: InstallItem[] = HOOKS_TO_INSTALL.map((file) => ({
|
|
306
|
+
name: file,
|
|
307
|
+
category: "hook" as const,
|
|
308
|
+
destPath: join(CODEX_HOOKS_DIR, file),
|
|
309
|
+
exists: existsSync(join(CODEX_HOOKS_DIR, file)),
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
const translatorItem: InstallItem = {
|
|
313
|
+
name: "hooks-translator.py",
|
|
314
|
+
category: "hook",
|
|
315
|
+
destPath: join(CODEX_HOOKS_DIR, "hooks-translator.py"),
|
|
316
|
+
exists: existsSync(join(CODEX_HOOKS_DIR, "hooks-translator.py")),
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const guidelineItem: InstallItem = {
|
|
320
|
+
name: "AGENTS.md",
|
|
321
|
+
category: "guideline",
|
|
322
|
+
destPath: CODEX_GUIDELINES_FILE,
|
|
323
|
+
exists: existsSync(CODEX_GUIDELINES_FILE),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
platform: this.name,
|
|
328
|
+
targetDir: CODEX_DIR,
|
|
329
|
+
items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
installSkills(sourceDir: string, exclude?: string[]): void {
|
|
334
|
+
ensureDir(CODEX_SKILLS_DIR);
|
|
335
|
+
const skills = exclude?.length
|
|
336
|
+
? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s))
|
|
337
|
+
: SKILLS_TO_INSTALL;
|
|
338
|
+
for (const skill of skills) {
|
|
339
|
+
const src = join(sourceDir, skill);
|
|
340
|
+
if (existsSync(src) && statSync(src).isDirectory()) {
|
|
341
|
+
copyDirRecursive(src, join(CODEX_SKILLS_DIR, skill));
|
|
342
|
+
log.step(`skill: ${skill}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
installAgents(sourceDir: string, exclude?: string[]): void {
|
|
348
|
+
ensureDir(CODEX_AGENTS_DIR);
|
|
349
|
+
const entries = readdirSync(sourceDir)
|
|
350
|
+
.filter((f) => f.endsWith(".md"))
|
|
351
|
+
.filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
|
|
352
|
+
for (const file of entries) {
|
|
353
|
+
const content = readFileSync(join(sourceDir, file), "utf8");
|
|
354
|
+
const transformed = transformAgentForCodex(content);
|
|
355
|
+
writeFileSync(join(CODEX_AGENTS_DIR, file), transformed);
|
|
356
|
+
log.step(`agent: ${file}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
installGuidelines(_pluginRoot: string): void {
|
|
361
|
+
ensureDir(CODEX_DIR);
|
|
362
|
+
writeFileSync(CODEX_GUIDELINES_FILE, generateCodexAgentsMd());
|
|
363
|
+
log.step(`guidelines: ${CODEX_GUIDELINES_FILE}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
installHooks(sourceDir: string, exclude?: string[]): void {
|
|
367
|
+
ensureDir(CODEX_HOOKS_DIR);
|
|
368
|
+
|
|
369
|
+
const hooks = exclude?.length
|
|
370
|
+
? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f))
|
|
371
|
+
: HOOKS_TO_INSTALL;
|
|
372
|
+
for (const file of hooks) {
|
|
373
|
+
const src = join(sourceDir, file);
|
|
374
|
+
if (existsSync(src)) {
|
|
375
|
+
copyFileSync(src, join(CODEX_HOOKS_DIR, file));
|
|
376
|
+
log.step(`hook: ${file}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const shimSrc = join(__dirname, "hooks-translator.py");
|
|
381
|
+
if (!existsSync(shimSrc)) {
|
|
382
|
+
throw new Error(`hooks-translator.py not found at ${shimSrc} — packaging error`);
|
|
383
|
+
}
|
|
384
|
+
copyFileSync(shimSrc, join(CODEX_HOOKS_DIR, "hooks-translator.py"));
|
|
385
|
+
log.step("hook: hooks-translator.py (shim)");
|
|
386
|
+
|
|
387
|
+
const hooksConfig = generateCodexHooksConfig();
|
|
388
|
+
mergeCodexHooksConfig(CODEX_HOOKS_CONFIG, hooksConfig);
|
|
389
|
+
log.step("hook: hooks.json (generated for Codex CLI)");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
postInstall(): void {
|
|
393
|
+
log.info("OpenAI Codex CLI install complete. Verify:");
|
|
394
|
+
log.info(` Skills: ${CODEX_SKILLS_DIR}`);
|
|
395
|
+
log.info(` Agents: ${CODEX_AGENTS_DIR}`);
|
|
396
|
+
log.info(` Hooks: ${CODEX_HOOKS_DIR}`);
|
|
397
|
+
log.info(` Hooks cfg: ${CODEX_HOOKS_CONFIG}`);
|
|
398
|
+
log.info(` Guidelines: ${CODEX_GUIDELINES_FILE}`);
|
|
399
|
+
log.info("");
|
|
400
|
+
log.info("MCP servers are not auto-configured. To register them, add");
|
|
401
|
+
log.info(` [mcp_servers.<name>] command = "..." args = [...]`);
|
|
402
|
+
log.info(`tables to ${CODEX_CONFIG_TOML} (STDIO transport).`);
|
|
403
|
+
log.info("");
|
|
404
|
+
log.info("If a hook silently fails to fire, the Codex tool name may have changed —");
|
|
405
|
+
log.info("update TOOL_MAP in platforms/codex/adapter.ts and reinstall.");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function mergeCodexHooksConfig(
|
|
410
|
+
configPath: string,
|
|
411
|
+
newConfig: Record<string, unknown>
|
|
412
|
+
): void {
|
|
413
|
+
let existing: Record<string, unknown> = {};
|
|
414
|
+
|
|
415
|
+
if (existsSync(configPath)) {
|
|
416
|
+
try {
|
|
417
|
+
const content = readFileSync(configPath, "utf8");
|
|
418
|
+
existing = JSON.parse(content);
|
|
419
|
+
} catch {
|
|
420
|
+
existing = {};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!existing.hooks) {
|
|
425
|
+
existing.hooks = {};
|
|
426
|
+
}
|
|
427
|
+
const existingHooks = existing.hooks as Record<string, unknown[]>;
|
|
428
|
+
const incomingHooks = (newConfig as Record<string, unknown>).hooks as Record<
|
|
429
|
+
string,
|
|
430
|
+
Array<{ matcher?: string; hooks: Array<{ command: string }> }>
|
|
431
|
+
>;
|
|
432
|
+
|
|
433
|
+
// Merge each event: strip prior ima-claude entries (identified by hooks-translator.py
|
|
434
|
+
// substring in any nested command), then append the new entries. User hooks survive.
|
|
435
|
+
for (const [event, entries] of Object.entries(incomingHooks)) {
|
|
436
|
+
if (!existingHooks[event]) {
|
|
437
|
+
existingHooks[event] = entries;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const userEntries = (
|
|
442
|
+
existingHooks[event] as Array<{ matcher?: string; hooks?: Array<{ command?: string }> }>
|
|
443
|
+
).filter(
|
|
444
|
+
(entry) =>
|
|
445
|
+
!entry.hooks?.some((h) => h.command?.includes("hooks-translator.py"))
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
existingHooks[event] = [...userEntries, ...entries];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
452
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""OpenAI Codex CLI → Claude Code tool name translator shim.
|
|
3
|
+
|
|
4
|
+
Sits between Codex CLI and ima-claude hook scripts, translating Codex
|
|
5
|
+
tool names to their Claude Code equivalents so existing hooks (which
|
|
6
|
+
read `tool_name="Bash"`, etc.) work unmodified.
|
|
7
|
+
|
|
8
|
+
Codex hook events (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart)
|
|
9
|
+
already match Claude Code's names, so only the `tool_name` field inside
|
|
10
|
+
the JSON payload needs rewriting.
|
|
11
|
+
|
|
12
|
+
Usage (in hooks.json):
|
|
13
|
+
python3 ~/.codex/hooks/hooks-translator.py ~/.codex/hooks/some_hook.py
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
# Codex CLI → Claude Code tool name mapping (reverse of adapter TOOL_MAP)
|
|
21
|
+
CODEX_TO_CLAUDE = {
|
|
22
|
+
"shell": "Bash",
|
|
23
|
+
"read": "Read",
|
|
24
|
+
"edit": "Edit",
|
|
25
|
+
"write": "Write",
|
|
26
|
+
"glob": "Glob",
|
|
27
|
+
"grep": "Grep",
|
|
28
|
+
"list": "LS",
|
|
29
|
+
"web_search": "WebSearch",
|
|
30
|
+
"fetch": "WebFetch",
|
|
31
|
+
"ExitPlanMode": "ExitPlanMode",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
if len(sys.argv) < 2:
|
|
37
|
+
print("Usage: hooks-translator.py <hook-script> [args...]", file=sys.stderr)
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
target_script = sys.argv[1]
|
|
41
|
+
extra_args = sys.argv[2:]
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
raw = sys.stdin.read()
|
|
45
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
46
|
+
except json.JSONDecodeError as e:
|
|
47
|
+
print(f"hooks-translator: invalid JSON from stdin: {e}", file=sys.stderr)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
tool_name = data.get("tool_name", "")
|
|
51
|
+
if tool_name in CODEX_TO_CLAUDE:
|
|
52
|
+
data["tool_name"] = CODEX_TO_CLAUDE[tool_name]
|
|
53
|
+
|
|
54
|
+
translated = json.dumps(data)
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
["python3", target_script] + extra_args,
|
|
57
|
+
input=translated,
|
|
58
|
+
stdout=None,
|
|
59
|
+
stderr=None,
|
|
60
|
+
text=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
sys.exit(result.returncode)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
|
@@ -3,12 +3,14 @@ import { ClaudeAdapter } from "../claude/adapter";
|
|
|
3
3
|
import { JunieAdapter } from "../junie/adapter";
|
|
4
4
|
import { GeminiAdapter } from "../gemini/adapter";
|
|
5
5
|
import { GhCopilotAdapter } from "../gh-copilot/adapter";
|
|
6
|
+
import { CodexAdapter } from "../codex/adapter";
|
|
6
7
|
|
|
7
8
|
const ADAPTERS: PlatformAdapter[] = [
|
|
8
9
|
new ClaudeAdapter(),
|
|
9
10
|
new JunieAdapter(),
|
|
10
11
|
new GeminiAdapter(),
|
|
11
12
|
new GhCopilotAdapter(),
|
|
13
|
+
new CodexAdapter(),
|
|
12
14
|
];
|
|
13
15
|
|
|
14
16
|
export function detectPlatforms(): DetectedPlatform[] {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima-claude",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "IMA's Claude Code skills for functional programming, architecture, and team standards.
|
|
3
|
+
"version": "2.27.0",
|
|
4
|
+
"description": "IMA's Claude Code skills for functional programming, architecture, and team standards. 63 skills, 24 hooks, default persona, 3-tier memory system.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "IMA",
|
|
7
7
|
"url": "https://github.com/Soabirw/ima-claude"
|
|
@@ -23,6 +23,33 @@ Use Serena as FIRST approach for ALL code investigation. Saves 40-70% tokens vs
|
|
|
23
23
|
|
|
24
24
|
Use Read only for specific symbol bodies under review. Fall back to Read/Grep for non-code files.
|
|
25
25
|
|
|
26
|
+
## Configured validators (REQUIRED — run first)
|
|
27
|
+
|
|
28
|
+
A review is incomplete without running the project's configured gate. Validator output is primary evidence; code-reading is supplementary.
|
|
29
|
+
|
|
30
|
+
Discover validators in this order:
|
|
31
|
+
|
|
32
|
+
| Ecosystem | Where to look |
|
|
33
|
+
|---|---|
|
|
34
|
+
| PHP | `composer.json` scripts — `check`, `test`, `test:unit`, `phpcs`, `phpcs:report`, `lint` |
|
|
35
|
+
| JS/TS | `package.json` scripts — `test`, `lint`, `typecheck`, `check`, `ci` |
|
|
36
|
+
| Make-based | `Makefile` targets — `check`, `test`, `lint`, `ci` |
|
|
37
|
+
| Python | `pyproject.toml` / `tox.ini` / `noxfile.py` |
|
|
38
|
+
| Ruby | `Rakefile` — `ci`, `test`, `rubocop` |
|
|
39
|
+
|
|
40
|
+
Run the project's aggregated gate if one exists (`composer check`, `npm run check`, `make check`, `rake ci`, etc.). Otherwise run lint + tests separately. If the project has none, that's a finding — not a silent pass.
|
|
41
|
+
|
|
42
|
+
**Every review output MUST include a "Validators run" block:**
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
## Validators run
|
|
46
|
+
- composer test:unit → exit 0 (308 tests, 0 failures)
|
|
47
|
+
- composer phpcs:report → exit 1 (0 errors, 82 warnings)
|
|
48
|
+
- (no JS lint configured)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A review with zero validator invocations is structurally incomplete. If you couldn't run a discovered validator (missing deps, auth required, etc.), say so explicitly and flag as a blocker for the review.
|
|
52
|
+
|
|
26
53
|
## PR review mode
|
|
27
54
|
|
|
28
55
|
When given a Gitea/GitHub PR URL or diff:
|
|
@@ -39,7 +66,7 @@ Diff-only analysis is the failure mode. Always read surrounding context.
|
|
|
39
66
|
|
|
40
67
|
**FP** — unnecessary mutation, side effects mixed with business logic, missing composition, custom FP utilities over native patterns
|
|
41
68
|
|
|
42
|
-
**Security** — input validation at boundaries, SQL injection, XSS, exposed secrets, auth/authz
|
|
69
|
+
**Security** — start with security-sniff output (WPCS security, eslint-plugin-security, bandit, etc.) if available; then input validation at boundaries, SQL injection, XSS, exposed secrets, auth/authz
|
|
43
70
|
|
|
44
71
|
**Quality** — naming clarity, over-engineering, dead code, pattern consistency
|
|
45
72
|
|
|
@@ -73,3 +100,4 @@ Parent (Opus) decides whether to expand scope, re-dispatch a focused follow-up r
|
|
|
73
100
|
- Flag style preferences that don't affect correctness
|
|
74
101
|
- Suggest adding comments/docstrings/types to unchanged code
|
|
75
102
|
- Report more than 10 findings — prioritize ruthlessly
|
|
103
|
+
- Assert on code standards, security, or test coverage without having run the corresponding validator. "Looks clean" is not a finding; "phpcs reports 0 errors" is.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "ima-git"
|
|
3
|
+
description: "IMA git workflow. Trunk-based development with release branches: main = dev trunk, release/* = release candidate branches, v* tags = production releases. Covers branch promotion, hotfix flow, push cadence for deploy-gate verification, commit atomicity. Use when: creating branches, opening PRs, cutting releases or tags, writing hotfixes, or asked about git workflow, branching strategy, trunk-based development, release process, or when a deploy gate fails because commits weren't pushed."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IMA Git Workflow
|
|
7
|
+
|
|
8
|
+
**Strategy:** Trunk-based development with release branches.
|
|
9
|
+
|
|
10
|
+
## Branch model
|
|
11
|
+
|
|
12
|
+
| Branch / tag | Role | Source | Deploys to |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| `main` | Dev trunk — latest integrated work | All new work lands here first | Dev environment |
|
|
15
|
+
| `release/<name>` | Release candidate | Fast-forwarded from `main` when ready | Staging environment |
|
|
16
|
+
| `v<version>` (tags) | Production release | Tag cut from a release branch at promotion | Production environment |
|
|
17
|
+
| `hotfix/<name>` | Emergency production fix | Branched off the production tag | Merged back to `main` + new tag cut |
|
|
18
|
+
|
|
19
|
+
## Core rules
|
|
20
|
+
|
|
21
|
+
1. **Commits originate on `main`.** Not on release branches. Feature work, bug fixes, refactors — all start on the trunk.
|
|
22
|
+
2. **Release branches only fast-forward from `main`.** Never merge sideways; never commit directly to a release branch.
|
|
23
|
+
3. **Hotfixes branch off the production tag**, not main. After the fix lands, cut a new tag and merge the hotfix back into `main`.
|
|
24
|
+
4. **Tags are immutable.** Rolling back to a prior production state means deploying the prior tag — not force-pushing or retagging.
|
|
25
|
+
|
|
26
|
+
## Deploy-gate verification cadence
|
|
27
|
+
|
|
28
|
+
Deploy tools (e.g., `trn-deploy` at `/home/eric/IMA/dev/sites/trn/deploy/`) **clone fresh from the remote** — they do not read the local filesystem. Unpushed commits are invisible to the gate.
|
|
29
|
+
|
|
30
|
+
**To verify a change:** push to `main` first, then invoke the dry-run.
|
|
31
|
+
|
|
32
|
+
Between commits in a multi-commit sequence:
|
|
33
|
+
|
|
34
|
+
1. Commit locally
|
|
35
|
+
2. `git push origin main`
|
|
36
|
+
3. Run `./deploy dev --dry-run`
|
|
37
|
+
4. Confirm exit 0 in `log.jsonl` before the next commit
|
|
38
|
+
|
|
39
|
+
## Commit atomicity
|
|
40
|
+
|
|
41
|
+
- **Security fixes: separate commits** from test-only changes and mechanical sweeps. A security change should land in a reviewable, revertable commit of its own.
|
|
42
|
+
- One commit per logical change class. Don't batch "fix XSS + rename variable + reformat whitespace" into one diff.
|
|
43
|
+
- Pure tests-only changes never mix with production code changes in the same commit.
|
|
44
|
+
|
|
45
|
+
## Commit messages
|
|
46
|
+
|
|
47
|
+
- Use a HEREDOC to pass multi-line messages: `git commit -m "$(cat <<'EOF' ... EOF)"`.
|
|
48
|
+
- Single-quoted `EOF` marker (`<<'EOF'`) prevents shell expansion — required to keep `$_POST`, `$_GET`, `$variable` etc. unescaped.
|
|
49
|
+
- A message with literal `\$_POST` in the body is a shell-escape artifact — amend before pushing, or treat it as a signal the HEREDOC was wrong.
|
|
50
|
+
|
|
51
|
+
## Deploy tool exit codes
|
|
52
|
+
|
|
53
|
+
Shared convention across IMA deploy tools:
|
|
54
|
+
|
|
55
|
+
| Code | Meaning |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 0 | Success |
|
|
58
|
+
| 1 | Generic failure |
|
|
59
|
+
| 2 | Validation / config error |
|
|
60
|
+
| 3 | Pre-flight failure (tests, lint, clean-state check) |
|
|
61
|
+
| 4 | Remote push failure (WPEngine or similar) |
|
|
62
|
+
|
|
63
|
+
The authoritative record is the tool's `log.jsonl`: `tail -1 log.jsonl` shows the last run's sha, exitCode, duration. The shell's `$?` may be masked by a pipe (`| tail`), so trust the log over stdout exit.
|
|
64
|
+
|
|
65
|
+
## Gitea / GitHub
|
|
66
|
+
|
|
67
|
+
- IMA internal repos live on Gitea: `ssh://git@gitea.theflccc.org:2222/IMA/<repo>.git`
|
|
68
|
+
- Some are mirrored to GitHub for FOSS / public presence.
|
|
69
|
+
- Use `mcp-gitea` for internal repo operations, `mcp-github` or `gh-cli` for public.
|
|
70
|
+
|
|
71
|
+
## When the deploy gate fails
|
|
72
|
+
|
|
73
|
+
1. Check `log.jsonl` for the exit code. Don't guess from terminal output.
|
|
74
|
+
2. Exit 3 = pre-flight. Look at the pipeline stage that failed — tests, lint, clean-state, composer check.
|
|
75
|
+
3. Reproduce locally with the project's configured validators (`composer check`, `npm run check`, `make check`, etc.) before touching code.
|
|
76
|
+
4. If a pre-flight failure is tests, invoke the source-quality triage pattern: categorize each failure as test rot, code bug, or design question **before** changing anything.
|
|
77
|
+
|
|
78
|
+
## Reference
|
|
79
|
+
|
|
80
|
+
- Qdrant `ima-knowledge` → article "IMA Git Workflow & Pre-flight Deploy Gate" for full detail on the gate architecture and the source-quality triage playbook.
|
|
81
|
+
- Project-specific deploy specifics live in the project's Serena memory (typically named `project-workflow` or similar).
|
|
@@ -26,9 +26,25 @@ Arguments are domain skills to evaluate against. If none provided, auto-detect f
|
|
|
26
26
|
3. Identify project's primary language(s) and framework(s)
|
|
27
27
|
4. Locate README (or note its absence)
|
|
28
28
|
|
|
29
|
-
### Step 2:
|
|
29
|
+
### Step 2: Run project validators (REQUIRED)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Before any grading, discover and run the project's configured gate. Same discovery order as the `reviewer` agent:
|
|
32
|
+
|
|
33
|
+
| Ecosystem | Where to look |
|
|
34
|
+
|---|---|
|
|
35
|
+
| PHP | `composer.json` scripts — `check`, `test`, `phpcs`, `phpcs:report` |
|
|
36
|
+
| JS/TS | `package.json` scripts — `test`, `lint`, `typecheck`, `check` |
|
|
37
|
+
| Make-based | `Makefile` targets — `check`, `test`, `lint`, `ci` |
|
|
38
|
+
| Python | `pyproject.toml` / `tox.ini` / `noxfile.py` |
|
|
39
|
+
| Ruby | `Rakefile` — `ci`, `test`, `rubocop` |
|
|
40
|
+
|
|
41
|
+
Capture exit codes, error/warning counts, and test coverage % (run the coverage variant — `composer test:coverage`, `npm test -- --coverage` — when available). These are primary evidence for Code Standards, Security, and Test Coverage grades.
|
|
42
|
+
|
|
43
|
+
Grades without validator backing are vibes grades. Don't ship them. No validators configured is itself a signal — floor Code Standards at C until they exist.
|
|
44
|
+
|
|
45
|
+
### Step 3: Parallel Review
|
|
46
|
+
|
|
47
|
+
Spawn parallel agents per category (`model: "sonnet"`). Each returns letter grade (A-F) + 2-3 justifying bullets rooted in validator output. Be honest — inflated scores help nobody.
|
|
32
48
|
|
|
33
49
|
| Category | What to Evaluate | Key Signals |
|
|
34
50
|
|----------|-----------------|-------------|
|
|
@@ -38,7 +54,7 @@ Spawn parallel agents per category (`model: "sonnet"`). Each returns letter grad
|
|
|
38
54
|
| **Documentation** | README quality, inline docs, API docs | Setup instructions, usage examples, architecture notes |
|
|
39
55
|
| **Maintainability** | Complexity, coupling, file organization | Small functions, clear boundaries, no circular deps |
|
|
40
56
|
|
|
41
|
-
### Step
|
|
57
|
+
### Step 4: Compile Scores
|
|
42
58
|
|
|
43
59
|
```markdown
|
|
44
60
|
## Scorecard
|
|
@@ -69,7 +85,7 @@ Spawn parallel agents per category (`model: "sonnet"`). Each returns letter grad
|
|
|
69
85
|
| D | 🔴 D | Poor — significant issues |
|
|
70
86
|
| F | 🔴 F | Failing — critical problems |
|
|
71
87
|
|
|
72
|
-
### Step
|
|
88
|
+
### Step 5: Insert into README
|
|
73
89
|
|
|
74
90
|
- Replace existing `## Scorecard` section, or insert after first heading if absent
|
|
75
91
|
- Present full scorecard to user before writing
|