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,
|
|
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 [
|
|
616
|
+
const [key, countStr] = line.split(':');
|
|
617
617
|
const count = parseInt(countStr, 10) || 0;
|
|
618
|
-
if (
|
|
619
|
-
result.
|
|
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 (
|
|
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/
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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.
|
|
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}`);
|
package/lib/gateway/router.js
CHANGED
|
@@ -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 [
|
|
579
|
+
const [key, cnt] = line.split(':');
|
|
579
580
|
const n = parseInt(cnt, 10) || 0;
|
|
580
|
-
if (
|
|
581
|
-
|
|
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 (
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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)
|
|
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,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
|
|
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}"
|