orchestrix-yuri 4.8.5 → 4.8.7

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.
@@ -679,10 +679,17 @@ class PhaseOrchestrator {
679
679
  this._lastActiveAgent = progress.currentAgent;
680
680
  }
681
681
 
682
- // Check if all stories done
682
+ // Check if all stories done — but ONLY if no agent is actively working.
683
+ // If an agent is busy (e.g., SM drafting new stories), story counts are stale.
683
684
  if (progress.totalStories > 0 && progress.doneStories >= progress.totalStories) {
684
- this._completeDev();
685
- return;
685
+ if (progress.currentAgent) {
686
+ // Agent still working — stories may be in flux (new ones being created)
687
+ log.engine(`All ${progress.doneStories}/${progress.totalStories} stories done but ${progress.currentAgent} is still active — waiting`);
688
+ } else {
689
+ // No active agent + all stories done → truly complete
690
+ this._completeDev();
691
+ return;
692
+ }
686
693
  }
687
694
 
688
695
  // Periodic progress report
@@ -841,10 +848,10 @@ class PhaseOrchestrator {
841
848
  }
842
849
 
843
850
  _progressBar(pct) {
851
+ const clamped = Math.min(100, Math.max(0, pct));
844
852
  const total = 20;
845
- const filled = Math.round(pct / 100 * total);
846
- const empty = total - filled;
847
- return '▓'.repeat(filled) + '░'.repeat(empty) + ` ${pct}%`;
853
+ const filled = Math.min(total, Math.round(clamped / 100 * total));
854
+ return '▓'.repeat(filled) + '░'.repeat(total - filled) + ` ${clamped}%`;
848
855
  }
849
856
 
850
857
  _formatDuration(ms) {
@@ -38,7 +38,7 @@ const META_COMMANDS = {
38
38
  // All natural language ("怎么样了", "进度如何") goes through dispatcher
39
39
  // so Claude's understanding ability decides the routing.
40
40
  const STATUS_PATTERNS = [
41
- /^\*status\b/i,
41
+ /^[*/]status\b/i,
42
42
  ];
43
43
 
44
44
  /**
@@ -652,10 +652,24 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
652
652
  try {
653
653
  const focus = yaml.load(fs.readFileSync(focusPath, 'utf8')) || {};
654
654
 
655
- const phaseNum = parseInt(focus.phase, 10);
655
+ // Always check for live dev session first — it's the ground truth.
656
+ // focus.yaml may be stale (e.g., still says phase 4 after dev restarted).
657
+ let hasLiveDevSession = false;
658
+ try {
659
+ const { execSync } = require('child_process');
660
+ const sessions = execSync('tmux list-sessions -F "#{session_name}" 2>/dev/null', { encoding: 'utf8' }).trim();
661
+ const projectName = path.basename(projectRoot).toLowerCase();
662
+ const devSession = sessions.split('\n').find((s) =>
663
+ s.startsWith('orchestrix-') && s.toLowerCase().includes(projectName)
664
+ ) || sessions.split('\n').find((s) => s.startsWith('orchestrix-'));
665
+ if (devSession) {
666
+ const tmx = require('./engine/tmux-utils');
667
+ hasLiveDevSession = tmx.hasSession(devSession);
668
+ }
669
+ } catch { /* no tmux */ }
656
670
 
657
- // Dev phase: generate progress card (no phase3.yaml dependency)
658
- if (phaseNum === 3) {
671
+ // If live dev session exists, show dev progress card regardless of focus.yaml phase
672
+ if (hasLiveDevSession) {
659
673
  try {
660
674
  const card = this._buildStatusCard(projectRoot, focus);
661
675
  if (card) parts.push(card);
@@ -664,8 +678,10 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
664
678
  }
665
679
  }
666
680
 
667
- // Test phase: generate test progress card from phase4.yaml
668
- if (phaseNum === 4 && parts.length === 0) {
681
+ const phaseNum = parseInt(focus.phase, 10);
682
+
683
+ // Test phase: only show if no live dev session and no card yet
684
+ if (phaseNum === 4 && parts.length === 0 && !hasLiveDevSession) {
669
685
  try {
670
686
  const card = this._buildTestStatusCard(projectRoot);
671
687
  if (card) parts.push(card);
@@ -1060,9 +1076,9 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
1060
1076
 
1061
1077
  // 6. Build card
1062
1078
  const total = totalPlanned || createdStories; // prefer planned, fallback to created
1063
- const pct = total > 0 ? Math.round(doneStories / total * 100) : 0;
1079
+ const pct = total > 0 ? Math.min(100, Math.round(doneStories / total * 100)) : 0;
1064
1080
  const barLen = 20;
1065
- const filled = Math.round(pct / 100 * barLen);
1081
+ const filled = Math.min(barLen, Math.round(pct / 100 * barLen));
1066
1082
  const bar = '▓'.repeat(filled) + '░'.repeat(barLen - filled);
1067
1083
 
1068
1084
  const lines = ['📊 **Dev Progress Report**', '━━━━━━━━━━━━━━━━━━━━━'];
@@ -1111,10 +1127,10 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
1111
1127
  const passed = state.epics.filter((e) => e.status === 'passed').length;
1112
1128
  const failed = state.epics.filter((e) => e.status === 'failed').length;
1113
1129
  const tested = passed + failed;
1114
- const pct = total > 0 ? Math.round((tested / total) * 100) : 0;
1130
+ const pct = total > 0 ? Math.min(100, Math.round((tested / total) * 100)) : 0;
1115
1131
 
1116
1132
  const barTotal = 20;
1117
- const filled = Math.round(pct / 100 * barTotal);
1133
+ const filled = Math.min(barTotal, Math.round(pct / 100 * barTotal));
1118
1134
  const bar = '▓'.repeat(filled) + '░'.repeat(barTotal - filled) + ` ${pct}%`;
1119
1135
 
1120
1136
  const lines = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "4.8.5",
3
+ "version": "4.8.7",
4
4
  "description": "Yuri — Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {