ima-claude 2.10.0 → 2.14.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/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.10.0";
14
+ var VERSION = "2.14.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,731 @@ 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
+
1054
+ // platforms/gh-copilot/adapter.ts
1055
+ import { join as join5, dirname as dirname3 } from "path";
1056
+ import { homedir as homedir4 } from "os";
1057
+ import { existsSync as existsSync5, readdirSync as readdirSync5, statSync as statSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, copyFileSync as copyFileSync4 } from "fs";
1058
+ import { fileURLToPath as fileURLToPath2 } from "url";
1059
+ var __filename2 = fileURLToPath2(import.meta.url);
1060
+ var __dirname3 = dirname3(__filename2);
1061
+ var COPILOT_DIR = join5(homedir4(), ".copilot");
1062
+ var COPILOT_SKILLS_DIR = join5(COPILOT_DIR, "skills");
1063
+ var COPILOT_AGENTS_DIR = join5(COPILOT_DIR, "agents");
1064
+ var COPILOT_HOOKS_DIR = join5(COPILOT_DIR, "hooks");
1065
+ var COPILOT_GUIDELINES_FILE = join5(COPILOT_DIR, "copilot-instructions.md");
1066
+ var TOOL_MAP2 = {
1067
+ Bash: "run_terminal_command",
1068
+ Read: "read_file",
1069
+ Edit: "edit_file",
1070
+ Write: "write_file",
1071
+ Glob: "find_files",
1072
+ Grep: "search_code",
1073
+ LS: "list_directory",
1074
+ WebSearch: "web_search",
1075
+ WebFetch: "fetch_url",
1076
+ ExitPlanMode: "ExitPlanMode"
1077
+ };
1078
+ var EVENT_MAP2 = {
1079
+ PreToolUse: "preToolUse",
1080
+ PostToolUse: "postToolUse",
1081
+ UserPromptSubmit: "userPromptSubmitted",
1082
+ SessionStart: "sessionStart"
1083
+ };
1084
+ function parseFrontmatter3(content) {
1085
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1086
+ if (!match) return { frontmatter: {}, body: content };
1087
+ const frontmatter = {};
1088
+ for (const line of match[1].split("\n")) {
1089
+ const colonIdx = line.indexOf(":");
1090
+ if (colonIdx === -1) continue;
1091
+ const key = line.slice(0, colonIdx).trim();
1092
+ const value = line.slice(colonIdx + 1).trim();
1093
+ if (key) frontmatter[key] = value;
1094
+ }
1095
+ return { frontmatter, body: match[2] };
1096
+ }
1097
+ function serializeFrontmatter3(frontmatter, body) {
1098
+ const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
1099
+ return `---
1100
+ ${lines.join("\n")}
1101
+ ---
1102
+ ${body}`;
1103
+ }
1104
+ function mapToolName2(claudeName) {
1105
+ return TOOL_MAP2[claudeName] ?? claudeName;
1106
+ }
1107
+ function transformAgentForCopilot(content) {
1108
+ const { frontmatter, body } = parseFrontmatter3(content);
1109
+ const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
1110
+ if (kept.tools) {
1111
+ const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName2).join(", ");
1112
+ kept.tools = mapped;
1113
+ }
1114
+ return serializeFrontmatter3(kept, body);
1115
+ }
1116
+ function translateMatcher2(matcher) {
1117
+ return TOOL_MAP2[matcher] ?? matcher;
1118
+ }
1119
+ function translateHookCommand2(command2) {
1120
+ const hooksDir = COPILOT_HOOKS_DIR;
1121
+ const translatorPath = join5(hooksDir, "hooks-translator.py");
1122
+ const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
1123
+ if (!match) return command2;
1124
+ const scriptName = match[1];
1125
+ const trailingArgs = match[2] ?? "";
1126
+ return `python3 ${translatorPath} ${join5(hooksDir, scriptName)}${trailingArgs}`;
1127
+ }
1128
+ function generateCopilotHooksConfig() {
1129
+ const copilotHooks = {};
1130
+ for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
1131
+ const copilotEvent = EVENT_MAP2[claudeEvent] ?? claudeEvent;
1132
+ const flatEntries = [];
1133
+ for (const entry of hookEntries) {
1134
+ const translatedMatcher = entry.matcher ? translateMatcher2(entry.matcher) : void 0;
1135
+ for (const hook of entry.hooks) {
1136
+ const flatEntry = {};
1137
+ if (translatedMatcher) {
1138
+ flatEntry.matcher = translatedMatcher;
1139
+ }
1140
+ flatEntry.type = hook.type;
1141
+ flatEntry.bash = translateHookCommand2(hook.command);
1142
+ flatEntries.push(flatEntry);
1143
+ }
1144
+ }
1145
+ copilotHooks[copilotEvent] = flatEntries;
1146
+ }
1147
+ return { version: 1, hooks: copilotHooks };
1148
+ }
1149
+ function generateCopilotInstructionsMd() {
1150
+ return `# ima-claude: AI Coding Agent Guidelines
1151
+
1152
+ > Generated by ima-claude v${VERSION} for GitHub Copilot.
1153
+ > Source: https://github.com/Soabirw/ima-claude
1154
+
1155
+ ## Default Persona: The Practitioner
1156
+
1157
+ A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
1158
+ Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
1159
+
1160
+ **Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
1161
+
1162
+ ---
1163
+
1164
+ ## Memory Routing
1165
+
1166
+ | Store what | Where | Why |
1167
+ |---|---|---|
1168
+ | Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
1169
+ | Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
1170
+ | Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
1171
+ | Future reminders | Vestige \`intention\` | Surfaces at next session |
1172
+
1173
+ At session start, check memory before asking questions:
1174
+ - Vestige: search for user preferences and project context
1175
+ - Vestige: check for pending reminders/intentions
1176
+ - Serena: list memories if in a Serena-activated project
1177
+
1178
+ Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
1179
+
1180
+ After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
1181
+
1182
+ ---
1183
+
1184
+ ## Orchestrator Protocol
1185
+
1186
+ You are the Orchestrator. Plan and delegate. Do NOT implement directly.
1187
+ - Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
1188
+ - Trivial = single file, < 5 lines, no judgment calls
1189
+
1190
+ ---
1191
+
1192
+ ## Available Agents
1193
+
1194
+ Delegate to named agents \u2014 they enforce tools and permissions automatically.
1195
+
1196
+ | Agent | Use For |
1197
+ |---|---|
1198
+ | \`explorer\` | File discovery, codebase exploration |
1199
+ | \`implementer\` | Feature dev, bug fixes, refactoring |
1200
+ | \`reviewer\` | Code review, security audit, FP checks |
1201
+ | \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
1202
+ | \`memory\` | Memory search, storage, consolidation |
1203
+
1204
+ ---
1205
+
1206
+ ## Code Navigation (Serena)
1207
+
1208
+ When Serena MCP is available, **prefer Serena over read_file/search_code for code investigation.** 40-70% token savings.
1209
+
1210
+ | Instead of | Use |
1211
+ |---|---|
1212
+ | Read file to understand structure | Serena get_symbols_overview |
1213
+ | search_code for class/function definition | Serena find_symbol |
1214
+ | search_code for callers/references | Serena find_referencing_symbols |
1215
+
1216
+ Use read_file only when you need the actual implementation body of a known, specific symbol.
1217
+
1218
+ ---
1219
+
1220
+ ## Complex Reasoning
1221
+
1222
+ Use sequential thinking before acting on:
1223
+ - Debugging / root cause analysis / "why is this failing"
1224
+ - Trade-off evaluation / "which approach"
1225
+ - Architectural decisions / design choices
1226
+ - Multi-step investigations where approach may change
1227
+
1228
+ ---
1229
+
1230
+ ## MCP Tool Routing
1231
+
1232
+ | Signal | Preferred Tool |
1233
+ |---|---|
1234
+ | "latest", "2025/2026", "what's new" | Tavily search |
1235
+ | Library/framework API question | Context7 |
1236
+ | URL content extraction | Tavily extract (use advanced for complex pages) |
1237
+
1238
+ Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
1239
+ Before external lookups: check Vestige memory first.
1240
+
1241
+ ---
1242
+
1243
+ ## Search Preference
1244
+
1245
+ Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
1246
+
1247
+ ---
1248
+
1249
+ ## Security
1250
+
1251
+ - Verify nonce usage and input sanitization in WordPress PHP code
1252
+ - Never concatenate user input directly into SQL \u2014 use parameterized queries
1253
+ - Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
1254
+
1255
+ ---
1256
+
1257
+ ## Code Style
1258
+
1259
+ - Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
1260
+ - In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
1261
+ - Prefer Bootstrap utility classes over custom CSS overrides
1262
+ - Run \`composer dump-autoload\` after creating new PHP files
1263
+
1264
+ ---
1265
+
1266
+ ## Documentation
1267
+
1268
+ Follow the three-tier documentation system:
1269
+ - **Active** \u2014 Living docs, kept current (README, API docs, architecture)
1270
+ - **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
1271
+ - **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
1272
+ `;
1273
+ }
1274
+ var GhCopilotAdapter = class {
1275
+ name = "gh-copilot";
1276
+ displayName = "GitHub Copilot";
1277
+ configDir = COPILOT_DIR;
1278
+ detect() {
1279
+ return existsSync5(COPILOT_DIR);
1280
+ }
1281
+ preview(sourceDir) {
1282
+ const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
1283
+ name: skill,
1284
+ category: "skill",
1285
+ destPath: join5(COPILOT_SKILLS_DIR, skill),
1286
+ exists: existsSync5(join5(COPILOT_SKILLS_DIR, skill))
1287
+ })).filter((item) => existsSync5(join5(sourceDir, "skills", item.name)));
1288
+ const agentsDir = join5(sourceDir, "agents");
1289
+ const agentItems = existsSync5(agentsDir) ? readdirSync5(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
1290
+ name: file.replace(/\.md$/, ""),
1291
+ category: "agent",
1292
+ destPath: join5(COPILOT_AGENTS_DIR, file.replace(/\.md$/, ".agent.md")),
1293
+ exists: existsSync5(join5(COPILOT_AGENTS_DIR, file.replace(/\.md$/, ".agent.md")))
1294
+ })) : [];
1295
+ const hookItems = HOOKS_TO_INSTALL.map((file) => ({
1296
+ name: file,
1297
+ category: "hook",
1298
+ destPath: join5(COPILOT_HOOKS_DIR, file),
1299
+ exists: existsSync5(join5(COPILOT_HOOKS_DIR, file))
1300
+ }));
1301
+ const translatorItem = {
1302
+ name: "hooks-translator.py",
1303
+ category: "hook",
1304
+ destPath: join5(COPILOT_HOOKS_DIR, "hooks-translator.py"),
1305
+ exists: existsSync5(join5(COPILOT_HOOKS_DIR, "hooks-translator.py"))
1306
+ };
1307
+ const guidelineItem = {
1308
+ name: "copilot-instructions.md",
1309
+ category: "guideline",
1310
+ destPath: COPILOT_GUIDELINES_FILE,
1311
+ exists: existsSync5(COPILOT_GUIDELINES_FILE)
1312
+ };
1313
+ return {
1314
+ platform: this.name,
1315
+ targetDir: COPILOT_DIR,
1316
+ items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
1317
+ };
1318
+ }
1319
+ installSkills(sourceDir, exclude) {
1320
+ ensureDir(COPILOT_SKILLS_DIR);
1321
+ const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
1322
+ for (const skill of skills) {
1323
+ const src = join5(sourceDir, skill);
1324
+ if (existsSync5(src) && statSync5(src).isDirectory()) {
1325
+ copyDirRecursive(src, join5(COPILOT_SKILLS_DIR, skill));
1326
+ log.step(`skill: ${skill}`);
1327
+ }
1328
+ }
1329
+ }
1330
+ installAgents(sourceDir, exclude) {
1331
+ ensureDir(COPILOT_AGENTS_DIR);
1332
+ const entries = readdirSync5(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
1333
+ for (const file of entries) {
1334
+ const content = readFileSync4(join5(sourceDir, file), "utf8");
1335
+ const transformed = transformAgentForCopilot(content);
1336
+ const destFile = file.replace(/\.md$/, ".agent.md");
1337
+ writeFileSync4(join5(COPILOT_AGENTS_DIR, destFile), transformed);
1338
+ log.step(`agent: ${destFile}`);
1339
+ }
1340
+ }
1341
+ installGuidelines(_pluginRoot) {
1342
+ ensureDir(COPILOT_DIR);
1343
+ writeFileSync4(COPILOT_GUIDELINES_FILE, generateCopilotInstructionsMd());
1344
+ log.step(`guidelines: ${COPILOT_GUIDELINES_FILE}`);
1345
+ }
1346
+ installHooks(sourceDir, exclude) {
1347
+ ensureDir(COPILOT_HOOKS_DIR);
1348
+ const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
1349
+ for (const file of hooks) {
1350
+ const src = join5(sourceDir, file);
1351
+ if (existsSync5(src)) {
1352
+ copyFileSync4(src, join5(COPILOT_HOOKS_DIR, file));
1353
+ log.step(`hook: ${file}`);
1354
+ }
1355
+ }
1356
+ const shimSrc = join5(__dirname3, "hooks-translator.py");
1357
+ if (!existsSync5(shimSrc)) {
1358
+ throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
1359
+ }
1360
+ copyFileSync4(shimSrc, join5(COPILOT_HOOKS_DIR, "hooks-translator.py"));
1361
+ log.step("hook: hooks-translator.py (shim)");
1362
+ const hooksConfig = generateCopilotHooksConfig();
1363
+ const hooksConfigPath = join5(COPILOT_HOOKS_DIR, "hooks.json");
1364
+ mergeCopilotHooksConfig(hooksConfigPath, hooksConfig);
1365
+ log.step("hook: hooks.json (generated for GitHub Copilot)");
1366
+ }
1367
+ postInstall() {
1368
+ log.info("GitHub Copilot install complete. Verify:");
1369
+ log.info(` Skills: ${COPILOT_SKILLS_DIR}`);
1370
+ log.info(` Agents: ${COPILOT_AGENTS_DIR}`);
1371
+ log.info(` Hooks: ${COPILOT_HOOKS_DIR}`);
1372
+ log.info(` Guidelines: ${COPILOT_GUIDELINES_FILE}`);
1373
+ }
1374
+ };
1375
+ function mergeCopilotHooksConfig(configPath, newConfig) {
1376
+ let existing = {};
1377
+ if (existsSync5(configPath)) {
1378
+ try {
1379
+ const content = readFileSync4(configPath, "utf8");
1380
+ existing = JSON.parse(content);
1381
+ } catch {
1382
+ existing = {};
1383
+ }
1384
+ }
1385
+ existing.version = newConfig.version ?? 1;
1386
+ if (!existing.hooks) {
1387
+ existing.hooks = {};
1388
+ }
1389
+ const existingHooks = existing.hooks;
1390
+ const incomingHooks = newConfig.hooks;
1391
+ for (const [event, entries] of Object.entries(incomingHooks)) {
1392
+ if (!existingHooks[event]) {
1393
+ existingHooks[event] = entries;
1394
+ continue;
1395
+ }
1396
+ const userHooks = existingHooks[event].filter(
1397
+ (h) => !h.bash?.includes("hooks-translator.py")
1398
+ );
1399
+ existingHooks[event] = [...userHooks, ...entries];
1400
+ }
1401
+ writeFileSync4(configPath, JSON.stringify(existing, null, 2) + "\n");
1402
+ }
1403
+
687
1404
  // platforms/shared/detector.ts
