omegon 0.8.3 → 0.8.4

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.
@@ -352,7 +352,7 @@ async function checkpointRelatedChanges(
352
352
  * Prevents infinite loops when the agent repeatedly picks actions
353
353
  * that don't resolve the dirty tree.
354
354
  */
355
- const MAX_PREFLIGHT_ATTEMPTS = 3;
355
+ export const MAX_PREFLIGHT_ATTEMPTS = 3;
356
356
 
357
357
  /**
358
358
  * Verify the tree is clean after an action. If only volatile files remain,
@@ -435,12 +435,23 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
435
435
  throw new Error(summary + "\n\nInteractive input is unavailable, so cleave cannot resolve the dirty tree automatically.");
436
436
  }
437
437
 
438
- // Mutable classification — refreshed after each action attempt.
438
+ // Mutable classification — refreshed after each resolution action.
439
439
  let currentClassification = classification;
440
- let attempts = 0;
441
-
442
- while (attempts < MAX_PREFLIGHT_ATTEMPTS) {
443
- attempts++;
440
+ // Only resolution actions (checkpoint, stash) increment this counter.
441
+ // Invalid input, empty guards, cancel, and proceed-without-cleave do NOT
442
+ // consume attempts they are navigational, not resolution attempts.
443
+ let resolutionAttempts = 0;
444
+
445
+ // Outer safety cap: total loop iterations including non-resolution turns.
446
+ // Prevents truly pathological loops (e.g. select always returning garbage).
447
+ const MAX_TOTAL_ITERATIONS = MAX_PREFLIGHT_ATTEMPTS * 3;
448
+ let totalIterations = 0;
449
+
450
+ while (resolutionAttempts < MAX_PREFLIGHT_ATTEMPTS) {
451
+ totalIterations++;
452
+ if (totalIterations > MAX_TOTAL_ITERATIONS) {
453
+ break; // Fall through to the exhaustion error below
454
+ }
444
455
 
445
456
  let answer: string | undefined;
446
457
  if (hasSelect) {
@@ -458,6 +469,7 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
458
469
  try {
459
470
  switch (answer) {
460
471
  case "checkpoint": {
472
+ resolutionAttempts++;
461
473
  const currentCheckpointPlan = buildCheckpointPlan(currentClassification, { changeName, openspecContext });
462
474
  await checkpointRelatedChanges(pi, options.repoPath, currentClassification, currentCheckpointPlan.message, options.ui);
463
475
 
@@ -493,6 +505,7 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
493
505
  });
494
506
  break;
495
507
  }
508
+ resolutionAttempts++;
496
509
  await stashPaths(pi, options.repoPath, "cleave-preflight-unrelated", toStash);
497
510
  const { clean, classification: postClassification } = await verifyCleanAfterAction(
498
511
  pi, options.repoPath, changeName, openspecContext, options.onUpdate,
@@ -509,6 +522,7 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
509
522
  });
510
523
  break;
511
524
  }
525
+ resolutionAttempts++;
512
526
  await stashPaths(pi, options.repoPath, "cleave-preflight-volatile", currentClassification.volatile);
513
527
  const { clean, classification: postClassification } = await verifyCleanAfterAction(
514
528
  pi, options.repoPath, changeName, openspecContext, options.onUpdate,
@@ -529,6 +543,8 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
529
543
  });
530
544
  }
531
545
  } catch (error) {
546
+ // Resolution action threw (e.g. git commit failed) — still counts
547
+ // as a resolution attempt since work was attempted.
532
548
  const message = error instanceof Error ? error.message : String(error);
533
549
  options.onUpdate?.({
534
550
  content: [{ type: "text", text: `Preflight action failed: ${message}` }],
@@ -537,7 +553,7 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
537
553
  }
538
554
  }
539
555
 
540
- // Exhausted attempts — report remaining dirty files and bail.
556
+ // Exhausted resolution attempts — report remaining dirty files and bail.
541
557
  const remaining = [
542
558
  ...currentClassification.related,
543
559
  ...currentClassification.unrelated,
@@ -545,7 +561,7 @@ export async function runDirtyTreePreflight(pi: ExtensionAPI, options: DirtyTree
545
561
  ...currentClassification.volatile,
546
562
  ];
547
563
  throw new Error(
548
- `Dirty tree not resolved after ${MAX_PREFLIGHT_ATTEMPTS} attempts. Remaining files:\n` +
564
+ `Dirty tree not resolved after ${resolutionAttempts} resolution attempt(s). Remaining files:\n` +
549
565
  remaining.map((f) => ` • ${f.path}`).join("\n") +
550
566
  "\n\nResolve manually (git commit/stash/checkout) and retry /cleave.",
551
567
  );
@@ -432,6 +432,9 @@ export function computeAssessmentSnapshot(repoPath: string, changeName: string):
432
432
  // assessment.json (which lives in-repo) changes HEAD, which invalidates
433
433
  // the fingerprint, making the assessment permanently stale.
434
434
  // Git HEAD is stored separately in the snapshot for informational use.
435
+ // The `dirty` flag still captures uncommitted-change state, which is
436
+ // the meaningful signal — it detects when scoped files have been
437
+ // modified since the last commit without requiring HEAD identity.
435
438
  const fingerprintSeed = JSON.stringify({
436
439
  changeName,
437
440
  dirty,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegon",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Omegon — an opinionated distribution of pi (by Mario Zechner) with extensions for lifecycle management, memory, orchestration, and visualization",
5
5
  "bin": {
6
6
  "omegon": "bin/omegon.mjs",