genesis-compiler 1.7.3 → 1.7.5

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
@@ -34,6 +34,7 @@ genesis/
34
34
  genesis-* Genesis workflow skills
35
35
  <technology>/ authoritative skills installed by selected Stack pieces
36
36
  .codex/hooks.json project-local Codex lifecycle integration
37
+ .claude/settings.json project-local Claude Code lifecycle integration
37
38
  .opencode/plugins/ project-local OpenCode lifecycle integration
38
39
  .genesis/
39
40
  machine-city.json derived detailed source and function map
@@ -82,7 +83,7 @@ The catalog is static Markdown and contains the complete curated set of
82
83
  language, database, UI, and framework pieces. A host may supply another catalog
83
84
  through the same contract.
84
85
 
85
- ### Try a new project with Codex or OpenCode
86
+ ### Try a new project with Codex, Claude Code or OpenCode
86
87
 
87
88
  Create an ordinary empty Git repository:
88
89
 
@@ -93,7 +94,7 @@ git init
93
94
  genesis init
94
95
  ```
95
96
 
96
- Start Codex or OpenCode in that directory. For a useful discovery test, begin
97
+ Start Codex, Claude Code or OpenCode in that directory. For a useful discovery test, begin
97
98
  with a deliberately incomplete request:
98
99
 
99
100
  > I want to build something with JSKIT.
@@ -180,11 +181,11 @@ empty or already-adopted project.
180
181
 
181
182
  `genesis init` creates an empty Blueprint, a focused engineering approach, an
182
183
  empty optional Stack, the current `genesis/version`, three project Agent Skills,
183
- and project-local guidance adapters for Codex and OpenCode. The skills are ordinary
184
+ and project-local guidance adapters for Codex, Claude Code and OpenCode. The skills are ordinary
184
185
  [Agent Skills](https://agentskills.io): `genesis-project`, `genesis-program`,
185
186
  and `genesis-deslop`, each with standard `SKILL.md` and `agents/openai.yaml`
186
187
  metadata. Open `/hooks` once in Codex to review and trust the guidance hook;
187
- OpenCode discovers its local project plugin automatically. Both use the same
188
+ Claude Code loads its project hooks and OpenCode discovers its local project plugin automatically. All three use the same
188
189
  Genesis-owned operating guide for new sessions and after compaction. When the
189
190
  optional first-party catalog is installed beside Genesis, that guide includes
190
191
  only its current component ids. If a named technology exactly matches an
@@ -236,7 +237,7 @@ files alone does not refresh its existing context.
236
237
 
237
238
  | Command | What it does |
238
239
  | --- | --- |
239
- | `genesis init` | Initializes Genesis files, workflow skills, code indexes, and local Codex/OpenCode guidance in a new or current-format project. It selects no technology. |
240
+ | `genesis init` | Initializes Genesis files, workflow skills, code indexes, and local Codex/Claude Code/OpenCode guidance in a new or current-format project. It selects no technology. |
240
241
  | `genesis adopt [guidance...]` | Initializes an existing codebase and prints the prompt for describing its real product and operational contracts without moving or modernizing source. |
241
242
  | `genesis codex install` | Installs the optional Codex discovery plugin that recommends adoption for nonempty projects without Genesis. |
242
243
  | `genesis engineering list` | Lists the installed engineering profiles. |
@@ -310,7 +311,7 @@ conversation is created or refreshed. They may then generate task prompts with
310
311
  `sessionContextInstalled: true`, preventing the stable Collaboration and
311
312
  Engineering guidance from being repeated in each task. A host can supply one
312
313
  explicit session/turn driver; Genesis performs no driver discovery and labels
313
- turn output untrusted. If the project-local Codex or OpenCode adapter runs in a
314
+ turn output untrusted. If the project-local Codex, Claude Code or OpenCode adapter runs in a
314
315
  separate process, the host may set `GENESIS_HOST_CONTEXT_RESOLVER` to one
315
316
  executable and optionally supply opaque JSON in
316
317
  `GENESIS_HOST_CONTEXT_RESOLVER_DATA`. Session and turn hooks pass only the
@@ -461,7 +462,7 @@ Python, Java, C#, C/C++, PHP, Go, Rust, Ruby, Kotlin, and Shell. A future techno
461
462
  adds its adapter through its Stack piece rather than teaching Genesis prompts
462
463
  to parse that language.
463
464
 
464
- The project Codex hook and OpenCode plugin use the same short operating guide
465
+ The project Codex and Claude Code hooks and OpenCode plugin use the same short operating guide
465
466
  for a new session and after compaction. They record no conversation message,
466
467
  block no completion, and request no follow-up turn. Implementation prompts
467
468
  compose selected Stack `Post-change` guidance into the original implementation
@@ -61,7 +61,7 @@ native placement. The driver never receives or rewrites `userMessage`, and
61
61
  Genesis supports only the one explicitly supplied driver—there is no discovery
62
62
  or plugin registry.
63
63
 
64
- When the project-local Codex or OpenCode adapter runs in a separate process,
64
+ When the project-local Codex, Claude Code or OpenCode adapter runs in a separate process,
65
65
  the host can configure that same one-driver boundary without adding another
66
66
  prompt compositor:
67
67
 
@@ -82,12 +82,12 @@ JSON on stdin:
82
82
  ```
