genesis-compiler 1.2.24 → 1.2.25

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
@@ -108,10 +108,10 @@ empty or already-adopted project.
108
108
 
109
109
  `genesis init` creates an empty Blueprint, a focused engineering approach, an
110
110
  empty optional Stack, the current `genesis/version`, three project Agent Skills,
111
- and project-local Codex hooks. The skills are ordinary
111
+ and one project-local Codex guidance hook. The skills are ordinary
112
112
  [Agent Skills](https://agentskills.io): `genesis-project`, `genesis-program`,
113
113
  and `genesis-deslop`, each with standard `SKILL.md` and `agents/openai.yaml`
114
- metadata. Open `/hooks` once in Codex to review and trust the hooks. After
114
+ metadata. Open `/hooks` once in Codex to review and trust the guidance hook. After
115
115
  that, use Codex normally. No technology is assumed until you explicitly select
116
116
  Stack components:
117
117
 
@@ -182,7 +182,7 @@ People may edit `## Project requirements` to name obligations the presets do
182
182
  not capture, including a specific organizational, compliance, cryptographic,
183
183
  or scale constraint. `genesis engineering set` changes only the profile and
184
184
  preserves those requirements. Generated prompts, path-focused context, Codex
185
- session guidance, Post-change work, and Deslop all receive the effective
185
+ session guidance, implementation Post-change work, and Deslop all receive the effective
186
186
  approach. A project upgraded from an older Genesis release safely uses
187
187
  `focused.v1` until `genesis init` or `genesis engineering set focused.v1`
188
188
  records the file.
@@ -263,27 +263,19 @@ Python, Java, C#, C/C++, PHP, Go, Rust, Ruby, Kotlin, and Shell. A future techno
263
263
  adds its adapter through its Stack piece rather than teaching Genesis prompts
264
264
  to parse that language.
265
265
 
266
- Before changing project files for a user-requested implementation, Codex runs
267
- `genesis hook authorize`. At the end of an authorized turn that changed
268
- Git-visible project files, the Stop hook asks Codex for bounded follow-up turns.
269
- Unmarked turns finish normally even if another process changes the same
270
- checkout. When the selected Stack contributes `Post-change` work, all
271
- contributions compose deterministically into one optional code-changing turn.
272
- Genesis then reconciles the Blueprint and affected Program explanations from
273
- the complete changed-path set and actual Git diff, and refreshes both City
274
- projections. A focused, behavior-preserving Deslop pass runs before both Cities
275
- are refreshed again; its response is the concise user-facing completion. A
276
- project with no selected Stack uses only the `genesis-deslop` skill. Selected
277
- Stack components enrich Post-change work, reconciliation, and cleanup with
278
- general Guidance, and enrich cleanup further with technology-specific Deslop
279
- guidance.
280
-
281
- The hook never starts another Codex process. Its small per-session phase is
282
- limited to `implementation -> optional post-change -> reconcile -> deslop ->
283
- done`, so it cannot keep continuing recursively. The Stop after Deslop clears
284
- the sequence rather than scheduling another model turn. Empty Post-change
285
- content is skipped rather than producing an empty turn. Question, explanation,
286
- review, diagnosis-only, and unchanged turns finish normally.
266
+ The project Codex hook injects a short operating guide at startup, resume,
267
+ clear, and compaction. It records no turn state, blocks no Stop, and requests no
268
+ follow-up turn. Implementation prompts compose selected Stack `Post-change`
269
+ guidance into the original implementation turn, and the project skill keeps
270
+ intentional Blueprint and affected Program changes aligned there.
271
+
272
+ Deslop runs only when explicitly requested. With no stated scope it reviews the
273
+ latest commit; “the last N commits” reviews the net surviving first-parent
274
+ range, and a named commit or range is honored exactly. It requires a clean
275
+ worktree and leaves ordinary uncommitted cleanup for review rather than
276
+ amending history. Genesis supplies the behavior-preserving boundary and Git
277
+ scope rules. Selected Stack components add technology-specific Deslop guidance
278
+ without weakening or broadening that contract.
287
279
 
288
280
  Projects can customize a selected technology through its matching file, such
289
281
  as `genesis/stack/jskit.md`:
@@ -303,7 +295,7 @@ Additional project-specific context about how JSKIT is used here.
303
295
 
304
296
  ### Post-change
305
297
 
306
- - Additional project-specific work to perform once after implementation.
298
+ - Additional project-specific work to perform before the implementation turn completes.
307
299
 
308
300
  ### Deslop
309
301
 
@@ -321,7 +313,7 @@ A replacement for the installed JSKIT description.
321
313
 
322
314
  ### Post-change
323
315
 
324
- - Replacement installed post-change work.
316
+ - Replacement installed post-change work for the implementation turn.
325
317
 
326
318
  ### Deslop
327
319
 
@@ -357,8 +349,9 @@ genesis prompt # implementation work
357
349
  genesis prompt --task start # open a new or existing project conversation
358
350
  genesis prompt --task adopt # import an existing project's complete contract
359
351
  genesis prompt "Add filtering by note title"
360
- genesis prompt --task deslop # explicit cleanup
361
- genesis prompt --task deslop "Only review billing"
352
+ genesis prompt --task deslop # clean up the latest commit
353
+ genesis prompt --task deslop "Deslop the last 5 commits"
354
+ genesis prompt --task deslop "Deslop commit a1b2c3d"
362
355
  genesis prompt --task program # refresh explanation
363
356
  genesis prompt --task blueprint "Start on notes" # update product intent
364
357
  genesis prompt --task describe # create/refresh Blueprint and complete Program
@@ -377,8 +370,9 @@ The eight task types are deliberately explicit:
377
370
  - `work` implements the Blueprint and optional request using current code,
378
371
  Program, the selected engineering approach, selected Stack context, and
379
372
  progressively loaded Agent Skills.
380
- - `deslop` requests cleanup explicitly. Codex projects also receive a bounded
381
- automatic Deslop turn after their separate explanatory reconciliation turn.
373
+ - `deslop` requests behavior-preserving cleanup of committed work explicitly.
374
+ It defaults to the latest commit, accepts a first-parent commit count or exact
375
+ commit/range, requires a clean worktree, and leaves its result uncommitted.
382
376
  - `program` edits only `genesis/program/` to explain the code that exists, with
383
377
  selected Stack guidance available for technology-specific correctness.
384
378
  - `blueprint` edits only `genesis/blueprint.md` from explicit user intent. It
@@ -418,19 +412,20 @@ Components may contribute:
418
412
 
419
413
  - a concise description and supplemental Guidance used across relevant tasks;
420
414
  - existing-project Adoption requirements used only while importing a codebase;
421
- - optional Post-change work composed once after a code-changing implementation;
415
+ - optional Post-change work composed into the original implementation turn;
422
416
  - one authoritative Agent Skill directory when the component owns one;
423
417
  - generic external-resource requirements;
424
- - cleanup advice used by explicit and automatic Deslop prompts;
418
+ - cleanup advice composed on top of the core explicit Deslop contract;
425
419
  - structural code indexers used by Machine City and function lookup;
426
420
  - default verification commands.
427
421
 
428
422
  Technology-specific supplemental rules intentionally remain in Stack pieces.
429
423
  General `Guidance` enriches implementation, review, Program, description, and
430
- cleanup tasks; `Adoption` adds import-only requirements; `Post-change` composes
431
- one optional code-changing continuation before explanation; `Deslop` adds
432
- cleanup-only rules. None shadows an official generic technology skill or
433
- assumes that an upstream skill contains every rule Genesis needs.
424
+ cleanup tasks; `Adoption` adds import-only requirements; `Post-change` adds
425
+ work to finish in the original implementation turn; `Deslop` adds
426
+ technology-specific cleanup rules to Genesis's invariant behavior-preserving
427
+ contract. None shadows an official generic technology skill or assumes that an
428
+ upstream skill contains every rule Genesis needs.
434
429
 
435
430
  Genesis core contains no Stack pieces and no database, framework, language, or
436
431
  platform dependency. See [Stack components](docs/stack-components.md).
@@ -34,8 +34,7 @@ project. It creates ordinary project files with mode `0666`, directories and
34
34
  executable skill files with mode `0777`, and lets the invoking process umask
35
35
  and the destination directory's inherited ACL establish the effective mode and
36
36
  group. Atomic replacements are created beside their destination so they inherit
37
- the same filesystem policy before becoming visible. Private hook continuation
38
- state remains explicitly `0600`.
37
+ the same filesystem policy before becoming visible.
39
38
 
40
39
  This keeps ordinary single-user projects conventional while allowing a managed
41
40
  host to require shared-group writes and no access for other users. The host is
@@ -77,22 +77,19 @@ does not perform adoption automatically. If the user accepts, Codex runs the
77
77
  command itself and follows its returned prompt in the same conversation. Other
78
78
  hosts can call `adoptProject()` and send its `prompt` directly.
79
79
 
80
- Codex can instead use the project-local hooks installed by `genesis init`.
81
- Those hooks inject a short operating guide and record the Git-visible state when
82
- each user prompt begins. Before editing for an explicit user implementation
83
- request, Codex marks that turn with `genesis hook authorize`. Only an authorized
84
- turn whose project files changed can request bounded continuations; unmarked
85
- turns finish normally even if another process changes the checkout. Selected
86
- `Post-change` contributions compose into one optional code-changing turn;
87
- Genesis then reconciles Blueprint and affected Program explanations from the
88
- complete Git-visible change set and Deslops the implementation. The Deslop
89
- response is the concise user-facing completion. The hooks invoke the same
90
- Genesis project operations and never start another agent process. A per-session
91
- phase permits only `implementation -> optional post-change -> reconcile ->
92
- deslop -> done`. Empty Post-change content skips that phase. Genesis refreshes
93
- both City projections after reconciliation so Deslop sees the current
94
- explanation and function inventory, then refreshes them again after Deslop in
95
- case cleanup changed the implementation map.
80
+ Codex can instead use the project-local hook installed by `genesis init`.
81
+ That hook injects a short operating guide at startup, resume, clear, and
82
+ compaction. It does not record user turns, authorize edits, intercept Stop, or
83
+ request another model turn. Selected `Post-change` contributions are included
84
+ in the original implementation prompt, whose project skill also keeps
85
+ intentional Blueprint and affected Program changes aligned.
86
+
87
+ Deslop is an explicit prompt task available to any host or command-line user.
88
+ It resolves the latest commit by default, a requested first-parent count, or an
89
+ exact commit/range; requires a clean worktree; and leaves cleanup as ordinary
90
+ uncommitted work. Genesis owns that invariant contract. Selected Stack
91
+ `Deslop` prose and applicable technology skills enrich the cleanup without
92
+ changing its scope or behavior-preserving boundary.
96
93
 
97
94
  Once Codex identifies relevant source, `genesis context <path...>` returns only
98
95
  Program modules citing those paths plus the effective engineering approach and
@@ -113,7 +110,6 @@ user-installed generic technology skills independent.
113
110
  Matching files under `genesis/stack/<component>.md` may add to or override that
114
111
  selected component's Description, Guidance, Adoption, Post-change, and Deslop
115
112
  fields. Effective Guidance is used by work, review, Program, describe, Deslop,
116
- and path-context generation. The Codex continuation flow also carries it into
117
- Post-change work, reconciliation, and cleanup; effective Post-change work runs
118
- once before reconciliation, while Deslop guidance remains cleanup-only. Effective Adoption
119
- guidance is included only in the adoption workflow.
113
+ and path-context generation. Effective Post-change work is composed into the
114
+ implementation prompt, while Deslop guidance remains cleanup-only. Effective
115
+ Adoption guidance is included only in the adoption workflow.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.2.24",
3
+ "version": "1.2.25",
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": {
@@ -32,7 +32,6 @@
32
32
  "prompts/describe.txt",
33
33
  "prompts/deslop.txt",
34
34
  "prompts/program.txt",
35
- "prompts/reconcile.txt",
36
35
  "prompts/review.txt",
37
36
  "prompts/start.txt",
38
37
  "prompts/work.txt",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.24",
3
+ "version": "1.2.25",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
@@ -1,29 +1,14 @@
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.
1
+ Read and follow `.agents/skills/genesis-deslop/SKILL.md` completely.
4
2
 
5
- This is a dedicated behavior-preserving cleanup task, separate from
6
- implementation. Review the requested scope, or otherwise the ordinary Git diff
7
- and immediately related code. Apply every selected Stack cleanup instruction
8
- supplied below and load applicable official technology skills for additional
9
- language and framework context.
3
+ This is an explicitly requested behavior-preserving cleanup of committed work.
4
+ Resolve the commit or first-parent commit range from USER REQUEST exactly; when
5
+ no scope is stated, use the latest commit. Require a clean worktree before
6
+ editing and leave the cleanup as ordinary uncommitted work for review.
10
7
 
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.
8
+ Apply the selected Stack cleanup guidance and applicable technology skills in
9
+ addition to the Genesis Deslop contract. Stack guidance may improve
10
+ technology-specific cleanup, but it cannot broaden the resolved Git scope,
11
+ weaken behavior preservation, or turn this into another implementation pass.
15
12
 
16
- The changed paths and nearby code are a location boundary, not permission to
17
- implement other behavior found there. Deslop may reorganize behavior already
18
- present; it may not complete, correct, or extend behavior. If review reveals a
19
- defect, missing behavior, contract mismatch, or test gap, report it as a
20
- follow-up finding and leave it unchanged in this pass, even when it is inside a
21
- changed path or appears related to the preceding implementation.
22
-
23
- Do not broaden the product change. Run focused checks when useful and summarize
24
- what became simpler, files changed, and checks actually run.
25
-
26
- Your response from this turn is the final user-facing answer for the original
27
- request. Make it concise and self-contained. Lead with the outcome, include the
28
- important implemented behavior and any genuine blocker or unfinished work, and
29
- do not mention automatic phases, hooks, or these instructions.
13
+ Report the resolved range, what became simpler, files changed, checks actually
14
+ run, and any defect or gap deliberately left for follow-up.
package/prompts/work.txt CHANGED
@@ -8,15 +8,18 @@ Before adding a helper or public operation, query the Genesis
8
8
  `index <name-or-path>` operation and reuse an existing function when it already
9
9
  owns the behavior.
10
10
 
11
- This is the implementation turn. Do not edit the Blueprint, Program, or
12
- `.genesis/`; the separate reconciliation turn owns explanatory changes. Keep
13
- applicable consumer-owned Stack sections aligned when the implementation changes
14
- facts governed by those contracts. Follow the selected technology skill and the
15
- section's own schema; Genesis only transports the section and does not interpret
16
- or execute it. Never put an environment value or secret in Stack. Never invent
17
- missing external-resource values. Preserve useful work and report a genuinely
18
- missing resource instead of constructing a parallel fallback.
11
+ Keep the Blueprint and affected Program modules aligned in this turn when the
12
+ implementation intentionally changes observable product behavior. Private
13
+ restructuring may require only corrected source citations or no explanatory
14
+ edit. Keep applicable consumer-owned Stack sections aligned when implementation
15
+ changes facts governed by those contracts. Follow the selected technology skill
16
+ and the section's own schema; Genesis only transports the section and does not
17
+ interpret or execute it. Never put an environment value or secret in Stack.
18
+ Never invent missing external-resource values. Preserve useful work and report
19
+ a genuinely missing resource instead of constructing a parallel fallback.
19
20
 
21
+ Apply any supplied Stack Post-change guidance before reporting completion. It
22
+ belongs to this implementation turn and does not request a follow-up agent turn.
20
23
  Run focused checks when useful. Summarize files changed, checks actually run,
21
24
  and anything still requiring attention.
22
25
 
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  name: genesis-deslop
3
- description: Perform a separate behavior-preserving cleanup pass after implementation. Use when asked to Deslop, simplify, remove repeated helpers, clarify ownership, or align changed code with established project and technology patterns.
3
+ description: Deslop committed work through an explicit behavior-preserving cleanup pass. Use when asked to Deslop a commit or recent commits, simplify completed work, remove repeated helpers, clarify ownership, or align changed code with established project and technology patterns.
4
4
  ---
5
5
 
6
6
  # Genesis Deslop
7
7
 
8
- Review and simplify the current local codebase without changing product
9
- behavior. Start with the requested scope, or otherwise the ordinary Git diff
10
- and the code immediately around it.
8
+ Review and simplify committed work without changing product behavior. Deslop is
9
+ explicit: never run it merely because implementation finished.
11
10
 
12
11
  When this repository is Genesis itself or has `genesis-compiler` installed
13
12
  locally, invoke every Genesis CLI operation through the project-pinned package:
@@ -16,6 +15,28 @@ package. Otherwise use `genesis <arguments>` only when that executable is
16
15
  already available on `PATH`. Never install or update Genesis merely to satisfy
17
16
  a workflow instruction.
18
17
 
18
+ ## Resolve the committed scope
19
+
20
+ Before reading or editing the selected change, require a clean worktree and
21
+ index with `git status --short`. Stop and report the existing paths when it is
22
+ not clean; do not stash, commit, discard, or mix them into Deslop.
23
+
24
+ Resolve the user's scope as follows:
25
+
26
+ - With no stated scope, use the latest commit.
27
+ - “The last N commits” means the net surviving change from the first parent
28
+ before those N commits through `HEAD`.
29
+ - A named commit means that commit against its first parent.
30
+ - An explicit commit range means exactly that range.
31
+ - A root commit uses Git's root-diff form because it has no parent.
32
+
33
+ Inspect the resolved commit identities and changed paths before editing. Review
34
+ the committed diff, current source, direct call sites, relevant tests, and the
35
+ affected Program and Stack context. Do not substitute the ordinary working-tree
36
+ diff for the selected committed scope.
37
+
38
+ ## Apply the cleanup contract
39
+
19
40
  Apply the engineering approach supplied with the task. If this skill is invoked
20
41
  directly, read `genesis/engineering.md` as well as the relevant project context.
21
42
  Cleanup must reduce or preserve complexity, never introduce unapproved
@@ -26,7 +47,7 @@ operation to find existing public and internal functions before introducing or
26
47
  consolidating an abstraction. Confirm every apparent duplicate in source and
27
48
  its call sites.
28
49
 
29
- Within the preceding implementation's intended behavior and scope, leave no
50
+ Within the selected commits' intended behavior and scope, leave no
30
51
  repeated helpers, no code that is unclear or hard to reason about, and no code
31
52
  that goes against established patterns in the codebase. Remove unnecessary
32
53
  wrappers, abandoned scaffolding, parallel framework plumbing, and speculative
@@ -34,7 +55,7 @@ abstractions. Consolidate ownership where one clear module is enough. Do not
34
55
  optimize for tiny files or indirection; optimize for a small, obvious design
35
56
  that a junior programmer can follow.
36
57
 
37
- Cleanup is not a second implementation pass. A changed path limits where cleanup
58
+ Cleanup is not a second implementation pass. A committed path limits where cleanup
38
59
  may occur; it does not authorize different behavior in that file. Deslop may
39
60
  reorganize behavior already present; it may not complete, correct, or extend
40
61
  behavior. If review reveals a defect, missing behavior, contract mismatch, or
@@ -43,15 +64,22 @@ is inside a changed path or appears related to the preceding implementation. Do
43
64
  not implement the finding or alter tests to accommodate it during Deslop.
44
65
 
45
66
  Apply every technology-specific Deslop instruction supplied by the selected
46
- Stack. Load applicable official technology skills for additional framework and
47
- language context, but do not expect them to contain Genesis's cleanup policy.
67
+ Stack and load applicable official technology skills for framework and language
68
+ context. Stack guidance enriches this contract; it cannot weaken the
69
+ behavior-preserving boundary, change the selected Git scope, or authorize
70
+ unrelated implementation.
48
71
 
49
72
  You may edit or delete implementation and test files when that is the clearest
50
- behavior-preserving cleanup. Do not edit `genesis/`, `.genesis/`, Git metadata,
51
- dependency directories, generated build output, retained migration history, or
52
- external resources. Do not weaken tests, public behavior, or data guarantees.
53
- Do not create a second architecture or perform unrelated rewrites.
54
-
55
- Run focused checks when useful. Final declared checks remain available through
56
- the Genesis `verify` operation. Summarize what became simpler, files changed,
57
- and checks actually run.
73
+ behavior-preserving cleanup. If moving or renaming selected source makes an
74
+ affected Program citation stale, update only that citation or informational
75
+ Implementation map. Do not change the Blueprint or a Program public contract to
76
+ excuse cleanup behavior. Do not edit `.genesis/`, Git metadata, dependency
77
+ directories, generated build output, retained migration history, or external
78
+ resources. Do not weaken tests, public behavior, or data guarantees. Do not
79
+ create a second architecture or perform unrelated rewrites.
80
+
81
+ Leave the cleanup as ordinary uncommitted work for review and a later normal
82
+ commit. Never amend or rewrite the selected commits. Run focused checks when
83
+ useful. Final declared checks remain available through the Genesis `verify`
84
+ operation. Summarize the resolved commit range, what became simpler, files
85
+ changed, checks actually run, and follow-up findings left unchanged.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Genesis Deslop"
3
- short_description: "Simplify changed code without behavior drift"
4
- default_prompt: "Use $genesis-deslop for a separate behavior-preserving cleanup pass over the current changes."
3
+ short_description: "Clean up committed work without behavior drift"
4
+ default_prompt: "Use $genesis-deslop to Deslop the latest commit."
@@ -65,19 +65,25 @@ Neither is authority or proof; both may be regenerated with the Genesis
65
65
  tradeoff. Never silently override the engineering approach.
66
66
  - Work directly in the current Git tree and leave useful edits visible in the
67
67
  ordinary diff.
68
- - Do not edit the Blueprint, Program, or `.genesis/` during implementation; the
69
- separate reconciliation turn owns explanatory updates. Keep exact
68
+ - Keep the Blueprint and affected Program modules aligned in this same turn
69
+ when implementation intentionally changes observable product behavior.
70
+ Private restructuring may require only corrected Sources or an informational
71
+ Implementation map, and may require no explanatory edit. Keep exact
70
72
  consumer-owned operation declarations, such as Vibe64 `## Outputs`, aligned
71
73
  when implementation intentionally changes the corresponding commands or
72
74
  capabilities. Record only capability metadata and environment-variable names
73
75
  there, never values or secrets; do not infer or execute the section as
74
- Genesis behavior.
76
+ Genesis behavior. Treat `.genesis/` as replaceable derived navigation data,
77
+ not authored product intent.
75
78
  - Follow established project and technology seams instead of creating parallel
76
79
  frameworks, persistence layers, transports, validators, or UI systems.
77
80
  - Never invent unavailable external-resource values or pass literal
78
81
  environment-variable names as data. Report the missing resource and preserve
79
82
  useful work.
80
- - Keep the implementation direct and cohesive. Run focused checks when useful.
83
+ - Keep the implementation direct and cohesive. Apply any supplied Stack
84
+ Post-change guidance before reporting completion; it is part of this
85
+ implementation turn, not a request for another agent turn. Run focused checks
86
+ when useful.
81
87
 
82
88
  ## Verify and report
83
89
 
package/src/cli.js CHANGED
@@ -20,12 +20,8 @@ import {
20
20
  verify,
21
21
  } from './index.js';
22
22
  import {
23
- authorizeCodexTurn,
24
23
  codexAdoptionRecommendation,
25
24
  codexSessionContext,
26
- completeCodexTurn,
27
- discardCodexTurn,
28
- recordCodexTurn,
29
25
  } from './index/codex-hooks.js';
30
26
  import { engineeringProfile, readEngineeringBaseline } from './index/engineering.js';
31
27
  import { asDiagnostic, fail } from './index/errors.js';
@@ -138,8 +134,8 @@ function parseCommand(argv) {
138
134
  'Command inspect requires environment or section <name>.',
139
135
  );
140
136
  }
