orchestrix-yuri 4.1.5 → 4.1.6

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.
@@ -613,12 +613,14 @@ 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 && count > 0) {
621
+ result.byStatus[key] = count;
620
622
  result.totalStories += count;
621
- if (status === 'Done') result.doneStories += count;
623
+ if (key === 'Done') result.doneStories += count;
622
624
  }
623
625
  }
624
626
  }
@@ -641,16 +643,14 @@ class PhaseOrchestrator {
641
643
  }
642
644
  }
643
645
 
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 */ }
646
+ // Count epics from docs/prd/ (epic YAML/MD files) as fallback
647
+ if (result.totalEpics === 0) {
648
+ const prdDir = path.join(this._projectRoot, 'docs', 'prd');
649
+ if (fs.existsSync(prdDir)) {
650
+ try {
651
+ result.totalEpics = fs.readdirSync(prdDir).filter((f) => /^epic/i.test(f)).length;
652
+ } catch { /* ok */ }
653
+ }
654
654
  }
655
655
 
656
656
  // Detect current active agent from tmux panes
@@ -575,12 +575,14 @@ class Router {
575
575
  const output = execSync(`bash "${scriptPath}" "${projectRoot}"`, { encoding: 'utf8', timeout: 10000 }).trim();
576
576
  if (output && output !== 'NO_STORIES_DIR') {
577
577
  for (const line of output.split('\n')) {
578
- const [status, cnt] = line.split(':');
578
+ const [key, cnt] = line.split(':');
579
579
  const n = parseInt(cnt, 10) || 0;
580
- if (n > 0) {
581
- byStatus[status] = n;
580
+ if (key === 'Epics') {
581
+ totalEpics = n;
582
+ } else if (n > 0) {
583
+ byStatus[key] = n;
582
584
  totalStories += n;
583
- if (status === 'Done') doneStories += n;
585
+ if (key === 'Done') doneStories += n;
584
586
  }
585
587
  }
586
588
  }
@@ -617,15 +619,14 @@ class Router {
617
619
  }
618
620
  }
619
621
 
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 */ }
622
+ // 4. Count epics from docs/prd/ (epic YAML files)
623
+ if (totalEpics === 0) {
624
+ const prdDir = path.join(projectRoot, 'docs', 'prd');
625
+ if (fs.existsSync(prdDir)) {
626
+ try {
627
+ totalEpics = fs.readdirSync(prdDir).filter((f) => /^epic/i.test(f)).length;
628
+ } catch { /* ok */ }
629
+ }
629
630
  }
630
631
 
631
632
  // 5. Running time
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "4.1.5",
3
+ "version": "4.1.6",
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,28 @@ 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 epics by unique filename prefix (e.g., 1.x → epic 1, 2.x → epic 2)
42
+ epics=$(ls "$STORIES_DIR" 2>/dev/null | grep -oE '^[0-9]+' | sort -u | wc -l | tr -d ' ')
43
+ echo "Epics:$epics"