Skip to content

Commit f1f46f0

Browse files
committed
fix: install Grok Skills under .grok and declare hookMatcher metadata
Review feedback: Grok discovers project Skills from .grok/skills, and platform-specific hook matchers belong on the Platform definition.
1 parent 92d402a commit f1f46f0

13 files changed

Lines changed: 150 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ All notable changes to @rpamis/comet will be documented in this file.
66

77
### Added
88

9-
- **Grok platform support**: `comet init`, `comet update`, `comet doctor`, and `comet uninstall` now treat Grok as a first-class host. Project Skills share the `.agents` root with Codex, while rules and the Hook Router live under `.grok/rules/` and `.grok/hooks/comet.json`. The Router recognizes `--platform grok` and matches Grok's native `write` / `search_replace` tools.
9+
- **Grok platform support**: `comet init`, `comet update`, `comet doctor`, and `comet uninstall` now treat Grok as a first-class host. Skills, rules, and the Hook Router live under `.grok/skills/`, `.grok/rules/`, and `.grok/hooks/comet.json`. The Router recognizes `--platform grok` and matches Grok's native `write` / `search_replace` tools.
1010

1111
## What's Changed [0.4.0-beta.19] - 2026-08-15
1212

README-zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ comet eval ./my-skill --suite langsmith --html
463463
| ForgeCode | `.forge/` | Trae | `.trae/` |
464464
| Trae CN | `.trae-cn/` | ZCode | `.zcode/` |
465465
| MimoCode | `.mimocode/` | CoStrict | `.cospec/` |
466-
| Grok | `.agents/` | | |
466+
| Grok | `.grok/` | | |
467467

