ima-claude 2.9.0 → 2.13.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 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 and Junie CLI** — with an extensible adapter architecture ready for more platforms.
5
+ **Supports Claude Code, Junie CLI, and Gemini 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 — Multi-Platform Installer
41
+ ### Junie CLI / Gemini CLI — Multi-Platform Installer
42
42
 
43
- For Junie (or any non-plugin platform), use the interactive CLI installer:
43
+ For Junie, Gemini, 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`) and Junie CLI (`~/.junie`)
51
+ 1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), and Gemini CLI (`~/.gemini`)
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
@@ -57,20 +57,21 @@ You can also target a specific platform directly:
57
57
 
58
58
  ```bash
59
59
  npx ima-claude install --target junie # Junie only
60
+ npx ima-claude install --target gemini # Gemini CLI only
60
61
  npx ima-claude install --target claude # Claude Code only (plugin recommended instead)
61
62
  npx ima-claude detect # Show detected platforms
62
63
  ```
63
64
 
64
- **What's different for Junie?**
65
+ **What's different per platform?**
65
66
 
66
- | | Claude Code | Junie CLI |
67
- |---|---|---|
68
- | **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` |
69
- | **Agents** | Plugin system (auto) | Transformed (strips `permissionMode`) `~/.junie/agents/` |
70
- | **Hooks** | 23 Python hook scripts | No hook system — translated into behavioral guidelines |
71
- | **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` with persona, workflow, and hook-derived rules |
67
+ | | Claude Code | Junie CLI | Gemini CLI |
68
+ |---|---|---|---|
69
+ | **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` |
70
+ | **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names |
71
+ | **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim |
72
+ | **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` |
72
73
 
73
- Junie doesn't support hooks, so the installer translates all 25 hook behaviors into persistent guidelines inside `AGENTS.md` — same enforcement, different mechanism.
74
+ Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini has hooks but uses different event names (`BeforeTool`/`AfterTool`) and tool names (`run_shell_command`, `replace`, etc.) a translator shim normalizes input so all existing hooks work unmodified.
74
75
 
75
76
  ### Adding New Platforms
76
77
 
@@ -80,7 +81,8 @@ The installer uses an adapter pattern. Adding support for a new platform (e.g.,
80
81
  platforms/
81
82
  ├── shared/types.ts # PlatformAdapter interface
82
83
  ├── claude/adapter.ts # Claude Code adapter
83
- └── junie/adapter.ts # Junie CLI adapter
84
+ ├── junie/adapter.ts # Junie CLI adapter
85
+ └── gemini/adapter.ts # Gemini CLI adapter
84
86
  ```
85
87
 
86
88
  See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface contract.
@@ -89,7 +91,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
89
91
 
90
92
  ## What's Included
91
93
 
92
- - **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code and Junie CLI, extensible adapter architecture for more
94
+ - **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, and Gemini CLI
93
95
  - **48 Skills**: Foundational + FP implementation + domain expert + integration + meta-skills
94
96
  - **6 Named Agents**: Explorer (haiku), Implementer (sonnet), Reviewer (sonnet), Tester (sonnet), WP Developer (sonnet), Memory (sonnet) — enforced constraints
95
97
  - **23 Hooks**: Automatic behavioral enforcement (security, memory, workflow, Serena, code quality) — translated to guidelines for platforms without hook support
@@ -102,7 +104,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
102
104
 
103
105
  ## Prerequisites
104
106
 
