genesis-compiler 1.2.19 → 1.2.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
package/prompts/adopt.txt CHANGED
@@ -9,17 +9,18 @@ Genesis again or require them to understand Genesis terminology.
9
9
  Application source, dependencies, migrations, runtime behavior, and deployment
10
10
  files are read-only in this turn. You may edit `genesis/blueprint.md`,
11
11
  `genesis/stack.md`, explanatory Markdown below `genesis/program/`, and
12
- Genesis-managed skills/indexes produced by `genesis stack add` or Genesis
13
- inspection commands. Do not install dependencies, prepare a database, run
14
- migrations or seeds, start a server, deploy, or add compatibility code.
12
+ Genesis-managed skills/indexes produced by the Genesis `stack add` operation or
13
+ Genesis inspection commands. Do not install dependencies, prepare a database,
14
+ run migrations or seeds, start a server, deploy, or add compatibility code.
15
15
 
16
16
  If Stack has no components, inspect direct evidence such as manifests,
17
17
  lockfiles, build files, entrypoints, package scripts, tests, configuration,
18
18
  database and migration code, and existing project-tool metadata. Record only
19
- technologies the implementation already uses with `genesis stack add
20
- <piece...>`; this is factual import, not a new-technology choice. Ask a concise
21
- product-language question only when evidence is genuinely ambiguous. Then run
22
- `genesis prompt --task adopt "Continue the approved existing-project import."`
19
+ technologies the implementation already uses with the Genesis `stack add
20
+ <piece...>` operation; this is factual import, not a new-technology choice. Ask
21
+ a concise product-language question only when evidence is genuinely ambiguous.
22
+ Then run the Genesis
23
+ `prompt --task adopt "Continue the approved existing-project import."` operation
23
24
  and follow the regenerated prompt so every selected Stack piece contributes its
24
25
  Adoption requirements.
25
26
 
@@ -51,9 +52,10 @@ Create the non-technical Blueprint and useful subsystem-oriented Program from
51
52
  current source and tests. Do not cite archive internals or Git object storage as
52
53
  implementation source; Program sources must be current Git-visible files.
53
54
 
54
- Run `genesis check` only for structural validation. Missing environment inputs,
55
- Verification commands, or evidence required by a consumer-owned contract are
56
- honest import findings, not permission to invent or execute them. Finish with a
57
- clear inventory of what was imported, what remains unconfigured or blocked, and
58
- what would require a separately approved implementation port. Never claim the
59
- project is ready merely because Stack components were selected.
55
+ Run the Genesis `check` operation only for structural validation. Missing
56
+ environment inputs, Verification commands, or evidence required by a
57
+ consumer-owned contract are honest import findings, not permission to invent or
58
+ execute them. Finish with a clear inventory of what was imported, what remains
59
+ unconfigured or blocked, and what would require a separately approved
60
+ implementation port. Never claim the project is ready merely because Stack
61
+ components were selected.
@@ -1,4 +1,6 @@
1
- Read and follow `.agents/skills/genesis-deslop/SKILL.md` completely.
1
+ For every repository listed in the task, read and follow that repository's
2
+ `.agents/skills/genesis-deslop/SKILL.md` completely. Review and clean every
3
+ listed repository before responding.
2
4
 
3
5
  This is a dedicated behavior-preserving cleanup task, separate from
4
6
  implementation. Review the requested scope, or otherwise the ordinary Git diff
@@ -6,9 +8,10 @@ and immediately related code. Apply every selected Stack cleanup instruction
6
8
  supplied below and load applicable official technology skills for additional
7
9
  language and framework context.
8
10
 
9
- Use `.genesis/machine-city.json` and `genesis index <name-or-path>` to find
10
- existing public and internal functions. Remove or consolidate genuine repeated
11
- helpers, but confirm behavior and call sites in source before changing them.
11
+ Use `.genesis/machine-city.json` and the Genesis `index <name-or-path>` operation
12
+ to find existing public and internal functions. Remove or consolidate genuine
13
+ repeated helpers, but confirm behavior and call sites in source before changing
14
+ them.
12
15
 
13
16
  The changed paths and nearby code are a location boundary, not permission to
14
17
  implement other behavior found there. Deslop may reorganize behavior already
@@ -1,5 +1,6 @@
1
- Read and follow `.agents/skills/genesis-program/SKILL.md` completely, operating
2
- in focused post-change reconciliation mode.
1
+ For every repository listed in the task, read and follow that repository's
2
+ `.agents/skills/genesis-program/SKILL.md` completely, operating in focused
3
+ post-change reconciliation mode.
3
4
 
4
5
  Use the preceding implementation turn, listed changed paths, actual Git diff or
5
6
  history, and relevant tests. Update Blueprint only for intentional observable
package/prompts/start.txt CHANGED
@@ -7,9 +7,10 @@ step are clear.
7
7
  When `projectKind` is `new`, first ask what the app is about. Ask up to three
8
8
  concise, high-impact questions at a time. Use `availableStackPieces` to present
9
9
  only the relevant choices, explain them in product language, and let the user
