genesis-compiler 1.2.21 → 1.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.2.21",
3
+ "version": "1.2.22",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.21",
3
+ "version": "1.2.22",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
@@ -214,6 +214,13 @@ function zeroPaths(buffer) {
214
214
  return normalizedPaths(buffer.toString('utf8').split('\0'));
215
215
  }
216
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
+
217
224
  async function repositorySnapshot(projectRoot) {
218
225
  const head = await runGitText(projectRoot, ['rev-parse', '--verify', 'HEAD']).catch(() => null);
219
226
  const results = await Promise.all([
@@ -227,10 +234,7 @@ async function repositorySnapshot(projectRoot) {
227
234
  .sort();
228
235
  return {
229
236
  head,
230
- dirty: await Promise.all(dirty.map(async (file) => ({
231
- path: file,
232
- state: await pathState(path.join(projectRoot, file)),
233
- }))),
237
+ dirty: await projectPathStates(projectRoot, dirty),
234
238
  };
235
239
  }
236
240
 
@@ -423,19 +427,27 @@ function changedPathLines(changedPaths) {
423
427
  }
424
428
 
425
429
  function changedRepositoryLines(repositories) {
426
- return repositories.flatMap(({ projectRoot, changedPaths }) => [
430
+ return repositories.flatMap(({ projectRoot, changedPaths, driftedPaths = [] }) => [
427
431
  `## Repository: \`${projectRoot}\``,
428
432
  '',
429
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
+ ] : []),
430
442
  '',
431
443
  ]);
432
444
  }
433
445
 
434
- async function changedRepositories(repositories) {
446
+ async function changedRepositories(repositories, { snapshotField = 'snapshot' } = {}) {
435
447
  return (await Promise.all(repositories.map(async (repository) => {
436
448
  const currentPaths = await changedProjectPaths(
437
449
  repository.projectRoot,
438
- repository.snapshot,
450
+ repository[snapshotField] || repository.snapshot,
439
451
  await repositorySnapshot(repository.projectRoot),
440
452
  );
441
453
  const changedPaths = normalizedPaths([
@@ -446,6 +458,47 @@ async function changedRepositories(repositories) {
446
458
  }))).filter(Boolean);
447
459
  }
448
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
+
449
502
  async function repositoryContexts(repositories) {
450
503
  return Promise.all(repositories.map(async (repository) => {
451
504
  const [stack, engineering] = await Promise.all([
@@ -572,13 +625,14 @@ export async function completeCodexTurn({ input, projectRoot } = {}) {
572
625
  await removeTurnState(statePath, input);
573
626
  return {};
574
627
  }
575
- const repositories = await changedRepositories(registeredRepositories(state, root));
576
- if (repositories.length === 0) {
628
+ const changed = await changedRepositories(registeredRepositories(state, root));
629
+ if (changed.length === 0) {
577
630
  await removeTurnState(statePath, input);
578
631
  return {};
579
632
  }
580
- const contexts = await repositoryContexts(repositories);
633
+ const contexts = await repositoryContexts(changed);
581
634
  const postChange = contexts.some(({ stack }) => Boolean(stack?.postChange?.trim()));
635
+ const repositories = await freezeChangedRepositories(changed, { startPostChange: postChange });
582
636
  await writeTurnState(statePath, {
583
637
  ...state,
584
638
  phase: postChange ? 'post-change' : 'reconcile',
@@ -598,11 +652,14 @@ export async function completeCodexTurn({ input, projectRoot } = {}) {
598
652
  }
599
653
 
600
654
  if (state.phase === 'post-change') {
601
- const repositories = await changedRepositories(registeredRepositories(state, root));
602
- if (repositories.length === 0) {
655
+ const changed = await changedRepositories(registeredRepositories(state, root), {
656
+ snapshotField: 'phaseSnapshot',
657
+ });
658
+ if (changed.length === 0) {
603
659
  await removeTurnState(statePath, input);
604
660
  return {};
605
661
  }
662
+ const repositories = await freezeChangedRepositories(changed);
606
663
  await writeTurnState(statePath, {
607
664
  ...state,
608
665
  phase: 'reconcile',
@@ -616,7 +673,7 @@ export async function completeCodexTurn({ input, projectRoot } = {}) {
616
673
 
617
674
  if (state.phase === 'reconcile') {
618
675
  const registered = registeredRepositories(state, root);
619
- const repositories = await changedRepositories(registered);
676
+ const repositories = await cleanupScopeRepositories(registered);
620
677
  if (repositories.length === 0) {
621
678
  await refreshProjectIndexes(registered);
622
679
  await removeTurnState(statePath, input);