mdboard 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/bin.js CHANGED
@@ -81,6 +81,8 @@ if (command === 'create') {
81
81
  handleList(resolveProjectDir(args), args.slice(1));
82
82
  } else if (command === 'preset') {
83
83
  require('./src/cli/preset').run(args.slice(1));
84
+ } else if (command === 'agentdocs') {
85
+ handleAgentDocs();
84
86
  } else if (command === 'config') {
85
87
  require('./src/cli/config').run(args.slice(1));
86
88
  // readline keeps process alive; config.js calls process.exit(0) when done
@@ -96,6 +98,65 @@ if (command === 'create') {
96
98
  process.exit(1);
97
99
  }
98
100
 
101
+ // --- Agent docs handler ---
102
+
103
+ function handleAgentDocs() {
104
+ const configEngine = require('./src/core/config');
105
+ const agentdocs = require('./src/cli/agentdocs');
106
+ const fs = require('fs');
107
+ const readline = require('readline');
108
+
109
+ const projectDir = process.cwd();
110
+ const projectMdboard = path.join(projectDir, 'project', 'mdboard.json');
111
+
112
+ if (!fs.existsSync(projectMdboard)) {
113
+ console.error('\n Error: no project found. Run `mdboard init` first.\n');
114
+ process.exit(1);
115
+ }
116
+
117
+ const cfg = configEngine.loadConfig(projectDir);
118
+ const preset = cfg._preset || configEngine.DEFAULT_PRESET;
119
+
120
+ // Read project name from PROJECT.md frontmatter
121
+ let projectName = path.basename(projectDir);
122
+ try {
123
+ const projMd = fs.readFileSync(path.join(projectDir, 'project', 'PROJECT.md'), 'utf-8');
124
+ const nameMatch = projMd.match(/^name:\s*"?([^"\n]+)"?/m);
125
+ if (nameMatch) projectName = nameMatch[1].trim();
126
+ } catch (e) { /* use directory name */ }
127
+
128
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
129
+ console.log('');
130
+ console.log(' Generate AI agent context files?');
131
+ console.log(' 1. Both CLAUDE.md + AGENTS.md (recommended)');
132
+ console.log(' 2. CLAUDE.md only (Claude Code)');
133
+ console.log(' 3. AGENTS.md only (Cursor, Copilot, etc.)');
134
+ rl.question(' Choice [1]: ', function (answer) {
135
+ rl.close();
136
+ const choice = (answer || '').trim() || '1';
137
+ let targets;
138
+ if (choice === '2') targets = ['claude'];
139
+ else if (choice === '3') targets = ['agents'];
140
+ else targets = ['claude', 'agents'];
141
+
142
+ // Check which files exist before writing
143
+ const existsBefore = {};
144
+ for (const t of targets) {
145
+ const fname = t === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
146
+ existsBefore[fname] = fs.existsSync(path.join(projectDir, fname));
147
+ }
148
+
149
+ const written = agentdocs.generate(projectDir, cfg, preset, projectName, targets);
150
+ console.log('');
151
+ for (const p of written) {
152
+ const fname = path.basename(p);
153
+ console.log(' ' + (existsBefore[fname] ? 'Updated' : 'Created') + ': ' + fname);
154
+ }
155
+ console.log('');
156
+ process.exit(0);
157
+ });
158
+ }
159
+
99
160
  // --- Cache handler ---
100
161
 
