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
@@ -0,0 +1,417 @@
1
+ /**
2
+ * mdboard skill — Generate and install AI skill for IDEs/agents
3
+ *
4
+ * Generates a dynamic skill document from the current project config
5
+ * and installs it to the appropriate location for the selected IDE/agent.
6
+ *
7
+ * Supported agents:
8
+ * - Claude Code (~/.claude/skills/mdboard/SKILL.md)
9
+ * - Cursor (.cursor/rules/mdboard.mdc)
10
+ * - Windsurf (.windsurf/rules/mdboard.md)
11
+ * - VS Code (.claude/skills/mdboard/SKILL.md)
12
+ *
13
+ * Usage:
14
+ * mdboard skill Interactive prompt
15
+ * mdboard skill --agent <name> Install directly
16
+ * mdboard skill --path <path> Install to custom path
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const os = require('os');
24
+ const readline = require('readline');
25
+ const configEngine = require('../core/config');
26
+
27
+ // --- Agent definitions ---
28
+
29
+ const AGENTS = {
30
+ 'claude-code': {
31
+ label: 'Claude Code',
32
+ path: function () { return path.join(os.homedir(), '.claude', 'skills', 'mdboard', 'SKILL.md'); },
33
+ format: 'skill-md',
34
+ },
35
+ 'cursor': {
36
+ label: 'Cursor',
37
+ path: function () { return path.join(process.cwd(), '.cursor', 'rules', 'mdboard.mdc'); },
38
+ format: 'mdc',
39
+ },
40
+ 'windsurf': {
41
+ label: 'Windsurf',
42
+ path: function () { return path.join(process.cwd(), '.windsurf', 'rules', 'mdboard.md'); },
43
+ format: 'skill-md',
44
+ },
45
+ 'vscode': {
46
+ label: 'VS Code',
47
+ path: function () { return path.join(process.cwd(), '.claude', 'skills', 'mdboard', 'SKILL.md'); },
48
+ format: 'skill-md',
49
+ },
50
+ };
51
+
52
+ // --- Skill content generator ---
53
+
54
+ /**
55
+ * Generate the skill document content from the current config.
56
+ *
57
+ * @param {object} cfg - Loaded config object
58
+ * @param {string} format - 'skill-md' or 'mdc'
59
+ * @returns {string}
60
+ */
61
+ function generateSkillContent(cfg, format) {
62
+ const types = configEngine.getEntityTypes(cfg);
63
+ const flat = configEngine.flattenHierarchy(cfg);
64
+ const hierarchyTypes = flat.filter(function (e) { return !e.standalone; });
65
+ const standaloneTypes = flat.filter(function (e) { return e.standalone; });
66
+ const methodology = cfg._preset || 'shape-up';
67
+
68
+ // Build hierarchy chain string
69
+ const hierarchyChain = hierarchyTypes.map(function (e) {
70
+ var entity = configEngine.getEntity(cfg, e.type);
71
+ return entity ? entity.singular : e.type;
72
+ }).join(' > ');
73
+
74
+ var lines = [];
75
+
76
+ // --- Frontmatter ---
77
+ if (format === 'mdc') {
78
+ lines.push('---');
79
+ lines.push('description: mdboard project management — ' + methodology + ' methodology');
80
+ lines.push('globs: "project/**/*.md"');
81
+ lines.push('alwaysApply: true');
82
+ lines.push('---');
83
+ } else {
84
+ lines.push('---');
85
+ lines.push('name: mdboard');
86
+ lines.push('description: mdboard project management — ' + methodology + ' methodology');
87
+ lines.push('---');
88
+ }
89
+
90
+ lines.push('');
91
+ lines.push('# mdboard — Project Management');
92
+ lines.push('');
93
+ lines.push('Manages a **' + methodology + '** project. Entity hierarchy: ' + hierarchyChain + '.');
94
+ if (standaloneTypes.length > 0) {
95
+ var standaloneNames = standaloneTypes.map(function (e) {
96
+ var entity = configEngine.getEntity(cfg, e.type);
97
+ return entity ? entity.plural : e.type;
98
+ }).join(', ');
99
+ lines.push('Standalone entities: ' + standaloneNames + '.');
100
+ }
101
+
102
+ // --- Entity Types ---
103
+ lines.push('');
104
+ lines.push('## Entity Types');
105
+ lines.push('');
106
+
107
+ for (var i = 0; i < types.length; i++) {
108
+ var type = types[i];
109
+ var entity = configEngine.getEntity(cfg, type);
110
+ if (!entity) continue;
111
+
112
+ var flatEntry = flat.find(function (e) { return e.type === type; });
113
+ var storage = entity.file === 'PREFIX-NNN.md' ? 'file-per-entity' : 'directory-per-entity';
114
+ var location = flatEntry ? flatEntry.dir : type;
115
+ var isStandalone = flatEntry && flatEntry.standalone;
116
+
117
+ lines.push('### ' + entity.singular + ' (`' + type + '`)');
118
+ lines.push('');
119
+ lines.push('- Prefix: `' + entity.prefix + '`');
120
+ lines.push('- Storage: ' + storage + ' in `' + location + '/`');
121
+ if (isStandalone) {
122
+ lines.push('- Standalone (not in hierarchy)');
123
+ }
124
+
125
+ // Fields
126
+ var fields = configEngine.getFields(cfg, type);
127
+ var fieldNames = Object.keys(fields);
128
+ if (fieldNames.length > 0) {
129
+ lines.push('- Fields:');
130
+ for (var f = 0; f < fieldNames.length; f++) {
131
+ var fname = fieldNames[f];
132
+ var fdef = fields[fname];
133
+ var desc = ' - `' + fname + '` (' + fdef.type + ')';
134
+ if (fdef.type === 'enum' && fdef.values) {
135
+ desc += ': ' + fdef.values.map(function (v) { return '`' + v.key + '`'; }).join(', ');
136
+ }
137
+ if (fdef.type === 'ref') {
138
+ desc += ' → ' + fdef.entity;
139
+ if (fdef.multiple) desc += ' (multiple)';
140
+ }
141
+ lines.push(desc);
142
+ }
143
+ }
144
+
145
+ // Statuses shortcut
146
+ var statuses = configEngine.getStatuses(cfg, type);
147
+ if (statuses) {
148
+ lines.push('- Statuses: ' + statuses.map(function (s) { return '`' + s.key + '`'; }).join(', '));
149
+ }
150
+
151
+ lines.push('');
152
+ }
153
+
154
+ // --- Directory Layout ---
155
+ lines.push('## Directory Layout');
156
+ lines.push('');
157
+ lines.push('```');
158
+ lines.push('project/');
159
+
160
+ for (var h = 0; h < hierarchyTypes.length; h++) {
161
+ var ht = hierarchyTypes[h];
162
+ var indent = ' '.repeat(h + 1);
163
+ lines.push(indent + ht.dir + '/');
164
+ if (h < hierarchyTypes.length - 1) {
165
+ var nextEntity = configEngine.getEntity(cfg, ht.type);
166
+ var slug = nextEntity ? nextEntity.prefix.toLowerCase() + '-nnn' : 'slug';
167
+ lines.push(indent + ' ' + slug + '/');
168
+ }
169
+ }
170
+
171
+ for (var s = 0; s < standaloneTypes.length; s++) {
172
+ lines.push(' ' + standaloneTypes[s].dir + '/');
173
+ }
174
+
175
+ lines.push('```');
176
+ lines.push('');
177
+
178
+ // --- Markdown Format ---
179
+ var leafType = hierarchyTypes.length > 0 ? hierarchyTypes[hierarchyTypes.length - 1].type : types[0];
180
+ var leafEntity = configEngine.getEntity(cfg, leafType);
181
+ var leafFields = configEngine.getFields(cfg, leafType);
182
+
183
+ lines.push('## Markdown Format');
184
+ lines.push('');
185
+ lines.push('Each entity is a markdown file with YAML frontmatter:');
186
+ lines.push('');
187
+ lines.push('```markdown');
188
+ lines.push('---');
189
+
190
+ if (leafEntity) {
191
+ lines.push('title: "Example ' + leafEntity.singular + '"');
192
+ var lfNames = Object.keys(leafFields);
193
+ for (var lf = 0; lf < lfNames.length; lf++) {
194
+ var lfName = lfNames[lf];
195
+ var lfDef = leafFields[lfName];
196
+ if (lfDef.type === 'enum' && lfDef.values && lfDef.values.length > 0) {
197
+ lines.push(lfName + ': ' + (lfDef.default || lfDef.values[0].key));
198
+ } else if (lfDef.type === 'number') {
199
+ lines.push(lfName + ': ' + (lfDef.default || 1));
200
+ } else if (lfDef.type === 'list') {
201
+ lines.push(lfName + ': []');
202
+ } else if (lfDef.type === 'date') {
203
+ lines.push(lfName + ': ' + new Date().toISOString().split('T')[0]);
204
+ } else if (lfDef.type === 'ref') {
205
+ lines.push(lfName + ': ' + (lfDef.multiple ? '[]' : '""'));
206
+ } else {
207
+ lines.push(lfName + ': ""');
208
+ }
209
+ }
210
+ }
211
+
212
+ lines.push('---');
213
+ lines.push('');
214
+ lines.push('Content goes here (markdown body).');
215
+ lines.push('```');
216
+ lines.push('');
217
+
218
+ // --- CLI Commands ---
219
+ lines.push('## CLI Commands');
220
+ lines.push('');
221
+ lines.push('### CRUD');
222
+ lines.push('');
223
+
224
+ for (var ci = 0; ci < types.length; ci++) {
225
+ var ctype = types[ci];
226
+ var centity = configEngine.getEntity(cfg, ctype);
227
+ if (!centity) continue;
228
+ var ancestors = configEngine.getAncestors(cfg, ctype);
229
+ var ancestorFlags = ancestors.map(function (a) { return '--' + a.replace(/_/g, '-') + ' <slug>'; }).join(' ');
230
+ var flagSuffix = ancestorFlags ? ' ' + ancestorFlags : '';
231
+
232
+ lines.push('- `mdboard create ' + ctype + ' "title"' + flagSuffix + '`');
233
+ lines.push('- `mdboard update ' + ctype + ' <id> --field value`');
234
+ lines.push('- `mdboard delete ' + ctype + ' <id>`');
235
+ lines.push('- `mdboard list ' + ctype + '`');
236
+ }
237
+
238
+ lines.push('');
239
+ lines.push('### System');
240
+ lines.push('');
241
+ lines.push('- `mdboard` — Start dashboard server');
242
+ lines.push('- `mdboard init [name]` — Scaffold new project');
243
+ lines.push('- `mdboard status` — Generate status report');
244
+ lines.push('- `mdboard config` — Setup preferences & AI skill');
245
+ lines.push('- `mdboard skill` — Install AI skill to IDE');
246
+ lines.push('- `mdboard cache list|clean` — Manage cache');
247
+ lines.push('- `mdboard history list|clean` — Manage history');
248
+ lines.push('- `mdboard --help` — Show all commands');
249
+
250
+ // --- API Endpoints ---
251
+ lines.push('');
252
+ lines.push('## API Endpoints');
253
+ lines.push('');
254
+ lines.push('Server runs at `http://localhost:3333` by default.');
255
+ lines.push('');
256
+ lines.push('### CRUD (per entity type)');
257
+ lines.push('');
258
+
259
+ for (var ai = 0; ai < types.length; ai++) {
260
+ var atype = types[ai];
261
+ var aentity = configEngine.getEntity(cfg, atype);
262
+ if (!aentity) continue;
263
+ var plural = aentity.plural.toLowerCase();
264
+ lines.push('- `GET /api/' + plural + '` — List ' + aentity.plural.toLowerCase());
265
+ lines.push('- `GET /api/' + plural + '/:id` — Get single');
266
+ lines.push('- `POST /api/' + plural + '` — Create');
267
+ lines.push('- `PATCH /api/' + plural + '/:id` — Update');
268
+ lines.push('- `DELETE /api/' + plural + '/:id` — Delete');
269
+ }
270
+
271
+ lines.push('');
272
+ lines.push('### System');
273
+ lines.push('');
274
+ lines.push('- `GET /api/config` — Dashboard configuration');
275
+ lines.push('- `GET /api/project` — Project metadata');
276
+ lines.push('- `GET /api/sources` — Workspace sources');
277
+ lines.push('- `GET /api/metrics` — Aggregated metrics');
278
+ lines.push('- `GET /api/health` — Project health');
279
+ lines.push('- `GET /api/status` — Status report');
280
+ lines.push('- `GET /api/events` — SSE stream');
281
+
282
+ // --- Best Practices ---
283
+ lines.push('');
284
+ lines.push('## Best Practices');
285
+ lines.push('');
286
+ lines.push('- Always use the CLI to create/update/delete entities — it handles ID generation, file placement, and frontmatter.');
287
+ lines.push('- Parent flags (`--cycle`, `--bet`, etc.) use **slugs** (directory names), not IDs.');
288
+ lines.push('- Do not manually edit entity IDs or prefixes.');
289
+ lines.push('- Status values are strict enums — only use the defined values listed above.');
290
+ lines.push('- Entity files live inside `project/` — respect the hierarchy structure.');
291
+ lines.push('- Use `mdboard status` to generate a summary report.');
292
+ lines.push('');
293
+
294
+ return lines.join('\n');
295
+ }
296
+
297
+ // --- Installer ---
298
+
299
+ /**
300
+ * Install the skill file to the given path.
301
+ *
302
+ * @param {string} projectDir - Project directory (for config loading)
303
+ * @param {string} filePath - Absolute path to write the skill file
304
+ * @param {string} format - 'skill-md' or 'mdc'
305
+ */
306
+ function installSkill(projectDir, filePath, format) {
307
+ var cfg = configEngine.loadConfig(projectDir, process.env.MDBOARD_CONFIG);
308
+ var content = generateSkillContent(cfg, format);
309
+
310
+ // Create directories
311
+ var dir = path.dirname(filePath);
312
+ if (!fs.existsSync(dir)) {
313
+ fs.mkdirSync(dir, { recursive: true });
314
+ }
315
+
316
+ fs.writeFileSync(filePath, content, 'utf-8');
317
+ }
318
+
319
+ // --- CLI runner ---
320
+
321
+ /**
322
+ * Run the skill command.
323
+ *
324
+ * @param {string[]} args - CLI arguments after 'skill'
325
+ */
326
+ function run(args) {
327
+ var agentName = null;
328
+ var customPath = null;
329
+
330
+ for (var i = 0; i < args.length; i++) {
331
+ if (args[i] === '--agent' && args[i + 1]) {
332
+ agentName = args[i + 1];
333
+ i++;
334
+ } else if (args[i] === '--path' && args[i + 1]) {
335
+ customPath = args[i + 1];
336
+ i++;
337
+ }
338
+ }
339
+
340
+ // Direct install with --agent
341
+ if (agentName) {
342
+ var agent = AGENTS[agentName];
343
+ if (!agent) {
344
+ console.error(' Error: unknown agent "' + agentName + '"');
345
+ console.error(' Available: ' + Object.keys(AGENTS).join(', '));
346
+ process.exit(1);
347
+ }
348
+ var filePath = agent.path();
349
+ installSkill(process.cwd(), filePath, agent.format);
350
+ console.log('\n Skill installed: ' + agent.label);
351
+ console.log(' Path: ' + filePath + '\n');
352
+ process.exit(0);
353
+ }
354
+
355
+ // Direct install with --path
356
+ if (customPath) {
357
+ var resolved = path.resolve(customPath);
358
+ var fmt = resolved.endsWith('.mdc') ? 'mdc' : 'skill-md';
359
+ installSkill(process.cwd(), resolved, fmt);
360
+ console.log('\n Skill installed: ' + resolved + '\n');
361
+ process.exit(0);
362
+ }
363
+
364
+ // Interactive prompt
365
+ var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
366
+
367
+ console.log('\n mdboard skill — Install AI skill to your IDE\n');
368
+ console.log(' Select target:\n');
369
+ console.log(' 1) Claude Code ~/.claude/skills/mdboard/SKILL.md');
370
+ console.log(' 2) Cursor .cursor/rules/mdboard.mdc');
371
+ console.log(' 3) Windsurf .windsurf/rules/mdboard.md');
372
+ console.log(' 4) VS Code .claude/skills/mdboard/SKILL.md');
373
+ console.log(' 5) Custom path');
374
+ console.log(' 6) Skip\n');
375
+
376
+ rl.question(' Choice [1-6]: ', function (answer) {
377
+ var choice = answer.trim();
378
+
379
+ if (choice === '6' || choice === '') {
380
+ console.log(' Skipped.\n');
381
+ rl.close();
382
+ process.exit(0);
383
+ return;
384
+ }
385
+
386
+ if (choice === '5') {
387
+ rl.question(' Path: ', function (pathAnswer) {
388
+ var p = path.resolve(pathAnswer.trim());
389
+ var f = p.endsWith('.mdc') ? 'mdc' : 'skill-md';
390
+ installSkill(process.cwd(), p, f);
391
+ console.log('\n Skill installed: ' + p + '\n');
392
+ rl.close();
393
+ process.exit(0);
394
+ });
395
+ return;
396
+ }
397
+
398
+ var agentKeys = ['claude-code', 'cursor', 'windsurf', 'vscode'];
399
+ var idx = parseInt(choice, 10) - 1;
400
+ if (idx < 0 || idx >= agentKeys.length) {
401
+ console.error(' Invalid choice.');
402
+ rl.close();
403
+ process.exit(1);
404
+ return;
405
+ }
406
+
407
+ var selected = AGENTS[agentKeys[idx]];
408
+ var fp = selected.path();
409
+ installSkill(process.cwd(), fp, selected.format);
410
+ console.log('\n Skill installed: ' + selected.label);
411
+ console.log(' Path: ' + fp + '\n');
412
+ rl.close();
413
+ process.exit(0);
414
+ });
415
+ }
416
+
417
+ module.exports = { run, installSkill, generateSkillContent, AGENTS };