688
1405
  var ADAPTERS = [
689
1406
  new ClaudeAdapter(),
690
- new JunieAdapter()
1407
+ new JunieAdapter(),
1408
+ new GeminiAdapter(),
1409
+ new GhCopilotAdapter()
691
1410
  ];
692
1411
  function detectPlatforms() {
693
1412
  return ADAPTERS.map((adapter) => {
694
1413
  const detected = adapter.detect();
695
- const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : void 0;
1414
+ 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
1415
  return { adapter, detected, note };
697
1416
  });
698
1417
  }
@@ -701,28 +1420,28 @@ function getAdapter(name) {
701
1420
  }
702
1421
 
703
1422
  // platforms/shared/installer.ts
704
- import { join as join5 } from "path";
1423
+ import { join as join7 } from "path";
705
1424
 
706
1425
  // 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";
1426
+ import { join as join6, resolve, dirname as dirname4 } from "path";
1427
+ import { existsSync as existsSync6 } from "fs";
1428
+ import { fileURLToPath as fileURLToPath3 } from "url";
710
1429
  function findPackageRoot() {
711
1430
  let dir;
712
1431
  try {
713
- dir = dirname2(fileURLToPath(import.meta.url));
1432
+ dir = dirname4(fileURLToPath3(import.meta.url));
714
1433
  } catch {
715
1434
  dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
716
1435
  }
717
1436
  for (let i = 0; i < 10; i++) {
718
- if (existsSync4(join4(dir, "package.json"))) return dir;
719
- const parent = join4(dir, "..");
1437
+ if (existsSync6(join6(dir, "package.json"))) return dir;
1438
+ const parent = join6(dir, "..");
720
1439
  if (parent === dir) break;
721
1440
  dir = parent;
722
1441
  }
723
1442
  return process.cwd();
724
1443
  }
