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.
Files changed (53) hide show
  1. package/bin.js +117 -59
  2. package/index.html +2161 -1579
  3. package/package.json +7 -5
  4. package/presets/kanban/api.json +91 -0
  5. package/presets/kanban/cli.json +69 -0
  6. package/presets/kanban/docs.json +29 -0
  7. package/presets/kanban/entities.json +128 -0
  8. package/presets/kanban/structure.json +15 -0
  9. package/presets/kanban/ui.json +86 -0
  10. package/presets/scrum/api.json +98 -0
  11. package/presets/scrum/cli.json +120 -0
  12. package/presets/scrum/docs.json +43 -0
  13. package/presets/scrum/entities.json +268 -0
  14. package/presets/scrum/structure.json +32 -0
  15. package/presets/scrum/ui.json +201 -0
  16. package/presets/shape-up/api.json +40 -0
  17. package/presets/shape-up/cli.json +44 -0
  18. package/presets/shape-up/docs.json +32 -0
  19. package/presets/shape-up/entities.json +140 -0
  20. package/presets/shape-up/structure.json +28 -0
  21. package/presets/shape-up/ui.json +114 -0
  22. package/src/cli/cli.js +186 -210
  23. package/src/cli/config.js +234 -0
  24. package/src/cli/init.js +128 -76
  25. package/src/cli/preset.js +849 -0
  26. package/src/cli/skill.js +417 -0
  27. package/src/cli/status.js +126 -96
  28. package/src/core/config.js +491 -38
  29. package/src/core/history.js +17 -1
  30. package/src/core/scanner.js +373 -463
  31. package/src/core/workspace.js +0 -15
  32. package/src/server/api.js +464 -741
  33. package/src/server/server.js +105 -130
  34. package/build.js +0 -44
  35. package/defaults.json +0 -43
  36. package/src/cli/sync.js +0 -194
  37. package/src/cli/theme.js +0 -142
  38. package/src/client/app.js +0 -266
  39. package/src/client/board.js +0 -157
  40. package/src/client/core.js +0 -331
  41. package/src/client/editor.js +0 -318
  42. package/src/client/history.js +0 -137
  43. package/src/client/metrics.js +0 -38
  44. package/src/client/milestones.js +0 -77
  45. package/src/client/notes.js +0 -183
  46. package/src/client/overview.js +0 -104
  47. package/src/client/panel.js +0 -637
  48. package/src/client/styles.css +0 -471
  49. package/src/client/table.js +0 -111
  50. package/src/client/template.html +0 -144
  51. package/src/client/themes.js +0 -261
  52. package/src/client/workspace.js +0 -164
  53. 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 the real model on disk.
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(`generated: ${now}`);
33
+ lines.push('generated: ' + status.generated);
31
34
  lines.push('---');
32
35
  lines.push('');
33
- lines.push(`# Project Status: ${projectName}`);
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('| Metric | Value |');
52
- lines.push('|--------|-------|');
53
- lines.push(`| Milestones | ${model.milestones.length} (${activeMilestones.length} active) |`);
54
- lines.push(`| Tasks | ${totalTasks} total, ${doneTasks} done (${donePercent}%) |`);
55
- lines.push(`| Points | ${donePoints}/${totalPoints} (${pointsPercent}%) |`);
56
- lines.push(`| Active Sprint | ${activeSprint ? activeSprint.id : 'none'} |`);
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
- // Milestones
60
- if (model.milestones.length > 0) {
61
- lines.push('## Milestones');
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 (const ms of model.milestones) {
64
- const msEpics = model.epics.filter(e => e._milestone === ms._dir);
65
- const msTasks = model.tasks.filter(t => t._milestone === ms._dir);
66
- const msDone = msTasks.filter(t => t.status === completedStatus).length;
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
- // Active Sprint
75
- if (activeSprint) {
76
- lines.push('## Active Sprint');
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
- lines.push(`### ${activeSprint.id}`);
79
- lines.push(`- File: [${activeSprint._file}](${activeSprint._file})`);
80
- if (activeSprint.goal) {
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
- const sprintFeatureIds = activeSprint.features || [];
85
- const sprintTasks = sprintFeatureIds.length > 0
86
- ? model.tasks.filter(t => sprintFeatureIds.includes(t.id))
87
- : model.tasks.filter(t => t.sprint === activeSprint.id);
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
- if (sprintTasks.length > 0) {
90
- lines.push('- Tasks:');
91
- for (const t of sprintTasks) {
92
- lines.push(` - ${t.id} [${t.status}] ${t.title || ''} -> [${t._file}](${t._file})`);
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
- // Tasks by status
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
- const statusOrder = Object.keys(statusGroups).sort((a, b) => {
107
- // Put completed last, in-progress first
108
- if (a === completedStatus) return 1;
109
- if (b === completedStatus) return -1;
110
- if (a === 'in-progress') return -1;
111
- if (b === 'in-progress') return 1;
112
- return a.localeCompare(b);
113
- });
114
-
115
- if (statusOrder.length > 0) {
116
- lines.push('## Tasks by Status');
117
- lines.push('');
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
- for (const status of statusOrder) {
120
- const tasks = statusGroups[status];
121
- lines.push(`### ${capitalize(status)} (${tasks.length})`);
122
- lines.push('');
123
- lines.push('| ID | Title | Epic | Priority | File |');
124
- lines.push('|----|-------|------|----------|------|');
125
- for (const t of tasks) {
126
- const title = t.title || '';
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
- const content = lines.join('\n') + '\n';
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
- console.log(` mdboard status — Generated ${path.relative(projectDir, statusPath)}`);
143
- console.log(` ${totalTasks} tasks, ${model.milestones.length} milestones, ${model.sprints.length} sprints`);
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) => (sep ? ' ' : '') + c.toUpperCase());
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 };