83
83
 
84
84
  For `genesis hook turn`, the input has the same shape with `scope: "turn"`.
85
- The executable returns only the requested host contribution as text. Codex's
85
+ The executable returns only the requested host contribution as text. Codex and Claude Code
86
86
  hook input and OpenCode's transform callbacks supply the native session id;
87
87
  Genesis extracts that id but does not understand the host registry or the
88
- provider's prompt-placement rules. The Codex prompt hook and OpenCode message
88
+ provider's prompt-placement rules. The Codex and Claude prompt hooks and OpenCode message
89
89
  transform place a nonempty turn result separately from authored user text. If
90
- no resolver is configured, both turn paths are empty and the adapters render
90
+ no resolver is configured, all turn paths are empty and the adapters render
91
91
  ordinary Genesis session context exactly as before.
92
92
 
93
93
  A host that has no turn contribution can set `GENESIS_TURN_CONTEXT_ENABLED=0`.
@@ -108,13 +108,15 @@ in `GENESIS_HOST_CONTEXT_INPUT` for host command shims. The resolver still
108
108
  receives the actual child's `providerSessionId`; Genesis does not interpret a
109
109
  host's ownership rules.
110
110
 
111
- After updating Genesis, hosts can inspect the generated adapter with
112
- `inspectOpenCodePlugin({ projectRoot })` and call `syncOpenCodePlugin({ projectRoot })`
113
- from `genesis-compiler` under their ordinary source-write admission. It updates
114
- only `.opencode/plugins/genesis-project-guidance.js`, returns `status` and
115
- `changedFiles`, and leaves authored project files and other plugins alone.
116
- Reload the provider instance to use an adapter that was already loaded. The
117
- existing `genesis migrate` command also refreshes this generated adapter.
111
+ After updating Genesis, hosts can inspect all generated integrations with
112
+ `inspectAgentIntegrations({ projectRoot })` and refresh them with
113
+ `syncAgentIntegrations({ projectRoot })` under ordinary source-write admission.
114
+ These finite operations cover Codex hooks, Claude settings and the OpenCode
115
+ plugin. Synchronization preserves unrelated settings and hooks and returns
116
+ `status` and `changedFiles`; it does not migrate authored project contracts.
117
+ The individual OpenCode inspection/sync exports remain available to callers
118
+ that own only that integration. A running provider picks up changes through its
119
+ native reload lifecycle. `genesis init` and migration install all three adapters.
118
120
 
119
121
  Tasks are `start`, `adopt`, `work`, `deslop`, `program`, `blueprint`, `describe`, and `review`.
120
122
  `start` is the host-independent first conversation: it classifies an initialized
@@ -191,11 +193,29 @@ does not perform adoption automatically. If the user accepts, Codex runs the
191
193
  command itself and follows its returned prompt in the same conversation. Other
192
194
  hosts can call `adoptProject()` and send its `prompt` directly.
193
195
 