10
- decide. Never silently select a technology. After an explicit choice, use
11
- `genesis stack add <piece...>`; do not edit `genesis/stack.md` by hand. Establish
12
- the non-technical Blueprint from the user's answers before implementation.
10
+ decide. Never silently select a technology. After an explicit choice, use the
11
+ Genesis `stack add <piece...>` operation; do not edit `genesis/stack.md` by
12
+ hand. Establish the non-technical Blueprint from the user's answers before
13
+ implementation.
13
14
  If the product direction becomes clear while Stack is still empty, do not end
14
15
  the turn after only writing or summarizing the Blueprint: present the relevant
15
16
  Stack choices in that same turn and ask the user to select one explicitly.
@@ -18,13 +19,15 @@ work, so do not force a separate profile question. When the user asks for a
18
19
  different engineering approach, or the product clearly has long-lived
19
20
  compatibility, operational, security, reliability, or compliance constraints,
20
21
  use `availableEngineeringProfiles` to explain only the relevant choices in
21
- product language. After an explicit choice, run `genesis engineering set
22
- <profile>`; do not invent or duplicate a built-in profile. Record additional
23
- explicit obligations in `genesis/engineering.md` under Project requirements.
22
+ product language. After an explicit choice, run the Genesis `engineering set
23
+ <profile>` operation; do not invent or duplicate a built-in profile. Record
24
+ additional explicit obligations in `genesis/engineering.md` under Project
25
+ requirements.
24
26
  This opening turn owns product direction and Stack selection, not final
25
- verification. Do not run `genesis verify` before implementation exists. If
26
- structural validation is useful, run `genesis check`; consumer-owned operations
27
- remain governed by their own tools and must not be inferred here.
27
+ verification. Do not run the Genesis `verify` operation before implementation
28
+ exists. If structural validation is useful, run the Genesis `check` operation;
29
+ consumer-owned operations remain governed by their own tools and must not be
30
+ inferred here.
28
31
 
29
32
  When `projectKind` is `existing`, ask what the user wants to understand or
30
33
  change. Use the current Blueprint, Stack, Program, and code index when present.
@@ -45,9 +48,9 @@ understand or change and strongly recommend preparing the existing project for
45
48
  guided editing before substantial work. Explain in product language that this
46
49
  preserves the implementation while importing its current setup, run, database,
47
50
  preview, verification, and product context. Do not require the user to know
48
- Genesis terminology or type a command. If they approve, run `genesis adopt`
49
- yourself and follow its adoption prompt. If they decline, continue normally and
50
- do not repeat the recommendation.
51
+ Genesis terminology or type a command. If they approve, run the Genesis `adopt`
52
+ operation yourself and follow its adoption prompt. If they decline, continue
53
+ normally and do not repeat the recommendation.
51
54
 
52
55
  If the request already contains an unambiguous product description and an
53
56
  explicit Stack choice, you may apply that choice and establish the Blueprint.
package/prompts/work.txt CHANGED
@@ -4,8 +4,9 @@ implement the request in the ordinary local Git tree.
4
4
  Load the applicable official or project technology skills listed below. Use
5
5
  their normal generators, commands, packages, and established seams. Do not
6
6
  search package internals or unrelated manuals merely to reconfirm an exact lane.
7
- Before adding a helper or public operation, query `genesis index <name-or-path>`
8
- and reuse an existing function when it already owns the behavior.
7
+ Before adding a helper or public operation, query the Genesis
8
+ `index <name-or-path>` operation and reuse an existing function when it already
9
+ owns the behavior.
9
10
 
10
11
  This is the implementation turn. Do not edit the Blueprint, Program, or
11
12
  `.genesis/`; the separate reconciliation turn owns explanatory changes. Keep
@@ -20,8 +21,9 @@ Run focused checks when useful. Summarize files changed, checks actually run,
20
21
  and anything still requiring attention.
21
22
 
22
23
  If no Stack components are selected, continue the project-opening
23
- conversation instead of guessing a technology. Use `genesis stack list` for
24
- the current catalog, present relevant choices, wait for an explicit decision,
25
- and add the selection with `genesis stack add <piece...>` before relying on
26
- technology-specific operations. Do not end after only updating
24
+ conversation instead of guessing a technology. Use the Genesis `stack list`
25
+ operation for the current catalog, present relevant choices, wait for an
26
+ explicit decision, and add the selection with the Genesis
27
+ `stack add <piece...>` operation before relying on technology-specific
28
+ operations. Do not end after only updating
27
29
  or summarizing the Blueprint while the Stack remains empty.
@@ -9,14 +9,22 @@ Review and simplify the current local codebase without changing product
9
9
  behavior. Start with the requested scope, or otherwise the ordinary Git diff
10
10
  and the code immediately around it.
11
11
 
12
+ When this repository is Genesis itself or has `genesis-compiler` installed
13
+ locally, invoke every Genesis CLI operation through the project-pinned package:
14
+ `npm exec --no -- genesis <arguments>`. This runs without fetching another
15
+ package. Otherwise use `genesis <arguments>` only when that executable is
16
+ already available on `PATH`. Never install or update Genesis merely to satisfy
17
+ a workflow instruction.
18
+
12
19
  Apply the engineering approach supplied with the task. If this skill is invoked
