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 +1 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/adopt.txt +15 -13
- package/prompts/deslop.txt +7 -4
- package/prompts/reconcile.txt +3 -2
- package/prompts/start.txt +15 -12
- package/prompts/work.txt +8 -6
- package/skills/genesis-deslop/SKILL.md +13 -5
- package/skills/genesis-project/SKILL.md +22 -11
- package/src/cli.js +1 -0
- package/src/index/codex-hooks.js +225 -78
- package/src/index/context.js +1 -1
- package/src/index/prompt.js +1 -1
package/package.json
CHANGED
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 `
|
|
13
|
-
inspection commands. Do not install dependencies, prepare a database,
|
|
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 `
|
|
20
|
-
<piece
|
|
21
|
-
product-language question only when evidence is genuinely ambiguous.
|
|
22
|
-
|
|
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 `
|
|
55
|
-
Verification commands, or evidence required by a
|
|
56
|
-
honest import findings, not permission to invent or
|
|
57
|
-
clear inventory of what was imported, what remains
|
|
58
|
-
what would require a separately approved
|
|
59
|
-
project is ready merely because Stack
|
|
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.
|
package/prompts/deslop.txt
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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 `
|
|
10
|
-
existing public and internal functions. Remove or consolidate genuine
|
|
11
|
-
helpers, but confirm behavior and call sites in source before changing
|
|
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
|
package/prompts/reconcile.txt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
`
|
|
12
|
-
the non-technical Blueprint from the user's answers before
|
|
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 `
|
|
22
|
-
<profile
|
|
23
|
-
explicit obligations in `genesis/engineering.md` under Project
|
|
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 `
|
|
26
|
-
structural validation is useful, run `
|
|
27
|
-
remain governed by their own tools and must not be
|
|
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 `
|
|
49
|
-
yourself and follow its adoption prompt. If they decline, continue
|
|
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
|
|
8
|
-
and reuse an existing function when it already
|
|
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 `
|
|
24
|
-
the current catalog, present relevant choices, wait for an
|
|
25
|
-
and add the selection with
|
|
26
|
-
|
|
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 `
|
|
18
|
-
find existing public and internal functions before introducing or
|
|
19
|
-
an abstraction. Confirm every apparent duplicate in source and
|
|
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
|
-
`
|
|
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 `
|
|
20
|
-
Reuse an existing function when it already owns the
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
|
74
|
-
verify` for the Stack's declared final checks. An unconfigured result
|
|
75
|
-
declared workspace or checks do not exist yet; it is not a failing
|
|
76
|
-
files changed, checks actually run, and anything still requiring
|
|
77
|
-
Never claim that an unrun check passed or that passing checks prove
|
|
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
|
}
|
package/src/index/codex-hooks.js
CHANGED
|
@@ -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 `
|
|
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 `
|
|
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
|
-
'-
|
|
157
|
-
'-
|
|
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
|
|
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 \`
|
|
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 `
|
|
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
|
|
269
|
+
turnId,
|
|
253
270
|
phase: 'implementation',
|
|
254
271
|
implementationAuthorized: false,
|
|
255
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
491
|
+
...changedRepositoryLines(repositories),
|
|
345
492
|
'',
|
|
346
493
|
instructions.trim(),
|
|
347
494
|
'',
|
|
348
|
-
'
|
|
495
|
+
'REPOSITORY GUIDANCE',
|
|
349
496
|
'',
|
|
350
|
-
|
|
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(
|
|
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
|
-
...
|
|
365
|
-
'',
|
|
366
|
-
'COMPOSED STACK POST-CHANGE WORK',
|
|
510
|
+
...changedRepositoryLines(repositories),
|
|
367
511
|
'',
|
|
368
|
-
|
|
512
|
+
'REPOSITORY GUIDANCE AND POST-CHANGE WORK',
|
|
369
513
|
'',
|
|
370
|
-
|
|
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(
|
|
378
|
-
const [instructions,
|
|
518
|
+
async function deslopContinuation(repositories) {
|
|
519
|
+
const [instructions, contexts] = await Promise.all([
|
|
379
520
|
readInstalledAsset('deslop'),
|
|
380
|
-
|
|
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
|
-
...
|
|
529
|
+
...changedRepositoryLines(repositories),
|
|
390
530
|
'',
|
|
391
531
|
instructions.trim(),
|
|
392
532
|
'',
|
|
393
|
-
'
|
|
533
|
+
'REPOSITORY GUIDANCE',
|
|
394
534
|
'',
|
|
395
|
-
|
|
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
|
|
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
|
|
572
|
+
await removeTurnState(statePath, input);
|
|
422
573
|
return {};
|
|
423
574
|
}
|
|
424
|
-
const
|
|
425
|
-
if (
|
|
426
|
-
await
|
|
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
|
|
430
|
-
|
|
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
|
-
|
|
585
|
+
repositories,
|
|
438
586
|
});
|
|
439
587
|
return {
|
|
440
588
|
decision: 'block',
|
|
441
589
|
reason: postChange
|
|
442
|
-
? postChangeContinuation(
|
|
443
|
-
: await reconciliationContinuation(
|
|
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
|
|
596
|
+
await removeTurnState(statePath, input);
|
|
449
597
|
return {};
|
|
450
598
|
}
|
|
451
599
|
|
|
452
600
|
if (state.phase === 'post-change') {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
609
|
+
repositories,
|
|
463
610
|
});
|
|
464
611
|
return {
|
|
465
612
|
decision: 'block',
|
|
466
|
-
reason: await reconciliationContinuation(
|
|
613
|
+
reason: await reconciliationContinuation(repositories),
|
|
467
614
|
};
|
|
468
615
|
}
|
|
469
616
|
|
|
470
617
|
if (state.phase === 'reconcile') {
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
if (
|
|
474
|
-
await
|
|
475
|
-
await
|
|
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
|
|
625
|
+
await refreshProjectIndexes(repositories);
|
|
479
626
|
await writeTurnState(statePath, {
|
|
480
627
|
...state,
|
|
481
628
|
phase: 'deslop',
|
|
482
|
-
|
|
629
|
+
repositories,
|
|
483
630
|
});
|
|
484
631
|
return {
|
|
485
632
|
decision: 'block',
|
|
486
|
-
reason: await deslopContinuation(
|
|
633
|
+
reason: await deslopContinuation(repositories),
|
|
487
634
|
};
|
|
488
635
|
}
|
|
489
636
|
|
|
490
637
|
if (state.phase === 'deslop') {
|
|
491
|
-
await
|
|
492
|
-
await
|
|
638
|
+
await refreshProjectIndexes(registeredRepositories(state, root));
|
|
639
|
+
await removeTurnState(statePath, input);
|
|
493
640
|
return {};
|
|
494
641
|
}
|
|
495
642
|
|
|
496
|
-
await
|
|
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
|
|
649
|
+
await removeTurnState(await hookStatePath(root, input), input);
|
|
503
650
|
return { status: 'discarded' };
|
|
504
651
|
}
|
package/src/index/context.js
CHANGED
|
@@ -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 \`
|
|
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] : []),
|
package/src/index/prompt.js
CHANGED