194
- Codex and OpenCode can use the project-local adapters installed by `genesis
195
- init`. Both receive the same short operating guide for a new session and after
196
- compaction. Codex uses `SessionStart` for startup, clear, and compact events.
196
+ Codex, Claude Code and OpenCode can use the project-local adapters installed by `genesis
197
+ init`. All receive the same short operating guide for a new session and after
198
+ compaction. The Codex session hook sets `additionalContextLimit: 0` so native
199
+ truncation cannot remove part of the bounded guide or host contribution.
200
+ Codex and Claude Code use `SessionStart` for startup, resume, clear, and compact events.
201
+ Claude's generated session hooks use `genesis hook session --claude-part <1-4>`.
202
+ Claude caps each hook output at 10,000 characters, so four hooks deliver the
203
+ unchanged guide in numbered parts below that limit. Empty parts emit nothing.
204
+ A guide above 36,000 characters delivers an explicit guidance-unavailable
205
+ warning instead of a partial guide. Claude does not enforce startup hook stop
206
+ requests; this warning instructs the model to report the problem before work,
207
+ and is not a runtime admission control. Turn guidance is already bounded at 512
208
+ bytes and uses the ordinary turn command. This is provider transport only;
209
+ composition and the host bridge remain shared.
210
+ Claude settings preserve unrelated hooks and configuration. The common operating
211
+ guide lists installed project skills and their canonical `.agents/skills/` paths;
212
+ an agent reads the relevant skill before following its workflow. Genesis exposes
213
+ the canonical skill directories to Claude through relative `.claude/skills/`
214
+ links, so native skill commands use the same files. Skills synchronization adds
215
+ new links and removes only its exact dangling links; existing native Claude
216
+ skills are preserved and reported as overrides.
197
217
  OpenCode renders once per session, keeps that value active in its stateless
198
- model requests, and rerenders after its `session.compacted` event. Neither
218
+ model requests, and rerenders after its `session.compacted` event. No
199
219
  adapter records a user message, authorizes edits, intercepts completion, or
200
220
  requests another model turn. Selected `Post-change` contributions are included
201
221
  in the original implementation prompt, whose project skill also keeps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -88,6 +88,7 @@ Options:
88
88
  --directory <path> Parser cache directory (parsers commands only)
89
89
  --all Select every parser (parsers install/verify only)
90
90
  --json Emit one machine-readable result
91
+ --claude-part <1-4> Emit one bounded Claude session-hook part
91
92
  -h, --help Show this help
92
93
 
93
94
  Genesis generates prompts; it never starts or manages an AI agent. Give the
