(projectRoot)
| 78 | options.projectRoot = path.resolve(argv[++i]); |
| 79 | } else if (arg === '--open') { |
| 80 | options.open = true; |
| 81 | } else if (arg === '--help' || arg === '-h') { |
| 82 | options.help = true; |
| 83 | } |
| 84 | } |
| 85 | return options; |
| 86 | } |
| 87 | |
| 88 | function usage() { |
| 89 | return [ |
| 90 | 'Usage: node scripts/dashboard-server.js [--port 4180] [--project-root <path>] [--open]', |
| 91 | '', |
| 92 | 'Serves local Mission Control over canonical .planning/ state.', |
| 93 | 'Binds to 127.0.0.1 only. See docs/DASHBOARD_SPEC.md.', |
| 94 | ].join('\n'); |
| 95 | } |
| 96 | |
| 97 | // --------------------------------------------------------------------------- |
| 98 | // Data collection (cached, invalidated by watcher or TTL) |
| 99 | // --------------------------------------------------------------------------- |
| 100 | |
| 101 | function createDataSource(projectRoot) { |
| 102 | const state = { cache: null, collectedAt: 0, dirty: true }; |
| 103 | |
| 104 | function readHandoffs() { |
| 105 | const dir = path.join(projectRoot, '.planning', 'handoffs'); |
| 106 | try { |
| 107 | if (!fs.existsSync(dir)) return []; |
| 108 | return fs.readdirSync(dir) |
| 109 | .filter((name) => name.endsWith('.md')) |
| 110 | .filter((name) => { |
| 111 | try { |
| 112 | const stat = fs.lstatSync(path.join(dir, name)); |
| 113 | return stat.isFile() && !stat.isSymbolicLink(); |
| 114 | } catch { return false; } |
| 115 | }) |
| 116 | .sort((a, b) => b.localeCompare(a)) |
| 117 | .slice(0, 50) |
| 118 | .map((name) => { |
| 119 | const filePath = path.join(dir, name); |
| 120 | let modifiedAt = null; |
| 121 | try { modifiedAt = fs.statSync(filePath).mtime.toISOString(); } catch { /* render as unknown */ } |
| 122 | return { name, path: `.planning/handoffs/${name}`, modifiedAt }; |
| 123 | }) |
| 124 | .sort((a, b) => String(b.modifiedAt).localeCompare(String(a.modifiedAt)) || b.name.localeCompare(a.name)); |
| 125 | } catch { |
| 126 | return []; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | function readDaemon() { |
| 131 | try { |
| 132 | const raw = fs.readFileSync(path.join(projectRoot, '.planning', 'daemon.json'), 'utf8'); |
| 133 | return JSON.parse(raw); |
| 134 | } catch { |
| 135 | return null; |
| 136 | } |
| 137 | } |
no outgoing calls
no test coverage detected