toga-ai 1.0.479 → 1.0.480

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.
Files changed (2) hide show
  1. package/knowledge.js +37 -6
  2. package/package.json +1 -1
package/knowledge.js CHANGED
@@ -48,6 +48,17 @@ function fail(msg) {
48
48
  process.exitCode = 1;
49
49
  }
50
50
 
51
+ /* Blocking sleep, for backing off between push retries. This script is a
52
+ * short-lived synchronous CLI, so there is no event loop to yield to and an
53
+ * async sleep would mean threading promises through every git call. */
54
+ function sleepSync(ms) {
55
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
56
+ }
57
+
58
+ /* stderr of the last failed push attempt, so reportSession() can say WHY
59
+ * instead of only that it failed. */
60
+ let lastPushError = '';
61
+
51
62
  function parseArgs(argv) {
52
63
  const out = {};
53
64
  for (const arg of argv) {
@@ -433,15 +444,23 @@ function cmdPublish(args) {
433
444
  const msg = String(args.msg || 'knowledge: capture session updates').replace(/["\\]/g, "'").slice(0, 200);
434
445
  run(`git commit -m "${msg}"`);
435
446
 
436
- // 6. rebase-before-push, retrying the CI version-bump race up to 3x
447
+ // 6. rebase-before-push, retrying the CI version-bump race up to 3x.
448
+ // --autostash is REQUIRED, not a nicety: plain `git rebase` aborts with
449
+ // "cannot rebase: You have unstaged changes" if ANY tracked file is dirty,
450
+ // even one unrelated to knowledge/ (.claude/settings.json is the usual
451
+ // culprit — the harness edits it). That failure is not a conflict and not a
452
+ // race, so it fails identically on all 3 attempts and every capture ends in
453
+ // a manual push. --autostash stashes the unrelated edits, rebases, restores.
454
+ let lastError = '';
437
455
  for (let attempt = 1; attempt <= 3; attempt++) {
438
456
  try {
439
457
  run('git fetch origin');
440
- run(`git rebase origin/${branch}`);
458
+ run(`git rebase --autostash origin/${branch}`);
441
459
  run(`git push origin HEAD:${branch}`);
442
460
  console.log(`PUBLISH: PUSHED to ${branch} — CI publishes a new npm version; teammates get it on \`npx toga-ai\``);
443
461
  return;
444
462
  } catch (e) {
463
+ lastError = String((e && (e.stderr || e.stdout)) || (e && e.message) || '').trim();
445
464
  let status = '';
446
465
  try { status = run('git status --porcelain'); } catch { /* ignore */ }
447
466
  if (/^(UU|AA|DD|AU|UA|DU|UD) /m.test(status)) {
@@ -449,10 +468,15 @@ function cmdPublish(args) {
449
468
  console.log(`PUBLISH: CONFLICT — rebase onto origin/${branch} hit a real conflict; resolve manually, then re-run publish`);
450
469
  process.exitCode = 1; return;
451
470
  }
452
- // else: almost certainly a fresh CI bump (non-fast-forward) — loop and retry
471
+ // else: most likely a fresh CI bump (non-fast-forward). Back off before
472
+ // retrying — an instant retry loses the same race three times over.
473
+ if (attempt < 3) sleepSync(2000 * attempt);
453
474
  }
454
475
  }
476
+ // Always surface WHY. Swallowing stderr here is what disguised the dirty-tree
477
+ // bug above as a flaky CI race for months.
455
478
  console.log(`PUBLISH: PUSH_FAILED after 3 attempts — run manually: git -C "${TEAM}" push origin HEAD:${branch}`);
479
+ if (lastError) console.log('PUBLISH: last git error — ' + lastError.split('\n').slice(0, 5).join(' | '));
456
480
  process.exitCode = 1;
457
481
  }
458
482
 
@@ -766,20 +790,25 @@ function pushStaged(opts) {
766
790
  }
767
791
  const msg = String((opts && opts.msg) || 'sessions: update').replace(/["\\]/g, "'").slice(0, 200);
768
792
  gitRun(`git commit -m "${msg}"`);
793
+ // --autostash is REQUIRED: plain `git rebase` refuses to run while ANY tracked
794
+ // file is dirty, even one unrelated to what we staged, which fails identically
795
+ // on every attempt and forces a manual push. See cmdPublish() for the detail.
769
796
  for (let attempt = 1; attempt <= 3; attempt++) {
770
797
  try {
771
798
  gitRun('git fetch origin');
772
- gitRun(`git rebase origin/${branch}`);
799
+ gitRun(`git rebase --autostash origin/${branch}`);
773
800
  gitRun(`git push origin HEAD:${branch}`);
774
801
  return 'PUSHED';
775
802
  } catch (e) {
803
+ lastPushError = String((e && (e.stderr || e.stdout)) || (e && e.message) || '').trim();
776
804
  let status = '';
777
805
  try { status = gitRun('git status --porcelain'); } catch { /* ignore */ }
778
806
  if (/^(UU|AA|DD|AU|UA|DU|UD) /m.test(status)) {
779
807
  try { gitRun('git rebase --abort'); } catch { /* ignore */ }
780
808
  return 'CONFLICT';
781
809
  }
782
- // else: almost certainly a fresh CI bump (non-fast-forward) — retry
810
+ // else: most likely a fresh CI bump (non-fast-forward) — back off, retry
811
+ if (attempt < 3) sleepSync(2000 * attempt);
783
812
  }
784
813
  }
785
814
  return 'PUSH_FAILED';
@@ -792,7 +821,9 @@ function reportSession(status, branch) {
792
821
  if (status === 'NO_CHANGES') { console.log('SESSION: NO_CHANGES — nothing new to push'); return; }
793
822
  if (status === 'CONFLICT') { console.log(`SESSION: CONFLICT — rebase onto origin/${br} hit a real conflict; resolve manually, then retry`); process.exitCode = 1; return; }
794
823
  if (status && status.startsWith('ABORT_OUTSIDE')) { console.log('SESSION: ABORT_OUTSIDE_SESSIONS — refusing to commit non-session files: ' + status.slice('ABORT_OUTSIDE:'.length)); process.exitCode = 1; return; }
795
- console.log(`SESSION: PUSH_FAILED after 3 attempts — run manually: git -C "${__dirname}" push origin HEAD:${br}`); process.exitCode = 1;
824
+ console.log(`SESSION: PUSH_FAILED after 3 attempts — run manually: git -C "${__dirname}" push origin HEAD:${br}`);
825
+ if (lastPushError) console.log('SESSION: last git error — ' + lastPushError.split('\n').slice(0, 5).join(' | '));
826
+ process.exitCode = 1;
796
827
  }
797
828
 
798
829
  function fetchQuiet() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.479",
3
+ "version": "1.0.480",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",