bigpowers 2.1.1 → 2.1.3
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.3](https://github.com/danielvm-git/bigpowers/compare/v2.1.2...v2.1.3) (2026-06-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **dashboard:** resolve wrong folder targeting and update TUI metrics ([bc8d84a](https://github.com/danielvm-git/bigpowers/commit/bc8d84af6221aa59aa131bf3fdd03f25bc67ac33))
|
|
7
|
+
|
|
8
|
+
## [2.1.2](https://github.com/danielvm-git/bigpowers/compare/v2.1.1...v2.1.2) (2026-06-12)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **dashboard:** support alternative epic id and title keys in yaml parser ([d911e8f](https://github.com/danielvm-git/bigpowers/commit/d911e8f61c608a96d8d3d42cb4a4d19cff71ee3d))
|
|
14
|
+
|
|
1
15
|
## [2.1.1](https://github.com/danielvm-git/bigpowers/compare/v2.1.0...v2.1.1) (2026-06-11)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -66,21 +66,48 @@ function readEpicShards(projectRoot) {
|
|
|
66
66
|
if (!fs.existsSync(epicsDir)) {
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
-
const files = fs.readdirSync(epicsDir)
|
|
70
|
-
.filter(f => f.endsWith('.yaml'))
|
|
71
|
-
.sort();
|
|
72
|
-
|
|
73
69
|
const epics = [];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
|
|
71
|
+
function scanDir(dirPath) {
|
|
72
|
+
if (!fs.existsSync(dirPath)) return;
|
|
73
|
+
const entries = fs.readdirSync(dirPath);
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const entryPath = path.join(dirPath, entry);
|
|
76
|
+
const stat = fs.statSync(entryPath);
|
|
77
|
+
if (stat.isDirectory()) {
|
|
78
|
+
if (entry === 'archive') {
|
|
79
|
+
scanDir(entryPath);
|
|
80
|
+
} else {
|
|
81
|
+
const possibleYaml = path.join(entryPath, 'epic.yaml');
|
|
82
|
+
if (fs.existsSync(possibleYaml)) {
|
|
83
|
+
loadYaml(possibleYaml);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else if (stat.isFile() && entry.endsWith('.yaml')) {
|
|
87
|
+
loadYaml(entryPath);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadYaml(yamlPath) {
|
|
93
|
+
const content = fs.readFileSync(yamlPath, 'utf8');
|
|
77
94
|
const data = yaml.load(content);
|
|
78
95
|
epics.push({
|
|
79
|
-
id: data.id || null,
|
|
80
|
-
title: data.title || null,
|
|
96
|
+
id: data.id || data.epic || null,
|
|
97
|
+
title: data.title || data.name || null,
|
|
81
98
|
stories: data.stories || []
|
|
82
99
|
});
|
|
83
100
|
}
|
|
101
|
+
|
|
102
|
+
scanDir(epicsDir);
|
|
103
|
+
|
|
104
|
+
// Sort epics by ID numerically/alphabetically
|
|
105
|
+
epics.sort((a, b) => {
|
|
106
|
+
const idA = a.id || '';
|
|
107
|
+
const idB = b.id || '';
|
|
108
|
+
return idA.localeCompare(idB, undefined, { numeric: true, sensitivity: 'base' });
|
|
109
|
+
});
|
|
110
|
+
|
|
84
111
|
return epics.length > 0 ? epics : null;
|
|
85
112
|
} catch (err) {
|
|
86
113
|
return null;
|
|
@@ -156,7 +156,7 @@ function start(projectRoot) {
|
|
|
156
156
|
// Title bar: fixed project identity
|
|
157
157
|
titleBar.setContent(' {bold}{cyan-fg}⚙ bigpowers factory{/cyan-fg}{/bold} {gray-fg}v2 — seed {cyan-fg}→{/cyan-fg} epics {cyan-fg}→{/cyan-fg} mvp{/gray-fg}');
|
|
158
158
|
|
|
159
|
-
renderMetricsBar(metricsBar, metrics, stateData, epics, cycleTimes);
|
|
159
|
+
renderMetricsBar(metricsBar, metrics, stateData, epics, cycleTimes, executionStatus);
|
|
160
160
|
renderPipeline(pipeline, stateData);
|
|
161
161
|
renderEpicQueue(epicQueue, epics, executionStatus);
|
|
162
162
|
renderStateYaml(actionLog, stateData);
|
|
@@ -1,29 +1,40 @@
|
|
|
1
|
-
function renderMetricsBar(box, projectMetrics, stateData, epics, cycleTimes) {
|
|
1
|
+
function renderMetricsBar(box, projectMetrics, stateData, epics, cycleTimes, executionStatus) {
|
|
2
2
|
if (!box || typeof box.setContent !== 'function') {
|
|
3
3
|
return;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
const ct = cycleTimes || [];
|
|
7
6
|
const epicList = epics && Array.isArray(epics) ? epics : [];
|
|
7
|
+
const statusMap = executionStatus?.epics || new Map();
|
|
8
8
|
|
|
9
|
-
// Compute totals
|
|
9
|
+
// Compute totals and completed counts dynamically from execution-status.yaml
|
|
10
10
|
let totalStories = 0;
|
|
11
11
|
let targetBcps = 0;
|
|
12
|
+
let doneStories = 0;
|
|
13
|
+
let deliveredBcps = 0;
|
|
14
|
+
let doneEpics = 0;
|
|
15
|
+
|
|
12
16
|
epicList.forEach(epic => {
|
|
17
|
+
let epicDone = true;
|
|
18
|
+
let hasStories = false;
|
|
13
19
|
if (epic.stories && Array.isArray(epic.stories)) {
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
epic.stories.forEach(s => {
|
|
21
|
+
hasStories = true;
|
|
22
|
+
totalStories++;
|
|
23
|
+
targetBcps += s.bcps || 0;
|
|
24
|
+
if (statusMap.get(s.id) === 'done') {
|
|
25
|
+
doneStories++;
|
|
26
|
+
deliveredBcps += s.bcps || 0;
|
|
27
|
+
} else {
|
|
28
|
+
epicDone = false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (hasStories && epicDone) {
|
|
33
|
+
doneEpics++;
|
|
16
34
|
}
|
|
17
35
|
});
|
|
18
36
|
|
|
19
|
-
const doneStories = ct.length;
|
|
20
37
|
const totalEpics = epicList.length;
|
|
21
|
-
const doneStoryIds = new Set(ct.map(c => c.id));
|
|
22
|
-
const doneEpics = epicList.filter(e =>
|
|
23
|
-
e.stories && e.stories.length > 0 && e.stories.every(s => doneStoryIds.has(s.id))
|
|
24
|
-
).length;
|
|
25
|
-
|
|
26
|
-
const deliveredBcps = projectMetrics?.totalBcps ?? 0;
|
|
27
38
|
const totalMin = projectMetrics?.totalMin ?? 0;
|
|
28
39
|
const avgBcpPerHour = projectMetrics?.avgBcpPerHour;
|
|
29
40
|
const version = stateData?.release?.target_version ?? '—';
|
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ function parseEpicsFromReleasePlan(text) {
|
|
|
41
41
|
const blocks = text.split(/\n\s*-\s+id:\s+/).slice(1);
|
|
42
42
|
for (const block of blocks) {
|
|
43
43
|
const id = block.match(/^(\S+)/)?.[1];
|
|
44
|
-
const title = block.match(/title:\s*"?([^"\n]+)"?/)?.[1];
|
|
44
|
+
const title = block.match(/(?:title|name):\s*"?([^"\n]+)"?/)?.[1];
|
|
45
45
|
const wsjf = parseFloat(block.match(/wsjf:\s*([\d.]+)/)?.[1] || '0');
|
|
46
46
|
const file = block.match(/file:\s*(\S+)/)?.[1];
|
|
47
47
|
if (id) epics.push({ id, title: title || id, wsjf, file });
|
|
@@ -50,7 +50,7 @@ function parseEpicsFromReleasePlan(text) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function parseSimpleEpic(text) {
|
|
53
|
-
const title = text.match(/^title:\s*"?([^"\n]+)"?/m)?.[1];
|
|
53
|
+
const title = text.match(/^(?:title|name):\s*"?([^"\n]+)"?/m)?.[1];
|
|
54
54
|
const stories = [];
|
|
55
55
|
const storyBlocks = text.split(/\n\s*-\s+id:\s+/).slice(1);
|
|
56
56
|
for (const sb of storyBlocks) {
|