claude-autopm 3.22.0 → 3.22.2

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.
@@ -132,8 +132,7 @@ function generateEpicFlowDiagram() {
132
132
  }
133
133
  } catch {}
134
134
  if (epics.length === 0 && prds.length === 0) {
135
- lines.push(' N[No epics or PRDs found]');
136
- return lines.join('\n');
135
+ return { mermaid: '', hasData: false };
137
136
  }
138
137
  let nodeId = 0;
139
138
  for (const prd of prds) {
@@ -159,7 +158,7 @@ function generateEpicFlowDiagram() {
159
158
  lines.push(` ${eid} --> ${tid}["${t.name} ${t.icon}"]`);
160
159
  }
161
160
  }
162
- return lines.join('\n');
161
+ return { mermaid: lines.join('\n'), hasData: true };
163
162
  }
164
163
 
165
164
  function generatePluginGraph() {
@@ -253,18 +252,22 @@ function getProjectDiagrams() {
253
252
  for (const f of entries) {
254
253
  if (!f.endsWith('.mmd')) continue;
255
254
  try {
256
- diagrams.push({
257
- name: f.replace(/\.mmd$/, ''),
258
- content: fs.readFileSync(path.join(diagramsDir, f), 'utf8')
259
- });
255
+ const name = f.replace(/\.mmd$/, '');
256
+ const d = { name, content: fs.readFileSync(path.join(diagramsDir, f), 'utf8') };
257
+ // Load metadata if available
258
+ const metaPath = path.join(diagramsDir, name + '.meta.json');
259
+ try { const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); d.type = meta.type || 'custom'; } catch { d.type = 'custom'; }
260
+ diagrams.push(d);
260
261
  } catch { /* skip unreadable */ }
261
262
  }
262
263
  return diagrams;
263
264
  }
264
265
 