725
- var PLUGIN_SOURCE = join4(
1444
+ var PLUGIN_SOURCE = join6(
726
1445
  findPackageRoot(),
727
1446
  "plugins",
728
1447
  "ima-claude"
@@ -838,9 +1557,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
838
1557
  return filter;
839
1558
  }
840
1559
  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");
1560
+ const skillsSource = join7(PLUGIN_SOURCE, "skills");
1561
+ const agentsSource = join7(PLUGIN_SOURCE, "agents");
1562
+ const hooksSource = join7(PLUGIN_SOURCE, "hooks");
844
1563
  const preview = adapter.preview(PLUGIN_SOURCE);
845
1564
  showPreview(adapter, preview.items);
846
1565
  const filter = await promptExclusions(preview.items);
@@ -908,7 +1627,7 @@ ${colors.cyan}Usage:${colors.reset}
908
1627
 
909
1628
  ${colors.cyan}Commands:${colors.reset}
910
1629
  install Interactive install (auto-detects platforms)
911
- install --target X Install for specific platform (claude, junie)
1630
+ install --target X Install for specific platform (claude, junie, gemini, gh-copilot)
912
1631
  upgrade Upgrade installed skills to latest version
913
1632
  detect Show detected platforms
914
1633
  help Show this help message
@@ -966,7 +1685,7 @@ ${colors.bright}ima-claude v${VERSION} \u2014 Multi-Platform Installer${colors.r
966
1685
  `
967
1686
  ${colors.yellow}No supported platforms detected.${colors.reset}`
968
1687
  );
969
- console.log("Install Claude Code or Junie CLI first, then run this installer again.\n");
1688
+ console.log("Install Claude Code, Junie CLI, Gemini CLI, or GitHub Copilot first, then run this installer again.\n");
970
1689
  return;
971
1690
  }
972
1691
  console.log("");
@@ -998,7 +1717,7 @@ async function targetedInstall(targetName) {
998
1717
  const adapter = getAdapter(targetName);
999
1718
  if (!adapter) {
1000
1719
  log.error(`Unknown target: ${targetName}`);
1001
- console.log("Available targets: claude, junie");
1720
+ console.log("Available targets: claude, junie, gemini, gh-copilot");
1002
1721
  process.exit(1);
1003
1722
  }
1004
1723
  if (!adapter.detect()) {