105
- - [Claude Code](https://claude.ai/code) or [Junie CLI](https://www.jetbrains.com/help/idea/junie.html) installed
107
+ - [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), or [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed
106
108
 
107
109
  ## MCP Servers (Highly Recommended)
108
110
 
@@ -200,6 +202,9 @@ ima-claude includes skills that teach Claude how to use each MCP server effectiv
200
202
  | **mcp-context7** | Library documentation lookup strategies |
201
203
  | **mcp-sequential** | Structured reasoning workflows |
202
204
  | **mcp-atlassian** | Jira & Confluence operations (Claude's bundled integration) |
205
+ | **mcp-gitea** | Internal Git management: PRs, issues, releases, branches, tags, wikis, CI/CD actions |
206
+ | **mcp-github** | FOSS/public repo management: PRs, issues, code review, repo search (`gh` CLI fallback) |
207
+ | **gh-cli** | GitHub CLI (`gh`) for PRs, issues, releases, Actions, code review, search, and raw API access |
203
208
  | ~~**compound-bridge**~~ | Compound Engineering integration — **deprecated**, only useful if your team actively uses Compound Engineering |
204
209
 
205
210
  ### Session Management Skills
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.9.0";
14
+ var VERSION = "2.13.0";
15
15
  var colors = {
16
16
  reset: "\x1B[0m",
17
17
  bright: "\x1B[1m",
@@ -107,6 +107,9 @@ var SKILLS_TO_INSTALL = [
107
107
  "compound-bridge",
108
108
  // MCP integration skills
109
109
  "mcp-atlassian",
110
+ "mcp-gitea",
111
+ "mcp-github",
112
+ "gh-cli",
110
113
  "mcp-tavily",
111
114
  "mcp-context7",
112
115
  "mcp-serena",
@@ -684,15 +687,380 @@ var JunieAdapter = class {
684
687
  }
685
688
  };
686
689
 
690
+ // platforms/gemini/adapter.ts
691
+ import { join as join4, dirname as dirname2 } from "path";
692
+ import { homedir as homedir3 } from "os";
693
+ import { existsSync as existsSync4, readdirSync as readdirSync4, statSync as statSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, copyFileSync as copyFileSync3 } from "fs";
694
+ import { fileURLToPath } from "url";
695
+ var __filename = fileURLToPath(import.meta.url);
696
+ var __dirname2 = dirname2(__filename);
697
+ var GEMINI_DIR = join4(homedir3(), ".gemini");
698
+ var GEMINI_SKILLS_DIR = join4(GEMINI_DIR, "skills");
699
+ var GEMINI_AGENTS_DIR = join4(GEMINI_DIR, "agents");
700
+ var GEMINI_HOOKS_DIR = join4(GEMINI_DIR, "hooks");
701
+ var GEMINI_SETTINGS_FILE = join4(GEMINI_DIR, "settings.json");
702
+ var GEMINI_GUIDELINES_FILE = join4(GEMINI_DIR, "GEMINI.md");
703
+ var TOOL_MAP = {
704
+ Bash: "run_shell_command",
705
+ Read: "read_file",
706
+ Edit: "replace",
707
+ Write: "write_file",
708
+ Glob: "glob",
709
+ Grep: "grep_search",
710
+ LS: "list_directory",
711
+ WebSearch: "google_web_search",
712
+ WebFetch: "web_fetch",
713
+ ExitPlanMode: "exit_plan_mode"
714
+ };
715
+ var EVENT_MAP = {
716
+ PreToolUse: "BeforeTool",
717
+ PostToolUse: "AfterTool",
718
+ UserPromptSubmit: "BeforeAgent",
719
+ SessionStart: "SessionStart"
720
+ };
721
+ function parseFrontmatter2(content) {
722
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
723
+ if (!match) return { frontmatter: {}, body: content };
724
+ const frontmatter = {};
725
+ for (const line of match[1].split("\n")) {
726
+ const colonIdx = line.indexOf(":");
727
+ if (colonIdx === -1) continue;
728
+ const key = line.slice(0, colonIdx).trim();
729
+ const value = line.slice(colonIdx + 1).trim();
730
+ if (key) frontmatter[key] = value;
731
+ }
732
+ return { frontmatter, body: match[2] };
733
+ }
734
+ function serializeFrontmatter2(frontmatter, body) {
735
+ const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
736
+ return `---
737
+ ${lines.join("\n")}
738
+ ---
739
+ ${body}`;
740
+ }
741
+ function mapToolName(claudeName) {
742
+ return TOOL_MAP[claudeName] ?? claudeName;
743
+ }
744
+ function transformAgentForGemini(content) {
745
+ const { frontmatter, body } = parseFrontmatter2(content);
746
+ const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
747
+ if (kept.tools) {
748
+ const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName).join(", ");
749
+ kept.tools = mapped;
750
+ }
751
+ return serializeFrontmatter2(kept, body);
752
+ }
753
+ function translateMatcher(matcher) {
754
+ return TOOL_MAP[matcher] ?? matcher;
755
+ }
756
+ function translateHookCommand(command2) {
757
+ const hooksDir = GEMINI_HOOKS_DIR;
758
+ const translatorPath = join4(hooksDir, "hooks-translator.py");
759
+ const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
760
+ if (!match) return command2;
761
+ const scriptName = match[1];
762
+ const trailingArgs = match[2] ?? "";
763
+ return `python3 ${translatorPath} ${join4(hooksDir, scriptName)}${trailingArgs}`;
764
+ }
765
+ function generateGeminiHooksConfig() {
766
+ const geminiHooks = {};
767
+ for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
768
+ const geminiEvent = EVENT_MAP[claudeEvent] ?? claudeEvent;
769
+ geminiHooks[geminiEvent] = hookEntries.map(
770
+ (entry) => {
771
+ const translated = {};
772
+ if (entry.matcher) {
773
+ translated.matcher = translateMatcher(entry.matcher);
774
+ }
775
+ translated.hooks = entry.hooks.map((h) => ({
776
+ type: h.type,
777
+ command: translateHookCommand(h.command)
778
+ }));
779
+ return translated;
780
+ }
781
+ );
782
+ }
783
+ return { hooks: geminiHooks };
784
+ }
785
+ function generateGeminiMd() {
786
+ return `# ima-claude: AI Coding Agent Guidelines
787
+
788
+ > Generated by ima-claude v${VERSION} for Gemini CLI.
789
+ > Source: https://github.com/Soabirw/ima-claude
790
+
791
+ ## Default Persona: The Practitioner
792
+
793
+ A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
794
+ Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
795
+
796
+ **Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
797
+
798
+ ---
799
+
800
+ ## Memory Routing
801
+
802
+ | Store what | Where | Why |
803
+ |---|---|---|
804
+ | Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
805
+ | Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
806
+ | Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
807
+ | Future reminders | Vestige \`intention\` | Surfaces at next session |
808
+
809
+ At session start, check memory before asking questions:
810
+ - Vestige: search for user preferences and project context
811
+ - Vestige: check for pending reminders/intentions
812
+ - Serena: list memories if in a Serena-activated project
813
+
814
+ Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
815
+
816
+ After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
817
+
818
+ ---
819
+
820
+ ## Orchestrator Protocol
821
+
822
+ You are the Orchestrator. Plan and delegate. Do NOT implement directly.
823
+ - Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
824
+ - Trivial = single file, < 5 lines, no judgment calls
825
+
826
+ ---
827
+
828
+ ## Available Agents
829
+
830
+ Delegate to named agents \u2014 they enforce tools and permissions automatically.
831
+
832
+ | Agent | Use For |
833
+ |---|---|
834
+ | \`explorer\` | File discovery, codebase exploration |
835
+ | \`implementer\` | Feature dev, bug fixes, refactoring |
836
+ | \`reviewer\` | Code review, security audit, FP checks |
837
+ | \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
838
+ | \`memory\` | Memory search, storage, consolidation |
839
+
840
+ ---
841
+
842
+ ## Code Navigation (Serena)
843
+
844
+ When Serena MCP is available, **prefer Serena over read_file/grep_search for code investigation.** 40-70% token savings.
845
+
846
+ | Instead of | Use |
847
+ |---|---|
848
+ | Read file to understand structure | Serena get_symbols_overview |
849
+ | grep_search for class/function definition | Serena find_symbol |
850
+ | grep_search for callers/references | Serena find_referencing_symbols |
851
+
852
+ Use read_file only when you need the actual implementation body of a known, specific symbol.
853
+
854
+ ---
855
+
856
+ ## Complex Reasoning
857
+
858
+ Use sequential thinking before acting on:
859
+ - Debugging / root cause analysis / "why is this failing"
860
+ - Trade-off evaluation / "which approach"
861
+ - Architectural decisions / design choices
862
+ - Multi-step investigations where approach may change
863
+
864
+ ---
865
+
866
+ ## MCP Tool Routing
867
+
868
+ | Signal | Preferred Tool |
869
+ |---|---|
870
+ | "latest", "2025/2026", "what's new" | Tavily search |
871
+ | Library/framework API question | Context7 |
872
+ | URL content extraction | Tavily extract (use advanced for complex pages) |
873
+
874
+ Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
875
+ Before external lookups: check Vestige memory first.
876
+
877
+ ---
878
+
879
+ ## Search Preference
880
+
881
+ Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
882
+
883
+ ---
884
+
885
+ ## Security
886
+
887
+ - Verify nonce usage and input sanitization in WordPress PHP code
888
+ - Never concatenate user input directly into SQL \u2014 use parameterized queries
889
+ - Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
890
+
891
+ ---
892
+
893
+ ## Code Style
894
+
895
+ - Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
896
+ - In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
897
+ - Prefer Bootstrap utility classes over custom CSS overrides
898
+ - Run \`composer dump-autoload\` after creating new PHP files
899
+
900
+ ---
901
+
902
+ ## Documentation
903
+
904
+ Follow the three-tier documentation system:
905
+ - **Active** \u2014 Living docs, kept current (README, API docs, architecture)
906
+ - **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
907
+ - **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
908
+ `;
909
+ }
910
+ var GeminiAdapter = class {
911
+ name = "gemini";
912
+ displayName = "Gemini CLI";
913
+ configDir = GEMINI_DIR;
914
+ detect() {
915
+ return existsSync4(GEMINI_DIR);
916
+ }
917
+ preview(sourceDir) {
918
+ const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
919
+ name: skill,
920
+ category: "skill",
921
+ destPath: join4(GEMINI_SKILLS_DIR, skill),
922
+ exists: existsSync4(join4(GEMINI_SKILLS_DIR, skill))
923
+ })).filter((item) => existsSync4(join4(sourceDir, "skills", item.name)));
924
+ const agentsDir = join4(sourceDir, "agents");
925
+ const agentItems = existsSync4(agentsDir) ? readdirSync4(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
926
+ name: file.replace(/\.md$/, ""),
927
+ category: "agent",
928
+ destPath: join4(GEMINI_AGENTS_DIR, file),
929
+ exists: existsSync4(join4(GEMINI_AGENTS_DIR, file))
930
+ })) : [];
931
+ const hookItems = HOOKS_TO_INSTALL.map((file) => ({
932
+ name: file,
933
+ category: "hook",
934
+ destPath: join4(GEMINI_HOOKS_DIR, file),
935
+ exists: existsSync4(join4(GEMINI_HOOKS_DIR, file))
936
+ }));
937
+ const translatorItem = {
938
+ name: "hooks-translator.py",
939
+ category: "hook",
940
+ destPath: join4(GEMINI_HOOKS_DIR, "hooks-translator.py"),
941
+ exists: existsSync4(join4(GEMINI_HOOKS_DIR, "hooks-translator.py"))
942
+ };
943
+ const guidelineItem = {
944
+ name: "GEMINI.md",
945
+ category: "guideline",
946
+ destPath: GEMINI_GUIDELINES_FILE,
947
+ exists: existsSync4(GEMINI_GUIDELINES_FILE)
948
+ };
949
+ return {
950
+ platform: this.name,
951
+ targetDir: GEMINI_DIR,
952
+ items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
953
+ };
954
+ }
955
+ installSkills(sourceDir, exclude) {
956
+ ensureDir(GEMINI_SKILLS_DIR);
957
+ const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
958
+ for (const skill of skills) {
959
+ const src = join4(sourceDir, skill);
960
+ if (existsSync4(src) && statSync4(src).isDirectory()) {
961
+ copyDirRecursive(src, join4(GEMINI_SKILLS_DIR, skill));
962
+ log.step(`skill: ${skill}`);
963
+ }
964
+ }
965
+ }
966
+ installAgents(sourceDir, exclude) {
967
+ ensureDir(GEMINI_AGENTS_DIR);
968
+ const entries = readdirSync4(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
969
+ for (const file of entries) {
970
+ const content = readFileSync3(join4(sourceDir, file), "utf8");
971
+ const transformed = transformAgentForGemini(content);
972
+ writeFileSync3(join4(GEMINI_AGENTS_DIR, file), transformed);
973
+ log.step(`agent: ${file}`);
974
+ }
975
+ }
976
+ installGuidelines(_pluginRoot) {
977
+ ensureDir(GEMINI_DIR);
978
+ writeFileSync3(GEMINI_GUIDELINES_FILE, generateGeminiMd());
979
+ log.step(`guidelines: ${GEMINI_GUIDELINES_FILE}`);
980
+ }
981
+ installHooks(sourceDir, exclude) {
982
+ ensureDir(GEMINI_HOOKS_DIR);
983
+ const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
984
+ for (const file of hooks) {
985
+ const src = join4(sourceDir, file);
986
+ if (existsSync4(src)) {
987
+ copyFileSync3(src, join4(GEMINI_HOOKS_DIR, file));
988
+ log.step(`hook: ${file}`);
989
+ }
990
+ }
991
+ const shimSrc = join4(__dirname2, "hooks-translator.py");
992
+ if (!existsSync4(shimSrc)) {
993
+ throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
994
+ }
995
+ copyFileSync3(shimSrc, join4(GEMINI_HOOKS_DIR, "hooks-translator.py"));
996
+ log.step("hook: hooks-translator.py (shim)");
997
+ const hooksConfig = generateGeminiHooksConfig();
998
+ writeFileSync3(join4(GEMINI_HOOKS_DIR, "hooks.json"), JSON.stringify(hooksConfig, null, 2) + "\n");
999
+ log.step("hook: hooks.json (generated for Gemini)");
1000
+ mergeGeminiHooksIntoSettings(hooksConfig);
1001
+ }
1002
+ postInstall() {
1003
+ log.info("Gemini CLI install complete. Verify:");
1004
+ log.info(` Skills: ${GEMINI_SKILLS_DIR}`);
1005
+ log.info(` Agents: ${GEMINI_AGENTS_DIR}`);
1006
+ log.info(` Hooks: ${GEMINI_HOOKS_DIR}`);
1007
+ log.info(` Guidelines: ${GEMINI_GUIDELINES_FILE}`);
1008
+ log.info("Also available as a Gemini extension \u2014 see gemini-extension.json in the repo.");
1009
+ }
1010
+ };
1011
+ function mergeGeminiHooksIntoSettings(hooksConfig) {
1012
+ let settings = {};
1013
+ if (existsSync4(GEMINI_SETTINGS_FILE)) {
1014
+ try {
1015
+ const content = readFileSync3(GEMINI_SETTINGS_FILE, "utf8");
1016
+ settings = JSON.parse(content);
1017
+ } catch {
1018
+ settings = {};
1019
+ }
1020
+ }
1021
+ if (!settings.hooks) {
1022
+ settings.hooks = {};
1023
+ }
1024
+ const settingsHooks = settings.hooks;
1025
+ const newHooks = hooksConfig.hooks;
1026
+ for (const [event, entries] of Object.entries(newHooks)) {
1027
+ if (!settingsHooks[event]) {
1028
+ settingsHooks[event] = entries;
1029
+ continue;
1030
+ }
1031
+ const existing = settingsHooks[event];
1032
+ for (const entry of entries) {
1033
+ if (entry.matcher) {
1034
+ const idx = existing.findIndex((h) => h.matcher === entry.matcher);
1035
+ if (idx >= 0) {
1036
+ existing[idx] = entry;
1037
+ } else {
1038
+ existing.push(entry);
1039
+ }
1040
+ } else {
1041
+ const matcherlessIdx = existing.findIndex((h) => !h.matcher);
1042
+ if (matcherlessIdx >= 0) {
1043
+ existing[matcherlessIdx] = entry;
1044
+ } else {
1045
+ existing.push(entry);
1046
+ }
1047
+ }
1048
+ }
1049
+ }
1050
+ writeFileSync3(GEMINI_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
1051
+ log.info("Merged hooks into ~/.gemini/settings.json");
1052
+ }
1053
+
687
1054
  // platforms/shared/detector.ts
688
1055
  var ADAPTERS = [
689
1056
  new ClaudeAdapter(),
690
- new JunieAdapter()
1057
+ new JunieAdapter(),
1058
+ new GeminiAdapter()
691
1059
  ];
692
1060
  function detectPlatforms() {
693
1061
  return ADAPTERS.map((adapter) => {
694
1062
  const detected = adapter.detect();
695
- const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : void 0;
1063
+ const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : adapter.name === "gemini" && detected ? "Also available as a Gemini extension" : void 0;
696
1064
  return { adapter, detected, note };
697
1065
  });
698
1066
  }
@@ -701,28 +1069,28 @@ function getAdapter(name) {
701
1069
  }
702
1070
 
703
1071
  // platforms/shared/installer.ts
704
- import { join as join5 } from "path";
1072
+ import { join as join6 } from "path";
705
1073
 
706
1074
  // platforms/shared/types.ts
707
- import { join as join4, resolve, dirname as dirname2 } from "path";
708
- import { existsSync as existsSync4 } from "fs";
709
- import { fileURLToPath } from "url";
1075
+ import { join as join5, resolve, dirname as dirname3 } from "path";
1076
+ import { existsSync as existsSync5 } from "fs";
1077
+ import { fileURLToPath as fileURLToPath2 } from "url";
710
1078
  function findPackageRoot() {
711
1079
  let dir;
712
1080
  try {
713
- dir = dirname2(fileURLToPath(import.meta.url));
1081
+ dir = dirname3(fileURLToPath2(import.meta.url));
714
1082
  } catch {
715
1083
  dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
716
1084
  }
717
1085
  for (let i = 0; i < 10; i++) {
718
- if (existsSync4(join4(dir, "package.json"))) return dir;
719
- const parent = join4(dir, "..");
1086
+ if (existsSync5(join5(dir, "package.json"))) return dir;
1087
+ const parent = join5(dir, "..");
720
1088
  if (parent === dir) break;
721
1089
  dir = parent;
722
1090
  }
723
1091
  return process.cwd();
724
1092
  }
725
- var PLUGIN_SOURCE = join4(
1093
+ var PLUGIN_SOURCE = join5(
726
1094
  findPackageRoot(),
727
1095
  "plugins",
728
1096
  "ima-claude"
@@ -838,9 +1206,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
838
1206
  return filter;
839
1207
  }
840
1208
  async function installForPlatform(adapter, options = {}) {
841
- const skillsSource = join5(PLUGIN_SOURCE, "skills");
842
- const agentsSource = join5(PLUGIN_SOURCE, "agents");
843
- const hooksSource = join5(PLUGIN_SOURCE, "hooks");
1209
+ const skillsSource = join6(PLUGIN_SOURCE, "skills");
1210
+ const agentsSource = join6(PLUGIN_SOURCE, "agents");
1211
+ const hooksSource = join6(PLUGIN_SOURCE, "hooks");
844
1212
  const preview = adapter.preview(PLUGIN_SOURCE);
845
1213
  showPreview(adapter, preview.items);
846
1214
  const filter = await promptExclusions(preview.items);
@@ -908,7 +1276,7 @@ ${colors.cyan}Usage:${colors.reset}
908
1276
 
909
1277
  ${colors.cyan}Commands:${colors.reset}
910
1278
  install Interactive install (auto-detects platforms)
911
- install --target X Install for specific platform (claude, junie)
1279
+ install --target X Install for specific platform (claude, junie, gemini)
912
1280
  upgrade Upgrade installed skills to latest version
913
1281
  detect Show detected platforms
914
1282
  help Show this help message
@@ -966,7 +1334,7 @@ ${colors.bright}ima-claude v${VERSION} \u2014 Multi-Platform Installer${colors.r
966
1334
  `
967
1335
  ${colors.yellow}No supported platforms detected.${colors.reset}`
968
1336
  );
969
- console.log("Install Claude Code or Junie CLI first, then run this installer again.\n");
1337
+ console.log("Install Claude Code, Junie CLI, or Gemini CLI first, then run this installer again.\n");
970
1338
  return;
971
1339
  }
972
1340
  console.log("");
@@ -998,7 +1366,7 @@ async function targetedInstall(targetName) {
998
1366
  const adapter = getAdapter(targetName);
999
1367
  if (!adapter) {
1000
1368
  log.error(`Unknown target: ${targetName}`);
1001
- console.log("Available targets: claude, junie");
1369
+ console.log("Available targets: claude, junie, gemini");
1002
1370
  process.exit(1);
1003
1371
  }
1004
1372
  if (!adapter.detect()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima-claude",
3
- "version": "2.9.0",
3
+ "version": "2.13.0",
4
4
  "description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, and more.",
5
5
  "type": "module",
6
6
  "scripts": {