mdboard 1.3.0 → 2.1.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/bin.js +117 -59
- package/index.html +2161 -1579
- package/package.json +7 -5
- package/presets/kanban/api.json +91 -0
- package/presets/kanban/cli.json +69 -0
- package/presets/kanban/docs.json +29 -0
- package/presets/kanban/entities.json +128 -0
- package/presets/kanban/structure.json +15 -0
- package/presets/kanban/ui.json +86 -0
- package/presets/scrum/api.json +98 -0
- package/presets/scrum/cli.json +120 -0
- package/presets/scrum/docs.json +43 -0
- package/presets/scrum/entities.json +268 -0
- package/presets/scrum/structure.json +32 -0
- package/presets/scrum/ui.json +201 -0
- package/presets/shape-up/api.json +40 -0
- package/presets/shape-up/cli.json +44 -0
- package/presets/shape-up/docs.json +32 -0
- package/presets/shape-up/entities.json +140 -0
- package/presets/shape-up/structure.json +28 -0
- package/presets/shape-up/ui.json +114 -0
- package/src/cli/cli.js +186 -210
- package/src/cli/config.js +234 -0
- package/src/cli/init.js +128 -76
- package/src/cli/preset.js +849 -0
- package/src/cli/skill.js +417 -0
- package/src/cli/status.js +126 -96
- package/src/core/config.js +491 -38
- package/src/core/history.js +17 -1
- package/src/core/scanner.js +373 -463
- package/src/core/workspace.js +0 -15
- package/src/server/api.js +464 -741
- package/src/server/server.js +105 -130
- package/build.js +0 -44
- package/defaults.json +0 -43
- package/src/cli/sync.js +0 -194
- package/src/cli/theme.js +0 -142
- package/src/client/app.js +0 -266
- package/src/client/board.js +0 -157
- package/src/client/core.js +0 -331
- package/src/client/editor.js +0 -318
- package/src/client/history.js +0 -137
- package/src/client/metrics.js +0 -38
- package/src/client/milestones.js +0 -77
- package/src/client/notes.js +0 -183
- package/src/client/overview.js +0 -104
- package/src/client/panel.js +0 -637
- package/src/client/styles.css +0 -471
- package/src/client/table.js +0 -111
- package/src/client/template.html +0 -144
- package/src/client/themes.js +0 -261
- package/src/client/workspace.js +0 -164
- package/src/core/agent-scanner.js +0 -260
package/src/cli/status.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* mdboard — Status generator
|
|
2
|
+
* mdboard — Status generator (config-driven)
|
|
3
3
|
*
|
|
4
|
-
* Generates project/status.md with computed project status
|
|
5
|
-
* from
|
|
4
|
+
* Generates project/status.md with computed project status.
|
|
5
|
+
* All entity types come from config — no hardcoded names.
|
|
6
6
|
*
|
|
7
|
-
* Usage: mdboard status
|
|
7
|
+
* Usage: mdboard status [--project <path>]
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
10
12
|
const fs = require('fs');
|
|
11
13
|
const path = require('path');
|
|
12
14
|
const { buildModel } = require('./cli');
|
|
15
|
+
const {
|
|
16
|
+
getEntityTypes, getEntity, flattenHierarchy, isCompletedStatus,
|
|
17
|
+
} = require('../core/config');
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Generate project/status.md
|
|
@@ -17,134 +22,159 @@ const { buildModel } = require('./cli');
|
|
|
17
22
|
* @param {string} projectDir - Workspace root directory
|
|
18
23
|
*/
|
|
19
24
|
function generateStatus(projectDir) {
|
|
20
|
-
const { model, config, projectPath } = buildModel(projectDir);
|
|
25
|
+
const { model, config: cfg, projectPath } = buildModel(projectDir);
|
|
26
|
+
const status = computeStatus(model, cfg);
|
|
21
27
|
|
|
22
28
|
const projectName = (model.project && model.project.name) || path.basename(projectDir);
|
|
23
|
-
const completedStatus = config.completedStatus;
|
|
24
|
-
const now = new Date().toISOString();
|
|
25
|
-
|
|
26
29
|
const lines = [];
|
|
27
30
|
|
|
28
31
|
// Frontmatter
|
|
29
32
|
lines.push('---');
|
|
30
|
-
lines.push(
|
|
33
|
+
lines.push('generated: ' + status.generated);
|
|
31
34
|
lines.push('---');
|
|
32
35
|
lines.push('');
|
|
33
|
-
lines.push(
|
|
36
|
+
lines.push('# Project Status: ' + projectName);
|
|
34
37
|
lines.push('');
|
|
35
38
|
lines.push('> Auto-generated by `mdboard status`. Do not edit manually.');
|
|
36
39
|
lines.push('');
|
|
37
40
|
|
|
38
|
-
// Summary
|
|
39
|
-
const totalTasks = model.tasks.length;
|
|
40
|
-
const doneTasks = model.tasks.filter(t => t.status === completedStatus).length;
|
|
41
|
-
const donePercent = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;
|
|
42
|
-
const totalPoints = model.tasks.reduce((sum, t) => sum + (t.points || 0), 0);
|
|
43
|
-
const donePoints = model.tasks.filter(t => t.status === completedStatus)
|
|
44
|
-
.reduce((sum, t) => sum + (t.points || 0), 0);
|
|
45
|
-
const pointsPercent = totalPoints > 0 ? Math.round((donePoints / totalPoints) * 100) : 0;
|
|
46
|
-
const activeMilestones = model.milestones.filter(m => m.status === 'active');
|
|
47
|
-
const activeSprint = model.sprints.find(s => s.status === 'active');
|
|
48
|
-
|
|
41
|
+
// Summary table
|
|
49
42
|
lines.push('## Summary');
|
|
50
43
|
lines.push('');
|
|
51
|
-
lines.push('|
|
|
52
|
-
lines.push('
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
lines.push('| Entity | Total | Completed | Progress |');
|
|
45
|
+
lines.push('|--------|-------|-----------|----------|');
|
|
46
|
+
for (var i = 0; i < status.summary.length; i++) {
|
|
47
|
+
var s = status.summary[i];
|
|
48
|
+
var extra = s.points ? ' (' + s.completedPoints + '/' + s.points + ' pts)' : '';
|
|
49
|
+
lines.push('| ' + s.label + ' | ' + s.total + ' | ' + s.completed + ' | ' + s.percent + '%' + extra + ' |');
|
|
50
|
+
}
|
|
57
51
|
lines.push('');
|
|
58
52
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
// Hierarchy sections
|
|
54
|
+
for (var hi = 0; hi < status.hierarchy.length; hi++) {
|
|
55
|
+
var h = status.hierarchy[hi];
|
|
56
|
+
lines.push('## ' + h.label);
|
|
62
57
|
lines.push('');
|
|
63
|
-
for (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
lines.push(`### ${ms.id}: ${ms.title || ms._dir} [${ms.status}] (${ms._progress || 0}%)`);
|
|
68
|
-
lines.push(`- File: [${ms._file}](${ms._file})`);
|
|
69
|
-
lines.push(`- Epics: ${msEpics.length}, Tasks: ${msDone}/${msTasks.length} done`);
|
|
58
|
+
for (var j = 0; j < h.items.length; j++) {
|
|
59
|
+
var item = h.items[j];
|
|
60
|
+
lines.push('### ' + item.id + ': ' + (item.title || '') + ' [' + (item.status || '-') + '] (' + item.progress + '%)');
|
|
61
|
+
lines.push('- Children: ' + item._completedCount + '/' + item._childCount + ' done');
|
|
70
62
|
lines.push('');
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
// Breakdown
|
|
67
|
+
for (var type in status.breakdown) {
|
|
68
|
+
var entity = getEntity(cfg, type);
|
|
69
|
+
var label = entity ? entity.plural : type;
|
|
70
|
+
lines.push('## ' + label + ' by Status');
|
|
77
71
|
lines.push('');
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
lines.push(`- Goal: ${activeSprint.goal}`);
|
|
72
|
+
var groups = status.breakdown[type];
|
|
73
|
+
for (var statusKey in groups) {
|
|
74
|
+
lines.push('- **' + capitalize(statusKey) + '**: ' + groups[statusKey]);
|
|
82
75
|
}
|
|
76
|
+
lines.push('');
|
|
77
|
+
}
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
var content = lines.join('\n') + '\n';
|
|
80
|
+
|
|
81
|
+
// Write to project/status.md
|
|
82
|
+
var statusPath = path.join(projectPath, 'status.md');
|
|
83
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
84
|
+
fs.writeFileSync(statusPath, content, 'utf-8');
|
|
85
|
+
|
|
86
|
+
console.log(' mdboard status — Generated ' + path.relative(projectDir, statusPath));
|
|
87
|
+
var summaryLine = status.summary.map(function(s) { return s.total + ' ' + s.label.toLowerCase(); }).join(', ');
|
|
88
|
+
console.log(' ' + summaryLine);
|
|
89
|
+
}
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Compute status data from model and config.
|
|
93
|
+
* Returns the JSON structure served by GET /api/status.
|
|
94
|
+
*/
|
|
95
|
+
function computeStatus(model, cfg) {
|
|
96
|
+
var types = getEntityTypes(cfg);
|
|
97
|
+
var flat = flattenHierarchy(cfg);
|
|
98
|
+
var hierarchyTypes = flat.filter(function(e) { return !e.standalone; });
|
|
99
|
+
var leafType = hierarchyTypes.length > 0 ? hierarchyTypes[hierarchyTypes.length - 1].type : null;
|
|
100
|
+
|
|
101
|
+
// Summary: per entity type
|
|
102
|
+
var summary = [];
|
|
103
|
+
for (var i = 0; i < types.length; i++) {
|
|
104
|
+
var type = types[i];
|
|
105
|
+
var entity = getEntity(cfg, type);
|
|
106
|
+
var items = model.entities[type] || [];
|
|
107
|
+
var total = items.length;
|
|
108
|
+
var completed = items.filter(function(it) { return isCompletedStatus(cfg, it.status); }).length;
|
|
109
|
+
var percent = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
110
|
+
var entry = {
|
|
111
|
+
type: type,
|
|
112
|
+
label: entity ? entity.plural : type,
|
|
113
|
+
total: total,
|
|
114
|
+
completed: completed,
|
|
115
|
+
percent: percent,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Points for leaf type
|
|
119
|
+
if (type === leafType) {
|
|
120
|
+
entry.points = items.reduce(function(sum, it) { return sum + (it.points || 0); }, 0);
|
|
121
|
+
entry.completedPoints = items
|
|
122
|
+
.filter(function(it) { return isCompletedStatus(cfg, it.status); })
|
|
123
|
+
.reduce(function(sum, it) { return sum + (it.points || 0); }, 0);
|
|
94
124
|
}
|
|
95
|
-
lines.push('');
|
|
96
|
-
}
|
|
97
125
|
|
|
98
|
-
|
|
99
|
-
const statusGroups = {};
|
|
100
|
-
for (const task of model.tasks) {
|
|
101
|
-
const s = task.status || 'unknown';
|
|
102
|
-
if (!statusGroups[s]) statusGroups[s] = [];
|
|
103
|
-
statusGroups[s].push(task);
|
|
126
|
+
summary.push(entry);
|
|
104
127
|
}
|
|
105
128
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
// Hierarchy: non-leaf types with progress
|
|
130
|
+
var hierarchy = [];
|
|
131
|
+
for (var hi = 0; hi < hierarchyTypes.length; hi++) {
|
|
132
|
+
var ht = hierarchyTypes[hi];
|
|
133
|
+
if (ht.type === leafType) continue;
|
|
134
|
+
var hEntity = getEntity(cfg, ht.type);
|
|
135
|
+
var hItems = model.entities[ht.type] || [];
|
|
136
|
+
var hMapped = hItems.map(function(it) {
|
|
137
|
+
return {
|
|
138
|
+
id: it.id,
|
|
139
|
+
title: it.title || '',
|
|
140
|
+
status: it.status || '',
|
|
141
|
+
progress: it._progress || 0,
|
|
142
|
+
_childCount: it._childCount || 0,
|
|
143
|
+
_completedCount: it._completedCount || 0,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
hierarchy.push({
|
|
147
|
+
type: ht.type,
|
|
148
|
+
label: hEntity ? hEntity.plural : ht.type,
|
|
149
|
+
items: hMapped,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const epic = t._epic || '';
|
|
128
|
-
const priority = t.priority || '';
|
|
129
|
-
lines.push(`| ${t.id} | ${title} | ${epic} | ${priority} | [${t._file}](${t._file}) |`);
|
|
130
|
-
}
|
|
131
|
-
lines.push('');
|
|
153
|
+
// Breakdown: leaf items grouped by status
|
|
154
|
+
var breakdown = {};
|
|
155
|
+
if (leafType) {
|
|
156
|
+
var leafItems = model.entities[leafType] || [];
|
|
157
|
+
var groups = {};
|
|
158
|
+
for (var li = 0; li < leafItems.length; li++) {
|
|
159
|
+
var st = leafItems[li].status || 'unknown';
|
|
160
|
+
groups[st] = (groups[st] || 0) + 1;
|
|
132
161
|
}
|
|
162
|
+
breakdown[leafType] = groups;
|
|
133
163
|
}
|
|
134
164
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Write to project/status.md
|
|
138
|
-
const statusPath = path.join(projectPath, 'status.md');
|
|
139
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
140
|
-
fs.writeFileSync(statusPath, content, 'utf-8');
|
|
165
|
+
var projectName = (model.project && model.project.name) || '';
|
|
141
166
|
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
return {
|
|
168
|
+
generated: new Date().toISOString(),
|
|
169
|
+
project: { name: projectName },
|
|
170
|
+
summary: summary,
|
|
171
|
+
hierarchy: hierarchy,
|
|
172
|
+
breakdown: breakdown,
|
|
173
|
+
};
|
|
144
174
|
}
|
|
145
175
|
|
|
146
176
|
function capitalize(str) {
|
|
147
|
-
return str.replace(/(^|-)(\w)/g, (_, sep, c)
|
|
177
|
+
return str.replace(/(^|-)(\w)/g, function(_, sep, c) { return (sep ? ' ' : '') + c.toUpperCase(); });
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
module.exports = { generateStatus };
|
|
180
|
+
module.exports = { generateStatus, computeStatus };
|