101
162
  function handleCache() {
@@ -184,5 +245,5 @@ function printHelp() {
184
245
  entityExamples = '\n CRUD examples:\n mdboard create <entity> "title" [--flags]\n mdboard update <entity> <id> --field value\n mdboard delete <entity> <id>\n mdboard list <entity>\n';
185
246
  }
186
247
 
187
- console.log('\n mdboard — Git-based project management dashboard\n\n Usage:\n mdboard Start the dashboard server\n mdboard init [name] Scaffold a new workspace\n mdboard create <entity> Create an entity\n mdboard update <entity> Update an entity\n mdboard delete <entity> Delete an entity\n mdboard list <entity> List entities\n mdboard status Generate status report\n mdboard preset Create & manage presets (scrum, kanban)\n mdboard config Setup preferences & AI skill\n mdboard skill Install AI skill to IDE/agent\n mdboard history list|clean Manage project switch history\n mdboard cache list|clean Manage remote source cache\n mdboard --version Print version\n mdboard --help Show this help\n' + entityExamples + '\n Options:\n --project <path> Workspace root (default: cwd)\n --preset <name> Methodology preset (shape-up, scrum, kanban)\n --config <path> Path to config directory\n --port <number> Server port (default: 3333)\n --workspace <path> Path to workspace.json\n');
248
+ console.log('\n mdboard — Git-based project management dashboard\n\n Usage:\n mdboard Start the dashboard server\n mdboard init [name] Scaffold a new workspace\n mdboard create <entity> Create an entity\n mdboard update <entity> Update an entity\n mdboard delete <entity> Delete an entity\n mdboard list <entity> List entities\n mdboard status Generate status report\n mdboard preset Create & manage presets (scrum, kanban)\n mdboard config Setup preferences & AI skill\n mdboard skill Install AI skill to IDE/agent\n mdboard agentdocs Generate CLAUDE.md / AGENTS.md context\n mdboard history list|clean Manage project switch history\n mdboard cache list|clean Manage remote source cache\n mdboard --version Print version\n mdboard --help Show this help\n' + entityExamples + '\n Options:\n --project <path> Workspace root (default: cwd)\n --preset <name> Methodology preset (shape-up, scrum, kanban)\n --config <path> Path to config directory\n --port <number> Server port (default: 3333)\n --workspace <path> Path to workspace.json\n');
188
249
  }
package/index.html CHANGED
@@ -204,8 +204,8 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
204
204
  .panel-overlay.open{opacity:1;pointer-events:auto}
205
205
  .detail-panel{top: 1rem;right: 1rem;height: calc(100vh - 2rem);border-radius: var(--radius);position:fixed;width:600px;max-width:92vw;background:var(--bg);border-left:1px solid var(--border);z-index:100;transform:translateX(100%);transition:transform .25s ease,top .25s ease,left .25s ease,right .25s ease,width .25s ease,height .25s ease;display:flex;flex-direction:column;overflow:hidden}
206
206
  .detail-panel.open{transform:translateX(0)}
207
- .detail-panel.expanded{top:50%;left:50%;right:auto;width:900px;height:85vh;border:1px solid var(--border);box-shadow:0 16px 48px rgba(0,0,0,.4)}
208
- .detail-panel.expanded.open{transform:translate(-50%,-50%)}
207
+ .detail-panel.expanded{top:1rem;left:50%;right:auto;width:1060px;height:calc(100vh - 2rem);border:1px solid var(--border);box-shadow:0 16px 48px rgba(0,0,0,.4)}
208
+ .detail-panel.expanded.open{transform:translateX(-50%)}
209
209
  .panel-header{padding:12px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;background:var(--surface)}
210
210
  .panel-type{font-size:10px;padding:3px 8px;border-radius:var(--radius-sm);background:var(--accent-dim);color:var(--accent);font-weight:700;text-transform:uppercase;letter-spacing:.04em}
