orchestrix-yuri 4.1.5 → 4.1.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.
@@ -596,7 +596,7 @@ class PhaseOrchestrator {
596
596
  */
597
597
  _gatherDevProgress() {
598
598
  const result = {
599
- totalEpics: 0, doneEpics: 0,
599
+ totalEpics: 0, currentEpic: 0,
600
600
  totalStories: 0, doneStories: 0,
601
601
  byStatus: {},
602
602
  currentAgent: null, currentWindow: null, currentStory: null,
@@ -613,12 +613,16 @@ class PhaseOrchestrator {
613
613
 
614
614
  if (output && output !== 'NO_STORIES_DIR') {
615
615
  for (const line of output.split('\n')) {
616
- const [status, countStr] = line.split(':');
616
+ const [key, countStr] = line.split(':');
617
617
  const count = parseInt(countStr, 10) || 0;
618
- if (status && count > 0) {
619
- result.byStatus[status] = count;
618
+ if (key === 'Epics') {
619
+ result.totalEpics = count;
620
+ } else if (key === 'CurrentEpic') {
621
+ result.currentEpic = count;
622
+ } else if (key && count > 0) {
623
+ result.byStatus[key] = count;
620
624
  result.totalStories += count;
621
- if (status === 'Done') result.doneStories += count;
625
+ if (key === 'Done') result.doneStories += count;
622
626
  }
623
627
  }
624
628
  }
@@ -641,16 +645,17 @@ class PhaseOrchestrator {
641
645
  }
642
646
  }
643
647
 
644
- // Count epics from docs/stories/ directory
645
- const storiesDir = path.join(this._projectRoot, 'docs', 'stories');
646
- if (fs.existsSync(storiesDir)) {
647
- try {
648
- const entries = fs.readdirSync(storiesDir);
649
- const epicDirs = entries.filter((e) => {
650
- return fs.statSync(path.join(storiesDir, e)).isDirectory() && e.startsWith('epic');
651
- });
652
- result.totalEpics = epicDirs.length || result.totalEpics;
653
- } catch { /* ok */ }
648
+ // Count epics from docs/prd/ (epic YAML/MD files) as fallback
649
+ if (result.totalEpics === 0) {
650
+ const prdDir = path.join(this._projectRoot, 'docs', 'prd');
651
+ if (fs.existsSync(prdDir)) {
652
+ try {
653
+ const epicNums = fs.readdirSync(prdDir)
654
+ .map((f) => { const m = f.match(/^epic-(\d+)/i); return m ? parseInt(m[1], 10) : 0; })
655
+ .filter((n) => n > 0);
656
+ result.totalEpics = epicNums.length > 0 ? Math.max(...epicNums) : 0;
657
+ } catch { /* ok */ }
658
+ }
654
659
  }
655
660
 
656
661
  // Detect current active agent from tmux panes
@@ -690,7 +695,7 @@ class PhaseOrchestrator {
690
695
  ];
691
696
 
692
697
  if (p.totalEpics > 0) {
693
- lines.push(`Epic: ${p.doneEpics}/${p.totalEpics}`);
698
+ lines.push(`Epic: ${p.currentEpic}/${p.totalEpics}`);
694
699
  }
695
700
  lines.push(`Story: ${p.doneStories}/${p.totalStories} (${pct}%)`);
696
701
  lines.push(`${bar}`);
@@ -570,17 +570,22 @@ class Router {
570
570
  let byStatus = {};
571
571
  let totalStories = 0;
572
572
  let doneStories = 0;
573
+ let currentEpic = 0;
573
574
  const scriptPath = path.join(os.homedir(), '.claude', 'skills', 'yuri', 'scripts', 'scan-stories.sh');
574
575
  if (fs.existsSync(scriptPath)) {
575
576
  const output = execSync(`bash "${scriptPath}" "${projectRoot}"`, { encoding: 'utf8', timeout: 10000 }).trim();
576
577
  if (output && output !== 'NO_STORIES_DIR') {
577
578
  for (const line of output.split('\n')) {
578
- const [status, cnt] = line.split(':');
579
+ const [key, cnt] = line.split(':');
579
580
  const n = parseInt(cnt, 10) || 0;
580
- if (n > 0) {
581
- byStatus[status] = n;
581
+ if (key === 'Epics') {
582
+ totalEpics = n;
583
+ } else if (key === 'CurrentEpic') {
584
+ currentEpic = n;
585
+ } else if (n > 0) {
586
+ byStatus[key] = n;
582
587
  totalStories += n;
583
- if (status === 'Done') doneStories += n;
588
+ if (key === 'Done') doneStories += n;
584
589
  }
585
590
  }
586
591
  }
@@ -617,15 +622,17 @@ class Router {
617
622
  }
618
623
  }
619
624
 
620
- // 4. Count epics
621
- let totalEpics = 0;
622
- const storiesDir = path.join(projectRoot, 'docs', 'stories');
623
- if (fs.existsSync(storiesDir)) {
624
- try {
625
- totalEpics = fs.readdirSync(storiesDir).filter((e) =>
626
- fs.statSync(path.join(storiesDir, e)).isDirectory() && /epic/i.test(e)
627
- ).length;
628
- } catch { /* ok */ }
625
+ // 4. Count epics from docs/prd/ (epic YAML files)
626
+ if (totalEpics === 0) {
627
+ const prdDir = path.join(projectRoot, 'docs', 'prd');
628
+ if (fs.existsSync(prdDir)) {
629
+ try {
630
+ const epicNums = fs.readdirSync(prdDir)
631
+ .map((f) => { const m = f.match(/^epic-(\d+)/i); return m ? parseInt(m[1], 10) : 0; })
632
+ .filter((n) => n > 0);
633
+ totalEpics = epicNums.length > 0 ? Math.max(...epicNums) : 0;
634
+ } catch { /* ok */ }
635
+ }
629
636
  }
630
637
 
631
638
  // 5. Running time
@@ -641,7 +648,9 @@ class Router {
641
648
  const bar = '▓'.repeat(filled) + '░'.repeat(barLen - filled);
642
649
 
643
650
  const lines = ['📊 **Dev Progress Report**', '━━━━━━━━━━━━━━━━━━━━━'];
644
- if (totalEpics > 0) lines.push(`Epic: ${totalEpics}`);
651
+ if (totalEpics > 0) {
652
+ lines.push(`Epic: ${currentEpic}/${totalEpics}`);
653
+ }
645
654
  lines.push(`Story: ${doneStories}/${totalStories} (${pct}%)`);
646
655
  lines.push(`${bar} ${pct}%`);
647
656
  lines.push('━━━━━━━━━━━━━━━━━━━━━');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "4.1.5",
3
+ "version": "4.1.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": {
@@ -1,6 +1,12 @@
1
1
  #!/bin/bash
2
2
  # Yuri Story Status Scanner
3
3
  # Usage: scan-stories.sh <project_root>
4
+ #
5
+ # Supports two status formats in story files:
6
+ # Format A: "Status: Done" (inline)
7
+ # Format B: "## Status\n\nDone" (heading + next non-empty line)
8
+ #
9
+ # Also outputs epic count based on filename prefix (e.g., 1.x, 2.x)
4
10
  set +e
5
11
 
6
12
  STORIES_DIR="$1/docs/stories"
@@ -10,7 +16,37 @@ if [ ! -d "$STORIES_DIR" ]; then
10
16
  exit 1
11
17
  fi
12
18
 
19
+ # Scan each story file for status
13
20
  for status in Done InProgress Review Blocked Approved AwaitingArchReview RequiresRevision Escalated; do
14
- count=$(grep -rl "Status:.*$status" "$STORIES_DIR" 2>/dev/null | wc -l | tr -d ' ')
21
+ count=0
22
+ for f in "$STORIES_DIR"/*.md "$STORIES_DIR"/*.yaml; do
23
+ [ -f "$f" ] || continue
24
+ # Format A: "Status: Done" or "Status:Done"
25
+ if grep -qi "Status:[[:space:]]*$status" "$f" 2>/dev/null; then
26
+ count=$((count + 1))
27
+ continue
28
+ fi
29
+ # Format B: "## Status" heading, then status on a subsequent non-empty line
30
+ if grep -q "## Status" "$f" 2>/dev/null; then
31
+ # Extract the first non-empty line after "## Status"
32
+ extracted=$(awk '/^## Status/{found=1; next} found && /^[[:space:]]*$/{next} found{print; exit}' "$f" 2>/dev/null)
33
+ if echo "$extracted" | grep -qi "^$status$" 2>/dev/null; then
34
+ count=$((count + 1))
35
+ fi
36
+ fi
37
+ done
15
38
  echo "$status:$count"
16
39
  done
40
+
41
+ # Count total epics from docs/prd/epic-N-*.yaml (max N = total epics)
42
+ PRD_DIR="$1/docs/prd"
43
+ if [ -d "$PRD_DIR" ]; then
44
+ epics=$(ls "$PRD_DIR" 2>/dev/null | grep -oE '^epic-[0-9]+' | grep -oE '[0-9]+' | sort -n | tail -1)
45
+ echo "Epics:${epics:-0}"
46
+ else
47
+ echo "Epics:0"
48
+ fi
49
+
50
+ # Current epic (max prefix from story files)
51
+ current_epic=$(ls "$STORIES_DIR" 2>/dev/null | grep -oE '^[0-9]+' | sort -n | tail -1)
52
+ echo "CurrentEpic:${current_epic:-0}"