13
20
  directly, read `genesis/engineering.md` as well as the relevant project context.
14
21
  Cleanup must reduce or preserve complexity, never introduce unapproved
15
22
  machinery that the selected profile or a concrete requirement does not need.
16
23
 
17
- Consult `.genesis/machine-city.json` or run `genesis index <name-or-path>` to
18
- find existing public and internal functions before introducing or consolidating
19
- an abstraction. Confirm every apparent duplicate in source and its call sites.
24
+ Consult `.genesis/machine-city.json` or run the Genesis `index <name-or-path>`
25
+ operation to find existing public and internal functions before introducing or
26
+ consolidating an abstraction. Confirm every apparent duplicate in source and
27
+ its call sites.
20
28
 
21
29
  Within the preceding implementation's intended behavior and scope, leave no
22
30
  repeated helpers, no code that is unclear or hard to reason about, and no code
@@ -45,5 +53,5 @@ external resources. Do not weaken tests, public behavior, or data guarantees.
45
53
  Do not create a second architecture or perform unrelated rewrites.
46
54
 
47
55
  Run focused checks when useful. Final declared checks remain available through
48
- `genesis verify`. Summarize what became simpler, files changed, and checks
49
- actually run.
56
+ the Genesis `verify` operation. Summarize what became simpler, files changed,
57
+ and checks actually run.
@@ -8,6 +8,15 @@ description: Work safely in a Genesis-enriched codebase using its Blueprint, Sta
8
8
  Genesis is an explanatory and verification companion. It does not replace the
9
9
  codebase, tests, Git review, or the coding agent.
10
10
 
11
+ ## Run Genesis commands
12
+
13
+ When this repository is Genesis itself or has `genesis-compiler` installed
14
+ locally, invoke every Genesis CLI operation through the project-pinned package:
15
+ `npm exec --no -- genesis <arguments>`. This runs without fetching another
16
+ package. Otherwise use `genesis <arguments>` only when that executable is
17
+ already available on `PATH`. Never install or update Genesis merely to satisfy
18
+ a workflow instruction.
19
+
11
20
  ## Establish context
12
21
 
13
22
  1. Read `genesis/blueprint.md` for non-technical product intent.
@@ -16,10 +25,11 @@ codebase, tests, Git review, or the coding agent.
16
25
  3. Read `genesis/stack.md` for the selected technical composition, resources,
17
26
  and verification commands.
18
27
  4. Locate the source involved in the request.
19
- 5. Run `genesis index <name-or-path>` before adding a helper or public operation.
20
- Reuse an existing function when it already owns the behavior.
21
- 6. Run `genesis context <path...>` for the relevant Program explanations and
22
- selected Stack context.
28
+ 5. Run the Genesis `index <name-or-path>` operation before adding a helper or
29
+ public operation. Reuse an existing function when it already owns the
30
+ behavior.
31
+ 6. Run the Genesis `context <path...>` operation for the relevant Program
32
+ explanations and selected Stack context.
23
33
  7. Load applicable technology skills from `.agents/skills/` or from the
24
34
  agent's own installed skill catalog.
25
35
 
@@ -38,7 +48,8 @@ tests, and runtime behavior.
38
48
 
39
49
  `.genesis/machine-city.json` is a derived detailed file/function map.
40
50
  `.genesis/program-city.json` is a derived simpler subsystem/operation map.
41
- Neither is authority or proof; both may be regenerated with `genesis index`.
51
+ Neither is authority or proof; both may be regenerated with the Genesis
52
+ `index` operation.
42
53
 
43
54
  ## Implement ordinary local work
44
55
 
@@ -70,9 +81,9 @@ Neither is authority or proof; both may be regenerated with `genesis index`.
70
81
 
71
82
  ## Verify and report
72
83
 