211
211
  .panel-item-id{font-family:var(--mono);font-size:13px;color:var(--text2)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdboard",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "Git-based project management dashboard. Reads markdown files with YAML frontmatter and serves a visual kanban board, table, milestones, and metrics views.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
@@ -0,0 +1,311 @@
1
+ /**
2
+ * mdboard agentdocs — Generate CLAUDE.md / AGENTS.md context for AI agents
3
+ *
4
+ * Dynamically builds project management documentation from the loaded preset
5
+ * configuration so that any AI agent always has context about how to use mdboard.
6
+ *
7
+ * Called during `mdboard init` and also available as `mdboard agentdocs`.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const configEngine = require('../core/config');
15
+
16
+ const SECTION_START = '<!-- mdboard:start -->';
17
+ const SECTION_END = '<!-- mdboard:end -->';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Content generation
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Build the full markdown section from config.
25
+ * @param {object} cfg - Loaded config from configEngine.loadConfig()
26
+ * @param {string} preset - Preset name (e.g. "shape-up")
27
+ * @param {string} projectName - Workspace/project name
28
+ * @returns {string}
29
+ */
30
+ function generateSection(cfg, preset, projectName) {
31
+ const lines = [];
32
+
33
+ lines.push(SECTION_START);
34
+ lines.push('');
35
+ lines.push('## mdboard — Project Management');
36
+ lines.push('');
37
+ lines.push('This project uses **mdboard** for project management. All board files live in `project/`.');
38
+ lines.push('');
39
+
40
+ // --- Config summary ---
41
+ lines.push('### Configuration');
42
+ lines.push('');
43
+ lines.push('- **Project**: ' + projectName);
44
+ lines.push('- **Preset**: ' + preset);
45
+ lines.push('- **Methodology**: ' + (cfg.entities && cfg.entities.methodology || preset));
46
+ lines.push('- **Root directory**: `project/`');
47
+ lines.push('');
48
+
49
+ // --- Hierarchy ---
50
+ const hierarchy = (cfg.structure && cfg.structure.hierarchy) || {};
51
+ const standalone = (cfg.structure && cfg.structure.standalone) || {};
52
+ const hierarchyChain = buildHierarchyChain(hierarchy);
53
+ const standaloneNames = Object.keys(standalone);
54
+
55
+ lines.push('### Entity hierarchy');
56
+ lines.push('');
57
+ if (hierarchyChain.length > 0) {
58
+ const entities = cfg.entities && cfg.entities.entities || {};
59
+ const chainLabels = hierarchyChain.map(t => {
60
+ const e = entities[t];
61
+ return e ? e.singular : t;
62
+ });
63
+ lines.push('```');
64
+ lines.push(chainLabels.join(' > '));
65
+ lines.push('```');
66
+ } else {
67
+ lines.push('No hierarchy (flat structure).');
68
+ }
69
+ if (standaloneNames.length > 0) {
70
+ const entities = cfg.entities && cfg.entities.entities || {};
71
+ const labels = standaloneNames.map(t => {
72
+ const e = entities[t];
73
+ return e ? '**' + e.singular + '**' : '**' + t + '**';
74
+ });
75
+ lines.push('');
76
+ lines.push('Standalone entities: ' + labels.join(', '));
77
+ }
78
+ lines.push('');
79
+
80
+ // --- File structure ---
81
+ lines.push('### File structure');
82
+ lines.push('');
83
+ lines.push('```');
84
+ const tree = buildFileTree(hierarchy, standalone, cfg.entities && cfg.entities.entities || {});
85
+ lines.push('project/');
86
+ for (const line of tree) {
87
+ lines.push(' ' + line);
88
+ }
89
+ lines.push('```');
90
+ lines.push('');
91
+
92
+ // --- Frontmatter enums ---
93
+ lines.push('### Frontmatter values (strict enums)');
94
+ lines.push('');
95
+ const enumTable = buildEnumTable(cfg);
96
+ if (enumTable.length > 0) {
97
+ lines.push('| Entity | Field | Allowed values |');
98
+ lines.push('|--------|-------|----------------|');
99
+ for (const row of enumTable) {
100
+ lines.push('| ' + row.entity + ' | ' + row.field + ' | ' + row.values + ' |');
101
+ }
102
+ }
103
+ lines.push('');
104
+
105
+ // --- CLI commands ---
106
+ lines.push('### CLI commands');
107
+ lines.push('');
108
+ lines.push('Always use the CLI to create/update/delete entities. Never edit IDs or prefixes manually.');
109
+ lines.push('');
110
+ lines.push('```bash');
111
+
112
+ // Create commands
113
+ const allTypes = configEngine.getEntityTypes(cfg);
114
+ for (const type of allTypes) {
115
+ const entity = configEngine.getEntity(cfg, type);
116
+ if (!entity) continue;
117
+ const ancestors = configEngine.getAncestors(cfg, type);
118
+ const parentFlags = ancestors.map(a => '--' + a + ' <slug>').join(' ');
119
+ const cmd = 'mdboard create ' + type + ' "title"';
120
+ lines.push(cmd + (parentFlags ? ' ' + parentFlags : ''));
121
+ }
122
+ lines.push('');
123
+
124
+ // Update / list / delete
125
+ lines.push('# Update any field');
126
+ lines.push('mdboard update <type> <ID> --<field> <value>');
127
+ lines.push('');
128
+ lines.push('# List and delete');
129
+ lines.push('mdboard list <type>');
130
+ lines.push('mdboard delete <type> <ID>');
131
+ lines.push('');
132
+
133
+ // System commands
134
+ lines.push('# System');
135
+ lines.push('mdboard status # Project status report');
136
+ lines.push('```');
137
+ lines.push('');
138
+
139
+ // --- Rules ---
140
+ lines.push('### Rules');
141
+ lines.push('');
142
+ lines.push('- Parent flags (`--cycle`, `--bet`, etc.) use **slugs** (directory names), not IDs.');
143
+ lines.push('- Status/enum values are **strict** — only use the values listed above.');
144
+ lines.push('- Use `mdboard status` to get a quick summary of project state.');
145
+ lines.push('- Do not manually edit entity IDs or prefixes.');
146
+ lines.push('');
147
+ lines.push(SECTION_END);
148
+
149
+ return lines.join('\n');
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Helpers
154
+ // ---------------------------------------------------------------------------
155
+
156
+ /**
157
+ * Walk hierarchy to produce an ordered array of type names.
158
+ * e.g. ["cycle", "bet", "scope", "task"]
159
+ */
160
+ function buildHierarchyChain(hierarchy) {
161
+ const chain = [];
162
+ function walk(node) {
163
+ for (const [type, def] of Object.entries(node)) {
164
+ chain.push(type);
165
+ if (def.children) walk(def.children);
166
+ }
167
+ }
168
+ walk(hierarchy);
169
+ return chain;
170
+ }
171
+
172
+ /**
173
+ * Build a visual file tree array from hierarchy + standalone.
174
+ */
175
+ function buildFileTree(hierarchy, standalone, entities) {
176
+ const lines = [];
177
+
178
+ function walkTree(node, indent) {
179
+ for (const [type, def] of Object.entries(node)) {
180
+ const entity = entities[type];
181
+ const isDir = entity && entity.file === 'README.md';
182
+ const isFile = entity && entity.file === 'PREFIX-NNN.md';
183
+
184
+ lines.push(indent + def.dir + '/');
185
+ if (isDir) {
186
+ // Directory-per-entity: show slug pattern
187
+ const slug = type + '-nnn/';
188
+ lines.push(indent + ' ' + slug);
189
+ if (def.children) {
190
+ walkTree(def.children, indent + ' ');
191
+ }
192
+ } else if (isFile && def.children) {
193
+ // File-per-entity but has children (unusual, handle anyway)
194
+ lines.push(indent + ' ' + (entity.prefix || type.toUpperCase()) + '-NNN.md');
195
+ walkTree(def.children, indent + ' ');
196
+ } else if (isFile) {
197
+ lines.push(indent + ' ' + (entity.prefix || type.toUpperCase()) + '-NNN.md');
198
+ }
199
+ }
200
+ }
201
+
202
+ walkTree(hierarchy, '');
203
+
204
+ for (const [type, def] of Object.entries(standalone)) {
205
+ const entity = entities[type];
206
+ lines.push(def.dir + '/');
207
+ if (entity && entity.file === 'PREFIX-NNN.md') {
208
+ lines.push(' ' + (entity.prefix || type.toUpperCase()) + '-NNN.md');
209
+ }
210
+ }
211
+
212
+ return lines;
213
+ }
214
+
215
+ /**
216
+ * Build rows for the enum values table.
217
+ */
218
+ function buildEnumTable(cfg) {
219
+ const rows = [];
220
+ const types = configEngine.getEntityTypes(cfg);
221
+
222
+ for (const type of types) {
223
+ const entity = configEngine.getEntity(cfg, type);
224
+ const fields = configEngine.getFields(cfg, type);
225
+ if (!entity || !fields) continue;
226
+
227
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
228
+ if (fieldDef.type !== 'enum') continue;
229
+ const values = fieldDef.values.map(v => '`' + v.key + '`').join(', ');
230
+ rows.push({
231
+ entity: entity.singular,
232
+ field: fieldName,
233
+ values: values
234
+ });
235
+ }
236
+ }
237
+
238
+ return rows;
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // File writing (create or append)
243
+ // ---------------------------------------------------------------------------
244
+
245
+ /**
246
+ * Write or update agent docs in a target file.
247
+ * If the file exists and already has an mdboard section, replace it.
248
+ * If the file exists without a section, append it.
249
+ * If the file doesn't exist, create it.
250
+ *
251
+ * @param {string} filePath - Absolute path to the target file
252
+ * @param {string} section - Generated markdown section
253
+ */
254
+ function writeAgentDoc(filePath, section) {
255
+ if (fs.existsSync(filePath)) {
256
+ let content = fs.readFileSync(filePath, 'utf-8');
257
+ const startIdx = content.indexOf(SECTION_START);
258
+ const endIdx = content.indexOf(SECTION_END);
259
+
260
+ if (startIdx !== -1 && endIdx !== -1) {
261
+ // Replace existing section
262
+ content = content.substring(0, startIdx) + section + content.substring(endIdx + SECTION_END.length);
263
+ } else {
264
+ // Append
265
+ content = content.trimEnd() + '\n\n' + section + '\n';
266
+ }
267
+
268
+ fs.writeFileSync(filePath, content, 'utf-8');
269
+ } else {
270
+ // Create new file
271
+ fs.writeFileSync(filePath, section + '\n', 'utf-8');
272
+ }
273
+ }
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // Public API
277
+ // ---------------------------------------------------------------------------
278
+
279
+ /**
280
+ * Generate and write agent documentation files.
281
+ *
282
+ * @param {string} cwd - Working directory (repo root)
283
+ * @param {object} cfg - Loaded config
284
+ * @param {string} preset - Preset name
285
+ * @param {string} projectName - Project/workspace name
286
+ * @param {string[]} targets - Which files to generate: ['claude', 'agents']
287
+ * @returns {string[]} - Paths of files written
288
+ */
289
+ function generate(cwd, cfg, preset, projectName, targets) {
290
+ const section = generateSection(cfg, preset, projectName);
291
+ const written = [];
292
+
293
+ for (const target of targets) {
294
+ let filename;
295
+ if (target === 'claude') {
296
+ filename = 'CLAUDE.md';
297
+ } else if (target === 'agents') {
298
+ filename = 'AGENTS.md';
299
+ } else {
300
+ continue;
301
+ }
302
+
303
+ const filePath = path.join(cwd, filename);
304
+ writeAgentDoc(filePath, section);
305
+ written.push(filePath);
306
+ }
307
+
308
+ return written;
309
+ }
310
+
311
+ module.exports = { generate, generateSection, writeAgentDoc, SECTION_START, SECTION_END };
package/src/cli/init.js CHANGED
@@ -14,6 +14,7 @@ const fs = require('fs');
14
14
  const path = require('path');
15
15
  const readline = require('readline');
16
16
  const configEngine = require('../core/config');
17
+ const agentdocs = require('./agentdocs');
17
18
 
18
19
  const cwd = process.cwd();
19
20
  const projectPath = path.join(cwd, 'project');
@@ -50,17 +51,39 @@ if (!fs.existsSync(presetDir)) {
50
51
  }
51
52
 
52
53
  if (nameArg) {
53
- scaffold(nameArg);
54
+ askAgentTargets(function (targets) { scaffold(nameArg, targets); });
54
55
  } else {
55
56
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
56
57
  rl.question(' Workspace name: ', function (answer) {
57
58
  rl.close();
58
59
  const name = answer.trim() || path.basename(cwd);
59
- scaffold(name);
60
+ askAgentTargets(function (targets) { scaffold(name, targets); });
60
61
  });
61
62
  }
62
63
 
63
- function scaffold(name) {
64
+ /**
65
+ * Prompt user to select which agent doc files to generate.
66
+ * @param {function} cb - Callback receiving string[] of targets
67
+ */
68
+ function askAgentTargets(cb) {
69
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
70
+ console.log('');
71
+ console.log(' Generate AI agent context files?');
72
+ console.log(' 1. Both CLAUDE.md + AGENTS.md (recommended)');
73
+ console.log(' 2. CLAUDE.md only (Claude Code)');
74
+ console.log(' 3. AGENTS.md only (Cursor, Copilot, etc.)');
75
+ console.log(' 4. Skip');
76
+ rl.question(' Choice [1]: ', function (answer) {
77
+ rl.close();
78
+ const choice = (answer || '').trim() || '1';
79
+ if (choice === '2') return cb(['claude']);
80
+ if (choice === '3') return cb(['agents']);
81
+ if (choice === '4') return cb([]);
82
+ return cb(['claude', 'agents']);
83
+ });
84
+ }
85
+
86
+ function scaffold(name, agentTargets) {
64
87
  // Load preset config
65
88
  const cfg = configEngine.loadConfig(null);
66
89
  const structure = cfg.structure || {};
@@ -119,6 +142,14 @@ function scaffold(name) {
119
142
  console.log(' project/mdboard.css');
120
143
  console.log(' workspace.json');
121
144
 
145
+ // Generate AI agent documentation files
146
+ if (agentTargets && agentTargets.length > 0) {
147
+ const written = agentdocs.generate(cwd, cfg, preset, name, agentTargets);
148
+ for (const p of written) {
149
+ console.log(' ' + path.basename(p));
150
+ }
151
+ }
152
+
122
153
  console.log('\n Next steps:');
123
154
  console.log(' 1. Edit project/PROJECT.md with your project details');
124
155