@rune-kit/rune 2.28.0 → 2.29.1

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.
Files changed (51) hide show
  1. package/.codex-plugin/plugin.json +14 -0
  2. package/README.md +114 -41
  3. package/compiler/__tests__/adapter-model-mapping.test.js +22 -24
  4. package/compiler/__tests__/adapters.test.js +4 -1
  5. package/compiler/__tests__/doctor-mesh.test.js +55 -1
  6. package/compiler/__tests__/governance-collector.test.js +1 -0
  7. package/compiler/__tests__/hooks-codex.test.js +85 -0
  8. package/compiler/__tests__/hooks-doctor-tier.test.js +3 -5
  9. package/compiler/__tests__/hooks-drift.test.js +2 -2
  10. package/compiler/__tests__/hooks-install.test.js +2 -2
  11. package/compiler/__tests__/hooks-tiers.test.js +3 -4
  12. package/compiler/__tests__/setup.test.js +59 -0
  13. package/compiler/__tests__/status.test.js +7 -1
  14. package/compiler/__tests__/tier-override.test.js +42 -1
  15. package/compiler/__tests__/transforms.test.js +15 -0
  16. package/compiler/__tests__/update.test.js +416 -0
  17. package/compiler/adapters/codex.js +88 -11
  18. package/compiler/adapters/hooks/codex.js +178 -0
  19. package/compiler/adapters/hooks/index.js +10 -0
  20. package/compiler/adapters/openclaw.js +2 -2
  21. package/compiler/bin/rune.js +24 -4
  22. package/compiler/commands/hooks/install.js +2 -2
  23. package/compiler/commands/hooks/status.js +1 -1
  24. package/compiler/commands/setup.js +101 -28
  25. package/compiler/commands/update.js +354 -0
  26. package/compiler/doctor.js +12 -6
  27. package/compiler/emitter.js +46 -3
  28. package/compiler/governance-collector.js +3 -2
  29. package/compiler/status.js +4 -7
  30. package/compiler/transforms/branding.js +2 -2
  31. package/compiler/transforms/subagents.js +3 -3
  32. package/hooks/codex-hooks.json +96 -0
  33. package/hooks/context-watch/index.cjs +8 -5
  34. package/hooks/intent-router/index.cjs +3 -0
  35. package/hooks/lib/hook-output.cjs +11 -3
  36. package/hooks/post-session-reflect/index.cjs +65 -24
  37. package/hooks/pre-compact/index.cjs +10 -4
  38. package/hooks/pre-tool-guard/index.cjs +38 -36
  39. package/hooks/run-hook +1 -1
  40. package/hooks/run-hook.cmd +1 -1
  41. package/hooks/secrets-scan/index.cjs +18 -2
  42. package/package.json +3 -2
  43. package/skills/browser-pilot/SKILL.md +1 -0
  44. package/skills/completion-gate/SKILL.md +5 -5
  45. package/skills/doc-processor/SKILL.md +2 -2
  46. package/skills/hallucination-guard/SKILL.md +1 -0
  47. package/skills/journal/SKILL.md +1 -0
  48. package/skills/retro/SKILL.md +2 -2
  49. package/skills/session-bridge/SKILL.md +1 -0
  50. package/skills/session-bridge/scripts/load-invariants.js +1 -1
  51. package/skills/video-creator/SKILL.md +1 -1
@@ -21,18 +21,21 @@
21
21
  * Codex GPT-5.6 tiers (verified against codex-cli 0.144.1 + developers.openai.com/codex/models):
22
22
  * sol = flagship/complex → opus
23
23
  * terra = balanced everyday → sonnet
24
- * luna = fast/affordable → haiku
24
+ * terra at low effort = fast/affordable → haiku
25
25
  * Codex also exposes `model_reasoning_effort = minimal|low|medium|high|xhigh`
26
26
  * (a config.toml key, not per-skill frontmatter) — Rune surfaces the tier→effort
27
27
  * suggestion in AGENTS.md rather than emitting a speculative per-skill field.
28
28
  */
29
29
 
