great-cto 2.98.1 → 2.99.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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "great_cto",
|
|
3
3
|
"id": "great_cto",
|
|
4
4
|
"description": "Engineering process for solo founders and teams up to 50 engineers. Agents do architecture, code review, QA, and security. You make two decisions per feature.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.99.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Great CTO",
|
|
8
8
|
"url": "https://github.com/avelikiy/great_cto"
|
|
@@ -11,7 +11,18 @@ import { log } from './log.mjs';
|
|
|
11
11
|
// .beads/interactions.jsonl changes (the file watcher in watchBeads() calls
|
|
12
12
|
// bdCacheInvalidate(cwd) before broadcasting). This avoids spawning `bd list`
|
|
13
13
|
// on every API call when 5+ projects are open in tabs.
|
|
14
|
-
|
|
14
|
+
// Measured, not guessed: `bd list` on this repository takes 3.0–4.6 s and
|
|
15
|
+
// `bd --version` — pure process startup, no query — takes 1.13 s. At a 2 s TTL
|
|
16
|
+
// the entry expired before the call that filled it had returned, so a page load
|
|
17
|
+
// firing /api/inbox, /api/tasks and /api/metrics paid full price three times and
|
|
18
|
+
// every endpoint sat at ~5 s. A cache whose TTL is shorter than the operation it
|
|
19
|
+
// caches is not a cache; it is a counter of missed opportunities.
|
|
20
|
+
//
|
|
21
|
+
// 30 s is safe because staleness is bounded by invalidation, not by the clock:
|
|
22
|
+
// `bdCacheInvalidate` fires on every write through the board, and the file
|
|
23
|
+
// watchers fire when anything changes the store underneath us. The TTL is only
|
|
24
|
+
// the backstop for a change that arrived through neither.
|
|
25
|
+
const BD_CACHE_TTL_MS = Number(process.env.GREAT_CTO_BD_CACHE_TTL_MS || 30000);
|
|
15
26
|
|
|
16
27
|
function bdCacheInvalidate(cwd) { bdCache.delete(cwd); }
|
|
17
28
|
|
|
@@ -500,6 +511,7 @@ function detectAgent(task) {
|
|
|
500
511
|
|
|
501
512
|
export {
|
|
502
513
|
bdCacheInvalidate,
|
|
514
|
+
BD_CACHE_TTL_MS,
|
|
503
515
|
BD_BIN,
|
|
504
516
|
bdEnv,
|
|
505
517
|
bd,
|
|
@@ -1144,8 +1144,32 @@ async function dispatch(req, res, url, cwd) {
|
|
|
1144
1144
|
|
|
1145
1145
|
// Build version — so the board shows which great_cto version it's running.
|
|
1146
1146
|
if (pathname === '/api/version') {
|
|
1147
|
+
// A board is a process, and a process keeps the version it started with.
|
|
1148
|
+
// This one served v2.95.0 for nine days while three installs in a row
|
|
1149
|
+
// reported success: the installer updated files, the running server kept
|
|
1150
|
+
// answering, and nothing compared the two. The installer restarts the board
|
|
1151
|
+
// now, but a board started any other way still needs to be able to say that
|
|
1152
|
+
// it is behind — an install the operator cannot see is not an install.
|
|
1153
|
+
//
|
|
1154
|
+
// Three states, not two. `unknown` when the cache cannot be read: a version
|
|
1155
|
+
// we failed to look up must not render as agreement.
|
|
1156
|
+
let installed = null;
|
|
1157
|
+
try {
|
|
1158
|
+
const root = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'local', 'great_cto');
|
|
1159
|
+
const versions = fs.readdirSync(root).filter((d) => /^\d+\.\d+\.\d+$/.test(d));
|
|
1160
|
+
installed = versions.sort((a, b) => {
|
|
1161
|
+
const x = a.split('.').map(Number), y = b.split('.').map(Number);
|
|
1162
|
+
return (x[0] - y[0]) || (x[1] - y[1]) || (x[2] - y[2]);
|
|
1163
|
+
}).pop() || null;
|
|
1164
|
+
} catch { installed = null; }
|
|
1165
|
+
const stale = installed && BUILD_VERSION !== 'unknown'
|
|
1166
|
+
? (installed !== BUILD_VERSION ? 'yes' : 'no')
|
|
1167
|
+
: 'unknown';
|
|
1147
1168
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
1148
|
-
res.end(JSON.stringify({
|
|
1169
|
+
res.end(JSON.stringify({
|
|
1170
|
+
version: BUILD_VERSION, installed, stale,
|
|
1171
|
+
surface: 'builder', node: process.version.replace(/^v/, ''),
|
|
1172
|
+
}));
|
|
1149
1173
|
return true;
|
|
1150
1174
|
}
|
|
1151
1175
|
|
|
@@ -153,6 +153,16 @@ button { font-family: inherit; cursor: pointer; }
|
|
|
153
153
|
radial-gradient(ellipse 70% 50% at 100% 100%, rgba(0, 217, 126, 0.03), transparent 70%);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
.stale-board {
|
|
157
|
+
display: flex; align-items: center; gap: 10px;
|
|
158
|
+
margin: 0 0 12px; padding: 10px 14px;
|
|
159
|
+
background: rgba(180, 83, 9, 0.10);
|
|
160
|
+
border: 1px solid var(--status-progress);
|
|
161
|
+
border-radius: 8px;
|
|
162
|
+
font-size: 13px; color: var(--text);
|
|
163
|
+
}
|
|
164
|
+
.stale-board code { font-family: var(--mono); font-size: 12px; }
|
|
165
|
+
|
|
156
166
|
/* ── Sidebar ──────────────────────────────────────────────────────────────── */
|
|
157
167
|
.sidebar {
|
|
158
168
|
position: relative; z-index: 1;
|
|
@@ -4564,6 +4574,7 @@ function connectSSE() {
|
|
|
4564
4574
|
// Sidebar counter FIRST, independent of render
|
|
4565
4575
|
updateNavCounts();
|
|
4566
4576
|
try { renderKanban(filterTasks(searchQuery)); } catch (e) { console.warn('SSE renderKanban failed', e); }
|
|
4577
|
+
checkBoardFreshness();
|
|
4567
4578
|
refreshInbox();
|
|
4568
4579
|
};
|
|
4569
4580
|
es.addEventListener('tasks', e => {
|
|
@@ -5452,6 +5463,33 @@ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSide();
|
|
|
5452
5463
|
// Three things a drawer owes a keyboard, all of them things a transform alone
|
|
5453
5464
|
// does not give you: it leaves the tab order when closed, Escape closes it, and
|
|
5454
5465
|
// focus moves in on open and back to the button that opened it on close.
|
|
5466
|
+
/* The board tells on itself.
|
|
5467
|
+
*
|
|
5468
|
+
* A board is a process, and a process keeps the version it started with. This
|
|
5469
|
+
* one served v2.95.0 for nine days while three installs in a row reported
|
|
5470
|
+
* success — the installer wrote files, the running server kept answering, and
|
|
5471
|
+
* nothing compared the two. The installer restarts the board now; this is for a
|
|
5472
|
+
* board started any other way, so that being behind is visible rather than
|
|
5473
|
+
* merely true.
|
|
5474
|
+
*
|
|
5475
|
+
* Three states, like everywhere else on this page: `yes` says so, `no` is
|
|
5476
|
+
* silent, and `unknown` — the plugin cache could not be read — is also silent,
|
|
5477
|
+
* because a comparison we could not make must not be announced as a mismatch.
|
|
5478
|
+
*/
|
|
5479
|
+
async function checkBoardFreshness() {
|
|
5480
|
+
const v = await api('/api/version');
|
|
5481
|
+
if (!v || v.stale !== 'yes') return;
|
|
5482
|
+
const host = document.querySelector('.workspace');
|
|
5483
|
+
if (!host || document.getElementById('stale-board')) return;
|
|
5484
|
+
const el = document.createElement('div');
|
|
5485
|
+
el.id = 'stale-board';
|
|
5486
|
+
el.className = 'stale-board';
|
|
5487
|
+
el.setAttribute('role', 'status');
|
|
5488
|
+
el.innerHTML = `<span>This board is running <code>v${esc(v.version)}</code> — `
|
|
5489
|
+
+ `<code>v${esc(v.installed)}</code> is installed. Restart it to pick up the new version.</span>`;
|
|
5490
|
+
host.insertBefore(el, host.firstChild);
|
|
5491
|
+
}
|
|
5492
|
+
|
|
5455
5493
|
function menuIsOpen() {
|
|
5456
5494
|
return !!document.getElementById('sidebar')?.classList.contains('open');
|
|
5457
5495
|
}
|
package/package.json
CHANGED