@@ -111,6 +112,7 @@ function parseCommand(argv) {
111
112
  strict: true,
112
113
  options: {
113
114
  json: { type: 'boolean', default: false },
115
+ 'claude-part': { type: 'string' },
114
116
  all: { type: 'boolean', default: false },
115
117
  directory: { type: 'string' },
116
118
  'project-root': { type: 'string' },
@@ -124,6 +126,7 @@ function parseCommand(argv) {
124
126
  }
125
127
  const options = {
126
128
  json: parsed.values.json,
129
+ claudePart: parsed.values['claude-part'],
127
130
  all: parsed.values.all,
128
131
  directory: parsed.values.directory,
129
132
  projectRoot: parsed.values['project-root'],
@@ -138,6 +141,9 @@ function parseCommand(argv) {
138
141
  task: parsed.values.task,
139
142
  };
140
143
  const operands = parsed.positionals;
144
+ if (options.claudePart !== undefined && (command !== 'hook' || operands[0] !== 'session' || !/^[1-4]$/u.test(options.claudePart) || options.json)) {
145
+ fail('CLI_OPTION_NOT_APPLICABLE', 'Option --claude-part requires hook session, a part from 1 to 4, and no --json.');
146
+ }
141
147
  if (command !== 'parsers' && (options.all || options.directory !== undefined)) {
142
148
  fail('CLI_OPTION_NOT_APPLICABLE', 'Options --all and --directory apply only to parsers commands.');
143
149
  }
@@ -665,9 +671,24 @@ export async function runCli(argv = process.argv.slice(2)) {
665
671
  projectFormat,
666
672
  formatDiagnostic,
667
673
  );
668
- if (options.json) line(process.stdout, JSON.stringify(result));
669
- else {
670
- writeResult(parsed.command, result);
674
+ if (options.claudePart !== undefined) {
675
+ // Claude spills each hook output over 10,000 characters to a file.
676
+ // Four native hooks deliver the exact text in bounded parts instead.
677
+ if (result.output.length > 36000) {
678
+ if (options.claudePart === '1') line(process.stdout,
679
+ 'GENESIS GUIDANCE UNAVAILABLE: The session guide exceeds the Claude hook capacity of 36,000 characters. The guide was not delivered. Tell the user before doing project work. Shorten the project guidance and restart the conversation to load it completely.');
680
+ return 0;
681
+ }
682
+ let start = (Number(options.claudePart) - 1) * 9000;
683
+ let end = start + 9000;
684
+ // Keep UTF-16 surrogate pairs together at the shared part boundaries.
685
+ if (/[\uDC00-\uDFFF]/u.test(result.output[start] || '')) start--;
686
+ if (/[\uDC00-\uDFFF]/u.test(result.output[end] || '')) end--;
687
+ const part = result.output.slice(start, end);
688
+ if (part) process.stdout.write(`Genesis session guidance, part ${options.claudePart} of ${Math.ceil(result.output.length / 9000)} (read in numeric order):\n${part}`);
689
+ } else if (options.json) line(process.stdout, JSON.stringify(result));
690
+ else writeResult(parsed.command, result);
691
+ if (!options.json) {
671
692
  for (const warning of result.warnings || []) {
672
693
  if (warning.code !== formatDiagnostic?.code) line(process.stderr, `WARNING: ${warning.code}: ${warning.message}`);
673
694
  }
@@ -0,0 +1,24 @@
1
+ import { inspectClaudeHooks, installClaudeHooks } from './claude-hooks.js';
2
+ import { inspectCodexHooks, installCodexHooks } from './codex-hooks.js';
3
+ import { inspectOpenCodePlugin, installOpenCodePlugin } from './opencode-plugin.js';
4
+
5
+ export async function inspectAgentIntegrations(options = {}) {
6
+ const [codex, claude, opencode] = await Promise.all([
7
+ inspectCodexHooks(options), inspectClaudeHooks(options), inspectOpenCodePlugin(options),
8
+ ]);
9
+ const integrations = { codex, claude, opencode };
10
+ const states = Object.values(integrations).map(({ status }) => status);
11
+ return {
12
+ status: states.includes('missing') ? 'missing' : states.includes('outdated') ? 'outdated' : 'current',
13
+ integrations,
14
+ };
15
+ }
16
+
17
+ export async function syncAgentIntegrations(options = {}) {
18
+ const results = await Promise.all([
19
+ installCodexHooks(options), installClaudeHooks(options), installOpenCodePlugin(options),
20
+ ]);
21
+ const changedFiles = results.flatMap(({ changedFiles }) => changedFiles).sort();
22
+ const warnings = results.flatMap((result) => result.warnings || []);
23
+ return { status: changedFiles.length ? 'updated' : 'unchanged', changedFiles, ...(warnings.length ? { warnings } : {}) };
24
+ }
@@ -16,6 +16,7 @@ import { fromMarkdown } from 'mdast-util-from-markdown';
16
16
  import { parse as parseYaml } from 'yaml';
17
17
 
18
18
  import { asDiagnostic, GenesisError } from './errors.js';
19
+ import { claudeSkillLinks } from './claude-hooks.js';
19
20
  import { sha256, stableJson, writeFileAtomic } from './utils.js';
20
21
 
21
22
  const packageRoot = fileURLToPath(new URL('../../', import.meta.url));
@@ -482,6 +483,9 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
482
483
  await writeFileAtomic(manifest.location, rendered);
483
484
  changedFiles.push(MANIFEST_PATH);
484
485
  }
486
+ const links = await claudeSkillLinks(projectRoot, true);
487
+ changedFiles.push(...links.changedFiles);
488
+ diagnostics.push(...(links.warnings || []));
485
489
  return {
486
490
  status: changedFiles.length > 0 ? 'updated' : 'unchanged',
487
491
  changedFiles: [...new Set(changedFiles)].sort(),
@@ -0,0 +1,104 @@
1
+ import { mkdir, readFile, readdir, readlink, rm, symlink } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { GenesisError } from './errors.js';
5
+ import { gitContext } from './git.js';
6
+ import { genesisHookCommand } from './hook-command.js';
7
+ import { writeFileAtomic } from './utils.js';
8
+
9
+ const SETTINGS_PATH = '.claude/settings.json';
10
+ const SESSION_COMMANDS = [1, 2, 3, 4].map((claudePart) => genesisHookCommand('session', { claudePart }));
11
+ const TURN_COMMAND = genesisHookCommand('turn');
12
+ // Refresh exact generated commands, including the previous plain-text output.
13
+ const OWNED_COMMANDS = new Set([...SESSION_COMMANDS, TURN_COMMAND,
14
+ genesisHookCommand('session')]);
15
+
16
+ async function renderedSettings(projectRoot) {
17
+ const root = (await gitContext(projectRoot)).repositoryRoot;
18
+ const location = path.join(root, SETTINGS_PATH);
19
+ let source = null;
20
+ let value = {};
21
+ try {
22
+ source = await readFile(location, 'utf8');
23
+ value = JSON.parse(source);
24
+ } catch (error) {
25
+ if (!['ENOENT', 'ENOTDIR'].includes(error.code)) {
26
+ throw new GenesisError('CLAUDE_SETTINGS_INVALID', `${SETTINGS_PATH} is not valid JSON: ${error.message}.`);
27
+ }
28
+ }
29
+ if (!value || typeof value !== 'object' || Array.isArray(value)
30
+ || (value.hooks !== undefined && (!value.hooks || typeof value.hooks !== 'object' || Array.isArray(value.hooks)))) {
31
+ throw new GenesisError('CLAUDE_SETTINGS_INVALID', `${SETTINGS_PATH} must contain an object with an optional hooks object.`);
32
+ }
33
+ value.hooks ||= {};
34
+ for (const [event, groups] of Object.entries(value.hooks)) {
35
+ if (!Array.isArray(groups)) {
36
+ throw new GenesisError('CLAUDE_SETTINGS_INVALID', `${SETTINGS_PATH} hooks.${event} must be an array.`);
37
+ }
38
+ value.hooks[event] = groups.flatMap((group) => {
39
+ if (!Array.isArray(group?.hooks)) return [group];
40
+ const hooks = group.hooks.filter((hook) => !OWNED_COMMANDS.has(hook?.command));
41
+ return hooks.length ? [{ ...group, hooks }] : [];
42
+ });
43
+ }
44
+ (value.hooks.SessionStart ||= []).push({
45
+ matcher: '^(startup|resume|clear|compact)$',
46
+ hooks: SESSION_COMMANDS.map((command) => ({ type: 'command', command, timeout: 30 })),
47
+ });
48
+ (value.hooks.UserPromptSubmit ||= []).push({
49
+ hooks: [{ type: 'command', command: TURN_COMMAND, timeout: 30 }],
50
+ });
51
+ return { root, location, source, rendered: `${JSON.stringify(value, null, 2)}\n` };
52
+ }
53
+
54
+ // Claude discovers .claude/skills; the canonical files remain in .agents/skills.
55
+ // Only exact generated links are owned here. User-owned Claude skills win.
56
+ export async function claudeSkillLinks(root, write = false) {
57
+ const [canonical, installed] = await Promise.all(['.agents/skills', '.claude/skills'].map(async (directory) => {
58
+ try { return await readdir(path.join(root, directory), { withFileTypes: true }); }
59
+ catch (error) { if (error.code === 'ENOENT') return []; throw error; }
60
+ }));
61
+ const names = new Set(canonical.filter((entry) => entry.isDirectory()).map(({ name }) => name));
62
+ const existing = new Map(installed.map((entry) => [entry.name, entry]));
63
+ const changedFiles = [];
64
+ const warnings = [];
65
+ for (const name of new Set([...names, ...existing.keys()])) {
66
+ const relative = `.claude/skills/${name}`;
67
+ const location = path.join(root, relative);
68
+ const target = `../../.agents/skills/${name}`;
69
+ const entry = existing.get(name);
70
+ const owned = entry?.isSymbolicLink() && await readlink(location) === target;
71
+ if (!names.has(name)) {
72
+ if (owned) {
73
+ changedFiles.push(relative);
74
+ if (write) await rm(location);
75
+ }
76
+ } else if (!entry) {
77
+ changedFiles.push(relative);
78
+ if (write) {
79
+ await mkdir(path.dirname(location), { recursive: true });
80
+ await symlink(target, location, 'dir');
81
+ }
82
+ } else if (!owned) {
83
+ warnings.push({ code: 'CLAUDE_SKILL_OVERRIDE', message: `${relative} is user-owned and was preserved; Claude uses it instead of the canonical .agents/skills/${name}.` });
84
+ }
85
+ }
86
+ return { changedFiles, ...(warnings.length ? { warnings } : {}) };
87
+ }
88
+
89
+ export async function installClaudeHooks({ projectRoot } = {}) {
90
+ const { root, location, source, rendered } = await renderedSettings(projectRoot);
91
+ const result = await claudeSkillLinks(root, true);
92
+ if (source !== rendered) {
93
+ await writeFileAtomic(location, rendered);
94
+ result.changedFiles.push(SETTINGS_PATH);
95
+ }
96
+ return { status: result.changedFiles.length ? 'updated' : 'unchanged', ...result };
97
+ }
98
+
99
+ export async function inspectClaudeHooks({ projectRoot } = {}) {
100
+ const { root, source, rendered } = await renderedSettings(projectRoot);
101
+ const { changedFiles, warnings } = await claudeSkillLinks(root);
102
+ return { status: source === null ? 'missing' : source !== rendered || changedFiles.length ? 'outdated' : 'current',
103
+ ...(warnings ? { warnings } : {}) };
104
+ }
@@ -6,16 +6,12 @@ import { gitContext } from './git.js';
6
6
  import { classifyProjectKind } from './project-files.js';
7
7
  import { writeFileAtomic } from './utils.js';
8
8
  import { inspectProjectFormatAtRoot } from './project-format.js';
9
+ import { genesisHookCommand as hookCommand } from './hook-command.js';
9
10
 
10
11
  const HOOKS_PATH = '.codex/hooks.json';
11
12
  const LEGACY_GENESIS_HOOKS_DESCRIPTION = 'Genesis project hooks.';
12
13
  const PROJECT_HOOK_ACTIONS = ['session', 'turn', 'begin', 'stop', 'end'];
13
14
 
14
- function hookCommand(action) {
15
- const local = `const {runCli}=await import('genesis-compiler/cli');process.exitCode=await runCli(['hook','${action}'])`;
16
- return `if command -v genesis >/dev/null 2>&1; then genesis hook ${action}; else node --input-type=module --eval "${local}"; fi`;
17
- }
18
-
19
15
  const SESSION_HOOK = {
20
16
  event: 'SessionStart',
21
17
  group: {
@@ -26,7 +22,7 @@ const SESSION_HOOK = {
26
22
  commandWindows: 'genesis hook session',
27
23
  timeout: 30,
28
24
  statusMessage: 'Loading Genesis guidance',
29
- additionalContextLimit: 4000,
25
+ additionalContextLimit: 0,
30
26
  }],
31
27
  },
32
28
  };
@@ -88,7 +84,7 @@ async function installedHooksSource(location) {
88
84
  }
89
85
  }
90
86
 
91
- export async function installCodexHooks({ projectRoot } = {}) {
87
+ async function renderedHooks(projectRoot) {
92
88
  const root = (await gitContext(projectRoot)).repositoryRoot;
93
89
  const location = path.join(root, HOOKS_PATH);
94
90
  const { source, value } = await installedHooksSource(location);
@@ -100,6 +96,16 @@ export async function installCodexHooks({ projectRoot } = {}) {
100
96
  const turnGroups = value.hooks[TURN_HOOK.event] ||= [];
101
97
  turnGroups.push(TURN_HOOK.group);
102
98
  const rendered = `${JSON.stringify(value, null, 2)}\n`;
99
+ return { source, rendered, location };
100
+ }
101
+
102
+ export async function inspectCodexHooks({ projectRoot } = {}) {
103
+ const { source, rendered } = await renderedHooks(projectRoot);
104
+ return { status: source === rendered ? 'current' : source === null ? 'missing' : 'outdated' };
105
+ }
106
+
107
+ export async function installCodexHooks({ projectRoot } = {}) {
108
+ const { source, rendered, location } = await renderedHooks(projectRoot);
103
109
  if (source === rendered) return { status: 'unchanged', changedFiles: [] };
104
110
  await writeFileAtomic(location, rendered);
105
111
  return { status: 'updated', changedFiles: [HOOKS_PATH] };
@@ -0,0 +1,4 @@
1
+ export function genesisHookCommand(action, { claudePart } = {}) {
2
+ const local = `const {runCli}=await import('genesis-compiler/cli');process.exitCode=await runCli(['hook','${action}'${claudePart ? `,'--claude-part','${claudePart}'` : ''}])`;
3
+ return `if command -v genesis >/dev/null 2>&1; then genesis hook ${action}${claudePart ? ` --claude-part ${claudePart}` : ''}; else node --input-type=module --eval "${local}"; fi`;
4
+ }
package/src/index/init.js CHANGED
@@ -4,11 +4,10 @@ import path from 'node:path';
4
4
 
5
5
  import { syncProjectSkills } from './agent-skills.js';
6
6
  import { BLUEPRINT_SKELETON_SOURCE } from './blueprint.js';
7
- import { installCodexHooks } from './codex-hooks.js';
7
+ import { syncAgentIntegrations } from './agent-integrations.js';
8
8
  import { COLLABORATION_SKELETON_SOURCE } from './collaboration.js';
9
9
  import { ENGINEERING_SKELETON_SOURCE } from './engineering.js';
10
10
  import { gitContext } from './git.js';
11
- import { installOpenCodePlugin } from './opencode-plugin.js';
12
11
  import {
13
12
  BLUEPRINT_PATH,
14
13
  COLLABORATION_PATH,
@@ -50,17 +49,13 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
50
49
  await mkdir(path.join(root, PROGRAM_ROOT), { recursive: true });
51
50
  const stack = await readStack(root, { stackPackages });
52
51
  const skills = await syncProjectSkills({ projectRoot: root, stack });
53
- const [hooks, openCodePlugin] = await Promise.all([
54
- installCodexHooks({ projectRoot: root }),
55
- installOpenCodePlugin({ projectRoot: root }),
56
- ]);
52
+ const integrations = await syncAgentIntegrations({ projectRoot: root });
57
53
  const version = projectFormat.status === 'uninitialized'
58
54
  ? await createIfMissing(root, PROJECT_VERSION_PATH, CURRENT_PROJECT_FORMAT_SOURCE)
59
55
  : null;
60
56
  const changedFiles = [
61
57
  ...created,
62
- ...hooks.changedFiles,
63
- ...openCodePlugin.changedFiles,
58
+ ...integrations.changedFiles,
64
59
  ...skills.changedFiles,
65
60
  version,
66
61
  ]
@@ -69,12 +64,14 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
69
64
  return {
70
65
  status: changedFiles.length > 0 ? 'updated' : 'unchanged',
71
66
  summary: changedFiles.length > 0
72
- ? 'Initialized Genesis, Agent Skills, and project guidance for Codex and OpenCode.'
73
- : 'Genesis, its Agent Skills, and project guidance for Codex and OpenCode are already initialized.',
67
+ ? 'Initialized Genesis, Agent Skills, and project guidance for Codex, Claude Code and OpenCode.'
68
+ : 'Genesis, its Agent Skills, and project guidance for Codex, Claude Code and OpenCode are already initialized.',
74
69
  changedFiles,
75
70
  diagnostics: skills.diagnostics,
71
+ ...(integrations.warnings ? { warnings: integrations.warnings } : {}),
76
72
  guidance: [
77
73
  'Open Codex and use /hooks to review and trust the project guidance hook.',
74
+ 'Claude Code loads the project guidance hooks from .claude/settings.json.',
78
75
  'OpenCode loads the project guidance plugin automatically.',
79
76
  'Genesis workflow skills are available in .agents/skills/.',
80
77
  'Describe product intent in genesis/blueprint.md.',
@@ -23,9 +23,11 @@ export const GENESIS_BOOTSTRAP_PATHS = Object.freeze([
23
23
  '.genesis/machine-city.json',
24
24
  '.genesis/program-city.json',
25
25
  '.codex/hooks.json',
26
+ '.claude/settings.json',
26
27
  '.opencode/plugins/genesis-project-guidance.js',
27
28
  '.agents/skills/.genesis-managed.json',
28
29
  ...['genesis-project', 'genesis-program', 'genesis-deslop'].flatMap((name) => [
30
+ `.claude/skills/${name}`,
29
31
  `.agents/skills/${name}/SKILL.md`,
30
32
  `.agents/skills/${name}/agents/openai.yaml`,
31
33
  ]),
@@ -33,10 +35,12 @@ export const GENESIS_BOOTSTRAP_PATHS = Object.freeze([
33
35
  const bootstrapPaths = new Set(GENESIS_BOOTSTRAP_PATHS);
34
36
 
35
37
  export async function inspectProjectContent(projectRoot) {
38
+ // Exclude dependency paths in Git before collecting and sorting its output.
39
+ const sourcePaths = ['--', '.', ':(exclude,glob)**/node_modules/**'];
36
40
  const [visible, deleted, ignored] = await Promise.all([
37
- runGit(projectRoot, ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '.']),
38
- runGit(projectRoot, ['ls-files', '-z', '--deleted', '--', '.']),
39
- runGit(projectRoot, ['ls-files', '-z', '--others', '--ignored', '--exclude-standard', '--', '.']),
41
+ runGit(projectRoot, ['ls-files', '-z', '--cached', '--others', '--exclude-standard', ...sourcePaths]),
42
+ runGit(projectRoot, ['ls-files', '-z', '--deleted', ...sourcePaths]),
43
+ runGit(projectRoot, ['ls-files', '-z', '--others', '--ignored', '--exclude-standard', ...sourcePaths]),
40
44
  ]);
41
45
  const absent = new Set(deleted.stdout.toString('utf8').split('\0').filter(Boolean));
42
46
  const paths = [...new Set([visible, ignored].flatMap(({ stdout }) => stdout.toString('utf8').split('\0').filter(Boolean)))]
@@ -1,5 +1,5 @@
1
1
  import { readCollaboration } from './collaboration.js';
2
- import { inspectProjectSkills } from './agent-skills.js';
2
+ import { inspectProjectSkills, renderAgentSkillCatalog } from './agent-skills.js';
3
3
  import { GENESIS_CONTRACTS } from './contracts.js';
4
4
  import { readEngineering, readEngineeringBaseline } from './engineering.js';
5
5
  import { asDiagnostic, GenesisError } from './errors.js';
@@ -166,6 +166,7 @@ export async function projectSessionContext({
166
166
  'COLLABORATION APPROACH',
167
167
  '',
168
168
  collaboration.guidance,
169
+ ...(skills.skills?.length ? ['', 'PROJECT AGENT SKILLS', '', renderAgentSkillCatalog(skills.skills)] : []),
169
170
  ...(hostContext ? ['', 'HOST CONTEXT', '', hostContext] : []),
170
171
  ].join('\n');
171
172
  return { ...composedContext(GENESIS_CONTRACTS.sessionContext, output), warnings: skills.diagnostics };
package/src/index.js CHANGED
@@ -31,6 +31,8 @@ import { projectSessionContext, projectTurnContext } from './index/session-conte
31
31
  import { withTrustedGitRepository } from './index/process.js';
32
32
 
33
33
  export { inspectProject };
34
+ export { inspectClaudeHooks, installClaudeHooks as syncClaudeHooks } from './index/claude-hooks.js';
35
+ export { inspectAgentIntegrations, syncAgentIntegrations } from './index/agent-integrations.js';
34
36
  export { listParsers, installParsers, verifyParsers, parserEnvironment } from './index/code-indexers/parsers.js';
35
37
  export { listTemplates } from './index/template-catalog.js';
36
38
  export { applyTemplate } from './index/template-project.js';