265
266
  function getDiagramsData() {
267
+ const epic = generateEpicFlowDiagram();
266
268
  return {
267
- epicFlow: generateEpicFlowDiagram(),
269
+ epicFlow: epic.mermaid,
270
+ epicFlowHasData: epic.hasData,
268
271
  projectDiagrams: getProjectDiagrams(),
269
272
  pluginGraph: generatePluginGraph(),
270
273
  agentTree: generateAgentTree()
@@ -287,6 +290,7 @@ function renderHTML() {
287
290
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
288
291
  <title>AutoPM Config Dashboard</title>
289
292
  <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"><\/script>
293
+ <script src="https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js"><\/script>
290
294
  <style>
291
295
  * { margin: 0; padding: 0; box-sizing: border-box; }
292
296
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; }
@@ -364,6 +368,67 @@ function renderHTML() {
364
368
  .toast.ok { background: #238636; }
365
369
  .toast.err { background: #da3633; }
366
370
  footer { margin-top: 24px; text-align: center; color: #484f58; font-size: 12px; }
371
+ /* Diagram list */
372
+ .diagram-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
373
+ .diagram-grid-item {
374
+ background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 16px; cursor: pointer; transition: border-color 0.15s;
375
+ }
376
+ .diagram-grid-item:hover { border-color: #58a6ff; }
377
+ .diagram-grid-item h4 { color: #58a6ff; margin-bottom: 4px; }
378
+ .diagram-grid-item .diagram-meta { color: #8b949e; font-size: 11px; }
379
+ .diagram-grid-item .diagram-preview { height: 100px; overflow: hidden; opacity: 0.6; margin-top: 8px; }
380
+ .diagram-grid-item .diagram-preview svg { max-height: 100px; width: 100%; }
381
+ /* Diagram split editor */
382
+ .diagram-editor { display: none; }
383
+ .diagram-editor.active { display: flex; flex-direction: column; }
384
+ .diagram-editor-toolbar {
385
+ display: flex; justify-content: space-between; align-items: center;
386
+ padding: 8px 0; margin-bottom: 8px;
387
+ }
388
+ .diagram-editor-toolbar .left { display: flex; align-items: center; gap: 12px; }
389
+ .diagram-editor-toolbar .right { display: flex; gap: 6px; }
390
+ .diagram-editor-toolbar button {
391
+ background: #21262d; color: #8b949e; border: 1px solid #30363d;
392
+ padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
393
+ }
394
+ .diagram-editor-toolbar button:hover { background: #30363d; color: #c9d1d9; }
395
+ .diagram-editor-toolbar .btn-save { background: #238636; color: #fff; border-color: #238636; }
396
+ .diagram-editor-toolbar .btn-save:hover { background: #2ea043; }
397
+ .diagram-editor-toolbar .btn-back { color: #58a6ff; }
398
+ .diagram-split {
399
+ display: flex; gap: 0; flex: 1; min-height: 500px;
400
+ border: 1px solid #30363d; border-radius: 8px; overflow: hidden;
401
+ }
402
+ .diagram-split .code-pane {
403
+ flex: 1; display: flex; flex-direction: column; border-right: 1px solid #30363d; min-width: 0;
404
+ }
405
+ .diagram-split .code-pane .pane-header {
406
+ background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
407
+ border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
408
+ }
409
+ .diagram-split .code-pane textarea {
410
+ flex: 1; background: #0d1117; color: #c9d1d9; border: none; padding: 12px;
411
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; font-size: 13px;
412
+ line-height: 1.5; resize: none; outline: none; tab-size: 2;
413
+ }
414
+ .diagram-split .preview-pane {
415
+ flex: 1; display: flex; flex-direction: column; background: #161b22; min-width: 0;
416
+ }
417
+ .diagram-split .preview-pane .pane-header {
418
+ background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
419
+ border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
420
+ }
421
+ .diagram-split .preview-pane .pane-header button {
422
+ background: none; border: none; color: #8b949e; cursor: pointer; font-size: 11px; padding: 2px 6px;
423
+ }
424
+ .diagram-split .preview-pane .pane-header button:hover { color: #c9d1d9; }
425
+ .diagram-split .preview-container {
426
+ flex: 1; overflow: hidden; position: relative; background: #0d1117;
427
+ }
428
+ .diagram-split .preview-container svg { width: 100%; height: 100%; }
429
+ .panzoom-hint {
430
+ position: absolute; bottom: 8px; right: 12px; color: #484f58; font-size: 10px; pointer-events: none; z-index: 1;
431
+ }
367
432
  </style>
368
433
  </head>
369
434
  <body>
@@ -494,13 +559,44 @@ function renderHTML() {
494
559
 
495
560
  <!-- Diagrams Tab -->
496
561
  <div id="tab-diagrams" class="tab-content">
497
- <div class="grid" style="grid-template-columns: 1fr 1fr;">
498
- <div class="card">
499
- <h3>Epic Flow</h3>
500
- <pre class="mermaid" id="diagram-epic"></pre>
562
+ <!-- Diagram list view -->
563
+ <div id="diagram-list-view">
564
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
565
+ <p class="desc" style="margin:0;">Click a diagram to view and edit. Create new: <code>/pm:diagram-new &lt;name&gt;</code></p>
566
+ </div>
567
+ <div id="diagram-grid" class="diagram-grid"></div>
568
+ </div>
569
+ <!-- Diagram editor view (split: code | preview) -->
570
+ <div id="diagram-editor-view" class="diagram-editor">
571
+ <div class="diagram-editor-toolbar">
572
+ <div class="left">
573
+ <button class="btn-back" onclick="closeDiagramEditor()">← Back</button>
574
+ <strong id="editor-diagram-name" style="color:#c9d1d9;font-size:14px;"></strong>
575
+ <span id="editor-diagram-meta" style="color:#8b949e;font-size:11px;"></span>
576
+ <span id="editor-unsaved" style="color:#d29922;font-size:11px;display:none;">● unsaved</span>
577
+ </div>
578
+ <div class="right">
579
+ <button onclick="downloadCurrentDiagram()">↓ .mmd</button>
580
+ <button class="btn-save" onclick="saveDiagram()">Save</button>
581
+ </div>
582
+ </div>
583
+ <div class="diagram-split">
584
+ <div class="code-pane">
585
+ <div class="pane-header"><span>Mermaid Source</span></div>
586
+ <textarea id="diagram-code" spellcheck="false"></textarea>
587
+ </div>
588
+ <div class="preview-pane">
589
+ <div class="pane-header">
590
+ <span>Preview</span>
591
+ <button onclick="resetPanZoom()">Reset zoom</button>
592
+ </div>
593
+ <div class="preview-container" id="diagram-preview-container">
594
+ <div id="diagram-preview-render"></div>
595
+ <div class="panzoom-hint">Scroll to zoom · Drag to pan</div>
596
+ </div>
597
+ </div>
501
598
  </div>
502
599
  </div>
503
- <div id="project-diagrams"></div>
504
600
  </div>
505
601
 
506
602
  <!-- Tests Tab -->
@@ -534,56 +630,182 @@ const headers = { 'Authorization': 'Bearer ' + TOKEN, 'Content-Type': 'applicati
534
630
  function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
535
631
 
536
632
  function showTab(name, btn) {
633
+ // Close diagram editor if leaving diagrams tab
634
+ if (currentDiagramName && name !== 'diagrams') closeDiagramEditor();
537
635
  document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
538
636
  document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
539
637
  document.getElementById('tab-' + name).classList.add('active');
540
638
  btn.classList.add('active');
541
- if (name === 'diagrams') renderMermaid();
639
+ if (name === 'diagrams') renderDiagramsTab();
640
+ // Pause auto-refresh on tabs with editable forms
641
+ editingLock = (name === 'mcp' || name === 'diagrams');
542
642
  }
543
643
 
544
- let diagramsRendered = false;
545
- function renderMermaid() {
546
- if (diagramsRendered) return;
644
+ // --- Diagram editor state ---
645
+ let diagramsLoaded = false;
646
+ let allDiagrams = [];
647
+ let currentDiagramName = null;
648
+ let currentDiagramSaved = '';
649
+ let panzoomInstance = null;
650
+ let previewDebounce = null;
651
+
652
+ function renderDiagramsTab() {
653
+ if (diagramsLoaded) return;
547
654
  fetch('/api/diagrams', { headers: { 'Authorization': 'Bearer ' + TOKEN } })
548
655
  .then(r => r.json())
549
656
  .then(data => {
550
- document.getElementById('diagram-epic').textContent = data.epicFlow;
551
- document.getElementById('diagram-plugins').textContent = data.pluginGraph;
552
- document.getElementById('diagram-agents').textContent = data.agentTree;
553
- // Render project diagrams dynamically
554
- const container = document.getElementById('project-diagrams');
555
- container.innerHTML = '';
556
- if (data.projectDiagrams && data.projectDiagrams.length > 0) {
657
+ // Merge system-generated + project diagrams into one list
658
+ allDiagrams = [];
659
+ if (data.epicFlowHasData) {
660
+ allDiagrams.push({ name: 'epic-flow', content: data.epicFlow, type: 'system', meta: 'PRD → Epic → Tasks' });
661
+ }
662
+ allDiagrams.push({ name: 'plugins', content: data.pluginGraph, type: 'system', meta: 'Plugin dependencies' });
663
+ allDiagrams.push({ name: 'agents', content: data.agentTree, type: 'system', meta: 'Agent selection tree' });
664
+ if (data.projectDiagrams) {
557
665
  data.projectDiagrams.forEach(d => {
558
- const card = document.createElement('div');
559
- card.className = 'card';
560
- card.innerHTML = '<h3>' + esc(d.name) + '</h3>';
561
- const pre = document.createElement('pre');
562
- pre.className = 'mermaid';
563
- pre.textContent = d.content;
564
- card.appendChild(pre);
565
- container.appendChild(card);
666
+ allDiagrams.push({ name: d.name, content: d.content, type: 'project', meta: d.type || 'custom' });
566
667
  });
567
- } else {
568
- const msg = document.createElement('div');
569
- msg.className = 'card';
570
- msg.innerHTML = '<p class="desc">No project diagrams yet. Run <code>/pm:diagram-new</code> to create your first diagram.</p>';
571
- container.appendChild(msg);
572
668
  }
573
- if (typeof mermaid !== 'undefined' && mermaid.run) {
574
- mermaid.run();
575
- } else {
576
- const errorMsg = 'Mermaid library unavailable. Check network connection.';
577
- ['diagram-epic','diagram-plugins','diagram-agents'].forEach(id => {
578
- const el = document.getElementById(id);
579
- if (el) el.textContent = errorMsg;
580
- });
581
- document.querySelectorAll('#project-diagrams .mermaid').forEach(el => {
582
- el.textContent = errorMsg;
583
- });
669
+ renderDiagramGrid();
670
+ diagramsLoaded = true;
671
+ });
672
+ }
673
+
674
+ function renderDiagramGrid() {
675
+ const grid = document.getElementById('diagram-grid');
676
+ grid.textContent = '';
677
+ if (allDiagrams.length === 0) {
678
+ grid.innerHTML = '<p class="desc">No diagrams yet. Run <code>/pm:diagram-new &lt;name&gt;</code> to create one.</p>';
679
+ return;
680
+ }
681
+ allDiagrams.forEach(d => {
682
+ const item = document.createElement('div');
683
+ item.className = 'diagram-grid-item';
684
+ item.setAttribute('role', 'button');
685
+ item.setAttribute('tabindex', '0');
686
+ item.onclick = function() { openDiagramEditor(d.name); };
687
+ item.onkeydown = function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDiagramEditor(d.name); } };
688
+ const h4 = document.createElement('h4');
689
+ h4.textContent = d.name;
690
+ const meta = document.createElement('div');
691
+ meta.className = 'diagram-meta';
692
+ meta.textContent = d.type === 'system' ? '⚙ ' + d.meta : '📄 ' + d.meta;
693
+ const preview = document.createElement('div');
694
+ preview.className = 'diagram-preview';
695
+ const pre = document.createElement('pre');
696
+ pre.className = 'mermaid';
697
+ pre.textContent = d.content;
698
+ preview.appendChild(pre);
699
+ item.appendChild(h4);
700
+ item.appendChild(meta);
701
+ item.appendChild(preview);
702
+ grid.appendChild(item);
703
+ });
704
+ // Render mini previews
705
+ if (typeof mermaid !== 'undefined' && mermaid.run) {
706
+ mermaid.run({ nodes: grid.querySelectorAll('.mermaid') });
707
+ }
708
+ }
709
+
710
+ function openDiagramEditor(name) {
711
+ const d = allDiagrams.find(x => x.name === name);
712
+ if (!d) return;
713
+ currentDiagramName = name;
714
+ currentDiagramSaved = d.content;
715
+ document.getElementById('editor-diagram-name').textContent = name;
716
+ document.getElementById('editor-diagram-meta').textContent = d.type === 'system' ? '(system — read-only)' : '';
717
+ document.getElementById('editor-unsaved').style.display = 'none';
718
+ const codeEl = document.getElementById('diagram-code');
719
+ codeEl.value = d.content;
720
+ codeEl.readOnly = d.type === 'system';
721
+ document.getElementById('diagram-list-view').style.display = 'none';
722
+ document.getElementById('diagram-editor-view').classList.add('active');
723
+ renderPreview(d.content);
724
+ }
725
+
726
+ function closeDiagramEditor() {
727
+ clearTimeout(previewDebounce); previewDebounce = null;
728
+ if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
729
+ document.getElementById('diagram-editor-view').classList.remove('active');
730
+ document.getElementById('diagram-list-view').style.display = '';
731
+ currentDiagramName = null;
732
+ }
733
+
734
+ function renderPreview(source) {
735
+ const container = document.getElementById('diagram-preview-render');
736
+ container.textContent = '';
737
+ if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
738
+ if (!source || !source.trim()) { container.textContent = 'Empty diagram'; return; }
739
+ const pre = document.createElement('pre');
740
+ pre.className = 'mermaid';
741
+ pre.textContent = source;
742
+ container.appendChild(pre);
743
+ if (typeof mermaid !== 'undefined' && mermaid.run) {
744
+ mermaid.run({ nodes: [pre] }).then(function() {
745
+ const svg = container.querySelector('svg');
746
+ if (svg && typeof panzoom !== 'undefined') {
747
+ svg.style.width = '100%';
748
+ svg.style.height = '100%';
749
+ panzoomInstance = panzoom(svg, { maxZoom: 10, minZoom: 0.1, smoothScroll: false });
584
750
  }
585
- diagramsRendered = true;
751
+ }).catch(function() {
752
+ container.textContent = 'Mermaid syntax error — check your code';
586
753
  });
754
+ }
755
+ }
756
+
757
+ function resetPanZoom() {
758
+ if (panzoomInstance) {
759
+ panzoomInstance.moveTo(0, 0);
760
+ panzoomInstance.zoomAbs(0, 0, 1);
761
+ }
762
+ }
763
+
764
+ // Live preview on code change
765
+ document.addEventListener('DOMContentLoaded', function() {
766
+ const codeEl = document.getElementById('diagram-code');
767
+ if (codeEl) {
768
+ codeEl.addEventListener('input', function() {
769
+ document.getElementById('editor-unsaved').style.display = codeEl.value !== currentDiagramSaved ? '' : 'none';
770
+ clearTimeout(previewDebounce);
771
+ previewDebounce = setTimeout(function() { renderPreview(codeEl.value); }, 600);
772
+ });
773
+ }
774
+ });
775
+
776
+ async function saveDiagram() {
777
+ if (!currentDiagramName) return;
778
+ const d = allDiagrams.find(x => x.name === currentDiagramName);
779
+ if (d && d.type === 'system') { toast('System diagrams are read-only', false); return; }
780
+ const content = document.getElementById('diagram-code').value;
781
+ try {
782
+ await api('POST', '/api/diagrams/' + encodeURIComponent(currentDiagramName), { content: content });
783
+ currentDiagramSaved = content;
784
+ if (d) d.content = content;
785
+ document.getElementById('editor-unsaved').style.display = 'none';
786
+ // Re-render grid so mini previews reflect saved content
787
+ diagramsLoaded = false;
788
+ toast('Diagram saved', true);
789
+ } catch (e) {
790
+ toast('Save failed: ' + e.message, false);
791
+ }
792
+ }
793
+
794
+ function downloadCurrentDiagram() {
795
+ const src = document.getElementById('diagram-code').value;
796
+ if (!src || !currentDiagramName) return;
797
+ const safeName = currentDiagramName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
798
+ const blob = new Blob([src], { type: 'text/plain' });
799
+ const a = document.createElement('a');
800
+ const url = URL.createObjectURL(blob);
801
+ a.href = url;
802
+ a.download = safeName;
803
+ a.style.display = 'none';
804
+ document.body.appendChild(a);
805
+ try { a.click(); } finally {
806
+ document.body.removeChild(a);
807
+ setTimeout(function() { URL.revokeObjectURL(url); }, 100);
808
+ }
587
809
  }
588
810
 
589
811
  function renderTestPlan(md) {
@@ -963,7 +1185,11 @@ function addEnvSuggestion(key, val, hint) {
963
1185
  addEnvRow(key, val);
964
1186
  }
965
1187
 
1188
+ // Pause auto-refresh while MCP & Keys tab is active (editing forms)
1189
+ let editingLock = false;
1190
+
966
1191
  async function refresh() {
1192
+ if (editingLock) return;
967
1193
  try {
968
1194
  const data = await api('GET', '/api/status');
969
1195
  loadStatus(data);
@@ -1030,6 +1256,41 @@ const server = http.createServer(async (req, res) => {
1030
1256
  return json(res, 200, getDiagramsData());
1031
1257
  }
1032
1258
 
1259
+ // Save diagram content
1260
+ const diagramMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
1261
+ if (req.method === 'POST' && diagramMatch) {
1262
+ const name = decodeURIComponent(diagramMatch[1]);
1263
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
1264
+ const { body, err } = await parseJSONBody(req, res);
1265
+ if (err) return;
1266
+ if (typeof body.content !== 'string') return json(res, 400, { error: 'content must be a string' });
1267
+ if (body.content.length > 500000) return json(res, 400, { error: 'content too large (max 500KB)' });
1268
+ const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
1269
+ if (!fs.existsSync(diagramsDir)) fs.mkdirSync(diagramsDir, { recursive: true });
1270
+ fs.writeFileSync(path.join(diagramsDir, name + '.mmd'), body.content, 'utf8');
1271
+ // Update metadata timestamp if exists, or create
1272
+ const metaPath = path.join(diagramsDir, name + '.meta.json');
1273
+ let meta = {};
1274
+ try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch {}
1275
+ meta.name = meta.name || name;
1276
+ meta.updated = new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');
1277
+ if (!meta.created) meta.created = meta.updated;
1278
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
1279
+ return json(res, 200, { ok: true });
1280
+ }
1281
+
1282
+ const diagramDeleteMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
1283
+ if (req.method === 'DELETE' && diagramDeleteMatch) {
1284
+ const name = decodeURIComponent(diagramDeleteMatch[1]);
1285
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
1286
+ const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
1287
+ const mmdPath = path.join(diagramsDir, name + '.mmd');
1288
+ const metaPath = path.join(diagramsDir, name + '.meta.json');
1289
+ try { fs.unlinkSync(mmdPath); } catch {}
1290
+ try { fs.unlinkSync(metaPath); } catch {}
1291
+ return json(res, 200, { ok: true });
1292
+ }
1293
+
1033
1294
  if (req.method === 'GET' && parsedUrl.pathname === '/api/tests') {
1034
1295
  return json(res, 200, getTestsData());
1035
1296
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "3.22.0",
3
+ "version": "3.22.2",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "workspaces": [