73
- After the selected technology's workspace substrate exists, use `genesis
74
- verify` for the Stack's declared final checks. An unconfigured result means the
75
- declared workspace or checks do not exist yet; it is not a failing check. Report
76
- files changed, checks actually run, and anything still requiring attention.
77
- Never claim that an unrun check passed or that passing checks prove the whole
78
- product.
84
+ After the selected technology's workspace substrate exists, use the Genesis
85
+ `verify` operation for the Stack's declared final checks. An unconfigured result
86
+ means the declared workspace or checks do not exist yet; it is not a failing
87
+ check. Report files changed, checks actually run, and anything still requiring
88
+ attention. Never claim that an unrun check passed or that passing checks prove
89
+ the whole product.
package/src/cli.js CHANGED
@@ -257,6 +257,7 @@ function writeResult(command, result) {
257
257
  }
258
258
  if (command === 'hook') {
259
259
  if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
260
+ else if (result.kind === 'authorize') line(process.stdout, `authorize: ${result.status}`);
260
261
  else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
261
262
  return;
262
263
  }
@@ -1,4 +1,5 @@
1
1
  import { readFile, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
2
3
  import path from 'node:path';
3
4
 
4
5
  import { readInstalledAsset } from './assets.js';
@@ -13,6 +14,7 @@ import { normalizeRelative, pathState, sha256, stableJson, writeFileAtomic } fro
13
14
 
14
15
  const HOOKS_PATH = '.codex/hooks.json';
15
16
  const HOOK_STATE_SCHEMA_VERSION = 2;
17
+ const HOOK_LOCATOR_SCHEMA_VERSION = 1;
16
18
  function hookCommand(action) {
17
19
  const local = `const {runCli}=await import('genesis-compiler/cli');process.exitCode=await runCli(['hook','${action}'])`;
18
20
  return `if command -v genesis >/dev/null 2>&1; then genesis hook ${action}; else node --input-type=module --eval "${local}"; fi`;
@@ -126,7 +128,7 @@ async function optionalEngineering(projectRoot) {
126
128
  '',
127
129
  await readEngineeringBaseline(),
128
130
  '',
129
- 'The project engineering profile is invalid. Do not infer a replacement; run `genesis check` and ask the user before implementation if the selected approach matters.',
131
+ 'The project engineering profile is invalid. Do not infer a replacement; run the Genesis `check` operation and ask the user before implementation if the selected approach matters.',
130
132
  ].join('\n'),
131
133
  profile: null,
132
134
  status: 'invalid',
@@ -143,7 +145,7 @@ export async function codexSessionContext({ projectRoot } = {}) {
143
145
  const selected = stack?.components.map(({ id }) => id) || [];
144
146
  const stackStatus = stack
145
147
  ? (selected.length > 0 ? selected.join(', ') : 'none')
146
- : 'unavailable; run `genesis check`';
148
+ : 'unavailable; run the Genesis `check` operation';
147
149
  return {
148
150
  status: 'ready',
149
151
  output: [
@@ -153,14 +155,16 @@ export async function codexSessionContext({ projectRoot } = {}) {
153
155
  '- `genesis/stack.md` selects optional technology guidance and verification.',
154
156
  '- `genesis/program/<subsystem>/` explains public operations and useful internal seams.',
155
157
  '- Project Agent Skills live in `.agents/skills/`; load applicable skills progressively.',
156
- '- After locating relevant source files, run `genesis context <path...>` to load their Program and selected Stack material.',
157
- '- Before creating a helper or public operation, run `genesis index <name-or-path>` and reuse an existing function when it already owns the behavior.',
158
+ '- When this repository is Genesis itself or has local `genesis-compiler`, run every Genesis CLI operation as `npm exec --no -- genesis <arguments>` so the project-pinned version is used without fetching another package. Otherwise use `genesis <arguments>` only when that executable is already available on `PATH`. Never install or update Genesis merely to run a workflow instruction.',
159
+ '- After locating relevant source files, run the Genesis `context <path...>` operation to load their Program and selected Stack material.',
160
+ '- Before creating a helper or public operation, run the Genesis `index <name-or-path>` operation and reuse an existing function when it already owns the behavior.',
158
161
  '- `.genesis/machine-city.json` is the detailed code/function map; `.genesis/program-city.json` is the simpler subsystem/operation map.',
159
162
  '- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
160
- '- Before changing project files for a user-requested implementation, run `genesis hook authorize`. Never authorize answer, explanation, review, or diagnosis-only turns.',
163
+ '- Before the first project-file edit in a user-requested implementation turn, silently run the Genesis `hook authorize` operation once in every Git repository you will edit. This is internal Genesis bookkeeping, not a request for user permission: do not ask the user, announce successful authorization, or include it in progress or final summaries. Mention it only if the command fails.',
164
+ '- Authorization is turn-scoped. Run it again for a later code-changing turn after a user follow-up or interruption, but not again within the same active turn after it succeeds. The command must report `authorize: authorized` or `authorize: unchanged`; otherwise stop before editing. Never authorize answer, explanation, review, diagnosis-only, reconciliation, or Deslop turns.',
161
165
  '- Apply the engineering approach below to implementation and cleanup; the Stop hook owns optional Stack Post-change work, Blueprint/Program reconciliation, and Deslop turns.',
162
166
  '- After an authorized code-changing turn, Genesis may request separate post-change, explanation, and cleanup turns.',
163
- `Engineering profile: ${engineering.profile?.id || 'invalid; run \`genesis check\`'}.`,
167
+ `Engineering profile: ${engineering.profile?.id || 'invalid; run the Genesis \`check\` operation'}.`,
164
168
  `Selected Stack components: ${stackStatus}.`,
165
169
  '',
166
170
  'ENGINEERING APPROACH',
@@ -187,7 +191,7 @@ export async function codexAdoptionRecommendation({ projectRoot = process.cwd()
187
191
  'This existing project has no Genesis explanatory layer.',
188
192
  'In your first response, strongly recommend preparing this existing project for guided editing before substantial work.',
189
193
  'Explain in product language that preparation preserves the implementation while importing its current operational and product context. Do not require the user to know Genesis terminology or type a command.',
190
- 'If the user approves, run `genesis adopt` yourself and follow the returned adoption prompt.',
194
+ 'If the user approves, run the Genesis `adopt` operation yourself and follow the returned adoption prompt.',
191
195
  'Do not run it without the user\'s approval. If they agree, run it and follow the returned prompt in this conversation.',
192
196
  'If the user declines, continue normally and do not repeat the recommendation in this session.',
193
197
  ].join('\n'),
@@ -236,6 +240,12 @@ async function hookStatePath(projectRoot, input) {
236
240
  return path.resolve(projectRoot, gitPath);
237
241
  }
238
242
 
243
+ function hookLocatorPath(input) {
244
+ const key = sha256(hookId(input, 'session_id')).slice('sha256:'.length);
245
+ const user = typeof process.getuid === 'function' ? process.getuid() : 'user';
246
+ return path.join(tmpdir(), `genesis-codex-hook-${user}-${key}.json`);
247
+ }
248
+
239
249
  function writeTurnState(statePath, state) {
240
250
  return writeFileAtomic(statePath, stableJson(state), { mode: 0o600 });
241
251
  }
@@ -247,14 +257,25 @@ async function refreshProjectIndex(projectRoot) {
247
257
  export async function recordCodexTurn({ input, projectRoot } = {}) {
248
258
  const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
249
259
  const statePath = await hookStatePath(root, input);
260
+ const turnId = hookId(input, 'turn_id');
261
+ const existing = await readTurnState(statePath);
262
+ if (existing?.schemaVersion === HOOK_STATE_SCHEMA_VERSION && existing.turnId === turnId) {
263
+ await writeHookLocator(input, root);
264
+ return { status: 'preserved' };
265
+ }
266
+ const repositories = registeredRepositories(existing || {}, root);
250
267
  const state = {
251
268
  schemaVersion: HOOK_STATE_SCHEMA_VERSION,
252
- turnId: hookId(input, 'turn_id'),
269
+ turnId,
253
270
  phase: 'implementation',
254
271
  implementationAuthorized: false,
255
- snapshot: await repositorySnapshot(root),
272
+ repositories,
273
+ snapshot: repositories.length > 0 && existing?.snapshot
274
+ ? existing.snapshot
275
+ : await repositorySnapshot(root),
256
276
  };
257
277
  await writeTurnState(statePath, state);
278
+ await writeHookLocator(input, root);
258
279
  return { status: 'recorded' };
259
280
  }
260
281
 
@@ -265,6 +286,41 @@ async function readTurnState(statePath) {
265
286
  }
266
287
  }
267
288
 
289
+ async function readHookLocator(input) {
290
+ try { return JSON.parse(await readFile(hookLocatorPath(input), 'utf8')); } catch (error) {
291
+ if (['ENOENT', 'ENOTDIR'].includes(error?.code)) return null;
292
+ throw error;
293
+ }
294
+ }
295
+
296
+ function writeHookLocator(input, coordinatorRoot) {
297
+ return writeFileAtomic(hookLocatorPath(input), stableJson({
298
+ schemaVersion: HOOK_LOCATOR_SCHEMA_VERSION,
299
+ coordinatorRoot,
300
+ }), { mode: 0o600 });
301
+ }
302
+
303
+ async function removeTurnState(statePath, input) {
304
+ await Promise.all([
305
+ rm(statePath, { force: true }),
306
+ rm(hookLocatorPath(input), { force: true }),
307
+ ]);
308
+ }
309
+
310
+ function registeredRepositories(state, coordinatorRoot) {
311
+ if (Array.isArray(state.repositories) && state.repositories.length > 0) {
312
+ return state.repositories;
313
+ }
314
+ if (state.implementationAuthorized === true && state.snapshot) {
315
+ return [{
316
+ projectRoot: coordinatorRoot,
317
+ snapshot: state.snapshot,
318
+ changedPaths: normalizedPaths(state.changedPaths || []).sort(),
319
+ }];
320
+ }
321
+ return [];
322
+ }
323
+
268
324
  export async function authorizeCodexTurn({
269
325
  projectRoot = process.cwd(),
270
326
  sessionId = process.env.CODEX_SESSION_ID,
@@ -275,15 +331,52 @@ export async function authorizeCodexTurn({
275
331
  'genesis hook authorize requires the current Codex session.',
276
332
  );
277
333
  }
334
+ const input = { session_id: sessionId };
278
335
  const root = (await gitContext(projectRoot)).repositoryRoot;
279
- const statePath = await hookStatePath(root, { session_id: sessionId });
336
+ const locator = await readHookLocator(input);
337
+ if (locator && (
338
+ locator.schemaVersion !== HOOK_LOCATOR_SCHEMA_VERSION
339
+ || typeof locator.coordinatorRoot !== 'string'
340
+ || !locator.coordinatorRoot
341
+ )) {
342
+ throw new GenesisError(
343
+ 'CODEX_HOOK_COORDINATOR_INVALID',
344
+ 'The current Codex turn has invalid Genesis coordination state.',
345
+ );
346
+ }
347
+ const coordinatorRoot = locator
348
+ ? (await gitContext(locator.coordinatorRoot)).repositoryRoot
349
+ : root;
350
+ const statePath = await hookStatePath(coordinatorRoot, input);
280
351
  const state = await readTurnState(statePath);
281
352
  if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION || state.phase !== 'implementation') {
282
- return { status: 'not-applicable' };
353
+ throw new GenesisError(
354
+ 'CODEX_HOOK_TURN_UNAVAILABLE',
355
+ 'No active Genesis implementation turn is available. Start from the session repository and authorize before editing.',
356
+ );
283
357
  }
284
- if (state.implementationAuthorized === true) return { status: 'unchanged' };
285
- await writeTurnState(statePath, { ...state, implementationAuthorized: true });
286
- return { status: 'authorized' };
358
+ const repositories = registeredRepositories(state, coordinatorRoot);
359
+ const registered = repositories.some((repository) => repository.projectRoot === root);
360
+ if (!registered) {
361
+ repositories.push({
362
+ projectRoot: root,
363
+ snapshot: root === coordinatorRoot && state.snapshot
364
+ ? state.snapshot
365
+ : await repositorySnapshot(root),
366
+ changedPaths: [],
367
+ });
368
+ }
369
+ await writeTurnState(statePath, {
370
+ ...state,
371
+ implementationAuthorized: true,
372
+ repositories,
373
+ });
374
+ await writeHookLocator(input, coordinatorRoot);
375
+ return {
376
+ status: registered && state.implementationAuthorized === true
377
+ ? 'unchanged'
378
+ : 'authorized',
379
+ };
287
380
  }
288
381
 
289
382
  async function committedPaths(projectRoot, before, after) {
@@ -329,72 +422,117 @@ function changedPathLines(changedPaths) {
329
422
  ];
330
423
  }
331
424
 
332
- async function reconciliationContinuation(projectRoot, changedPaths) {
333
- const [instructions, stack, engineering] = await Promise.all([
425
+ function changedRepositoryLines(repositories) {
426
+ return repositories.flatMap(({ projectRoot, changedPaths }) => [
427
+ `## Repository: \`${projectRoot}\``,
428
+ '',
429
+ ...changedPathLines(changedPaths),
430
+ '',
431
+ ]);
432
+ }
433
+
434
+ async function changedRepositories(repositories) {
435
+ return (await Promise.all(repositories.map(async (repository) => {
436
+ const currentPaths = await changedProjectPaths(
437
+ repository.projectRoot,
438
+ repository.snapshot,
439
+ await repositorySnapshot(repository.projectRoot),
440
+ );
441
+ const changedPaths = normalizedPaths([
442
+ ...(repository.changedPaths || []),
443
+ ...currentPaths,
444
+ ]).sort();
445
+ return changedPaths.length > 0 ? { ...repository, changedPaths } : null;
446
+ }))).filter(Boolean);
447
+ }
448
+
449
+ async function repositoryContexts(repositories) {
450
+ return Promise.all(repositories.map(async (repository) => {
451
+ const [stack, engineering] = await Promise.all([
452
+ optionalStack(repository.projectRoot),
453
+ optionalEngineering(repository.projectRoot),
454
+ ]);
455
+ return { ...repository, stack, engineering };
456
+ }));
457
+ }
458
+
459
+ function repositoryGuidanceLines({ projectRoot, stack, engineering }, {
460
+ deslop = false,
461
+ postChange = false,
462
+ } = {}) {
463
+ return [
464
+ `## Repository: \`${projectRoot}\``,
465
+ '',
466
+ 'ENGINEERING APPROACH',
467
+ '',
468
+ engineering.guidance,
469
+ ...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
470
+ ...(postChange && stack?.postChange
471
+ ? ['', 'COMPOSED STACK POST-CHANGE WORK', '', stack.postChange]
472
+ : []),
473
+ ...(deslop && stack?.deslop
474
+ ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', stack.deslop]
475
+ : []),
476
+ '',
477
+ ];
478
+ }
479
+
480
+ async function reconciliationContinuation(repositories, knownContexts) {
481
+ const [instructions, contexts] = await Promise.all([
334
482
  readInstalledAsset('reconcile'),
335
- optionalStack(projectRoot),
336
- optionalEngineering(projectRoot),
483
+ knownContexts || repositoryContexts(repositories),
337
484
  ]);
338
485
  return [
339
486
  'This is the automatic Genesis explanation turn for the preceding implementation turn.',
340
- 'Use the same conversation context and the Git-visible changes below. Do not perform Deslop yet.',
487
+ 'Use the same conversation context and the Git-visible changes below in every listed repository. Do not perform Deslop yet.',
341
488
  '',
342
- 'CHANGED PROJECT PATHS',
489
+ 'REPOSITORIES AND CHANGED PROJECT PATHS',
343
490
  '',
344
- ...changedPathLines(changedPaths),
491
+ ...changedRepositoryLines(repositories),
345
492
  '',
346
493
  instructions.trim(),
347
494
  '',
348
- 'ENGINEERING APPROACH',
495
+ 'REPOSITORY GUIDANCE',
349
496
  '',
350
- engineering.guidance,
351
- ...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
497
+ ...contexts.flatMap((context) => repositoryGuidanceLines(context)),
352
498
  ].join('\n');
353
499
  }