141
- } else if (command === 'hook' && (operands.length !== 1 || !['authorize', 'begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
142
- fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, authorize, stop, end.');
137
+ } else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session'].includes(operands[0]))) {
138
+ fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session.');
143
139
  } else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
144
140
  fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
145
141
  }
@@ -266,8 +262,6 @@ function writeResult(command, result) {
266
262
  }
267
263
  if (command === 'hook') {
268
264
  if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
269
- else if (result.kind === 'authorize') line(process.stdout, `authorize: ${result.status}`);
270
- else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
271
265
  return;
272
266
  }
273
267
  if (command === 'stack' && Array.isArray(result.pieces)) {
@@ -292,20 +286,6 @@ function writeResult(command, result) {
292
286
  line(process.stdout, `${command}: ${result.status}`);
293
287
  }
294
288
 
295
- async function hookInput() {
296
- if (process.stdin.isTTY) fail('CODEX_HOOK_INPUT_INVALID', 'Codex hook input must be supplied on stdin.');
297
- const chunks = [];
298
- let bytes = 0;
299
- for await (const chunk of process.stdin) {
300
- bytes += chunk.length;
301
- if (bytes > 1024 * 1024) fail('CODEX_HOOK_INPUT_INVALID', 'Codex hook input exceeds 1 MiB.');
302
- chunks.push(chunk);
303
- }
304
- try { return JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch (error) {
305
- fail('CODEX_HOOK_INPUT_INVALID', `Codex hook input is invalid JSON: ${error.message}.`);
306
- }
307
- }
308
-
309
289
  async function execute({ command, operands, options }, { signal } = {}) {
310
290
  const projectRoot = options.projectRoot || process.cwd();
311
291
  const stackPackages = options.stackPackages || [];
@@ -382,20 +362,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
382
362
  if (operands[0] === 'discover') {
383
363
  return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
384
364
  }
385
- if (operands[0] === 'session') {
386
- return { kind: 'session', ...await codexSessionContext({ projectRoot }) };
387
- }
388
- if (operands[0] === 'authorize') {
389
- return { kind: 'authorize', ...await authorizeCodexTurn({ projectRoot }) };
390
- }
391
- const input = await hookInput();
392
- if (operands[0] === 'begin') {
393
- return { kind: 'begin', ...await recordCodexTurn({ input, projectRoot }) };
394
- }
395
- if (operands[0] === 'end') {
396
- return { kind: 'end', ...await discardCodexTurn({ input, projectRoot }) };
397
- }
398
- return { kind: 'stop', status: 'ready', output: await completeCodexTurn({ input, projectRoot }) };
365
+ return { kind: 'session', ...await codexSessionContext({ projectRoot }) };
399
366
  }
400
367
  if (command === 'verify') {
401
368
  return verify({
@@ -6,7 +6,6 @@ const assets = {
6
6
  adopt: new URL('prompts/adopt.txt', root),
7
7
  work: new URL('prompts/work.txt', root),
8
8
  describe: new URL('prompts/describe.txt', root),
9
- reconcile: new URL('prompts/reconcile.txt', root),
10
9
  deslop: new URL('prompts/deslop.txt', root),
11
10
  program: new URL('prompts/program.txt', root),
12
11
  blueprint: new URL('prompts/blueprint.txt', root),
@@ -1,82 +1,62 @@
1
- import { readFile, rm } from 'node:fs/promises';
2
- import { tmpdir } from 'node:os';
1
+ import { readFile } from 'node:fs/promises';
3
2
  import path from 'node:path';
4
3
 
5
- import { readInstalledAsset } from './assets.js';
6
- import { buildProjectIndex } from './code-index.js';
7
4
  import { readEngineering, readEngineeringBaseline } from './engineering.js';
8
5
  import { GenesisError } from './errors.js';
9
6
  import { gitContext } from './git.js';
10
7
  import { isProjectContentPath } from './paths.js';
11
- import { runGit, runGitText } from './process.js';
8
+ import { runGit } from './process.js';
12
9
  import { readStack } from './stack.js';
13
- import { normalizeRelative, pathState, sha256, stableJson, writeFileAtomic } from './utils.js';
10
+ import { normalizeRelative, pathState, writeFileAtomic } from './utils.js';
14
11
 
15
12
  const HOOKS_PATH = '.codex/hooks.json';
16
- const HOOK_STATE_SCHEMA_VERSION = 2;
17
- const HOOK_LOCATOR_SCHEMA_VERSION = 1;
13
+ const PROJECT_HOOK_ACTIONS = ['session', 'begin', 'stop', 'end'];
14
+
18
15
  function hookCommand(action) {
19
16
  const local = `const {runCli}=await import('genesis-compiler/cli');process.exitCode=await runCli(['hook','${action}'])`;
20
17
  return `if command -v genesis >/dev/null 2>&1; then genesis hook ${action}; else node --input-type=module --eval "${local}"; fi`;
21
18
  }
22
19
 
23
- const HOOK_SPECS = [
24
- {
25
- event: 'SessionStart',
26
- command: hookCommand('session'),
27
- group: {
28
- matcher: '^(startup|resume|clear|compact)$',
29
- hooks: [{
30
- type: 'command',
31
- command: hookCommand('session'),
32
- commandWindows: 'genesis hook session',
33
- timeout: 5,
34
- statusMessage: 'Loading Genesis guidance',
35
- additionalContextLimit: 4000,
36
- }],
37
- },
38
- },
39
- {
40
- event: 'UserPromptSubmit',
41
- command: hookCommand('begin'),
42
- group: {
43
- hooks: [{
44
- type: 'command',
45
- command: hookCommand('begin'),
46
- commandWindows: 'genesis hook begin',
47
- timeout: 10,
48
- }],
49
- },
50
- },
51
- {
52
- event: 'Stop',
53
- command: hookCommand('stop'),
54
- group: {
55
- hooks: [{
56
- type: 'command',
57
- command: hookCommand('stop'),
58
- commandWindows: 'genesis hook stop',
59
- timeout: 30,
60
- statusMessage: 'Checking whether Genesis follow-up work is needed',
61
- }],
62
- },
20
+ const SESSION_HOOK = {
21
+ event: 'SessionStart',
22
+ group: {
23
+ matcher: '^(startup|resume|clear|compact)$',
24
+ hooks: [{
25
+ type: 'command',
26
+ command: hookCommand('session'),
27
+ commandWindows: 'genesis hook session',
28
+ timeout: 5,
29
+ statusMessage: 'Loading Genesis guidance',
30
+ additionalContextLimit: 4000,
31
+ }],
63
32
  },
64
- {
65
- event: 'SessionEnd',
66
- command: hookCommand('end'),
67
- group: {
68
- hooks: [{
69
- type: 'command',
70
- command: hookCommand('end'),
71
- commandWindows: 'genesis hook end',
72
- timeout: 3,
73
- }],
74
- },
75
- },
76
- ];
33
+ };
34
+
35
+ const OBSOLETE_PROJECT_HOOK_COMMANDS = new Set(PROJECT_HOOK_ACTIONS.flatMap((action) => [
36
+ hookCommand(action),
37
+ `genesis hook ${action}`,
38
+ ]));
77
39
 
78
- function genesisHookInstalled(groups, command) {
79
- return groups.some((group) => group?.hooks?.some((hook) => hook?.command === command));
40
+ function genesisProjectHook(hook) {
41
+ return hook && (
42
+ OBSOLETE_PROJECT_HOOK_COMMANDS.has(hook.command)
43
+ || OBSOLETE_PROJECT_HOOK_COMMANDS.has(hook.commandWindows)
44
+ );
45
+ }
46
+
47
+ function removeGenesisProjectHooks(hooks) {
48
+ for (const [event, groups] of Object.entries(hooks)) {
49
+ if (!Array.isArray(groups)) {
50
+ throw new GenesisError('CODEX_HOOKS_INVALID', `${HOOKS_PATH} hooks.${event} must be an array.`);
51
+ }
52
+ const retained = groups.flatMap((group) => {
53
+ if (!Array.isArray(group?.hooks)) return [group];
54
+ const groupHooks = group.hooks.filter((hook) => !genesisProjectHook(hook));
55
+ return groupHooks.length > 0 ? [{ ...group, hooks: groupHooks }] : [];
56
+ });
57
+ if (retained.length > 0) hooks[event] = retained;
58
+ else delete hooks[event];
59
+ }
80
60
  }
81
61
 
82
62
  async function installedHooksSource(location) {
@@ -101,13 +81,9 @@ export async function installCodexHooks({ projectRoot } = {}) {
101
81
  const location = path.join(root, HOOKS_PATH);
102
82
  const { source, value } = await installedHooksSource(location);
103
83
  value.hooks ||= {};
104
- for (const spec of HOOK_SPECS) {
105
- const groups = value.hooks[spec.event] ||= [];
106
- if (!Array.isArray(groups)) {
107
- throw new GenesisError('CODEX_HOOKS_INVALID', `${HOOKS_PATH} hooks.${spec.event} must be an array.`);
108
- }
109
- if (!genesisHookInstalled(groups, spec.command)) groups.push(spec.group);
110
- }
84
+ removeGenesisProjectHooks(value.hooks);
85
+ const groups = value.hooks[SESSION_HOOK.event] ||= [];
86
+ groups.push(SESSION_HOOK.group);
111
87
  const rendered = `${JSON.stringify(value, null, 2)}\n`;
112
88
  if (source === rendered) return { status: 'unchanged', changedFiles: [] };
113
89
  await writeFileAtomic(location, rendered);
@@ -131,7 +107,6 @@ async function optionalEngineering(projectRoot) {
131
107
  '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.',
132
108
  ].join('\n'),
133
109
  profile: null,
134
- status: 'invalid',
135
110
  };
136
111
  }
137
112
  }
@@ -150,21 +125,14 @@ export async function codexSessionContext({ projectRoot } = {}) {
150
125
  status: 'ready',
151
126
  output: [
152
127
  'This is a Genesis-enriched project.',
153
- '- `genesis/blueprint.md` describes non-technical product intent.',
154
- '- `genesis/engineering.md` selects the project engineering approach and any project-specific requirements.',
155
- '- `genesis/stack.md` selects optional technology guidance and verification.',
156
- '- `genesis/program/<subsystem>/` explains public operations and useful internal seams.',
157
- '- Project Agent Skills live in `.agents/skills/`; load applicable skills progressively.',
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.',
161
- '- `.genesis/machine-city.json` is the detailed code/function map; `.genesis/program-city.json` is the simpler subsystem/operation map.',
128
+ '- Read `genesis/blueprint.md`, `genesis/engineering.md`, and `genesis/stack.md` for product intent, engineering approach, and selected technology.',
129
+ '- Use the relevant project Agent Skills below `.agents/skills/`.',
130
+ '- After locating source, run `genesis context <path...>`; before adding a helper or public operation, run `genesis index <name-or-path...>` and reuse an existing owner.',
162
131
  '- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
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.',
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.',
166
- '- After an authorized code-changing turn, Genesis may request separate post-change, explanation, and cleanup turns.',
167
- `Engineering profile: ${engineering.profile?.id || 'invalid; run the Genesis \`check\` operation'}.`,
132
+ '- Keep Blueprint and affected Program explanations aligned with intentional observable product behavior in the same implementation turn. Private restructuring may need only source citations or no explanatory change.',
133
+ '- Deslop only when explicitly requested. Genesis defines its behavior-preserving committed scope; selected Stack components may add technology-specific cleanup guidance.',
134
+ '- This guidance refreshes after startup, resume, clear, and compaction. Continue the active request without restarting completed work.',
135
+ `Engineering profile: ${engineering.profile?.id || 'invalid; run the Genesis `check` operation'}.`,
168
136
  `Selected Stack components: ${stackStatus}.`,
169
137
  '',
170
138
  'ENGINEERING APPROACH',
@@ -174,6 +142,10 @@ export async function codexSessionContext({ projectRoot } = {}) {
174
142
  };
175
143
  }
176
144
 
145
+ function zeroPaths(buffer) {
146
+ return [...new Set(buffer.toString('utf8').split('\0').map(normalizeRelative).filter(Boolean))];
147
+ }
148
+
177
149
  export async function codexAdoptionRecommendation({ projectRoot = process.cwd() } = {}) {
178
150
  let root;
179
151
  try { root = (await gitContext(projectRoot)).repositoryRoot; } catch { return { status: 'not-applicable', output: '' }; }
@@ -197,512 +169,3 @@ export async function codexAdoptionRecommendation({ projectRoot = process.cwd()
197
169
  ].join('\n'),
198
170
  };
199
171
  }
200
-
201
- function hookId(input, field) {
202
- const value = input?.[field];
203
- if (typeof value !== 'string' || !value || value.length > 512) {
204
- throw new GenesisError('CODEX_HOOK_INPUT_INVALID', `Codex hook input requires ${field}.`);
205
- }
206
- return value;
207
- }
208
-
209
- function normalizedPaths(values = []) {
210
- return [...new Set(values.map(normalizeRelative).filter(Boolean))];
211
- }
212
-
213
- function zeroPaths(buffer) {
214
- return normalizedPaths(buffer.toString('utf8').split('\0'));
215
- }
216
-
217
- function projectPathStates(projectRoot, files) {
218
- return Promise.all(files.map(async (file) => ({
219
- path: file,
220
- state: await pathState(path.join(projectRoot, file)),
221
- })));
222
- }
223
-
224
- async function repositorySnapshot(projectRoot) {
225
- const head = await runGitText(projectRoot, ['rev-parse', '--verify', 'HEAD']).catch(() => null);
226
- const results = await Promise.all([
227
- runGit(projectRoot, ['diff', '--no-ext-diff', '--no-renames', '--name-only', '-z']),
228
- runGit(projectRoot, ['diff', '--cached', '--no-ext-diff', '--no-renames', '--name-only', '-z']),
229
- runGit(projectRoot, ['ls-files', '-z', '--others', '--exclude-standard']),
230
- runGit(projectRoot, ['ls-files', '-z', '--deleted']),
231
- ]);
232
- const dirty = [...new Set(results.flatMap(({ stdout }) => zeroPaths(stdout)))]
233
- .filter(isProjectContentPath)
234
- .sort();
235
- return {
236
- head,
237
- dirty: await projectPathStates(projectRoot, dirty),
238
- };
239
- }
240
-
241
- async function hookStatePath(projectRoot, input) {
242
- const key = sha256(hookId(input, 'session_id')).slice('sha256:'.length);
243
- const gitPath = await runGitText(projectRoot, ['rev-parse', '--git-path', `genesis/hooks/${key}.json`]);
244
- return path.resolve(projectRoot, gitPath);
245
- }
246
-
247
- function hookLocatorPath(input) {
248
- const key = sha256(hookId(input, 'session_id')).slice('sha256:'.length);
249
- const user = typeof process.getuid === 'function' ? process.getuid() : 'user';
250
- return path.join(tmpdir(), `genesis-codex-hook-${user}-${key}.json`);
251
- }
252
-
253
- function writeTurnState(statePath, state) {
254
- return writeFileAtomic(statePath, stableJson(state), { mode: 0o600 });
255
- }
256
-
257
- async function refreshProjectIndex(projectRoot) {
258
- await buildProjectIndex({ projectRoot }).catch(() => null);
259
- }
260
-
261
- export async function recordCodexTurn({ input, projectRoot } = {}) {
262
- const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
263
- const statePath = await hookStatePath(root, input);
264
- const turnId = hookId(input, 'turn_id');
265
- const existing = await readTurnState(statePath);
266
- if (existing?.schemaVersion === HOOK_STATE_SCHEMA_VERSION && existing.turnId === turnId) {
267
- await writeHookLocator(input, root);
268
- return { status: 'preserved' };
269
- }
270
- const repositories = registeredRepositories(existing || {}, root);
271
- const state = {
272
- schemaVersion: HOOK_STATE_SCHEMA_VERSION,
273
- turnId,
274
- phase: 'implementation',
275
- implementationAuthorized: false,
276
- repositories,
277
- snapshot: repositories.length > 0 && existing?.snapshot
278
- ? existing.snapshot
279
- : await repositorySnapshot(root),
280
- };
281
- await writeTurnState(statePath, state);
282
- await writeHookLocator(input, root);
283
- return { status: 'recorded' };
284
- }
285
-
286
- async function readTurnState(statePath) {
287
- try { return JSON.parse(await readFile(statePath, 'utf8')); } catch (error) {
288
- if (['ENOENT', 'ENOTDIR'].includes(error?.code)) return null;
289
- throw error;
290
- }
291
- }
292
-
293
- async function readHookLocator(input) {
294
- try { return JSON.parse(await readFile(hookLocatorPath(input), 'utf8')); } catch (error) {
295
- if (['ENOENT', 'ENOTDIR'].includes(error?.code)) return null;
296
- throw error;
297
- }
298
- }
299
-
300
- function writeHookLocator(input, coordinatorRoot) {
301
- return writeFileAtomic(hookLocatorPath(input), stableJson({
302
- schemaVersion: HOOK_LOCATOR_SCHEMA_VERSION,
303
- coordinatorRoot,
304
- }), { mode: 0o600 });
305
- }
306
-
307
- async function removeTurnState(statePath, input) {
308
- await Promise.all([
309
- rm(statePath, { force: true }),
310
- rm(hookLocatorPath(input), { force: true }),
311
- ]);
312
- }
313
-
314
- function registeredRepositories(state, coordinatorRoot) {
315
- if (Array.isArray(state.repositories) && state.repositories.length > 0) {
316
- return state.repositories;
317
- }
318
- if (state.implementationAuthorized === true && state.snapshot) {
319
- return [{
320
- projectRoot: coordinatorRoot,
321
- snapshot: state.snapshot,
322
- changedPaths: normalizedPaths(state.changedPaths || []).sort(),
323
- }];
324
- }
325
- return [];
326
- }
327
-
328
- export async function authorizeCodexTurn({
329
- projectRoot = process.cwd(),
330
- sessionId = process.env.CODEX_SESSION_ID,
331
- } = {}) {
332
- if (!sessionId) {
333
- throw new GenesisError(
334
- 'CODEX_SESSION_ID_MISSING',
335
- 'genesis hook authorize requires the current Codex session.',
336
- );
337
- }
338
- const input = { session_id: sessionId };
339
- const root = (await gitContext(projectRoot)).repositoryRoot;
340
- const locator = await readHookLocator(input);
341
- if (locator && (
342
- locator.schemaVersion !== HOOK_LOCATOR_SCHEMA_VERSION
343
- || typeof locator.coordinatorRoot !== 'string'
344
- || !locator.coordinatorRoot
345
- )) {
346
- throw new GenesisError(
347
- 'CODEX_HOOK_COORDINATOR_INVALID',
348
- 'The current Codex turn has invalid Genesis coordination state.',
349
- );
350
- }
351
- const coordinatorRoot = locator
352
- ? (await gitContext(locator.coordinatorRoot)).repositoryRoot
353
- : root;
354
- const statePath = await hookStatePath(coordinatorRoot, input);
355
- const state = await readTurnState(statePath);
356
- if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION || state.phase !== 'implementation') {
357
- throw new GenesisError(
358
- 'CODEX_HOOK_TURN_UNAVAILABLE',
359
- 'No active Genesis implementation turn is available. Start from the session repository and authorize before editing.',
360
- );
361
- }
362
- const repositories = registeredRepositories(state, coordinatorRoot);
363
- const registered = repositories.some((repository) => repository.projectRoot === root);
364
- if (!registered) {
365
- repositories.push({
366
- projectRoot: root,
367
- snapshot: root === coordinatorRoot && state.snapshot
368
- ? state.snapshot
369
- : await repositorySnapshot(root),
370
- changedPaths: [],
371
- });
372
- }
373
- await writeTurnState(statePath, {
374
- ...state,
375
- implementationAuthorized: true,
376
- repositories,
377
- });
378
- await writeHookLocator(input, coordinatorRoot);
379
- return {
380
- status: registered && state.implementationAuthorized === true
381
- ? 'unchanged'
382
- : 'authorized',
383
- };
384
- }
385
-
386
- async function committedPaths(projectRoot, before, after) {
387
- if (before === after) return [];
388
- if (!after) return ['repository history changed'];
389
- try {
390
- const args = before
391
- ? ['diff', '--no-renames', '--name-only', '-z', before, after]
392
- : ['diff-tree', '--root', '--no-commit-id', '--no-renames', '--name-only', '-r', '-z', after];
393
- return zeroPaths((await runGit(projectRoot, args)).stdout).filter(isProjectContentPath);
394
- } catch {
395
- return ['repository history changed'];
396
- }
397
- }
398
-
399
- async function changedProjectPaths(projectRoot, before, after) {
400
- const previous = new Map(before.dirty.map(({ path: file, state }) => [file, state]));
401
- const current = new Map(after.dirty.map(({ path: file, state }) => [file, state]));
402
- const dirtyChanges = [...new Set([...previous.keys(), ...current.keys()])].filter((file) => (
403
- stableJson(previous.get(file)) !== stableJson(current.get(file))
404
- ));
405
- const candidates = [...new Set([
406
- ...dirtyChanges,
407
- ...await committedPaths(projectRoot, before.head, after.head),
408
- ])].sort();
409
- const changed = await Promise.all(candidates.map(async (file) => {
410
- if (!previous.has(file)) return file;
411
- // Moving pre-turn file bytes into HEAD is a save operation, not new implementation.
412
- const finalState = current.has(file)
413
- ? current.get(file)
414
- : await pathState(path.join(projectRoot, file));
415
- return stableJson(previous.get(file)) === stableJson(finalState) ? null : file;
416
- }));
417
- return changed.filter(Boolean);
418
- }
419
-
420
- function changedPathLines(changedPaths) {
421
- const shown = changedPaths.slice(0, 50);
422
- const omitted = changedPaths.length - shown.length;
423
- return [
424
- ...shown.map((file) => `- ${file}`),
425
- ...(omitted > 0 ? [`- ... and ${omitted} more`] : []),
426
- ];
427
- }
428
-
429
- function changedRepositoryLines(repositories) {
430
- return repositories.flatMap(({ projectRoot, changedPaths, driftedPaths = [] }) => [
431
- `## Repository: \`${projectRoot}\``,
432
- '',
433
- ...changedPathLines(changedPaths),
434
- ...(driftedPaths.length > 0 ? [
435
- '',
436
- '### Concurrent changes excluded from cleanup',
437
- '',
438
- 'These paths changed after the implementation scope was frozen. Report them, but do not edit them:',
439
- '',
440
- ...changedPathLines(driftedPaths),
441
- ] : []),
442
- '',
443
- ]);
444
- }
445
-
446
- async function changedRepositories(repositories, { snapshotField = 'snapshot' } = {}) {
447
- return (await Promise.all(repositories.map(async (repository) => {
448
- const currentPaths = await changedProjectPaths(
449
- repository.projectRoot,
450
- repository[snapshotField] || repository.snapshot,
451
- await repositorySnapshot(repository.projectRoot),
452
- );
453
- const changedPaths = normalizedPaths([
454
- ...(repository.changedPaths || []),
455
- ...currentPaths,
456
- ]).sort();
457
- return changedPaths.length > 0 ? { ...repository, changedPaths } : null;
458
- }))).filter(Boolean);
459
- }
460
-
461
- async function freezeChangedRepositories(repositories, { startPostChange = false } = {}) {
462
- return Promise.all(repositories.map(async (repository) => {
463
- const changedPaths = normalizedPaths(repository.changedPaths || []).sort();
464
- const [pathStates, phaseSnapshot] = await Promise.all([
465
- projectPathStates(repository.projectRoot, changedPaths),
466
- startPostChange ? repositorySnapshot(repository.projectRoot) : null,
467
- ]);
468
- const frozen = {
469
- ...repository,
470
- changedPaths,
471
- pathStates,
472
- };
473
- delete frozen.driftedPaths;
474
- delete frozen.phaseSnapshot;
475
- if (phaseSnapshot) frozen.phaseSnapshot = phaseSnapshot;
476
- return frozen;
477
- }));
478
- }
479
-
480
- async function cleanupScopeRepositories(repositories) {
481
- return (await Promise.all(repositories.map(async (repository) => {
482
- const ownedPaths = normalizedPaths(repository.changedPaths || []).sort();
483
- if (ownedPaths.length === 0) return null;
484
- const expected = new Map((repository.pathStates || [])
485
- .map(({ path: file, state }) => [file, state]));
486
- const changedPaths = [];
487
- const driftedPaths = [];
488
- for (const { path: file, state } of await projectPathStates(repository.projectRoot, ownedPaths)) {
489
- const destination = expected.has(file) && stableJson(expected.get(file)) !== stableJson(state)
490
- ? driftedPaths
491
- : changedPaths;
492
- destination.push(file);
493
- }
494
- return {
495
- ...repository,
496
- changedPaths,
497
- driftedPaths,
498
- };
499
- }))).filter(Boolean);
500
- }
501
-
502
- async function repositoryContexts(repositories) {
503
- return Promise.all(repositories.map(async (repository) => {
504
- const [stack, engineering] = await Promise.all([
505
- optionalStack(repository.projectRoot),
506
- optionalEngineering(repository.projectRoot),
507
- ]);
508
- return { ...repository, stack, engineering };
509
- }));
510
- }
511
-
512
- function repositoryGuidanceLines({ projectRoot, stack, engineering }, {
513
- deslop = false,
514
- postChange = false,
515
- } = {}) {
516
- return [
517
- `## Repository: \`${projectRoot}\``,
518
- '',
519
- 'ENGINEERING APPROACH',
520
- '',
521
- engineering.guidance,
522
- ...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
523
- ...(postChange && stack?.postChange
524
- ? ['', 'COMPOSED STACK POST-CHANGE WORK', '', stack.postChange]
525
- : []),
526
- ...(deslop && stack?.deslop
527
- ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', stack.deslop]
528
- : []),
529
- '',
530
- ];
531
- }
532
-
533
- async function reconciliationContinuation(repositories, knownContexts) {
534
- const [instructions, contexts] = await Promise.all([
535
- readInstalledAsset('reconcile'),
536
- knownContexts || repositoryContexts(repositories),
537
- ]);
538
- return [
539
- 'This is the automatic Genesis explanation turn for the preceding implementation turn.',
540
- 'Use the same conversation context and the Git-visible changes below in every listed repository. Do not perform Deslop yet.',
541
- '',
542
- 'REPOSITORIES AND CHANGED PROJECT PATHS',
543
- '',
544
- ...changedRepositoryLines(repositories),
545
- '',
546
- instructions.trim(),
547
- '',
548
- 'REPOSITORY GUIDANCE',
549
- '',
550
- ...contexts.flatMap((context) => repositoryGuidanceLines(context)),
551
- ].join('\n');
552
- }
553
-
554
- function postChangeContinuation(repositories, contexts) {
555
- return [
556
- 'This is the one automatic Stack Post-change turn for the preceding implementation.',
557
- 'Use the same conversation context and the Git-visible changes below in every listed repository.',
558
- 'Perform only the composed Post-change work. Do not broaden the task, reconcile Genesis explanations, or perform Deslop yet.',
559
- 'Do not repeat or schedule another Post-change turn. If none of the configured work applies, make no changes and respond minimally.',
560
- '',
561
- 'REPOSITORIES AND CHANGED PROJECT PATHS',
562
- '',
563
- ...changedRepositoryLines(repositories),
564
- '',
565
- 'REPOSITORY GUIDANCE AND POST-CHANGE WORK',
566
- '',
567
- ...contexts.flatMap((context) => repositoryGuidanceLines(context, { postChange: true })),
568
- ].join('\n');
569
- }
570
-
571
- async function deslopContinuation(repositories) {
572
- const [instructions, contexts] = await Promise.all([
573
- readInstalledAsset('deslop'),
574
- repositoryContexts(repositories),
575
- ]);
576
- return [
577
- 'This is the final automatic Genesis Deslop turn for the preceding implementation.',
578
- 'Review every listed repository and only that work and the paths listed below. Do not broaden the task.',
579
- '',
580
- 'REPOSITORIES AND CHANGED PROJECT PATHS',
581
- '',
582
- ...changedRepositoryLines(repositories),
583
- '',
584
- instructions.trim(),
585
- '',
586
- 'REPOSITORY GUIDANCE',
587
- '',
588
- ...contexts.flatMap((context) => repositoryGuidanceLines(context, { deslop: true })),
589
- '',
590
- 'PROGRAM CITATION MAINTENANCE',
591
- '',
592
- 'If this cleanup renames or moves a source or helper cited by an affected Program module,',
593
- 'update only that module\'s Sources or informational Implementation map. This narrow',
594
- 'exception overrides the default prohibition on editing `genesis/`; never change the',
595
- 'Blueprint or a Program public contract to excuse cleanup behavior.',
596
- ].join('\n');
597
- }
598
-
599
- async function refreshProjectIndexes(repositories) {
600
- await Promise.all(repositories.map(({ projectRoot }) => refreshProjectIndex(projectRoot)));
601
- }
602
-
603
- export async function completeCodexTurn({ input, projectRoot } = {}) {
604
- const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
605
- const statePath = await hookStatePath(root, input);
606
- const state = await readTurnState(statePath);
607
- if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION) return {};
608
-
609
- if (state.implementationAuthorized !== true) {
610
- const repositories = await changedRepositories(registeredRepositories(state, root));
611
- if (repositories.length === 0) {
612
- await removeTurnState(statePath, input);
613
- return {};
614
- }
615
- await writeTurnState(statePath, {
616
- ...state,
617
- phase: 'implementation',
618
- repositories,
619
- });
620
- return {};
621
- }
622
-
623
- if (state.phase === 'implementation') {
624
- if (input?.stop_hook_active === true || state.turnId !== hookId(input, 'turn_id')) {
625
- await removeTurnState(statePath, input);
626
- return {};
627
- }
628
- const changed = await changedRepositories(registeredRepositories(state, root));
629
- if (changed.length === 0) {
630
- await removeTurnState(statePath, input);
631
- return {};
632
- }
633
- const contexts = await repositoryContexts(changed);
634
- const postChange = contexts.some(({ stack }) => Boolean(stack?.postChange?.trim()));
635
- const repositories = await freezeChangedRepositories(changed, { startPostChange: postChange });
636
- await writeTurnState(statePath, {
637
- ...state,
638
- phase: postChange ? 'post-change' : 'reconcile',
639
- repositories,
640
- });
641
- return {
642
- decision: 'block',
643
- reason: postChange
644
- ? postChangeContinuation(repositories, contexts)
645
- : await reconciliationContinuation(repositories, contexts),
646
- };
647
- }
648
-
649
- if (input?.stop_hook_active !== true) {
650
- await removeTurnState(statePath, input);
651
- return {};
652
- }
653
-
654
- if (state.phase === 'post-change') {
655
- const changed = await changedRepositories(registeredRepositories(state, root), {
656
- snapshotField: 'phaseSnapshot',
657
- });
658
- if (changed.length === 0) {
659
- await removeTurnState(statePath, input);
660
- return {};
661
- }
662
- const repositories = await freezeChangedRepositories(changed);
663
- await writeTurnState(statePath, {
664
- ...state,
665
- phase: 'reconcile',
666
- repositories,
667
- });
668
- return {
669
- decision: 'block',
670
- reason: await reconciliationContinuation(repositories),
671
- };
672
- }
673
-
674
- if (state.phase === 'reconcile') {
675
- const registered = registeredRepositories(state, root);
676
- const repositories = await cleanupScopeRepositories(registered);
677
- if (repositories.length === 0) {
678
- await refreshProjectIndexes(registered);
679
- await removeTurnState(statePath, input);
680
- return {};
681
- }
682
- await refreshProjectIndexes(repositories);
683
- await writeTurnState(statePath, {
684
- ...state,
685
- phase: 'deslop',
686
- repositories,
687
- });
688
- return {
689
- decision: 'block',
690
- reason: await deslopContinuation(repositories),
691
- };
692
- }
693
-
694
- if (state.phase === 'deslop') {
695
- await refreshProjectIndexes(registeredRepositories(state, root));
696
- await removeTurnState(statePath, input);
697
- return {};
698
- }
699
-
700
- await removeTurnState(statePath, input);
701
- return {};
702
- }
703
-
704
- export async function discardCodexTurn({ input, projectRoot } = {}) {
705
- const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
706
- await removeTurnState(await hookStatePath(root, input), input);
707
- return { status: 'discarded' };
708
- }
package/src/index/init.js CHANGED
@@ -54,12 +54,12 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
54
54
  return {
55
55
  status: changedFiles.length > 0 ? 'updated' : 'unchanged',
56
56
  summary: changedFiles.length > 0
57
- ? 'Initialized Genesis, Agent Skills, and project Codex hooks.'
58
- : 'Genesis, its Agent Skills, and project Codex hooks are already initialized.',
57
+ ? 'Initialized Genesis, Agent Skills, and the project Codex guidance hook.'
58
+ : 'Genesis, its Agent Skills, and the project Codex guidance hook are already initialized.',
59
59
  changedFiles,
60
60
  diagnostics: skills.diagnostics,
61
61
  guidance: [
62
- 'Open Codex and use /hooks to review and trust the project hooks.',
62
+ 'Open Codex and use /hooks to review and trust the project guidance hook.',
63
63
  'Genesis workflow skills are available in .agents/skills/.',
64
64
  'Describe product intent in genesis/blueprint.md.',
65
65
  'Choose the project engineering approach with genesis engineering set <profile>.',
@@ -25,7 +25,7 @@ const DEFAULT_REQUEST = {
25
25
  start: 'Start a conversation about this project.',
26
26
  adopt: 'Import the existing project into a truthful Genesis project contract.',
27
27
  work: 'Implement the product intent expressed by the current Blueprint.',
28
- deslop: 'Simplify the current Git-visible work without making unrelated changes.',
28
+ deslop: 'Deslop the latest commit.',
29
29
  program: 'Refresh the complete useful Program for the code that exists now.',
30
30
  describe: 'Create or refresh the complete Blueprint and useful Program for the codebase that exists now.',
31
31
  review: 'Review the complete useful relationship between Blueprint, code, Program, and tests.',
@@ -107,6 +107,7 @@ function renderPrompt({
107
107
  skills = '',
108
108
  guidance = '',
109
109
  cleanup = '',
110
+ postChange = '',
110
111
  adoption = '',
111
112
  engineeringGuidance = '',
112
113
  }) {
@@ -126,6 +127,7 @@ function renderPrompt({
126
127
  ...(guidance ? ['', 'SELECTED STACK GUIDANCE', '', guidance] : []),
127
128
  ...(adoption ? ['', 'SELECTED STACK ADOPTION GUIDANCE', '', adoption] : []),
128
129
  ...(skills ? ['', 'AVAILABLE AGENT SKILLS', '', skills] : []),
130
+ ...(postChange ? ['', 'SELECTED STACK POST-CHANGE GUIDANCE', '', postChange] : []),
129
131
  ...(cleanup ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', cleanup] : []),
130
132
  '',
131
133
  ].join('\n');
@@ -451,6 +453,7 @@ export async function generateProjectPrompt({
451
453
  engineeringGuidance: engineering.guidance,
452
454
  guidance: stack.guidance,
453
455
  skills: renderAgentSkillCatalog(projectSkills.skills),
456
+ postChange: task === 'work' ? stack.postChange : '',
454
457
  cleanup: task === 'deslop' ? stack.deslop : '',
455
458
  });
456
459
  return {
@@ -1,13 +0,0 @@
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.
4
-
5
- Use the preceding implementation turn, listed changed paths, actual Git diff or
6
- history, and relevant tests. Update Blueprint only for intentional observable
7
- product behavior. Update only affected Program modules. Private restructuring
8
- may require only Sources or an informational Implementation map, and may require
9
- no explanatory edit at all.
10
-
11
- Do not simplify or edit implementation, tests, configuration, dependencies,
12
- Stack, or `.genesis/` during this turn. Do not run Deslop yet. Summarize every
13
- explanatory change and genuine ambiguity.