great-cto 3.26.1 → 3.26.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "great-cto",
|
|
3
3
|
"description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 70 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
|
|
4
|
-
"version": "3.26.
|
|
4
|
+
"version": "3.26.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -648,9 +648,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
648
648
|
.tab-btn:hover { background: var(--bg-muted); color: var(--text); }
|
|
649
649
|
.tab-btn.active { background: var(--bg-strong); color: var(--text); font-weight: 500; }
|
|
650
650
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
651
|
+
/* A stray `}` stood here, left behind when `.leash-chip` was removed (34657cf5
|
|
652
|
+
added it, a later cleanup took the rule and not its brace) along with the
|
|
653
|
+
comment that introduced it. At the top level a `}` opens a qualified rule, so
|
|
654
|
+
it swallowed everything up to the next `{` — brace, dead comment and the
|
|
655
|
+
`.icon-btn` selector became one invalid prelude, and `.icon-btn`'s box went
|
|
656
|
+
with it. The icon buttons had been rendering at the browser's default size,
|
|
657
|
+
beside a `:hover` rule that still worked. */
|
|
654
658
|
.icon-btn {
|
|
655
659
|
background: transparent; border: 1px solid transparent;
|
|
656
660
|
width: 30px; height: 30px;
|
|
@@ -1433,6 +1437,26 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
1433
1437
|
/* Same shape as `.muted`: the `--mono` token exists and is used 115×, the
|
|
1434
1438
|
CLASS that applies it did not. */
|
|
1435
1439
|
.mono { font-family: var(--mono); }
|
|
1440
|
+
/* The board has two text inputs styled in place — `#agent-panel input` and
|
|
1441
|
+
`.fleet-search input[type="search"]` — and a third, the Sessions search box,
|
|
1442
|
+
that asked for the house look by name and got the user agent's. Same values
|
|
1443
|
+
as those two, said once, so the name now means what it says. */
|
|
1444
|
+
.input {
|
|
1445
|
+
background: var(--bg-muted); border: 1px solid var(--border);
|
|
1446
|
+
color: var(--text); font-family: var(--mono); font-size: var(--fs-body);
|
|
1447
|
+
padding: 8px 12px; border-radius: 0;
|
|
1448
|
+
}
|
|
1449
|
+
.input:focus { outline: 2px solid var(--focus-ring); outline-offset: 1px; border-color: transparent; }
|
|
1450
|
+
/* Visually hidden, still read aloud. The one use — the verdict modal's
|
|
1451
|
+
`aria-labelledby` target — carried the whole pattern inline, which is how a
|
|
1452
|
+
second use would have got a heading that is merely invisible to everyone, or
|
|
1453
|
+
merely visible to everyone. `clip-path` over the old `left:-10000px`: an
|
|
1454
|
+
off-screen box still counts as layout, and in an RTL context widens the page. */
|
|
1455
|
+
.sr-only {
|
|
1456
|
+
position: absolute; width: 1px; height: 1px;
|
|
1457
|
+
margin: -1px; padding: 0; border: 0;
|
|
1458
|
+
overflow: hidden; clip-path: inset(50%); white-space: nowrap;
|
|
1459
|
+
}
|
|
1436
1460
|
.tier-badge { font-size: var(--fs-caption); padding: 1px 8px; border-radius: 999px; margin-left: 6px;
|
|
1437
1461
|
font-family: var(--mono); white-space: nowrap; }
|
|
1438
1462
|
.tier-badge:empty { display: none; }
|
|
@@ -2115,11 +2139,30 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
2115
2139
|
background: var(--bg-card); color: var(--text); min-width: 26ch;
|
|
2116
2140
|
}
|
|
2117
2141
|
.budgets-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
|
|
2118
|
-
.
|
|
2142
|
+
/* Restored from ad779c7a~1. That commit — about subagent batching — replaced
|
|
2143
|
+
this selector with a bare `.` and left `id var(--border);`, the tail of a
|
|
2144
|
+
duplicated border-bottom, before the closing brace.
|
|
2145
|
+
|
|
2146
|
+
`.` is not a selector, and CSS does not stop there: the prelude runs on to the
|
|
2147
|
+
next `{`, which is `td`'s, and takes `td`'s block with it. Confirmed against
|
|
2148
|
+
the released 3.26.0 with the browser's own parser — of this region it keeps
|
|
2149
|
+
`.budgets-table`, `.bt-slug`, `.bt-desc`, `.bt-runs`, `.bt-cap`, `.bt-spend`,
|
|
2150
|
+
`.bt-usd`, `th[title]` and `.budgets-idle summary`, and exactly `th` and `td`
|
|
2151
|
+
are gone. So the table has had no header typography AND no cell padding or
|
|
2152
|
+
borders since that commit. `th[title] { cursor: help }` survived, one rule
|
|
2153
|
+
further down, which is part of why nobody looked: the headers still did
|
|
2154
|
+
something on hover.
|
|
2155
|
+
|
|
2156
|
+
`font-weight: 500` is kept deliberately and is NOT a line that crept in. It
|
|
2157
|
+
was ad779c7a's own edit — one of seven in that commit, and its sibling
|
|
2158
|
+
`.cost-table th` took the identical change on the line above and parses fine.
|
|
2159
|
+
The weight normalisation was the point of the commit; only this rule's
|
|
2160
|
+
selector was destroyed while applying it. Dropping it would leave `th` at the
|
|
2161
|
+
UA's 700, the one weight this design says it does not have. */
|
|
2162
|
+
.budgets-table th {
|
|
2119
2163
|
text-align: left; font-family: var(--mono); font-weight: 500; font-size: var(--fs-eyebrow);
|
|
2120
2164
|
letter-spacing: 0.08em; text-transform: uppercase; color: var(--text2);
|
|
2121
2165
|
padding: 0 12px 8px 0; border-bottom: 1px solid var(--border);
|
|
2122
|
-
id var(--border);
|
|
2123
2166
|
}
|
|
2124
2167
|
.budgets-table td { padding: 10px 12px 10px 0; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
|
2125
2168
|
/* A row has to answer three questions: what is this agent, what does it cost,
|
|
@@ -4554,7 +4597,7 @@ async function loadJudgeStatus() {
|
|
|
4554
4597
|
<span class="js-where">${esc(s.fingerprint || '')} · from ${esc(String(s.from || '').replace(/^.*\/(?=\.great_cto)/, '~/'))}</span>
|
|
4555
4598
|
<span class="js-form">
|
|
4556
4599
|
<input id="judge-key" type="password" autocomplete="off" spellcheck="false" placeholder="replace key (sk-or-…)">
|
|
4557
|
-
<button class="btn" onclick="saveJudgeKey()">Replace</button>
|
|
4600
|
+
<button class="btn-black" onclick="saveJudgeKey()">Replace</button>
|
|
4558
4601
|
</span>`
|
|
4559
4602
|
: `<span class="js-dot"></span>
|
|
4560
4603
|
<span><strong>No judge connected.</strong> Every stage will be scored
|
|
@@ -4563,7 +4606,7 @@ async function loadJudgeStatus() {
|
|
|
4563
4606
|
? `<span class="js-where">${esc(s.problems.join('; '))}</span>` : ''}
|
|
4564
4607
|
<span class="js-form">
|
|
4565
4608
|
<input id="judge-key" type="password" autocomplete="off" spellcheck="false" placeholder="OpenRouter key (sk-or-…)">
|
|
4566
|
-
<button class="btn" onclick="saveJudgeKey()">Connect</button>
|
|
4609
|
+
<button class="btn-black" onclick="saveJudgeKey()">Connect</button>
|
|
4567
4610
|
</span>`;
|
|
4568
4611
|
}
|
|
4569
4612
|
|
|
@@ -4685,7 +4728,7 @@ async function loadBudgetsPage() {
|
|
|
4685
4728
|
<div class="bf-why">${esc(String(err && err.message || err))}</div>
|
|
4686
4729
|
<div class="bf-note">Caps already set are unaffected — this failed to
|
|
4687
4730
|
<em>read</em> them, and the pipeline reads them itself, not from here.</div>
|
|
4688
|
-
<button class="btn" onclick="loadBudgetsPage()">Try again</button>
|
|
4731
|
+
<button class="btn-black" onclick="loadBudgetsPage()">Try again</button>
|
|
4689
4732
|
</div>`;
|
|
4690
4733
|
return;
|
|
4691
4734
|
}
|
|
@@ -5070,7 +5113,7 @@ function showVerdictDetail(idx) {
|
|
|
5070
5113
|
modal.innerHTML = `
|
|
5071
5114
|
<div class="rdm-backdrop" onclick="document.getElementById('resume-detail-modal').remove()"></div>
|
|
5072
5115
|
<div class="rdm-card" role="dialog" aria-modal="true" aria-labelledby="rdm-title-verdict">
|
|
5073
|
-
<h2 id="rdm-title-verdict" class="sr-only"
|
|
5116
|
+
<h2 id="rdm-title-verdict" class="sr-only">Verdict detail: ${esc(v.agent || 'agent')} ${esc(v.verdict || '')}</h2>
|
|
5074
5117
|
<div class="rdm-head">
|
|
5075
5118
|
<span class="ri-tag ${cls}">${esc(v.agent || 'agent')}</span>
|
|
5076
5119
|
<span class="rdm-verdict ${cls}">${esc(v.verdict || '')}</span>
|
|
@@ -5971,8 +6014,8 @@ async function loadSessions() {
|
|
|
5971
6014
|
<span class="muted" style="white-space:nowrap">${ago(s.modified)} · ${fmtMB(s.size_bytes)}</span>
|
|
5972
6015
|
</div>
|
|
5973
6016
|
<div style="margin-top:6px">
|
|
5974
|
-
<button class="btn-sm" onclick="openSession('${esc(s.id)}')">transcript</button>
|
|
5975
|
-
<button class="btn-sm" onclick="openEdits('${esc(s.id)}')">edits</button>
|
|
6017
|
+
<button class="btn-ghost-sm" onclick="openSession('${esc(s.id)}')">transcript</button>
|
|
6018
|
+
<button class="btn-ghost-sm" onclick="openEdits('${esc(s.id)}')">edits</button>
|
|
5976
6019
|
</div>
|
|
5977
6020
|
<div id="sess-${esc(s.id)}" style="margin-top:8px"></div>
|
|
5978
6021
|
</div>`).join('');
|
|
@@ -6024,12 +6067,12 @@ async function searchTranscripts() {
|
|
|
6024
6067
|
if (!d) { body.innerHTML = '<div class="muted">Search failed.</div>'; return; }
|
|
6025
6068
|
if (!d.hits?.length) {
|
|
6026
6069
|
body.innerHTML = `<div class="muted">No match for “${esc(q)}”.${d.note ? ' ' + esc(d.note) : ''}
|
|
6027
|
-
<button class="btn-sm" onclick="loadSessions()">back</button></div>`;
|
|
6070
|
+
<button class="btn-ghost-sm" onclick="loadSessions()">back</button></div>`;
|
|
6028
6071
|
return;
|
|
6029
6072
|
}
|
|
6030
6073
|
body.innerHTML =
|
|
6031
6074
|
`<div class="muted" style="margin-bottom:8px">${d.hits.length} match(es)${d.truncated ? ', capped' : ''}
|
|
6032
|
-
<button class="btn-sm" onclick="loadSessions()">back</button></div>` +
|
|
6075
|
+
<button class="btn-ghost-sm" onclick="loadSessions()">back</button></div>` +
|
|
6033
6076
|
d.hits.map(h => `
|
|
6034
6077
|
<div class="card" style="margin-bottom:6px">
|
|
6035
6078
|
<div class="muted" style="font-size:.8em">${esc(h.title || h.session)} · ${esc(h.role)} · ${h.ts ? ago(h.ts) : ''}</div>
|
|
@@ -32,13 +32,38 @@
|
|
|
32
32
|
* authoring work, done deliberately, a few documents at a time.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
35
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
36
36
|
import path from 'node:path';
|
|
37
37
|
|
|
38
38
|
/** Generated summaries and translations are copies, not documents. */
|
|
39
39
|
const IS_SUMMARY = /\.summary\.md$/;
|
|
40
|
-
const IS_TRANSLATION = /^docs\/[a-z]{2}(-[A-Z]{2})?\//;
|
|
41
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A two-letter directory is not evidence of a language.
|
|
43
|
+
*
|
|
44
|
+
* This used to be `/^docs\/[a-z]{2}(-[A-Z]{2})?\//`, which reads `docs/qa/`,
|
|
45
|
+
* `docs/ai/`, `docs/ci/`, `docs/ux/` and `docs/db/` as language codes and drops
|
|
46
|
+
* every document under them — silently, from the board's docs tab and from the
|
|
47
|
+
* link graph both, because a dropped document declares nothing that could go
|
|
48
|
+
* missing. `docs/qa/` was already being eaten; the rest were waiting.
|
|
49
|
+
*
|
|
50
|
+
* A whitelist of language codes would rot the first time someone adds one, so
|
|
51
|
+
* the test is STRUCTURAL instead: a translation MIRRORS a document at the docs
|
|
52
|
+
* root. `docs/ru/index.md` is a translation because `docs/index.md` exists;
|
|
53
|
+
* `docs/qa/QA-judge-provenance.md` is not, because `docs/QA-judge-provenance.md`
|
|
54
|
+
* does not. Measured across all ten two-letter directories here — de, es, fr,
|
|
55
|
+
* ja, ko, pt-BR, ru, zh-CN, zh-TW each mirror 1 of 1; qa mirrors 0 of 2 — and it
|
|
56
|
+
* self-corrects when a language is added or removed.
|
|
57
|
+
*
|
|
58
|
+
* Consequence worth stating, because it is a visible surface change hiding
|
|
59
|
+
* inside a classifier fix: `docs/qa/` drafts now APPEAR on the board's docs tab.
|
|
60
|
+
* That is the intended direction — the board is local and should show drafts —
|
|
61
|
+
* but it is not what a reader expects from a regex change.
|
|
62
|
+
*/
|
|
63
|
+
const LANG_DIR = /^docs\/[a-z]{2}(?:-[A-Z]{2})?\//;
|
|
64
|
+
|
|
65
|
+
/** The basename a translation would be a translation OF. */
|
|
66
|
+
const baseName = (rel) => rel.slice(rel.lastIndexOf('/') + 1);
|
|
42
67
|
export function listDocs(root = 'docs') {
|
|
43
68
|
// The translation rule is written against a path that starts at the docs
|
|
44
69
|
// directory. `root` may be relative ('docs') or absolute (the board serves
|
|
@@ -47,7 +72,7 @@ export function listDocs(root = 'docs') {
|
|
|
47
72
|
// matches nothing on an absolute walk and translations count as documents,
|
|
48
73
|
// which is a wrong number that looks like a right one.
|
|
49
74
|
const base = path.dirname(root);
|
|
50
|
-
const
|
|
75
|
+
const found = [];
|
|
51
76
|
const walk = (dir) => {
|
|
52
77
|
let entries;
|
|
53
78
|
try { entries = readdirSync(dir); } catch { return; }
|
|
@@ -56,11 +81,24 @@ export function listDocs(root = 'docs') {
|
|
|
56
81
|
let st;
|
|
57
82
|
try { st = statSync(full); } catch { continue; }
|
|
58
83
|
if (st.isDirectory()) walk(full);
|
|
59
|
-
else if (e.endsWith('.md') && !IS_SUMMARY.test(e)
|
|
84
|
+
else if (e.endsWith('.md') && !IS_SUMMARY.test(e)) found.push({ full, rel: path.relative(base, full) });
|
|
60
85
|
}
|
|
61
86
|
};
|
|
62
87
|
walk(root);
|
|
63
|
-
|
|
88
|
+
|
|
89
|
+
// Two passes, because whether a file under a two-letter directory is a
|
|
90
|
+
// TRANSLATION depends on the rest of the tree: it is one when a document of
|
|
91
|
+
// the same name lives outside every language directory. One pass cannot know
|
|
92
|
+
// that, and the single-pass version of this rule only looked at the docs
|
|
93
|
+
// root — which called `docs/ru/ADR-001-a.md` a document because its source
|
|
94
|
+
// sits in `docs/adr/`.
|
|
95
|
+
const sources = new Set(
|
|
96
|
+
found.filter((f) => !LANG_DIR.test(f.rel)).map((f) => baseName(f.rel)),
|
|
97
|
+
);
|
|
98
|
+
return found
|
|
99
|
+
.filter((f) => !(LANG_DIR.test(f.rel) && sources.has(baseName(f.rel))))
|
|
100
|
+
.map((f) => f.full)
|
|
101
|
+
.sort();
|
|
64
102
|
}
|
|
65
103
|
|
|
66
104
|
/**
|
package/package.json
CHANGED