30
+ import { existsSync } from 'node:fs';
31
+ import { readFile } from 'node:fs/promises';
32
+ import path from 'node:path';
30
33
  import { BRANDING_FOOTER } from '../transforms/branding.js';
31
34
 
32
35
  const MODEL_MAP = {
33
36
  opus: 'gpt-5.6-sol',
34
37
  sonnet: 'gpt-5.6-terra',
35
- haiku: 'gpt-5.6-luna',
38
+ haiku: 'gpt-5.6-terra',
36
39
  };
37
40
 
38
41
  // Suggested config.toml `model_reasoning_effort` per tier (global key, documented in AGENTS.md).
@@ -78,8 +81,6 @@ export default {
78
81
  generateHeader(skill) {
79
82
  const desc = (skill.description || '').replace(/"/g, '\\"');
80
83
  const lines = ['---', `name: rune-${skill.name}`, `description: "${desc}"`];
81
- const translatedModel = skill.model ? MODEL_MAP[skill.model] || skill.model : null;
82
- if (translatedModel) lines.push(`model: ${translatedModel}`);
83
84
  lines.push('---', '', '');
84
85
  return lines.join('\n');
85
86
  },
@@ -102,25 +103,27 @@ export default {
102
103
 
103
104
  // Codex (and OpenAI's AGENTS.md convention generally) reads AGENTS.md at the
104
105
  // project root for high-level context. Migrated from emitter.js v2.18.
105
- generateExtraFiles({ stats }) {
106
- const agentsMd = [
106
+ async generateExtraFiles({ stats, outputRoot }) {
107
+ const runeBlock = [
108
+ '<!-- rune:codex:start -->',
107
109
  '# Rune — Project Configuration',
108
110
  '',
109
111
  '## Overview',
110
112
  '',
111
113
  'Rune is an interconnected skill ecosystem for AI coding assistants.',
112
- `${stats.skillCount} core skills | 5-layer mesh architecture | ${stats.crossRefsResolved} connections | Multi-platform.`,
114
+ `${stats.coreSkillCount ?? stats.skillCount} core skills${stats.tierSkillCount ? ` + ${stats.tierSkillCount} tier skills` : ''} | 5-layer mesh architecture | ${stats.crossRefsResolved} connections | Multi-platform.`,
113
115
  'Philosophy: "Less skills. Deeper connections."',
114
116
  '',
115
117
  'Platform: codex',
116
118
  '',
117
119
  '## Skills',
118
120
  '',
119
- `**${stats.skillCount} core skills** + **${stats.packCount} extension packs**`,
121
+ `**${stats.coreSkillCount ?? stats.skillCount} core skills**${stats.tierSkillCount ? ` + **${stats.tierSkillCount} tier skills**` : ''} + **${stats.packCount} extension packs**`,
120
122
  '',
121
123
  '## Usage',
122
124
  '',
123
- 'Reference skills using the `Skill` tool or delegate to subagents using the `Agent` tool.',
125
+ 'Mention a skill as `$rune-<name>` or let Codex activate it from the task description.',
126
+ 'When an applicable Rune skill requests delegation, Codex may run bounded subagents in parallel.',
124
127
  '',
125
128
  '## Skills Directory',
126
129
  '',
@@ -134,12 +137,86 @@ export default {
134
137
  `- sonnet → \`${MODEL_MAP.sonnet}\` (\`model_reasoning_effort = "${REASONING_EFFORT_MAP.sonnet}"\`) — code / edit / review`,
135
138
  `- haiku → \`${MODEL_MAP.haiku}\` (\`model_reasoning_effort = "${REASONING_EFFORT_MAP.haiku}"\`) — scan / read-only`,
136
139
  '',
137
- 'Set `model` + `model_reasoning_effort` in `~/.codex/config.toml` to match the tier of the task.',
140
+ 'Generated project-scoped roles live in `.codex/agents/` so spawned agents can apply these settings natively.',
138
141
  '',
139
142
  '---',
140
143
  '> Rune Skill Mesh — https://github.com/rune-kit/rune',
144
+ '<!-- rune:codex:end -->',
141
145
  '',
142
146
  ].join('\n');
143
- return [{ path: 'AGENTS.md', content: agentsMd }];
147
+
148
+ const agentsMd = await mergeAgentsMd(outputRoot, runeBlock);
149
+ return [
150
+ { path: 'AGENTS.md', content: agentsMd },
151
+ {
152
+ path: '.codex/agents/rune-heavy.toml',
153
+ content: renderAgentRole({
154
+ name: 'rune_heavy',
155
+ description: 'Rune worker for architecture, security, planning, and other high-complexity tasks.',
156
+ model: MODEL_MAP.opus,
157
+ effort: REASONING_EFFORT_MAP.opus,
158
+ instructions:
159
+ 'Follow the applicable Rune skill exactly. Prioritize correctness, explicit assumptions, impact analysis, and complete verification.',
160
+ }),
161
+ },
162
+ {
163
+ path: '.codex/agents/rune-standard.toml',
164
+ content: renderAgentRole({
165
+ name: 'rune_standard',
166
+ description: 'Rune worker for implementation, debugging, review, and everyday engineering tasks.',
167
+ model: MODEL_MAP.sonnet,
168
+ effort: REASONING_EFFORT_MAP.sonnet,
169
+ instructions:
170
+ 'Follow the applicable Rune skill exactly. Keep changes scoped, preserve user work, and verify behavior before reporting completion.',
171
+ }),
172
+ },
173
+ {
174
+ path: '.codex/agents/rune-fast.toml',
175
+ content: renderAgentRole({
176
+ name: 'rune_fast',
177
+ description: 'Read-heavy Rune worker for scouting, dependency checks, and focused evidence gathering.',
178
+ model: MODEL_MAP.haiku,
179
+ effort: REASONING_EFFORT_MAP.haiku,
180
+ instructions:
181
+ 'Stay read-only unless the parent explicitly requests edits. Return concise evidence with file and line references.',
182
+ }),
183
+ },
184
+ ];
144
185
  },
145
186
  };
187
+
188
+ const RUNE_AGENTS_START = '<!-- rune:codex:start -->';
189
+ const RUNE_AGENTS_END = '<!-- rune:codex:end -->';
190
+
191
+ async function mergeAgentsMd(outputRoot, runeBlock) {
192
+ if (!outputRoot) return runeBlock;
193
+
194
+ const agentsPath = path.join(outputRoot, 'AGENTS.md');
195
+ if (!existsSync(agentsPath)) return runeBlock;
196
+
197
+ const existing = await readFile(agentsPath, 'utf-8');
198
+ const start = existing.indexOf(RUNE_AGENTS_START);
199
+ const end = existing.indexOf(RUNE_AGENTS_END);
200
+ if (start !== -1 && end > start) {
201
+ const after = end + RUNE_AGENTS_END.length;
202
+ return `${existing.slice(0, start)}${runeBlock}${existing.slice(after)}`;
203
+ }
204
+
205
+ // Migrate files produced by Rune versions before managed markers existed.
206
+ if (existing.trimStart().startsWith('# Rune — Project Configuration')) return runeBlock;
207
+
208
+ return `${existing.trimEnd()}\n\n${runeBlock}`;
209
+ }
210
+
211
+ function renderAgentRole({ name, description, model, effort, instructions }) {
212
+ return [
213
+ `name = "${name}"`,
214
+ `description = "${description}"`,
215
+ `model = "${model}"`,
216
+ `model_reasoning_effort = "${effort}"`,
217
+ 'developer_instructions = """',
218
+ instructions,
219
+ '"""',
220
+ '',
221
+ ].join('\n');
222
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * OpenAI Codex hooks adapter.
3
+ *
4
+ * Target: `.codex/hooks.json` — Codex-native lifecycle hooks. Codex currently
5
+ * skips handlers marked `async`, so Rune emits every command synchronously and
6
+ * relies on the preset's `--gentle` flag for warn-only behavior.
7
+ */
8
+
9
+ import { existsSync } from 'node:fs';
10
+ import { readFile } from 'node:fs/promises';
11
+ import path from 'node:path';
12
+ import {
13
+ appendHookBlock,
14
+ detectPreset,
15
+ stripHooksBySkill,
16
+ stripRuneHooks,
17
+ summarizeRuneHooks,
18
+ } from '../../commands/hooks/merge.js';
19
+ import { buildPreset, WIRED_SKILLS } from '../../commands/hooks/presets.js';
20
+
21
+ export const id = 'codex';
22
+ export const HOOKS_REL_PATH = '.codex/hooks.json';
23
+
24
+ const RUNE_DESCRIPTION = 'Rune lifecycle hooks for OpenAI Codex.';
25
+
26
+ export function detect(projectRoot) {
27
+ return existsSync(path.join(projectRoot, '.codex'));
28
+ }
29
+
30
+ export async function emit({ preset, projectRoot, tierManifests = [] }) {
31
+ if (preset === 'off' && tierManifests.length === 0) return uninstall({ projectRoot });
32
+ if (preset !== 'off' && preset !== 'strict' && preset !== 'gentle') {
33
+ throw new Error(`codex adapter: invalid preset '${preset}'`);
34
+ }
35
+
36
+ const hooksPath = path.join(projectRoot, HOOKS_REL_PATH);
37
+ const existing = await readJson(hooksPath);
38
+ let merged = stripRuneHooks(existing);
39
+ const notes = [
40
+ 'Codex requires review/trust for new or changed project hooks. Open `/hooks` after installation.',
41
+ 'Codex does not execute async command hooks; Rune emitted synchronous handlers with preset-controlled behavior.',
42
+ ];
43
+
44
+ if (!merged.description) merged.description = RUNE_DESCRIPTION;
45
+ if (preset !== 'off') {
46
+ merged = appendHookBlock(merged, normalizeBlock(buildPreset(preset)));
47
+ }
48
+
49
+ for (const manifest of tierManifests) {
50
+ const overrideKeys = Object.keys(manifest.overrides || {});
51
+ if (overrideKeys.length > 0) merged = stripHooksBySkill(merged, overrideKeys);
52
+
53
+ const tierBlock = buildCodexTierBlock(manifest);
54
+ if (Object.keys(tierBlock.hooks).length > 0) {
55
+ merged = appendHookBlock(merged, tierBlock);
56
+ }
57
+ if (tierBlock.skipped.length > 0) {
58
+ notes.push(`${manifest.tier}: skipped Codex-unsupported entries (${tierBlock.skipped.join(', ')}).`);
59
+ }
60
+ }
61
+
62
+ return {
63
+ files: [{ path: hooksPath, content: `${JSON.stringify(merged, null, 2)}\n` }],
64
+ notes,
65
+ };
66
+ }
67
+
68
+ export async function uninstall({ projectRoot }) {
69
+ const hooksPath = path.join(projectRoot, HOOKS_REL_PATH);
70
+ if (!existsSync(hooksPath)) {
71
+ return { files: [], notes: ['no .codex/hooks.json — nothing to uninstall'] };
72
+ }
73
+
74
+ const existing = await readJson(hooksPath);
75
+ const stripped = stripRuneHooks(existing);
76
+ const keys = Object.keys(stripped);
77
+ const isRuneOnlyShell = keys.length === 1 && keys[0] === 'description' && stripped.description === RUNE_DESCRIPTION;
78
+
79
+ return {
80
+ files: [{ path: hooksPath, content: isRuneOnlyShell ? null : `${JSON.stringify(stripped, null, 2)}\n` }],
81
+ notes: [],
82
+ };
83
+ }
84
+
85
+ export async function status(projectRoot) {
86
+ const hooksPath = path.join(projectRoot, HOOKS_REL_PATH);
87
+ if (!existsSync(hooksPath)) {
88
+ return {
89
+ installed: false,
90
+ preset: null,
91
+ wired: [],
92
+ missing: [...WIRED_SKILLS],
93
+ notes: ['no .codex/hooks.json'],
94
+ };
95
+ }
96
+
97
+ const settings = await readJson(hooksPath);
98
+ const summary = summarizeRuneHooks(settings);
99
+ const preset = detectPreset(settings);
100
+ const wired = Array.from(new Set(Object.values(summary.events).flat()));
101
+ return {
102
+ installed: summary.total > 0,
103
+ preset: preset === 'none' ? null : preset,
104
+ wired,
105
+ missing: WIRED_SKILLS.filter((skill) => !wired.includes(skill)),
106
+ events: summary.events,
107
+ notes: ['Use `/hooks` in Codex to inspect the current trust state.'],
108
+ };
109
+ }
110
+
111
+ function normalizeBlock(block) {
112
+ const hooks = {};
113
+ for (const [event, groups] of Object.entries(block.hooks || {})) {
114
+ hooks[event] = groups.map((group) => {
115
+ const normalized = {
116
+ ...group,
117
+ hooks: group.hooks.map(normalizeCommand),
118
+ };
119
+ if (event === 'UserPromptSubmit' || event === 'Stop') delete normalized.matcher;
120
+ return normalized;
121
+ });
122
+ }
123
+ return { hooks };
124
+ }
125
+
126
+ function normalizeCommand(entry) {
127
+ const { async: _unsupported, ...normalized } = entry;
128
+ const commandWindows = toWindowsCommand(normalized.command);
129
+ if (commandWindows !== normalized.command) normalized.commandWindows = commandWindows;
130
+ return normalized;
131
+ }
132
+
133
+ function buildCodexTierBlock(manifest) {
134
+ const hooks = {};
135
+ const skipped = [];
136
+
137
+ for (const entry of manifest.entries) {
138
+ if (entry.event === 'statusLine' || entry.claudeOnly || entry.platforms?.codex === 'unsupported') {
139
+ skipped.push(entry.id || entry.skill);
140
+ continue;
141
+ }
142
+
143
+ const event = entry.event;
144
+ const matcher = entry.matcher || '.*';
145
+ if (!hooks[event]) hooks[event] = [];
146
+ let group = hooks[event].find((candidate) => candidate.matcher === matcher);
147
+ if (!group) {
148
+ group = { matcher, hooks: [] };
149
+ hooks[event].push(group);
150
+ }
151
+ group.hooks.push(
152
+ normalizeCommand({
153
+ type: 'command',
154
+ command: entry.command,
155
+ }),
156
+ );
157
+ }
158
+
159
+ return { ...normalizeBlock({ hooks }), skipped };
160
+ }
161
+
162
+ function toWindowsCommand(command) {
163
+ if (typeof command !== 'string') return command;
164
+ return command.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, '%$1%');
165
+ }
166
+
167
+ async function readJson(hooksPath) {
168
+ if (!existsSync(hooksPath)) return {};
169
+ const raw = await readFile(hooksPath, 'utf-8');
170
+ if (!raw.trim()) return {};
171
+ try {
172
+ return JSON.parse(raw);
173
+ } catch (err) {
174
+ throw new Error(
175
+ `${hooksPath} is not valid JSON — fix it manually or delete the file and re-run \`rune hooks install --platform codex\`. (${err.message})`,
176
+ );
177
+ }
178
+ }
@@ -15,11 +15,13 @@
15
15
 
16
16
  import * as antigravity from './antigravity.js';
17
17
  import * as claude from './claude.js';
18
+ import * as codex from './codex.js';
18
19
  import * as cursor from './cursor.js';
19
20
  import * as windsurf from './windsurf.js';
20
21
 
21
22
  export const ADAPTERS = Object.freeze({
22
23
  claude,
24
+ codex,
23
25
  cursor,
24
26
  windsurf,
25
27
  antigravity,
@@ -55,6 +57,14 @@ export const CAPABILITIES = Object.freeze({
55
57
  stop: true,
56
58
  notes: 'Full hook parity — PreToolUse/PostToolUse/Stop all supported.',
57
59
  },
60
+ codex: {
61
+ maturity: 'stable',
62
+ preToolEdit: true,
63
+ preToolBash: true,
64
+ postToolEdit: true,
65
+ stop: true,
66
+ notes: 'Native hooks via `.codex/hooks.json`; operator trust is required before first execution.',
67
+ },
58
68
  cursor: {
59
69
  maturity: 'beta',
60
70
  preToolEdit: 'rule-injection',
@@ -105,7 +105,7 @@ export default {
105
105
  id: 'rune',
106
106
  name: 'Rune',
107
107
  kind: 'skills',
108
- description: `${skills.length}-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 203 sync connections + 40 async signals, 14 extension packs.`,
108
+ description: `${skills.length}-skill mesh for AI coding assistants. Routes all code tasks through specialized skills. 248 sync connections + 45 async signals, 14 extension packs.`,
109
109
  version: pluginJson.version || '0.0.0',
110
110
  skills: ['./skills'],
111
111
  artifactConvention: {
@@ -170,7 +170,7 @@ export default {
170
170
 
171
171
  > Less skills. Deeper connections.
172
172
 
173
- **${skills.length}-skill mesh** for AI coding assistants — 5-layer architecture, 215+ connections, 14 extension packs.
173
+ **${skills.length}-skill mesh** for AI coding assistants — 5-layer architecture, 248 connections + 45 signals, 14 extension packs.
174
174
 
175
175
  ## Install
176
176
 
@@ -25,6 +25,7 @@ import { installHooks } from '../commands/hooks/install.js';
25
25
  import { hookStatus } from '../commands/hooks/status.js';
26
26
  import { uninstallHooks } from '../commands/hooks/uninstall.js';
27
27
  import { formatSetupResult, runSetup } from '../commands/setup.js';
28
+ import { formatUpdateResult, runUpdate } from '../commands/update.js';
28
29
  import { generateComprehensionHTML } from '../comprehension.js';
29
30
  import { generateDashboardHTML } from '../dashboard.js';
30
31
  import { checkMeshIntegrity, formatDoctorResults, formatMeshResults, runDoctor } from '../doctor.js';
@@ -63,9 +64,10 @@ function detectPlatform(projectRoot) {
63
64
  if (existsSync(path.join(projectRoot, '.claude-plugin'))) return 'claude';
64
65
  if (existsSync(path.join(projectRoot, '.cursor'))) return 'cursor';
65
66
  if (existsSync(path.join(projectRoot, '.windsurf'))) return 'windsurf';
66
- if (existsSync(path.join(projectRoot, '.agents'))) return 'antigravity';
67
- if (existsSync(path.join(projectRoot, '.openclaw'))) return 'openclaw';
68
67
  if (existsSync(path.join(projectRoot, '.codex'))) return 'codex';
68
+ if (existsSync(path.join(projectRoot, '.agents', 'skills'))) return 'codex';
69
+ if (existsSync(path.join(projectRoot, '.antigravity'))) return 'antigravity';
70
+ if (existsSync(path.join(projectRoot, '.openclaw'))) return 'openclaw';
69
71
  if (existsSync(path.join(projectRoot, '.opencode'))) return 'opencode';
70
72
  return null;
71
73
  }
@@ -337,6 +339,18 @@ async function cmdSetup(projectRoot, args) {
337
339
  }
338
340
  }
339
341
 
342
+ async function cmdUpdate(projectRoot, args) {
343
+ try {
344
+ const result = await runUpdate({ projectRoot, runeRoot: RUNE_ROOT, args });
345
+ log(formatUpdateResult(result));
346
+ if (!result.ok) process.exit(1);
347
+ } catch (err) {
348
+ log('');
349
+ log(` ✗ Update failed: ${err.message}`);
350
+ process.exit(1);
351
+ }
352
+ }
353
+
340
354
  async function readVersion() {
341
355
  try {
342
356
  const pkg = JSON.parse(await readFile(path.join(RUNE_ROOT, 'package.json'), 'utf-8'));
@@ -655,7 +669,7 @@ async function cmdHooks(projectRoot, args, subcommand) {
655
669
  ' status [--platform <name>|all] [--tier pro|business] Show active preset, wired skills, tier coverage',
656
670
  );
657
671
  log('');
658
- log(' Platforms: claude, cursor, windsurf, antigravity (auto-detected if omitted)');
672
+ log(' Platforms: claude, codex, cursor, windsurf, antigravity (auto-detected if omitted)');
659
673
  log(' Tiers: pro, business — requires $RUNE_PRO_ROOT / $RUNE_BUSINESS_ROOT env var or monorepo sibling.');
660
674
  log(' Options:');
661
675
  log(' --dry Preview changes without writing');
@@ -851,6 +865,9 @@ async function main() {
851
865
  case 'setup':
852
866
  await cmdSetup(projectRoot, args);
853
867
  break;
868
+ case 'update':
869
+ await cmdUpdate(projectRoot, args);
870
+ break;
854
871
  case 'status':
855
872
  await cmdStatus(projectRoot, args);
856
873
  break;
@@ -886,6 +903,9 @@ async function main() {
886
903
  ' setup Interactive wizard — auto-detect tiers, pick scope, install hooks (recommended for first-time)',
887
904
  );
888
905
  log(' [--here|--global] [--tier pro,business] [--preset gentle|strict] [--dry]');
906
+ log(' update Update an existing install — git-pull detected tier repos, re-run the managed');
907
+ log(' setup rewrite in place (reuses installed platforms/preset/tiers), verify with doctor');
908
+ log(' [--no-pull] [--preset gentle|strict] [--tier pro,business] [--dry]');
889
909
  log(' init Interactive setup for build pipeline (auto-detects platform)');
890
910
  log(' build Compile skills for configured platform');
891
911
  log(' doctor Validate compiled output + mesh integrity');
@@ -899,7 +919,7 @@ async function main() {
899
919
  log(' [--days <n>] Lookback window for gate counts (default: 30)');
900
920
  log(' hooks Install/uninstall/status for multi-platform auto-discipline');
901
921
  log(
902
- ' hooks install [--preset gentle|strict|off] [--platform claude|cursor|windsurf|antigravity|all] [--global]',
922
+ ' hooks install [--preset gentle|strict|off] [--platform claude|codex|cursor|windsurf|antigravity|all] [--global]',
903
923
  );
904
924
  log(' hooks uninstall [--platform <name>|all]');
905
925
  log(' hooks status [--platform <name>|all]');
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `rune hooks install [--preset strict|gentle|off] [--platform claude|cursor|windsurf|antigravity|all]`
2
+ * `rune hooks install [--preset strict|gentle|off] [--platform claude|codex|cursor|windsurf|antigravity|all]`
3
3
  *
4
4
  * Writes Rune-managed hook/rule/workflow entries for one or more platforms.
5
5
  * Idempotent: re-running replaces existing Rune entries, preserves user entries.
@@ -41,7 +41,7 @@ export async function installHooks(projectRoot, args = {}) {
41
41
  results: [],
42
42
  written: false,
43
43
  notes: [
44
- 'No target platform detected. Create `.claude/`, `.cursor/`, `.windsurf/`, or `.antigravity/` first, or pass `--platform <name>`.',
44
+ 'No target platform detected. Create `.claude/`, `.codex/`, `.cursor/`, `.windsurf/`, or `.antigravity/` first, or pass `--platform <name>`.',
45
45
  ...tierNotes,
46
46
  ],
47
47
  };
@@ -34,7 +34,7 @@ export async function hookStatus(projectRoot, runeRoot, args = {}) {
34
34
  tiers,
35
35
  missingInRepo,
36
36
  notes: [
37
- 'No target platform detected. Create `.claude/`, `.cursor/`, `.windsurf/`, or `.antigravity/` first, or pass `--platform <name>`.',
37
+ 'No target platform detected. Create `.claude/`, `.codex/`, `.cursor/`, `.windsurf/`, or `.antigravity/` first, or pass `--platform <name>`.',
38
38
  ],
39
39
  };
40
40
  }