354
500
 
355
- function postChangeContinuation(changedPaths, stack, engineering) {
501
+ function postChangeContinuation(repositories, contexts) {
356
502
  return [
357
503
  'This is the one automatic Stack Post-change turn for the preceding implementation.',
358
- 'Use the same conversation context and the Git-visible changes below.',
504
+ 'Use the same conversation context and the Git-visible changes below in every listed repository.',
359
505
  'Perform only the composed Post-change work. Do not broaden the task, reconcile Genesis explanations, or perform Deslop yet.',
360
506
  'Do not repeat or schedule another Post-change turn. If none of the configured work applies, make no changes and respond minimally.',
361
507
  '',
362
- 'CHANGED PROJECT PATHS',
508
+ 'REPOSITORIES AND CHANGED PROJECT PATHS',
363
509
  '',
364
- ...changedPathLines(changedPaths),
365
- '',
366
- 'COMPOSED STACK POST-CHANGE WORK',
510
+ ...changedRepositoryLines(repositories),
367
511
  '',
368
- stack.postChange,
512
+ 'REPOSITORY GUIDANCE AND POST-CHANGE WORK',
369
513
  '',
370
- 'ENGINEERING APPROACH',
371
- '',
372
- engineering.guidance,
373
- ...(stack.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
514
+ ...contexts.flatMap((context) => repositoryGuidanceLines(context, { postChange: true })),
374
515
  ].join('\n');
375
516
  }
376
517
 
377
- async function deslopContinuation(projectRoot, changedPaths) {
378
- const [instructions, stack, engineering] = await Promise.all([
518
+ async function deslopContinuation(repositories) {
519
+ const [instructions, contexts] = await Promise.all([
379
520
  readInstalledAsset('deslop'),
380
- optionalStack(projectRoot),
381
- optionalEngineering(projectRoot),
521
+ repositoryContexts(repositories),
382
522
  ]);
383
523
  return [
384
524
  'This is the final automatic Genesis Deslop turn for the preceding implementation.',
385
- 'Review only that work and the paths listed below. Do not broaden the task.',
525
+ 'Review every listed repository and only that work and the paths listed below. Do not broaden the task.',
386
526
  '',
387
- 'CHANGED PROJECT PATHS',
527
+ 'REPOSITORIES AND CHANGED PROJECT PATHS',
388
528
  '',
389
- ...changedPathLines(changedPaths),
529
+ ...changedRepositoryLines(repositories),
390
530
  '',
391
531
  instructions.trim(),
392
532
  '',
393
- 'ENGINEERING APPROACH',
533
+ 'REPOSITORY GUIDANCE',
394
534
  '',
395
- engineering.guidance,
396
- ...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
397
- ...(stack?.deslop ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', stack.deslop] : []),
535
+ ...contexts.flatMap((context) => repositoryGuidanceLines(context, { deslop: true })),
398
536
  '',
399
537
  'PROGRAM CITATION MAINTENANCE',
400
538
  '',
@@ -405,6 +543,10 @@ async function deslopContinuation(projectRoot, changedPaths) {
405
543
  ].join('\n');
406
544
  }
407
545
 
546
+ async function refreshProjectIndexes(repositories) {
547
+ await Promise.all(repositories.map(({ projectRoot }) => refreshProjectIndex(projectRoot)));
548
+ }
549
+
408
550
  export async function completeCodexTurn({ input, projectRoot } = {}) {
409
551
  const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
410
552
  const statePath = await hookStatePath(root, input);
@@ -412,93 +554,98 @@ export async function completeCodexTurn({ input, projectRoot } = {}) {
412
554
  if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION) return {};
413
555
 
414
556
  if (state.implementationAuthorized !== true) {
415
- await rm(statePath, { force: true });
557
+ const repositories = await changedRepositories(registeredRepositories(state, root));
558
+ if (repositories.length === 0) {
559
+ await removeTurnState(statePath, input);
560
+ return {};
561
+ }
562
+ await writeTurnState(statePath, {
563
+ ...state,
564
+ phase: 'implementation',
565
+ repositories,
566
+ });
416
567
  return {};
417
568
  }
418
569
 
419
570
  if (state.phase === 'implementation') {
420
571
  if (input?.stop_hook_active === true || state.turnId !== hookId(input, 'turn_id')) {
421
- await rm(statePath, { force: true });
572
+ await removeTurnState(statePath, input);
422
573
  return {};
423
574
  }
424
- const changedPaths = await changedProjectPaths(root, state.snapshot, await repositorySnapshot(root));
425
- if (changedPaths.length === 0) {
426
- await rm(statePath, { force: true });
575
+ const repositories = await changedRepositories(registeredRepositories(state, root));
576
+ if (repositories.length === 0) {
577
+ await removeTurnState(statePath, input);
427
578
  return {};
428
579
  }
429
- const [stack, engineering] = await Promise.all([
430
- optionalStack(root),
431
- optionalEngineering(root),
432
- ]);
433
- const postChange = Boolean(stack?.postChange?.trim());
580
+ const contexts = await repositoryContexts(repositories);
581
+ const postChange = contexts.some(({ stack }) => Boolean(stack?.postChange?.trim()));
434
582
  await writeTurnState(statePath, {
435
583
  ...state,
436
584
  phase: postChange ? 'post-change' : 'reconcile',
437
- changedPaths,
585
+ repositories,
438
586
  });
439
587
  return {
440
588
  decision: 'block',
441
589
  reason: postChange
442
- ? postChangeContinuation(changedPaths, stack, engineering)
443
- : await reconciliationContinuation(root, changedPaths),
590
+ ? postChangeContinuation(repositories, contexts)
591
+ : await reconciliationContinuation(repositories, contexts),
444
592
  };
445
593
  }
446
594
 
447
595
  if (input?.stop_hook_active !== true) {
448
- await rm(statePath, { force: true });
596
+ await removeTurnState(statePath, input);
449
597
  return {};
450
598
  }
451
599
 
452
600
  if (state.phase === 'post-change') {
453
- const currentPaths = await changedProjectPaths(root, state.snapshot, await repositorySnapshot(root));
454
- const changedPaths = normalizedPaths([...state.changedPaths, ...currentPaths]).sort();
455
- if (changedPaths.length === 0) {
456
- await rm(statePath, { force: true });
601
+ const repositories = await changedRepositories(registeredRepositories(state, root));
602
+ if (repositories.length === 0) {
603
+ await removeTurnState(statePath, input);
457
604
  return {};
458
605
  }
459
606
  await writeTurnState(statePath, {
460
607
  ...state,
461
608
  phase: 'reconcile',
462
- changedPaths,
609
+ repositories,
463
610
  });
464
611
  return {
465
612
  decision: 'block',
466
- reason: await reconciliationContinuation(root, changedPaths),
613
+ reason: await reconciliationContinuation(repositories),
467
614
  };
468
615
  }
469
616
 
470
617
  if (state.phase === 'reconcile') {
471
- const currentPaths = await changedProjectPaths(root, state.snapshot, await repositorySnapshot(root));
472
- const changedPaths = normalizedPaths([...state.changedPaths, ...currentPaths]).sort();
473
- if (changedPaths.length === 0) {
474
- await refreshProjectIndex(root);
475
- await rm(statePath, { force: true });
618
+ const registered = registeredRepositories(state, root);
619
+ const repositories = await changedRepositories(registered);
620
+ if (repositories.length === 0) {
621
+ await refreshProjectIndexes(registered);
622
+ await removeTurnState(statePath, input);
476
623
  return {};
477
624
  }
478
- await refreshProjectIndex(root);
625
+ await refreshProjectIndexes(repositories);
479
626
  await writeTurnState(statePath, {
480
627
  ...state,
481
628
  phase: 'deslop',
482
- changedPaths,
629
+ repositories,
483
630
  });
484
631
  return {
485
632
  decision: 'block',
486
- reason: await deslopContinuation(root, changedPaths),
633
+ reason: await deslopContinuation(repositories),
487
634
  };
488
635
  }
489
636
 
490
637
  if (state.phase === 'deslop') {
491
- await refreshProjectIndex(root);
492
- await rm(statePath, { force: true });
638
+ await refreshProjectIndexes(registeredRepositories(state, root));
639
+ await removeTurnState(statePath, input);
493
640
  return {};
494
641
  }
495
642
 
496
- await rm(statePath, { force: true });
643
+ await removeTurnState(statePath, input);
497
644
  return {};
498
645
  }
499
646
 
500
647
  export async function discardCodexTurn({ input, projectRoot } = {}) {
501
648
  const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
502
- await rm(await hookStatePath(root, input), { force: true });
649
+ await removeTurnState(await hookStatePath(root, input), input);
503
650
  return { status: 'discarded' };
504
651
  }
@@ -116,7 +116,7 @@ export async function contextForProjectPaths({ paths, projectRoot, stackPackages
116
116
  : functions.slice(0, 100).map((entry) => (
117
117
  `- ${entry.visibility} \`${entry.qualifiedName}(${entry.parameters.join(', ')})\` — \`${entry.path}:${entry.line}\``
118
118
  ))),
119
- ...(functions.length > 100 ? [`- ... and ${functions.length - 100} more; run \`genesis index ${targets.join(' ')}\`.`] : []),
119
+ ...(functions.length > 100 ? [`- ... and ${functions.length - 100} more; run the Genesis \`index ${targets.join(' ')}\` operation.`] : []),
120
120
  '',
121
121
  'Search the Machine City before adding a helper or public operation; reuse an existing function when it already owns the behavior.',
122
122
  ...(skills ? ['', '## Agent Skills', '', skills] : []),
@@ -96,7 +96,7 @@ function codeIndexContext(index) {
96
96
  status: index.program.status,
97
97
  operations: index.operationCount,
98
98
  },
99
- query: 'genesis index <function-or-path...>',
99
+ query: 'Genesis index operation: <function-or-path...>',
100
100
  };
101
101
  }
102
102