genesis-compiler 1.2.18 → 1.2.20
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 +18 -17
- package/docs/prompt-integration.md +13 -12
- package/package.json +1 -2
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/deslop.txt +8 -1
- package/prompts/reconcile.txt +3 -2
- package/src/cli.js +7 -2
- package/src/index/assets.js +0 -1
- package/src/index/codex-hooks.js +239 -75
- package/prompts/final-summary.txt +0 -12
package/README.md
CHANGED
|
@@ -261,26 +261,27 @@ Python, Java, C#, C/C++, PHP, Go, Rust, Ruby, Kotlin, and Shell. A future techno
|
|
|
261
261
|
adds its adapter through its Stack piece rather than teaching Genesis prompts
|
|
262
262
|
to parse that language.
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
264
|
+
Before changing project files for a user-requested implementation, Codex runs
|
|
265
|
+
`genesis hook authorize`. At the end of an authorized turn that changed
|
|
266
|
+
Git-visible project files, the Stop hook asks Codex for bounded follow-up turns.
|
|
267
|
+
Unmarked turns finish normally even if another process changes the same
|
|
268
|
+
checkout. When the selected Stack contributes `Post-change` work, all
|
|
269
|
+
contributions compose deterministically into one optional code-changing turn.
|
|
270
|
+
Genesis then reconciles the Blueprint and affected Program explanations from
|
|
271
|
+
the complete changed-path set and actual Git diff, and refreshes both City
|
|
272
|
+
projections. A focused, behavior-preserving Deslop pass runs before both Cities
|
|
273
|
+
are refreshed again; its response is the concise user-facing completion. A
|
|
274
|
+
project with no selected Stack uses only the `genesis-deslop` skill. Selected
|
|
275
|
+
Stack components enrich Post-change work, reconciliation, and cleanup with
|
|
276
|
+
general Guidance, and enrich cleanup further with technology-specific Deslop
|
|
277
|
+
guidance.
|
|
276
278
|
|
|
277
279
|
The hook never starts another Codex process. Its small per-session phase is
|
|
278
280
|
limited to `implementation -> optional post-change -> reconcile -> deslop ->
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
Question-only and unchanged turns finish normally.
|
|
281
|
+
done`, so it cannot keep continuing recursively. The Stop after Deslop clears
|
|
282
|
+
the sequence rather than scheduling another model turn. Empty Post-change
|
|
283
|
+
content is skipped rather than producing an empty turn. Question, explanation,
|
|
284
|
+
review, diagnosis-only, and unchanged turns finish normally.
|
|
284
285
|
|
|
285
286
|
Projects can customize a selected technology through its matching file, such
|
|
286
287
|
as `genesis/stack/jskit.md`:
|
|
@@ -77,20 +77,21 @@ command itself and follows its returned prompt in the same conversation. Other
|
|
|
77
77
|
hosts can call `adoptProject()` and send its `prompt` directly.
|
|
78
78
|
|
|
79
79
|
Codex can instead use the project-local hooks installed by `genesis init`.
|
|
80
|
-
Those hooks inject a short operating guide
|
|
81
|
-
|
|
80
|
+
Those hooks inject a short operating guide and record the Git-visible state when
|
|
81
|
+
each user prompt begins. Before editing for an explicit user implementation
|
|
82
|
+
request, Codex marks that turn with `genesis hook authorize`. Only an authorized
|
|
83
|
+
turn whose project files changed can request bounded continuations; unmarked
|
|
84
|
+
turns finish normally even if another process changes the checkout. Selected
|
|
82
85
|
`Post-change` contributions compose into one optional code-changing turn;
|
|
83
86
|
Genesis then reconciles Blueprint and affected Program explanations from the
|
|
84
|
-
complete Git-visible change set and Deslops the implementation.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
then refreshes them again after Deslop in case cleanup changed the
|
|
93
|
-
implementation map.
|
|
87
|
+
complete Git-visible change set and Deslops the implementation. The Deslop
|
|
88
|
+
response is the concise user-facing completion. The hooks invoke the same
|
|
89
|
+
Genesis project operations and never start another agent process. A per-session
|
|
90
|
+
phase permits only `implementation -> optional post-change -> reconcile ->
|
|
91
|
+
deslop -> done`. Empty Post-change content skips that phase. Genesis refreshes
|
|
92
|
+
both City projections after reconciliation so Deslop sees the current
|
|
93
|
+
explanation and function inventory, then refreshes them again after Deslop in
|
|
94
|
+
case cleanup changed the implementation map.
|
|
94
95
|
|
|
95
96
|
Once Codex identifies relevant source, `genesis context <path...>` returns only
|
|
96
97
|
Program modules citing those paths plus the effective engineering approach and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genesis-compiler",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.20",
|
|
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": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"prompts/adopt.txt",
|
|
32
32
|
"prompts/describe.txt",
|
|
33
33
|
"prompts/deslop.txt",
|
|
34
|
-
"prompts/final-summary.txt",
|
|
35
34
|
"prompts/program.txt",
|
|
36
35
|
"prompts/reconcile.txt",
|
|
37
36
|
"prompts/review.txt",
|
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
|
|
@@ -19,3 +21,8 @@ changed path or appears related to the preceding implementation.
|
|
|
19
21
|
|
|
20
22
|
Do not broaden the product change. Run focused checks when useful and summarize
|
|
21
23
|
what became simpler, files changed, and checks actually run.
|
|
24
|
+
|
|
25
|
+
Your response from this turn is the final user-facing answer for the original
|
|
26
|
+
request. Make it concise and self-contained. Lead with the outcome, include the
|
|
27
|
+
important implemented behavior and any genuine blocker or unfinished work, and
|
|
28
|
+
do not mention automatic phases, hooks, or these instructions.
|
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/src/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
verify,
|
|
20
20
|
} from './index.js';
|
|
21
21
|
import {
|
|
22
|
+
authorizeCodexTurn,
|
|
22
23
|
codexAdoptionRecommendation,
|
|
23
24
|
codexSessionContext,
|
|
24
25
|
completeCodexTurn,
|
|
@@ -131,8 +132,8 @@ function parseCommand(argv) {
|
|
|
131
132
|
'Command inspect requires environment or section <name>.',
|
|
132
133
|
);
|
|
133
134
|
}
|
|
134
|
-
} else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
|
|
135
|
-
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, stop, end.');
|
|
135
|
+
} else if (command === 'hook' && (operands.length !== 1 || !['authorize', 'begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
|
|
136
|
+
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, authorize, stop, end.');
|
|
136
137
|
} else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
|
|
137
138
|
fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
|
|
138
139
|
}
|
|
@@ -256,6 +257,7 @@ function writeResult(command, result) {
|
|
|
256
257
|
}
|
|
257
258
|
if (command === 'hook') {
|
|
258
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}`);
|
|
259
261
|
else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
|
|
260
262
|
return;
|
|
261
263
|
}
|
|
@@ -373,6 +375,9 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
373
375
|
if (operands[0] === 'session') {
|
|
374
376
|
return { kind: 'session', ...await codexSessionContext({ projectRoot }) };
|
|
375
377
|
}
|
|
378
|
+
if (operands[0] === 'authorize') {
|
|
379
|
+
return { kind: 'authorize', ...await authorizeCodexTurn({ projectRoot }) };
|
|
380
|
+
}
|
|
376
381
|
const input = await hookInput();
|
|
377
382
|
if (operands[0] === 'begin') {
|
|
378
383
|
return { kind: 'begin', ...await recordCodexTurn({ input, projectRoot }) };
|
package/src/index/assets.js
CHANGED
|
@@ -8,7 +8,6 @@ const assets = {
|
|
|
8
8
|
describe: new URL('prompts/describe.txt', root),
|
|
9
9
|
reconcile: new URL('prompts/reconcile.txt', root),
|
|
10
10
|
deslop: new URL('prompts/deslop.txt', root),
|
|
11
|
-
finalSummary: new URL('prompts/final-summary.txt', root),
|
|
12
11
|
program: new URL('prompts/program.txt', root),
|
|
13
12
|
blueprint: new URL('prompts/blueprint.txt', root),
|
|
14
13
|
review: new URL('prompts/review.txt', root),
|
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`;
|
|
@@ -157,8 +159,10 @@ export async function codexSessionContext({ projectRoot } = {}) {
|
|
|
157
159
|
'- Before creating a helper or public operation, run `genesis index <name-or-path>` and reuse an existing function when it already owns the behavior.',
|
|
158
160
|
'- `.genesis/machine-city.json` is the detailed code/function map; `.genesis/program-city.json` is the simpler subsystem/operation map.',
|
|
159
161
|
'- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
|
|
162
|
+
'- Before the first project-file edit in a user-requested implementation turn, silently run `genesis hook authorize` 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.',
|
|
163
|
+
'- 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.',
|
|
160
164
|
'- Apply the engineering approach below to implementation and cleanup; the Stop hook owns optional Stack Post-change work, Blueprint/Program reconciliation, and Deslop turns.',
|
|
161
|
-
'- After
|
|
165
|
+
'- After an authorized code-changing turn, Genesis may request separate post-change, explanation, and cleanup turns.',
|
|
162
166
|
`Engineering profile: ${engineering.profile?.id || 'invalid; run \`genesis check\`'}.`,
|
|
163
167
|
`Selected Stack components: ${stackStatus}.`,
|
|
164
168
|
'',
|
|
@@ -235,6 +239,12 @@ async function hookStatePath(projectRoot, input) {
|
|
|
235
239
|
return path.resolve(projectRoot, gitPath);
|
|
236
240
|
}
|
|
237
241
|
|
|
242
|
+
function hookLocatorPath(input) {
|
|
243
|
+
const key = sha256(hookId(input, 'session_id')).slice('sha256:'.length);
|
|
244
|
+
const user = typeof process.getuid === 'function' ? process.getuid() : 'user';
|
|
245
|
+
return path.join(tmpdir(), `genesis-codex-hook-${user}-${key}.json`);
|
|
246
|
+
}
|
|
247
|
+
|
|
238
248
|
function writeTurnState(statePath, state) {
|
|
239
249
|
return writeFileAtomic(statePath, stableJson(state), { mode: 0o600 });
|
|
240
250
|
}
|
|
@@ -246,13 +256,25 @@ async function refreshProjectIndex(projectRoot) {
|
|
|
246
256
|
export async function recordCodexTurn({ input, projectRoot } = {}) {
|
|
247
257
|
const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
|
|
248
258
|
const statePath = await hookStatePath(root, input);
|
|
259
|
+
const turnId = hookId(input, 'turn_id');
|
|
260
|
+
const existing = await readTurnState(statePath);
|
|
261
|
+
if (existing?.schemaVersion === HOOK_STATE_SCHEMA_VERSION && existing.turnId === turnId) {
|
|
262
|
+
await writeHookLocator(input, root);
|
|
263
|
+
return { status: 'preserved' };
|
|
264
|
+
}
|
|
265
|
+
const repositories = registeredRepositories(existing || {}, root);
|
|
249
266
|
const state = {
|
|
250
267
|
schemaVersion: HOOK_STATE_SCHEMA_VERSION,
|
|
251
|
-
turnId
|
|
268
|
+
turnId,
|
|
252
269
|
phase: 'implementation',
|
|
253
|
-
|
|
270
|
+
implementationAuthorized: false,
|
|
271
|
+
repositories,
|
|
272
|
+
snapshot: repositories.length > 0 && existing?.snapshot
|
|
273
|
+
? existing.snapshot
|
|
274
|
+
: await repositorySnapshot(root),
|
|
254
275
|
};
|
|
255
276
|
await writeTurnState(statePath, state);
|
|
277
|
+
await writeHookLocator(input, root);
|
|
256
278
|
return { status: 'recorded' };
|
|
257
279
|
}
|
|
258
280
|
|
|
@@ -263,6 +285,99 @@ async function readTurnState(statePath) {
|
|
|
263
285
|
}
|
|
264
286
|
}
|
|
265
287
|
|
|
288
|
+
async function readHookLocator(input) {
|
|
289
|
+
try { return JSON.parse(await readFile(hookLocatorPath(input), 'utf8')); } catch (error) {
|
|
290
|
+
if (['ENOENT', 'ENOTDIR'].includes(error?.code)) return null;
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function writeHookLocator(input, coordinatorRoot) {
|
|
296
|
+
return writeFileAtomic(hookLocatorPath(input), stableJson({
|
|
297
|
+
schemaVersion: HOOK_LOCATOR_SCHEMA_VERSION,
|
|
298
|
+
coordinatorRoot,
|
|
299
|
+
}), { mode: 0o600 });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function removeTurnState(statePath, input) {
|
|
303
|
+
await Promise.all([
|
|
304
|
+
rm(statePath, { force: true }),
|
|
305
|
+
rm(hookLocatorPath(input), { force: true }),
|
|
306
|
+
]);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function registeredRepositories(state, coordinatorRoot) {
|
|
310
|
+
if (Array.isArray(state.repositories) && state.repositories.length > 0) {
|
|
311
|
+
return state.repositories;
|
|
312
|
+
}
|
|
313
|
+
if (state.implementationAuthorized === true && state.snapshot) {
|
|
314
|
+
return [{
|
|
315
|
+
projectRoot: coordinatorRoot,
|
|
316
|
+
snapshot: state.snapshot,
|
|
317
|
+
changedPaths: normalizedPaths(state.changedPaths || []).sort(),
|
|
318
|
+
}];
|
|
319
|
+
}
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function authorizeCodexTurn({
|
|
324
|
+
projectRoot = process.cwd(),
|
|
325
|
+
sessionId = process.env.CODEX_SESSION_ID,
|
|
326
|
+
} = {}) {
|
|
327
|
+
if (!sessionId) {
|
|
328
|
+
throw new GenesisError(
|
|
329
|
+
'CODEX_SESSION_ID_MISSING',
|
|
330
|
+
'genesis hook authorize requires the current Codex session.',
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const input = { session_id: sessionId };
|
|
334
|
+
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
335
|
+
const locator = await readHookLocator(input);
|
|
336
|
+
if (locator && (
|
|
337
|
+
locator.schemaVersion !== HOOK_LOCATOR_SCHEMA_VERSION
|
|
338
|
+
|| typeof locator.coordinatorRoot !== 'string'
|
|
339
|
+
|| !locator.coordinatorRoot
|
|
340
|
+
)) {
|
|
341
|
+
throw new GenesisError(
|
|
342
|
+
'CODEX_HOOK_COORDINATOR_INVALID',
|
|
343
|
+
'The current Codex turn has invalid Genesis coordination state.',
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
const coordinatorRoot = locator
|
|
347
|
+
? (await gitContext(locator.coordinatorRoot)).repositoryRoot
|
|
348
|
+
: root;
|
|
349
|
+
const statePath = await hookStatePath(coordinatorRoot, input);
|
|
350
|
+
const state = await readTurnState(statePath);
|
|
351
|
+
if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION || state.phase !== 'implementation') {
|
|
352
|
+
throw new GenesisError(
|
|
353
|
+
'CODEX_HOOK_TURN_UNAVAILABLE',
|
|
354
|
+
'No active Genesis implementation turn is available. Start from the session repository and authorize before editing.',
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const repositories = registeredRepositories(state, coordinatorRoot);
|
|
358
|
+
const registered = repositories.some((repository) => repository.projectRoot === root);
|
|
359
|
+
if (!registered) {
|
|
360
|
+
repositories.push({
|
|
361
|
+
projectRoot: root,
|
|
362
|
+
snapshot: root === coordinatorRoot && state.snapshot
|
|
363
|
+
? state.snapshot
|
|
364
|
+
: await repositorySnapshot(root),
|
|
365
|
+
changedPaths: [],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
await writeTurnState(statePath, {
|
|
369
|
+
...state,
|
|
370
|
+
implementationAuthorized: true,
|
|
371
|
+
repositories,
|
|
372
|
+
});
|
|
373
|
+
await writeHookLocator(input, coordinatorRoot);
|
|
374
|
+
return {
|
|
375
|
+
status: registered && state.implementationAuthorized === true
|
|
376
|
+
? 'unchanged'
|
|
377
|
+
: 'authorized',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
266
381
|
async function committedPaths(projectRoot, before, after) {
|
|
267
382
|
if (before === after) return [];
|
|
268
383
|
if (!after) return ['repository history changed'];
|
|
@@ -306,72 +421,117 @@ function changedPathLines(changedPaths) {
|
|
|
306
421
|
];
|
|
307
422
|
}
|
|
308
423
|
|
|
309
|
-
|
|
310
|
-
|
|
424
|
+
function changedRepositoryLines(repositories) {
|
|
425
|
+
return repositories.flatMap(({ projectRoot, changedPaths }) => [
|
|
426
|
+
`## Repository: \`${projectRoot}\``,
|
|
427
|
+
'',
|
|
428
|
+
...changedPathLines(changedPaths),
|
|
429
|
+
'',
|
|
430
|
+
]);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function changedRepositories(repositories) {
|
|
434
|
+
return (await Promise.all(repositories.map(async (repository) => {
|
|
435
|
+
const currentPaths = await changedProjectPaths(
|
|
436
|
+
repository.projectRoot,
|
|
437
|
+
repository.snapshot,
|
|
438
|
+
await repositorySnapshot(repository.projectRoot),
|
|
439
|
+
);
|
|
440
|
+
const changedPaths = normalizedPaths([
|
|
441
|
+
...(repository.changedPaths || []),
|
|
442
|
+
...currentPaths,
|
|
443
|
+
]).sort();
|
|
444
|
+
return changedPaths.length > 0 ? { ...repository, changedPaths } : null;
|
|
445
|
+
}))).filter(Boolean);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function repositoryContexts(repositories) {
|
|
449
|
+
return Promise.all(repositories.map(async (repository) => {
|
|
450
|
+
const [stack, engineering] = await Promise.all([
|
|
451
|
+
optionalStack(repository.projectRoot),
|
|
452
|
+
optionalEngineering(repository.projectRoot),
|
|
453
|
+
]);
|
|
454
|
+
return { ...repository, stack, engineering };
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function repositoryGuidanceLines({ projectRoot, stack, engineering }, {
|
|
459
|
+
deslop = false,
|
|
460
|
+
postChange = false,
|
|
461
|
+
} = {}) {
|
|
462
|
+
return [
|
|
463
|
+
`## Repository: \`${projectRoot}\``,
|
|
464
|
+
'',
|
|
465
|
+
'ENGINEERING APPROACH',
|
|
466
|
+
'',
|
|
467
|
+
engineering.guidance,
|
|
468
|
+
...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
|
|
469
|
+
...(postChange && stack?.postChange
|
|
470
|
+
? ['', 'COMPOSED STACK POST-CHANGE WORK', '', stack.postChange]
|
|
471
|
+
: []),
|
|
472
|
+
...(deslop && stack?.deslop
|
|
473
|
+
? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', stack.deslop]
|
|
474
|
+
: []),
|
|
475
|
+
'',
|
|
476
|
+
];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function reconciliationContinuation(repositories, knownContexts) {
|
|
480
|
+
const [instructions, contexts] = await Promise.all([
|
|
311
481
|
readInstalledAsset('reconcile'),
|
|
312
|
-
|
|
313
|
-
optionalEngineering(projectRoot),
|
|
482
|
+
knownContexts || repositoryContexts(repositories),
|
|
314
483
|
]);
|
|
315
484
|
return [
|
|
316
485
|
'This is the automatic Genesis explanation turn for the preceding implementation turn.',
|
|
317
|
-
'Use the same conversation context and the Git-visible changes below. Do not perform Deslop yet.',
|
|
486
|
+
'Use the same conversation context and the Git-visible changes below in every listed repository. Do not perform Deslop yet.',
|
|
318
487
|
'',
|
|
319
|
-
'CHANGED PROJECT PATHS',
|
|
488
|
+
'REPOSITORIES AND CHANGED PROJECT PATHS',
|
|
320
489
|
'',
|
|
321
|
-
...
|
|
490
|
+
...changedRepositoryLines(repositories),
|
|
322
491
|
'',
|
|
323
492
|
instructions.trim(),
|
|
324
493
|
'',
|
|
325
|
-
'
|
|
494
|
+
'REPOSITORY GUIDANCE',
|
|
326
495
|
'',
|
|
327
|
-
|
|
328
|
-
...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
|
|
496
|
+
...contexts.flatMap((context) => repositoryGuidanceLines(context)),
|
|
329
497
|
].join('\n');
|
|
330
498
|
}
|
|
331
499
|
|
|
332
|
-
function postChangeContinuation(
|
|
500
|
+
function postChangeContinuation(repositories, contexts) {
|
|
333
501
|
return [
|
|
334
502
|
'This is the one automatic Stack Post-change turn for the preceding implementation.',
|
|
335
|
-
'Use the same conversation context and the Git-visible changes below.',
|
|
503
|
+
'Use the same conversation context and the Git-visible changes below in every listed repository.',
|
|
336
504
|
'Perform only the composed Post-change work. Do not broaden the task, reconcile Genesis explanations, or perform Deslop yet.',
|
|
337
505
|
'Do not repeat or schedule another Post-change turn. If none of the configured work applies, make no changes and respond minimally.',
|
|
338
506
|
'',
|
|
339
|
-
'CHANGED PROJECT PATHS',
|
|
340
|
-
'',
|
|
341
|
-
...changedPathLines(changedPaths),
|
|
342
|
-
'',
|
|
343
|
-
'COMPOSED STACK POST-CHANGE WORK',
|
|
507
|
+
'REPOSITORIES AND CHANGED PROJECT PATHS',
|
|
344
508
|
'',
|
|
345
|
-
|
|
509
|
+
...changedRepositoryLines(repositories),
|
|
346
510
|
'',
|
|
347
|
-
'
|
|
511
|
+
'REPOSITORY GUIDANCE AND POST-CHANGE WORK',
|
|
348
512
|
'',
|
|
349
|
-
|
|
350
|
-
...(stack.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
|
|
513
|
+
...contexts.flatMap((context) => repositoryGuidanceLines(context, { postChange: true })),
|
|
351
514
|
].join('\n');
|
|
352
515
|
}
|
|
353
516
|
|
|
354
|
-
async function deslopContinuation(
|
|
355
|
-
const [instructions,
|
|
517
|
+
async function deslopContinuation(repositories) {
|
|
518
|
+
const [instructions, contexts] = await Promise.all([
|
|
356
519
|
readInstalledAsset('deslop'),
|
|
357
|
-
|
|
358
|
-
optionalEngineering(projectRoot),
|
|
520
|
+
repositoryContexts(repositories),
|
|
359
521
|
]);
|
|
360
522
|
return [
|
|
361
523
|
'This is the final automatic Genesis Deslop turn for the preceding implementation.',
|
|
362
|
-
'Review only that work and the paths listed below. Do not broaden the task.',
|
|
524
|
+
'Review every listed repository and only that work and the paths listed below. Do not broaden the task.',
|
|
363
525
|
'',
|
|
364
|
-
'CHANGED PROJECT PATHS',
|
|
526
|
+
'REPOSITORIES AND CHANGED PROJECT PATHS',
|
|
365
527
|
'',
|
|
366
|
-
...
|
|
528
|
+
...changedRepositoryLines(repositories),
|
|
367
529
|
'',
|
|
368
530
|
instructions.trim(),
|
|
369
531
|
'',
|
|
370
|
-
'
|
|
532
|
+
'REPOSITORY GUIDANCE',
|
|
371
533
|
'',
|
|
372
|
-
|
|
373
|
-
...(stack?.guidance ? ['', 'SELECTED STACK GUIDANCE', '', stack.guidance] : []),
|
|
374
|
-
...(stack?.deslop ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', stack.deslop] : []),
|
|
534
|
+
...contexts.flatMap((context) => repositoryGuidanceLines(context, { deslop: true })),
|
|
375
535
|
'',
|
|
376
536
|
'PROGRAM CITATION MAINTENANCE',
|
|
377
537
|
'',
|
|
@@ -382,8 +542,8 @@ async function deslopContinuation(projectRoot, changedPaths) {
|
|
|
382
542
|
].join('\n');
|
|
383
543
|
}
|
|
384
544
|
|
|
385
|
-
async function
|
|
386
|
-
|
|
545
|
+
async function refreshProjectIndexes(repositories) {
|
|
546
|
+
await Promise.all(repositories.map(({ projectRoot }) => refreshProjectIndex(projectRoot)));
|
|
387
547
|
}
|
|
388
548
|
|
|
389
549
|
export async function completeCodexTurn({ input, projectRoot } = {}) {
|
|
@@ -392,95 +552,99 @@ export async function completeCodexTurn({ input, projectRoot } = {}) {
|
|
|
392
552
|
const state = await readTurnState(statePath);
|
|
393
553
|
if (!state || state.schemaVersion !== HOOK_STATE_SCHEMA_VERSION) return {};
|
|
394
554
|
|
|
555
|
+
if (state.implementationAuthorized !== true) {
|
|
556
|
+
const repositories = await changedRepositories(registeredRepositories(state, root));
|
|
557
|
+
if (repositories.length === 0) {
|
|
558
|
+
await removeTurnState(statePath, input);
|
|
559
|
+
return {};
|
|
560
|
+
}
|
|
561
|
+
await writeTurnState(statePath, {
|
|
562
|
+
...state,
|
|
563
|
+
phase: 'implementation',
|
|
564
|
+
repositories,
|
|
565
|
+
});
|
|
566
|
+
return {};
|
|
567
|
+
}
|
|
568
|
+
|
|
395
569
|
if (state.phase === 'implementation') {
|
|
396
570
|
if (input?.stop_hook_active === true || state.turnId !== hookId(input, 'turn_id')) {
|
|
397
|
-
await
|
|
571
|
+
await removeTurnState(statePath, input);
|
|
398
572
|
return {};
|
|
399
573
|
}
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
402
|
-
await
|
|
574
|
+
const repositories = await changedRepositories(registeredRepositories(state, root));
|
|
575
|
+
if (repositories.length === 0) {
|
|
576
|
+
await removeTurnState(statePath, input);
|
|
403
577
|
return {};
|
|
404
578
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
optionalEngineering(root),
|
|
408
|
-
]);
|
|
409
|
-
const postChange = Boolean(stack?.postChange?.trim());
|
|
579
|
+
const contexts = await repositoryContexts(repositories);
|
|
580
|
+
const postChange = contexts.some(({ stack }) => Boolean(stack?.postChange?.trim()));
|
|
410
581
|
await writeTurnState(statePath, {
|
|
411
582
|
...state,
|
|
412
583
|
phase: postChange ? 'post-change' : 'reconcile',
|
|
413
|
-
|
|
584
|
+
repositories,
|
|
414
585
|
});
|
|
415
586
|
return {
|
|
416
587
|
decision: 'block',
|
|
417
588
|
reason: postChange
|
|
418
|
-
? postChangeContinuation(
|
|
419
|
-
: await reconciliationContinuation(
|
|
589
|
+
? postChangeContinuation(repositories, contexts)
|
|
590
|
+
: await reconciliationContinuation(repositories, contexts),
|
|
420
591
|
};
|
|
421
592
|
}
|
|
422
593
|
|
|
423
594
|
if (input?.stop_hook_active !== true) {
|
|
424
|
-
await
|
|
595
|
+
await removeTurnState(statePath, input);
|
|
425
596
|
return {};
|
|
426
597
|
}
|
|
427
598
|
|
|
428
599
|
if (state.phase === 'post-change') {
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
await rm(statePath, { force: true });
|
|
600
|
+
const repositories = await changedRepositories(registeredRepositories(state, root));
|
|
601
|
+
if (repositories.length === 0) {
|
|
602
|
+
await removeTurnState(statePath, input);
|
|
433
603
|
return {};
|
|
434
604
|
}
|
|
435
605
|
await writeTurnState(statePath, {
|
|
436
606
|
...state,
|
|
437
607
|
phase: 'reconcile',
|
|
438
|
-
|
|
608
|
+
repositories,
|
|
439
609
|
});
|
|
440
610
|
return {
|
|
441
611
|
decision: 'block',
|
|
442
|
-
reason: await reconciliationContinuation(
|
|
612
|
+
reason: await reconciliationContinuation(repositories),
|
|
443
613
|
};
|
|
444
614
|
}
|
|
445
615
|
|
|
446
616
|
if (state.phase === 'reconcile') {
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
await
|
|
451
|
-
await
|
|
617
|
+
const registered = registeredRepositories(state, root);
|
|
618
|
+
const repositories = await changedRepositories(registered);
|
|
619
|
+
if (repositories.length === 0) {
|
|
620
|
+
await refreshProjectIndexes(registered);
|
|
621
|
+
await removeTurnState(statePath, input);
|
|
452
622
|
return {};
|
|
453
623
|
}
|
|
454
|
-
await
|
|
624
|
+
await refreshProjectIndexes(repositories);
|
|
455
625
|
await writeTurnState(statePath, {
|
|
456
626
|
...state,
|
|
457
627
|
phase: 'deslop',
|
|
458
|
-
|
|
628
|
+
repositories,
|
|
459
629
|
});
|
|
460
630
|
return {
|
|
461
631
|
decision: 'block',
|
|
462
|
-
reason: await deslopContinuation(
|
|
632
|
+
reason: await deslopContinuation(repositories),
|
|
463
633
|
};
|
|
464
634
|
}
|
|
465
635
|
|
|
466
636
|
if (state.phase === 'deslop') {
|
|
467
|
-
await
|
|
468
|
-
await
|
|
469
|
-
|
|
470
|
-
phase: 'summary',
|
|
471
|
-
});
|
|
472
|
-
return {
|
|
473
|
-
decision: 'block',
|
|
474
|
-
reason: await finalSummaryContinuation(),
|
|
475
|
-
};
|
|
637
|
+
await refreshProjectIndexes(registeredRepositories(state, root));
|
|
638
|
+
await removeTurnState(statePath, input);
|
|
639
|
+
return {};
|
|
476
640
|
}
|
|
477
641
|
|
|
478
|
-
await
|
|
642
|
+
await removeTurnState(statePath, input);
|
|
479
643
|
return {};
|
|
480
644
|
}
|
|
481
645
|
|
|
482
646
|
export async function discardCodexTurn({ input, projectRoot } = {}) {
|
|
483
647
|
const root = (await gitContext(projectRoot || input?.cwd)).repositoryRoot;
|
|
484
|
-
await
|
|
648
|
+
await removeTurnState(await hookStatePath(root, input), input);
|
|
485
649
|
return { status: 'discarded' };
|
|
486
650
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
This is the final automatic user-facing summary for the original request.
|
|
2
|
-
|
|
3
|
-
Use only the existing conversation context from the implementation and its
|
|
4
|
-
follow-up turns. Do not inspect the repository or codebase, call tools, run
|
|
5
|
-
commands or tests, edit files, reconcile explanations, or perform cleanup.
|
|
6
|
-
|
|
7
|
-
Write one concise, self-contained answer to the user. Lead with the outcome.
|
|
8
|
-
Include the important behavior that changed, checks that were actually run,
|
|
9
|
-
useful human verification steps, and any genuine blocker or unfinished work.
|
|
10
|
-
Do not mention automatic phases, hooks, internal follow-up turns, or these
|
|
11
|
-
instructions. Do not claim checks, commits, pushes, or deployments that are not
|
|
12
|
-
already established in the conversation. After the answer, stop.
|