great-cto 2.77.0 → 2.77.1

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 (34) hide show
  1. package/board/.claude-plugin/plugin.json +256 -0
  2. package/board/packages/board/lib/alerts.mjs +413 -0
  3. package/board/packages/board/lib/beads.mjs +233 -0
  4. package/board/packages/board/lib/config.mjs +45 -0
  5. package/board/packages/board/lib/data-readers.mjs +340 -0
  6. package/board/packages/board/lib/fleet.mjs +363 -0
  7. package/board/packages/board/lib/metrics.mjs +282 -0
  8. package/board/packages/board/lib/notifications.mjs +50 -0
  9. package/board/packages/board/lib/projects.mjs +256 -0
  10. package/board/packages/board/lib/routes.mjs +1036 -0
  11. package/board/packages/board/lib/share.mjs +143 -0
  12. package/board/packages/board/lib/sse.mjs +20 -0
  13. package/board/packages/board/lib/state.mjs +31 -0
  14. package/board/packages/board/lib/util.mjs +36 -0
  15. package/board/packages/board/lib/verdicts.mjs +168 -0
  16. package/board/packages/board/lib/watchers.mjs +87 -0
  17. package/board/packages/board/mcp-server.mjs +310 -0
  18. package/board/packages/board/public/assets/apple-touch-icon.png +0 -0
  19. package/board/packages/board/public/assets/favicon-16.png +0 -0
  20. package/board/packages/board/public/assets/favicon-32.png +0 -0
  21. package/board/packages/board/public/assets/favicon.ico +0 -0
  22. package/board/packages/board/public/assets/favicon.svg +8 -0
  23. package/board/packages/board/public/index.html +5452 -0
  24. package/board/packages/board/public/share.html +1190 -0
  25. package/board/packages/board/public/sw.js +79 -0
  26. package/board/packages/board/push-adapter.mjs +222 -0
  27. package/board/packages/board/server.mjs +133 -0
  28. package/board/packages/cli/dist/archetypes.js +1461 -0
  29. package/board/scripts/lib/change-tier.mjs +119 -0
  30. package/board/scripts/lib/gate-plan.mjs +119 -0
  31. package/board/scripts/lib/judge-model.mjs +42 -0
  32. package/dist/board-path.js +47 -0
  33. package/dist/main.js +3 -31
  34. package/package.json +3 -1
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * great_cto MCP server — exposes board metrics as MCP tools.
4
+ *
5
+ * Implements the Model Context Protocol over stdio (JSON-RPC 2.0).
6
+ * Zero external dependencies — pure Node.js built-ins only.
7
+ *
8
+ * Tools exposed:
9
+ * project_status — pipeline stages, open gates, blocked tasks
10
+ * cost_summary — LLM spend, budget burn, top features
11
+ * pipeline_stages — detailed stage list with verdicts
12
+ * recent_verdicts — last N agent verdicts (default 10)
13
+ *
14
+ * Usage:
15
+ * # Start as MCP server (board must be running on --port)
16
+ * node packages/board/mcp-server.mjs --port 3141
17
+ *
18
+ * # Or via great-cto CLI:
19
+ * great-cto mcp [--port 3141]
20
+ *
21
+ * .mcp.json (Claude Code / Cursor):
22
+ * {
23
+ * "mcpServers": {
24
+ * "great_cto": {
25
+ * "command": "node",
26
+ * "args": ["/path/to/great_cto/packages/board/mcp-server.mjs"],
27
+ * "env": { "GREAT_CTO_PORT": "3141" }
28
+ * }
29
+ * }
30
+ * }
31
+ */
32
+
33
+ import { createInterface } from 'node:readline';
34
+
35
+ const PORT = parseInt(process.env.GREAT_CTO_PORT || '3141', 10);
36
+ const BASE_URL = `http://127.0.0.1:${PORT}`;
37
+
38
+ // ── MCP protocol constants ─────────────────────────────────────────────────
39
+ const PROTO_VERSION = '2024-11-05';
40
+ const SERVER_INFO = { name: 'great_cto', version: '1.0.0' };
41
+
42
+ // ── Tool definitions ───────────────────────────────────────────────────────
43
+ const TOOLS = [
44
+ {
45
+ name: 'project_status',
46
+ description:
47
+ 'Returns the current pipeline status for the great_cto project: ' +
48
+ 'open gates awaiting approval, blocked tasks, P0 incidents, and ' +
49
+ 'in-progress work. Use this before starting expensive agent tasks to ' +
50
+ 'check if there are blockers that need resolution first.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ project: {
55
+ type: 'string',
56
+ description: 'Project slug (optional). Defaults to the board\'s active project.',
57
+ },
58
+ },
59
+ },
60
+ },
61
+ {
62
+ name: 'cost_summary',
63
+ description:
64
+ 'Returns AI agent LLM spend for the project: total cost over a period, ' +
65
+ 'daily burn rate, projected monthly cost, budget status, and cost ' +
66
+ 'broken down by feature. Use this to check budget before spawning ' +
67
+ 'resource-intensive agent runs.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ days: {
72
+ type: 'number',
73
+ description: 'Window in days (1, 7, 30, 90, 365). Default: 30.',
74
+ },
75
+ project: {
76
+ type: 'string',
77
+ description: 'Project slug (optional).',
78
+ },
79
+ },
80
+ },
81
+ },
82
+ {
83
+ name: 'pipeline_stages',
84
+ description:
85
+ 'Returns the full pipeline stage list with status (idle/done/failed) ' +
86
+ 'and the last verdict for each agent. Useful for understanding which ' +
87
+ 'stage the current feature is at and what the last agent decided.',
88
+ inputSchema: {
89
+ type: 'object',
90
+ properties: {
91
+ project: {
92
+ type: 'string',
93
+ description: 'Project slug (optional).',
94
+ },
95
+ },
96
+ },
97
+ },
98
+ {
99
+ name: 'recent_verdicts',
100
+ description:
101
+ 'Returns the most recent agent verdict lines — timestamps, agent names, ' +
102
+ 'verdict values (APPROVED/DONE/BLOCKED), and costs. Useful for a quick ' +
103
+ 'recap of what agents have done recently.',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ limit: {
108
+ type: 'number',
109
+ description: 'Number of verdicts to return (1–50). Default: 10.',
110
+ },
111
+ project: {
112
+ type: 'string',
113
+ description: 'Project slug (optional).',
114
+ },
115
+ },
116
+ },
117
+ },
118
+ ];
119
+
120
+ // ── HTTP helpers ───────────────────────────────────────────────────────────
121
+ async function boardFetch(path) {
122
+ try {
123
+ const res = await fetch(`${BASE_URL}${path}`);
124
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
125
+ return res.json();
126
+ } catch (e) {
127
+ throw new Error(`Board unreachable at ${BASE_URL} — is great-cto board running? (${e.message})`);
128
+ }
129
+ }
130
+
131
+ function pqs(project) {
132
+ return project ? `?project=${encodeURIComponent(project)}` : '';
133
+ }
134
+
135
+ // ── Tool handlers ──────────────────────────────────────────────────────────
136
+ async function callTool(name, args = {}) {
137
+ const proj = args.project;
138
+ const qs = pqs(proj);
139
+
140
+ if (name === 'project_status') {
141
+ const [inbox, metrics] = await Promise.all([
142
+ boardFetch(`/api/inbox${qs}`),
143
+ boardFetch(`/api/metrics${qs}`),
144
+ ]);
145
+ const summary = inbox.summary || {};
146
+ const lines = [
147
+ `## Project status${proj ? ` — ${proj}` : ''}`,
148
+ '',
149
+ `**Open gates:** ${summary.gates ?? 0}`,
150
+ `**Blocked tasks:** ${summary.blocked ?? 0}`,
151
+ `**P0 incidents:** ${summary.p0 ?? 0}`,
152
+ `**Stale in-progress:** ${summary.stale ?? 0}`,
153
+ ];
154
+ if ((inbox.pending_gates || []).length > 0) {
155
+ lines.push('', '### Gates awaiting approval');
156
+ for (const g of inbox.pending_gates.slice(0, 5)) {
157
+ lines.push(`- **${g.id}** ${g.title} _(${g.status})_`);
158
+ }
159
+ }
160
+ if ((inbox.blocked || []).length > 0) {
161
+ lines.push('', '### Blocked tasks');
162
+ for (const b of inbox.blocked.slice(0, 5)) {
163
+ lines.push(`- **${b.id}** ${b.title}`);
164
+ }
165
+ }
166
+ if (metrics) {
167
+ const done = metrics.done ?? 0;
168
+ const total = metrics.total ?? 0;
169
+ lines.push('', `**Tasks:** ${done}/${total} done`);
170
+ }
171
+ return lines.join('\n');
172
+ }
173
+
174
+ if (name === 'cost_summary') {
175
+ const days = Math.min(365, Math.max(1, args.days || 30));
176
+ const d = await boardFetch(`/api/cost${pqs(proj)}&days=${days}`);
177
+ const lines = [
178
+ `## Cost summary${proj ? ` — ${proj}` : ''} (last ${days} days)`,
179
+ '',
180
+ `**Total LLM spend:** $${(d.total_llm || 0).toFixed(2)}`,
181
+ `**Daily burn (active days):** $${(d.daily_avg || 0).toFixed(2)}/day`,
182
+ `**Projected monthly:** $${(d.projected_monthly || 0).toFixed(0)}`,
183
+ ];
184
+ if (d.monthly_budget) {
185
+ const status = d.over_budget ? '⚠️ OVER BUDGET' : '✅ within budget';
186
+ lines.push(`**Budget:** $${d.monthly_budget}/month — ${status}`);
187
+ }
188
+ if (d.savings_x) {
189
+ lines.push(`**vs Human team:** ${d.savings_x}× cheaper`);
190
+ }
191
+ if ((d.by_feature || []).length > 0) {
192
+ lines.push('', '### Top features by AI spend');
193
+ for (const f of d.by_feature.slice(0, 8)) {
194
+ lines.push(`- **${f.feature}**: $${f.llm.toFixed(2)} (${f.runs} agent runs)`);
195
+ }
196
+ }
197
+ return lines.join('\n');
198
+ }
199
+
200
+ if (name === 'pipeline_stages') {
201
+ const stages = await boardFetch(`/api/pipeline${qs}`);
202
+ const lines = [
203
+ `## Pipeline stages${proj ? ` — ${proj}` : ''}`,
204
+ '',
205
+ '| Stage | Status | Verdict | Last run |',
206
+ '|---|---|---|---|',
207
+ ];
208
+ for (const s of stages || []) {
209
+ const status = s.status || 'idle';
210
+ const verdict = s.verdict || '—';
211
+ const ts = s.last_ts ? s.last_ts.slice(0, 16) : '—';
212
+ lines.push(`| ${s.stage} | ${status} | ${verdict} | ${ts} |`);
213
+ }
214
+ return lines.join('\n');
215
+ }
216
+
217
+ if (name === 'recent_verdicts') {
218
+ const limit = Math.min(50, Math.max(1, args.limit || 10));
219
+ const data = await boardFetch(`/api/metrics${qs}`);
220
+ const verdicts = (data.verdicts || []).slice(-limit).reverse();
221
+ if (verdicts.length === 0) return 'No recent verdicts found.';
222
+ const lines = [
223
+ `## Recent verdicts${proj ? ` — ${proj}` : ''} (last ${verdicts.length})`,
224
+ '',
225
+ '| Time | Agent | Verdict | Cost |',
226
+ '|---|---|---|---|',
227
+ ];
228
+ for (const v of verdicts) {
229
+ const ts = (v.ts || '').slice(0, 16);
230
+ const cost = v.cost_usd != null ? `$${v.cost_usd.toFixed(2)}` : '—';
231
+ lines.push(`| ${ts} | ${v.agent || '—'} | ${v.verdict || '—'} | ${cost} |`);
232
+ }
233
+ return lines.join('\n');
234
+ }
235
+
236
+ throw new Error(`Unknown tool: ${name}`);
237
+ }
238
+
239
+ // ── MCP JSON-RPC dispatcher ────────────────────────────────────────────────
240
+ async function handleRequest(req) {
241
+ const id = req.id ?? null;
242
+
243
+ function ok(result) {
244
+ return { jsonrpc: '2.0', id, result };
245
+ }
246
+ function err(code, message, data) {
247
+ return { jsonrpc: '2.0', id, error: { code, message, ...(data ? { data } : {}) } };
248
+ }
249
+
250
+ try {
251
+ const method = req.method;
252
+
253
+ if (method === 'initialize') {
254
+ return ok({
255
+ protocolVersion: PROTO_VERSION,
256
+ serverInfo: SERVER_INFO,
257
+ capabilities: { tools: {} },
258
+ });
259
+ }
260
+
261
+ if (method === 'initialized') return null; // notification, no response
262
+
263
+ if (method === 'tools/list') {
264
+ return ok({ tools: TOOLS });
265
+ }
266
+
267
+ if (method === 'tools/call') {
268
+ const { name, arguments: args } = req.params || {};
269
+ if (!name) return err(-32602, 'Missing tool name');
270
+ try {
271
+ const text = await callTool(name, args || {});
272
+ return ok({ content: [{ type: 'text', text }] });
273
+ } catch (e) {
274
+ return ok({
275
+ content: [{ type: 'text', text: `Error: ${e.message}` }],
276
+ isError: true,
277
+ });
278
+ }
279
+ }
280
+
281
+ if (method === 'ping') return ok({});
282
+
283
+ return err(-32601, `Method not found: ${method}`);
284
+ } catch (e) {
285
+ return err(-32603, e.message);
286
+ }
287
+ }
288
+
289
+ // ── stdio transport ────────────────────────────────────────────────────────
290
+ const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
291
+
292
+ rl.on('line', async (line) => {
293
+ const trimmed = line.trim();
294
+ if (!trimmed) return;
295
+ let req;
296
+ try {
297
+ req = JSON.parse(trimmed);
298
+ } catch {
299
+ const resp = { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } };
300
+ process.stdout.write(JSON.stringify(resp) + '\n');
301
+ return;
302
+ }
303
+ const resp = await handleRequest(req);
304
+ if (resp !== null) {
305
+ process.stdout.write(JSON.stringify(resp) + '\n');
306
+ }
307
+ });
308
+
309
+ rl.on('close', () => process.exit(0));
310
+ process.stderr.write(`great_cto MCP server ready (board: ${BASE_URL})\n`);
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="greatcto">
2
+ <rect width="64" height="64" rx="12" fill="#0a0e0c"/>
3
+ <g stroke="#00d97e" stroke-width="9" stroke-linecap="round">
4
+ <line x1="32" y1="14" x2="32" y2="50"/>
5
+ <line x1="16.4" y1="23" x2="47.6" y2="41"/>
6
+ <line x1="16.4" y1="41" x2="47.6" y2="23"/>
7
+ </g>
8
+ </svg>