bigpowers 1.5.0 → 2.0.0
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 +35 -0
- package/SKILL-INDEX.md +2 -2
- package/assess-impact/SKILL.md +2 -2
- package/build-epic/SKILL.md +5 -5
- package/change-request/SKILL.md +4 -4
- package/dashboard/src/loaders/reader.js +2 -1
- package/dashboard/src/tui/filesystem.js +4 -5
- package/dashboard/src/tui/index.js +2 -2
- package/dashboard/src/tui/ledger.js +3 -5
- package/dashboard/src/tui/state-yaml.js +5 -7
- package/deepen-architecture/SKILL.md +6 -6
- package/define-success/SKILL.md +1 -1
- package/develop-tdd/SKILL.md +3 -3
- package/elaborate-spec/SKILL.md +7 -7
- package/execute-plan/SKILL.md +4 -4
- package/investigate-bug/SKILL.md +1 -1
- package/kickoff-branch/SKILL.md +1 -1
- package/map-codebase/SKILL.md +4 -4
- package/migrate-spec/SKILL.md +8 -8
- package/model-domain/SKILL.md +8 -8
- package/orchestrate-project/SKILL.md +2 -2
- package/package.json +1 -1
- package/plan-release/SKILL.md +73 -27
- package/plan-work/SKILL.md +23 -7
- package/release-branch/SKILL.md +15 -0
- package/research-first/SKILL.md +2 -2
- package/run-evals/SKILL.md +2 -2
- package/run-planning/SKILL.md +1 -1
- package/scope-work/SKILL.md +4 -4
- package/scripts/land-branch.sh +28 -0
- package/seed-conventions/SKILL.md +37 -6
- package/session-state/SKILL.md +34 -3
- package/slice-tasks/SKILL.md +4 -4
- package/survey-context/SKILL.md +2 -2
- package/trace-requirement/SKILL.md +6 -6
- package/verify-work/SKILL.md +35 -2
- package/write-document/SKILL.md +1 -1
- package/dashboard/src/data/gate-status.js +0 -32
- package/dashboard/src/data/metrics.js +0 -89
- package/dashboard/src/data/pipeline-map.js +0 -32
- package/dashboard/src/data/reader.js +0 -122
- package/dashboard/src/data/watcher.js +0 -108
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const COLOR_MAP = {
|
|
2
|
-
ready: 'green',
|
|
3
|
-
blocked: 'red',
|
|
4
|
-
in_progress: 'yellow',
|
|
5
|
-
done: 'green',
|
|
6
|
-
pending: 'dim',
|
|
7
|
-
active: 'cyan'
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const ICON_MAP = {
|
|
11
|
-
ready: '●',
|
|
12
|
-
blocked: '✕',
|
|
13
|
-
in_progress: '◐',
|
|
14
|
-
done: '✓',
|
|
15
|
-
pending: '○',
|
|
16
|
-
active: '◐'
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
function gateColor(gateStatus) {
|
|
20
|
-
return COLOR_MAP[gateStatus] || 'dim';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function gateIcon(gateStatus) {
|
|
24
|
-
return ICON_MAP[gateStatus] || '-';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
module.exports = {
|
|
28
|
-
gateColor,
|
|
29
|
-
gateIcon,
|
|
30
|
-
COLOR_MAP,
|
|
31
|
-
ICON_MAP
|
|
32
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* computeEpicMetrics(cycleTimes)
|
|
3
|
-
* Groups cycle times by epic prefix (e.g. e01 from e01s01)
|
|
4
|
-
* Returns Map of epicId to { avgCycleMin, totalBcps, avgBcpPerHour }
|
|
5
|
-
*/
|
|
6
|
-
function computeEpicMetrics(cycleTimes) {
|
|
7
|
-
if (!cycleTimes || cycleTimes.length === 0) {
|
|
8
|
-
return new Map();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const epicMap = new Map();
|
|
12
|
-
for (const story of cycleTimes) {
|
|
13
|
-
// Extract epic prefix: e01s01 -> e01
|
|
14
|
-
const epicId = story.id ? story.id.replace(/s\d+$/, '') : null;
|
|
15
|
-
if (!epicId) continue;
|
|
16
|
-
|
|
17
|
-
if (!epicMap.has(epicId)) {
|
|
18
|
-
epicMap.set(epicId, { stories: [] });
|
|
19
|
-
}
|
|
20
|
-
epicMap.get(epicId).stories.push(story);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const result = new Map();
|
|
24
|
-
for (const [epicId, data] of epicMap) {
|
|
25
|
-
const stories = data.stories;
|
|
26
|
-
const totalBcps = stories.reduce((sum, s) => sum + (s.bcps || 0), 0);
|
|
27
|
-
const totalMin = stories.reduce((sum, s) => sum + (s.cycleMin || 0), 0);
|
|
28
|
-
const avgCycleMin = stories.length > 0 ? totalMin / stories.length : 0;
|
|
29
|
-
const avgBcpPerHour = totalMin > 0 ? (totalBcps * 60) / totalMin : 0;
|
|
30
|
-
|
|
31
|
-
result.set(epicId, {
|
|
32
|
-
avgCycleMin,
|
|
33
|
-
totalBcps,
|
|
34
|
-
avgBcpPerHour
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* computeProjectMetrics(cycleTimes)
|
|
43
|
-
* Returns { totalBcps, totalMin, avgBcpPerHour } or null if no data
|
|
44
|
-
*/
|
|
45
|
-
function computeProjectMetrics(cycleTimes) {
|
|
46
|
-
if (!cycleTimes || cycleTimes.length === 0) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const totalBcps = cycleTimes.reduce((sum, s) => sum + (s.bcps || 0), 0);
|
|
51
|
-
const totalMin = cycleTimes.reduce((sum, s) => sum + (s.cycleMin || 0), 0);
|
|
52
|
-
const avgBcpPerHour = totalMin > 0 ? (totalBcps * 60) / totalMin : 0;
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
totalBcps,
|
|
56
|
-
totalMin,
|
|
57
|
-
avgBcpPerHour
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* computeCurrentVelocity(cycleTimes, windowStories=3)
|
|
63
|
-
* Rolling average of last N stories
|
|
64
|
-
* Returns { avgBcpPerHour, avgCycleMin } or null if no data
|
|
65
|
-
*/
|
|
66
|
-
function computeCurrentVelocity(cycleTimes, windowStories = 3) {
|
|
67
|
-
if (!cycleTimes || cycleTimes.length === 0) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const window = Math.min(windowStories, cycleTimes.length);
|
|
72
|
-
const recentStories = cycleTimes.slice(-window);
|
|
73
|
-
|
|
74
|
-
const totalBcps = recentStories.reduce((sum, s) => sum + (s.bcps || 0), 0);
|
|
75
|
-
const totalMin = recentStories.reduce((sum, s) => sum + (s.cycleMin || 0), 0);
|
|
76
|
-
const avgCycleMin = recentStories.length > 0 ? totalMin / recentStories.length : 0;
|
|
77
|
-
const avgBcpPerHour = totalMin > 0 ? (totalBcps * 60) / totalMin : 0;
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
avgBcpPerHour,
|
|
81
|
-
avgCycleMin
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
module.exports = {
|
|
86
|
-
computeEpicMetrics,
|
|
87
|
-
computeProjectMetrics,
|
|
88
|
-
computeCurrentVelocity
|
|
89
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const STEPS = [
|
|
2
|
-
'survey-context',
|
|
3
|
-
'plan-work',
|
|
4
|
-
'kickoff-branch',
|
|
5
|
-
'develop-tdd',
|
|
6
|
-
'verify-work',
|
|
7
|
-
'audit-code',
|
|
8
|
-
'commit-message',
|
|
9
|
-
'release-branch'
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
function stepIndex(currentStep) {
|
|
13
|
-
return STEPS.indexOf(currentStep);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function stepLabel(index) {
|
|
17
|
-
if (index < 0 || index >= STEPS.length) {
|
|
18
|
-
return '-';
|
|
19
|
-
}
|
|
20
|
-
return STEPS[index];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function allSteps() {
|
|
24
|
-
return [...STEPS];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
module.exports = {
|
|
28
|
-
stepIndex,
|
|
29
|
-
stepLabel,
|
|
30
|
-
allSteps,
|
|
31
|
-
STEPS
|
|
32
|
-
};
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const yaml = require('js-yaml');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* readStateYaml(projectRoot)
|
|
7
|
-
* Reads specs/state.yaml and returns an object with mapped fields
|
|
8
|
-
* Returns null if file doesn't exist
|
|
9
|
-
*/
|
|
10
|
-
function readStateYaml(projectRoot) {
|
|
11
|
-
try {
|
|
12
|
-
const filePath = path.join(projectRoot, 'specs', 'state.yaml');
|
|
13
|
-
if (!fs.existsSync(filePath)) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
17
|
-
const data = yaml.load(content);
|
|
18
|
-
return {
|
|
19
|
-
activeFlow: data.active_flow || null,
|
|
20
|
-
activeEpic: data.active_epic_id || null,
|
|
21
|
-
activeStory: data.active_story_id || null,
|
|
22
|
-
epicCycle: data.epic_cycle || {},
|
|
23
|
-
gitBranch: data.git?.branch || null,
|
|
24
|
-
metrics: data.metrics || null,
|
|
25
|
-
release: data.release || {},
|
|
26
|
-
handoff: data.handoff || {}
|
|
27
|
-
};
|
|
28
|
-
} catch (err) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* readExecutionStatus(projectRoot)
|
|
35
|
-
* Reads specs/execution-status.yaml and returns { epics: Map }
|
|
36
|
-
* Map keys are story/epic IDs, values are status strings
|
|
37
|
-
* Returns null if file doesn't exist
|
|
38
|
-
*/
|
|
39
|
-
function readExecutionStatus(projectRoot) {
|
|
40
|
-
try {
|
|
41
|
-
const filePath = path.join(projectRoot, 'specs', 'execution-status.yaml');
|
|
42
|
-
if (!fs.existsSync(filePath)) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
-
const data = yaml.load(content);
|
|
47
|
-
const statusMap = new Map();
|
|
48
|
-
const devStatus = data.development_status || {};
|
|
49
|
-
for (const [key, value] of Object.entries(devStatus)) {
|
|
50
|
-
statusMap.set(key, value);
|
|
51
|
-
}
|
|
52
|
-
return { epics: statusMap };
|
|
53
|
-
} catch (err) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* readEpicShards(projectRoot)
|
|
60
|
-
* Reads all files in specs/epics/*.yaml
|
|
61
|
-
* Returns array of { id, title, stories } objects or null
|
|
62
|
-
*/
|
|
63
|
-
function readEpicShards(projectRoot) {
|
|
64
|
-
try {
|
|
65
|
-
const epicsDir = path.join(projectRoot, 'specs', 'epics');
|
|
66
|
-
if (!fs.existsSync(epicsDir)) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const files = fs.readdirSync(epicsDir)
|
|
70
|
-
.filter(f => f.endsWith('.yaml'))
|
|
71
|
-
.sort();
|
|
72
|
-
|
|
73
|
-
const epics = [];
|
|
74
|
-
for (const file of files) {
|
|
75
|
-
const filePath = path.join(epicsDir, file);
|
|
76
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
77
|
-
const data = yaml.load(content);
|
|
78
|
-
epics.push({
|
|
79
|
-
id: data.id || null,
|
|
80
|
-
title: data.title || null,
|
|
81
|
-
stories: data.stories || []
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return epics.length > 0 ? epics : null;
|
|
85
|
-
} catch (err) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* readCycleTimes(projectRoot)
|
|
92
|
-
* Reads specs/metrics/cycle-times.yaml stories array
|
|
93
|
-
* Returns array of { id, bcps, start, end, cycleMin, bcpPerHour } or null
|
|
94
|
-
*/
|
|
95
|
-
function readCycleTimes(projectRoot) {
|
|
96
|
-
try {
|
|
97
|
-
const filePath = path.join(projectRoot, 'specs', 'metrics', 'cycle-times.yaml');
|
|
98
|
-
if (!fs.existsSync(filePath)) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
102
|
-
const data = yaml.load(content);
|
|
103
|
-
const stories = data.stories || [];
|
|
104
|
-
return stories.map(s => ({
|
|
105
|
-
id: s.id || null,
|
|
106
|
-
bcps: s.bcps || 0,
|
|
107
|
-
start: s.start || null,
|
|
108
|
-
end: s.end || null,
|
|
109
|
-
cycleMin: s.cycle_min || 0,
|
|
110
|
-
bcpPerHour: s.bcp_per_hour || 0
|
|
111
|
-
}));
|
|
112
|
-
} catch (err) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
module.exports = {
|
|
118
|
-
readStateYaml,
|
|
119
|
-
readExecutionStatus,
|
|
120
|
-
readEpicShards,
|
|
121
|
-
readCycleTimes
|
|
122
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
const EventEmitter = require('events');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
let chokidar;
|
|
6
|
-
try {
|
|
7
|
-
chokidar = require('chokidar');
|
|
8
|
-
} catch (err) {
|
|
9
|
-
chokidar = null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function watch(projectRoot) {
|
|
13
|
-
const emitter = new EventEmitter();
|
|
14
|
-
const debounceTimers = {};
|
|
15
|
-
|
|
16
|
-
function notifyChange(file) {
|
|
17
|
-
const key = file;
|
|
18
|
-
if (debounceTimers[key]) {
|
|
19
|
-
clearTimeout(debounceTimers[key]);
|
|
20
|
-
}
|
|
21
|
-
debounceTimers[key] = setTimeout(() => {
|
|
22
|
-
emitter.emit('change', { file, data: null });
|
|
23
|
-
delete debounceTimers[key];
|
|
24
|
-
}, 300);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (chokidar) {
|
|
28
|
-
const filesToWatch = [
|
|
29
|
-
path.join(projectRoot, 'specs/state.yaml'),
|
|
30
|
-
path.join(projectRoot, 'specs/execution-status.yaml'),
|
|
31
|
-
path.join(projectRoot, 'specs/metrics/cycle-times.yaml'),
|
|
32
|
-
path.join(projectRoot, 'specs/epics')
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const watcher = chokidar.watch(filesToWatch, {
|
|
36
|
-
persistent: true,
|
|
37
|
-
awaitWriteFinish: {
|
|
38
|
-
stabilityThreshold: 100,
|
|
39
|
-
pollInterval: 100
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
watcher.on('change', (file) => {
|
|
44
|
-
notifyChange(file);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
watcher.on('add', (file) => {
|
|
48
|
-
notifyChange(file);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
emitter.close = () => watcher.close();
|
|
52
|
-
} else {
|
|
53
|
-
const fileStats = {};
|
|
54
|
-
const filesToWatch = [
|
|
55
|
-
path.join(projectRoot, 'specs/state.yaml'),
|
|
56
|
-
path.join(projectRoot, 'specs/execution-status.yaml'),
|
|
57
|
-
path.join(projectRoot, 'specs/metrics/cycle-times.yaml')
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const pollInterval = setInterval(() => {
|
|
61
|
-
filesToWatch.forEach((file) => {
|
|
62
|
-
try {
|
|
63
|
-
const stat = fs.statSync(file);
|
|
64
|
-
const mtime = stat.mtime.getTime();
|
|
65
|
-
|
|
66
|
-
if (!fileStats[file]) {
|
|
67
|
-
fileStats[file] = mtime;
|
|
68
|
-
} else if (fileStats[file] !== mtime) {
|
|
69
|
-
fileStats[file] = mtime;
|
|
70
|
-
notifyChange(file);
|
|
71
|
-
}
|
|
72
|
-
} catch (err) {
|
|
73
|
-
// File doesn't exist yet or was deleted
|
|
74
|
-
if (fileStats[file]) {
|
|
75
|
-
delete fileStats[file];
|
|
76
|
-
notifyChange(file);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Check specs/epics directory
|
|
82
|
-
const epicsDir = path.join(projectRoot, 'specs/epics');
|
|
83
|
-
try {
|
|
84
|
-
const entries = fs.readdirSync(epicsDir);
|
|
85
|
-
entries.forEach((entry) => {
|
|
86
|
-
const fullPath = path.join(epicsDir, entry);
|
|
87
|
-
const stat = fs.statSync(fullPath);
|
|
88
|
-
const mtime = stat.mtime.getTime();
|
|
89
|
-
|
|
90
|
-
if (!fileStats[fullPath]) {
|
|
91
|
-
fileStats[fullPath] = mtime;
|
|
92
|
-
} else if (fileStats[fullPath] !== mtime) {
|
|
93
|
-
fileStats[fullPath] = mtime;
|
|
94
|
-
notifyChange(fullPath);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
} catch (err) {
|
|
98
|
-
// epics directory doesn't exist yet
|
|
99
|
-
}
|
|
100
|
-
}, 200);
|
|
101
|
-
|
|
102
|
-
emitter.close = () => clearInterval(pollInterval);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return emitter;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { watch };
|