468468
</details>
469469

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ does not expand the backend command list; see the [Skill creation guide](docs/op
492492
| ForgeCode | `.forge/` | Trae | `.trae/` |
493493
| Trae CN | `.trae-cn/` | ZCode | `.zcode/` |
494494
| MimoCode | `.mimocode/` | CoStrict | `.cospec/` |
495-
| Grok | `.agents/` | | |
495+
| Grok | `.grok/` | | |
496496

497497
</details>
498498

app/commands/init.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,7 @@ export async function initCommand(
718718
['zcode', 'mimocode'].includes(id),
719719
);
720720
const mirrorCodeBuddyPlatformIds = selectedPlatformIdsForOs.filter((id) => id === 'workbuddy');
721+
const mirrorCodexPlatformIds = selectedPlatformIdsForOs.filter((id) => id === 'grok');
721722

722723
const selectedNpmDeps = await selectNpmDeps(
723724
projectPath,
@@ -786,6 +787,7 @@ export async function initCommand(
786787
osFailureReason = error.message;
787788
},
788789
mirrorCodeBuddyPlatformIds,
790+
mirrorCodexPlatformIds,
789791
);
790792
if (osGlobalStatus === 'installed' && requiresClassicArtifactRoot) {
791793
await assertClassicProjectMutationAllowed?.();

app/commands/update.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,9 @@ async function updateSingleProject(
18301830
const mirrorCodeBuddyPlatformIds = scopeTargets
18311831
.map((target) => target.platform.id)
18321832
.filter((id) => id === 'workbuddy');
1833+
const mirrorCodexPlatformIds = scopeTargets
1834+
.map((target) => target.platform.id)
1835+
.filter((id) => id === 'grok');
18331836
const artifactLayout = scope === 'project' ? classicArtifactLayout : 'legacy';
18341837
try {
18351838
if (scope === 'project') {
@@ -1845,6 +1848,7 @@ async function updateSingleProject(
18451848
scope === 'project' ? assertClassicProjectMutationAllowed : undefined,
18461849
undefined,
18471850
mirrorCodeBuddyPlatformIds,
1851+
mirrorCodexPlatformIds,
18481852
);
18491853
if (status === 'failed') {
18501854
openSpecStatus = 'failed';

domains/integrations/openspec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ async function mergeGeneratedToolDirectories(
314314
mirrorOpenCodePlatformIds: readonly string[],
315315
mirrorCodeBuddyPlatformIds: readonly string[],
316316
projectMutationGuard?: ProjectMutationGuard,
317+
mirrorCodexPlatformIds: readonly string[] = [],
317318
): Promise<void> {
318319
for (const copy of copies) {
319320
await copyGeneratedToolDirectory(
@@ -364,6 +365,29 @@ async function mergeGeneratedToolDirectories(
364365
}
365366
}
366367
}
368+
369+
if (toolIds.includes('codex') && mirrorCodexPlatformIds.length > 0) {
370+
const codexPlatform = PLATFORMS.find((platform) => platform.id === 'codex');
371+
if (codexPlatform) {
372+
const source = path.join(
373+
stagingProject,
374+
codexPlatform.openspecSkillsDir ?? codexPlatform.skillsDir,
375+
);
376+
if (fs.existsSync(source)) {
377+
for (const platformId of new Set(mirrorCodexPlatformIds)) {
378+
const platform = PLATFORMS.find((candidate) => candidate.id === platformId);
379+
if (!platform || platform.id === 'codex') continue;
380+
await copyGeneratedToolDirectory(
381+
stagingProject,
382+
source,
383+
projectPath,
384+
path.join(projectPath, getPlatformSkillsDir(platform, 'project')),
385+
projectMutationGuard,
386+
);
387+
}
388+
}
389+
}
390+
}
367391
}
368392

369393
const ALL_WORKFLOWS_CONFIG =
@@ -708,6 +732,7 @@ async function installOpenSpec(
708732
projectMutationGuard?: ProjectMutationGuard,
709733
failureObserver?: OpenSpecFailureObserver,
710734
mirrorCodeBuddyPlatformIds: string[] = [],
735+
mirrorCodexPlatformIds: string[] = [],
711736
): Promise<'installed' | 'failed' | 'skipped'> {
712737
if (scope === 'project') {
713738
try {
@@ -783,6 +808,7 @@ async function installOpenSpec(
783808
mirrorOpenCodePlatformIds,
784809
mirrorCodeBuddyPlatformIds,
785810
projectMutationGuard,
811+
mirrorCodexPlatformIds,
786812
);
787813
}
788814
await assertProjectMutationAllowed(projectMutationGuard, 'after-external', true);
@@ -819,6 +845,15 @@ async function installOpenSpec(
819845
}
820846
}
821847

848+
if (scope === 'global' && mirrorCodexPlatformIds.length > 0 && toolIds.includes('codex')) {
849+
mirrorOpenCodeCompatibleOpenSpecPathsFromSource(
850+
openspecTargetBase,
851+
scope,
852+
'codex',
853+
mirrorCodexPlatformIds,
854+
);
855+
}
856+
822857
if (openspecWritesGlobal && toolIds.includes('opencode')) {
823858
migrateOpenCodeOpenSpecPaths(os.homedir());
824859
}

domains/integrations/superpowers.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const SKILLS_AGENT_MAP: Record<string, string | null> = {
1818
continue: 'continue',
1919
'github-copilot': 'github-copilot',
2020
gemini: 'gemini-cli',
21-
// Grok reads Superpowers from the shared .agents root that Codex owns.
22-
grok: 'codex',
21+
// Grok has no Skills CLI agent; stage through Claude and copy into .grok/skills.
22+
grok: null,
2323
'amazon-q': 'universal',
2424
qwen: 'qwen-code',
2525
kilocode: 'kilo',
@@ -57,6 +57,7 @@ const LINGMA_PLATFORM_ID = 'lingma';
5757
const ZCODE_PLATFORM_ID = 'zcode';
5858
const MIMOCODE_PLATFORM_ID = 'mimocode';
5959
const WORKBUDDY_PLATFORM_ID = 'workbuddy';
60+
const GROK_PLATFORM_ID = 'grok';
6061
const STAGE_AGENT = 'claude-code';
6162

6263
function buildSuperpowersInstallCommand(
@@ -117,6 +118,13 @@ function buildWorkBuddySuperpowersStageCommand(): { command: string; args: strin
117118
};
118119
}
119120

121+
function buildGrokSuperpowersStageCommand(): { command: string; args: string[] } {
122+
return {
123+
command: getNpxExecutable(),
124+
args: ['skills', 'add', 'obra/superpowers', '-y', '--agent', STAGE_AGENT],
125+
};
126+
}
127+
120128
function getNpxExecutable(platform: NodeJS.Platform = process.platform): string {
121129
return platform === 'win32' ? 'npx.cmd' : 'npx';
122130
}
@@ -185,6 +193,19 @@ async function installSuperpowersForWorkBuddy(
185193
);
186194
}
187195

196+
async function installSuperpowersForGrok(
197+
projectPath: string,
198+
scope: InstallScope,
199+
): Promise<'installed' | 'failed'> {
200+
return stageAndCopySuperpowers(
201+
GROK_PLATFORM_ID,
202+
buildGrokSuperpowersStageCommand(),
203+
projectPath,
204+
scope,
205+
'Grok',
206+
);
207+
}
208+
188209
/**
189210
* Shared staging flow for platforms whose agent is not supported by the skills CLI
190211
* (e.g. Lingma, WorkBuddy, ZCode, MimoCode). Superpowers are staged into a temp dir via
@@ -246,6 +267,7 @@ async function installSuperpowersForPlatforms(
246267
const shouldInstallZCode = platformIds.includes(ZCODE_PLATFORM_ID);
247268
const shouldInstallMimoCode = platformIds.includes(MIMOCODE_PLATFORM_ID);
248269
const shouldInstallWorkBuddy = platformIds.includes(WORKBUDDY_PLATFORM_ID);
270+
const shouldInstallGrok = platformIds.includes(GROK_PLATFORM_ID);
249271
let failed = false;
250272

251273
if (skillsCliPlatformIds.length > 0) {
@@ -285,6 +307,11 @@ async function installSuperpowersForPlatforms(
285307
if (workbuddyStatus === 'failed') failed = true;
286308
}
287309

310+
if (shouldInstallGrok) {
311+
const grokStatus = await installSuperpowersForGrok(projectPath, scope);
312+
if (grokStatus === 'failed') failed = true;
313+
}
314+
288315
return failed ? 'failed' : 'installed';
289316
}
290317

@@ -295,5 +322,6 @@ export {
295322
buildZCodeSuperpowersStageCommand,
296323
buildMimoCodeSuperpowersStageCommand,
297324
buildWorkBuddySuperpowersStageCommand,
325+
buildGrokSuperpowersStageCommand,
298326
SKILLS_AGENT_MAP,
299327
};

domains/skill/platform-inspect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
computeRuleDestPath,
1313
isManagedHookCommand,
1414
readManifest,
15-
resolveClaudeCodeHookMatcher,
15+
resolveInstalledHookMatcher,
1616
} from './platform-install.js';
1717
import { readJsonObjectFile } from './json-object.js';
1818
import type { InitWorkflowSelection } from '../comet-entry/types.js';
@@ -395,7 +395,7 @@ export async function inspectCometHooksForPlatform(
395395
(config) => collectGroupedCommands(config, 'PreToolUse'),
396396
(config, expected) =>
397397
countGroupedHookMatches(config, 'PreToolUse', expected, (matcher) =>
398-
resolveClaudeCodeHookMatcher(platform.id, matcher),
398+
resolveInstalledHookMatcher(platform, matcher),
399399
),
400400
);
401401
for (const legacyFile of platform.legacyHookConfigFiles ?? []) {
@@ -405,7 +405,7 @@ export async function inspectCometHooksForPlatform(
405405
(config) => collectGroupedCommands(config, 'PreToolUse'),
406406
(config, expected) =>
407407
countGroupedHookMatches(config, 'PreToolUse', expected, (matcher) =>
408-
resolveClaudeCodeHookMatcher(platform.id, matcher),
408+
resolveInstalledHookMatcher(platform, matcher),
409409
),
410410
);
411411
if (legacy.error) {

domains/skill/platform-install.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const RETIRED_COMET_OWNED_SKILL_PATHS = [
7171
interface HookCommandContext {
7272
platformId: string;
7373
scope: InstallScope;
74+
hookMatcher?: string;
7475
}
7576

7677
type HookInstallStatus = 'installed' | 'skipped' | 'failed';
@@ -1134,7 +1135,7 @@ async function installCometHooksForPlatform(
11341135
hooksConfig,
11351136
platform.hookConfigFile ?? 'settings.local.json',
11361137
platform.name,
1137-
{ platformId: platform.id, scope },
1138+
{ platformId: platform.id, scope, hookMatcher: platform.hookMatcher },
11381139
);
11391140
if (result.status === 'installed') {
11401141
const failedLegacyFiles: string[] = [];
@@ -1168,7 +1169,7 @@ async function installCometHooksForPlatform(
11681169
skillsDir,
11691170
hooksConfig,
11701171
platform.name,
1171-
{ platformId: platform.id, scope },
1172+
{ platformId: platform.id, scope, hookMatcher: platform.hookMatcher },
11721173
);
11731174
case 'gemini':
11741175
return await installGeminiHooks(
@@ -1177,7 +1178,7 @@ async function installCometHooksForPlatform(
11771178
skillsDir,
11781179
hooksConfig,
11791180
platform.name,
1180-
{ platformId: platform.id, scope },
1181+
{ platformId: platform.id, scope, hookMatcher: platform.hookMatcher },
11811182
);
11821183
case 'windsurf':
11831184
return await installWindsurfHooks(
@@ -1186,7 +1187,7 @@ async function installCometHooksForPlatform(
11861187
skillsDir,
11871188
hooksConfig,
11881189
platform.name,
1189-
{ platformId: platform.id, scope },
1190+
{ platformId: platform.id, scope, hookMatcher: platform.hookMatcher },
11901191
);
11911192
case 'copilot':
11921193
return await installCopilotHooks(baseDir, platformBase, skillsDir, hooksConfig, {
@@ -1205,7 +1206,7 @@ async function installCometHooksForPlatform(
12051206
skillsDir,
12061207
hooksConfig,
12071208
platform.name,
1208-
{ platformId: platform.id, scope },
1209+
{ platformId: platform.id, scope, hookMatcher: platform.hookMatcher },
12091210
);
12101211
default:
12111212
return { status: 'failed', reason: `unsupported hook format: ${hookFormat}` };
@@ -1451,12 +1452,11 @@ async function readSettingsJsonObject(
14511452
});
14521453
}
14531454

1454-
/** Grok aliases Write/Edit to search_replace, but its native write tool is a separate name. */
1455-
function resolveClaudeCodeHookMatcher(platformId: string, matcher: string): string {
1456-
if (platformId === 'grok' && matcher === 'Write|Edit') {
1457-
return 'Write|Edit|write|search_replace';
1458-
}
1459-
return matcher;
1455+
function resolveInstalledHookMatcher(
1456+
platform: Pick<Platform, 'hookMatcher'>,
1457+
matcher: string,
1458+
): string {
1459+
return platform.hookMatcher ?? matcher;
14601460
}
14611461

14621462
/**
@@ -1484,7 +1484,7 @@ async function installClaudeCodeHooks(
14841484
const matcherGroups: Record<string, Array<{ type: string; command: string }>> = {};
14851485
for (const [scriptRelPath, config] of Object.entries(hooksConfig)) {
14861486
const command = buildHookCommand(baseDir, skillsDir, scriptRelPath, context);
1487-
const matcher = resolveClaudeCodeHookMatcher(context.platformId, config.matcher);
1487+
const matcher = resolveInstalledHookMatcher(context, config.matcher);
14881488
if (!matcherGroups[matcher]) {
14891489
matcherGroups[matcher] = [];
14901490
}
@@ -2143,7 +2143,7 @@ export {
21432143
computeRuleDestPath,
21442144
formatRuleContent,
21452145
isManagedHookCommand,
2146-
resolveClaudeCodeHookMatcher,
2146+
resolveInstalledHookMatcher,
21472147
removeManagedCopilotHookEntries,
21482148
buildHookCommand,
21492149
removeManagedHooksFromJsonFile,

platform/install/platforms.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export interface Platform {
4444
hookConfigFile?: string;
4545
/** Historical hook config filenames checked during migration and uninstall. */
4646
legacyHookConfigFiles?: string[];
47+
/** Installed PreToolUse matcher when it differs from the portable hook descriptor. */
48+
hookMatcher?: string;
4749
}
4850

4951
const PLATFORM_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
@@ -191,22 +193,17 @@ export const PLATFORMS: Platform[] = [
191193
{
192194
id: 'grok',
193195
name: 'Grok',
194-
// Project Skills share Codex's .agents root; Grok also scans that directory.
195-
// Global Skills and all Grok-owned config live under .grok.
196-
skillsDir: '.agents',
196+
skillsDir: '.grok',
197197
globalSkillsDir: '.grok',
198-
configDir: '.grok',
199-
globalConfigDir: '.grok',
200198
detectionPaths: ['.grok'],
201-
// OpenSpec has no grok tool; reuse Codex so Classic skills land in .agents.
199+
// OpenSpec has no grok tool; generate Codex output and mirror it into .grok.
202200
openspecToolId: 'codex',
203-
openspecSkillsDir: '.agents',
204-
rulesBaseDir: '.grok',
205201
rulesDir: 'rules',
206202
rulesFormat: 'md',
207203
supportsHooks: true,
208204
hookFormat: 'claude-code',
209205
hookConfigFile: 'hooks/comet.json',
206+
hookMatcher: 'Write|Edit|write|search_replace',
210207
},
211208
{
212209
id: 'amazon-q',

0 commit comments

Comments
 (0)