great-cto 3.66.0 → 3.67.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.
- package/board/.claude-plugin/plugin.json +1 -1
- package/board/agents/architect.md +19 -4
- package/board/agents/code-reviewer.md +9 -3
- package/board/packages/board/lib/routes.mjs +12 -2
- package/board/packages/board/public/index.html +5 -1
- package/board/scripts/lib/gate-brief.mjs +49 -1
- package/package.json +1 -1
|
@@ -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 72 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.
|
|
4
|
+
"version": "3.67.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -502,7 +502,7 @@ constraint in the new ARCH doc — document why this design choice was not taken
|
|
|
502
502
|
|
|
503
503
|
**If the product's core claim is empirical** — a systematic-trading strategy, a signal, a model whose value IS its measured edge — the architecture question is downstream of whether the claim survives testing. Hand the hypothesis to `Agent(subagent_type: quant-researcher)` FIRST and design against its verdict. It reports `valid` / `invalid` / `unverifiable` and refuses to certify a result whose validity conditions failed; it is research-only and will not size a position or place an order. Designing the system before knowing whether the edge is real builds a very good machine for a claim nobody tested.
|
|
504
504
|
|
|
505
|
-
4. **Write** `docs/architecture/ARCH-<feature>.md` with: Problem, Decision (with alternatives), Components, API/Data contracts, Security considerations, DB migration plan (if schema changes), Implementation tasks, Definition of Done, Cost Estimate, Requirements Checklist. **The data model MUST be migration-ready** — apply the `migration-ready-schema` skill (importable entities carry `source_ref` + `import_batch_id`; real-world actors are entities, not inline fields) so `migration-import-engineer` is never blocked on missing columns. **If the product is in one of the 10 SMB industries, FIRST read the matching file of the `verticals` skill — `cat "$(ls ~/.claude/plugins/cache/*/great_cto/*/skills/verticals/<industry>.md 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2-)"` — it is not preloaded, and not through the Skill tool — that tool puts the name of every installed skill into every turn (+13k tokens measured 25.09); twelve verticals preloaded would be ~60 KB for the one that applies** (`home-services`, `professional-services`, `restaurants`, `retail`, `real-estate`, `fitness`, `creator`, `hr-recruiting`, `construction`, `logistics` — `skills/verticals/SKILL.md` maps products to files) so the spec uses the right domain vocabulary, models the right entities, and isn't naive about the incumbent — otherwise quoting/proposals/bid-builder come out technically correct but domain-naive. **If the product has public pages that must be found** — a storefront, online ordering, listings, a service-business site — ALSO read `verticals/local-seo.md` and put its structured data and on-page signals in the spec. **If the product moves money on a phone** — a wallet, payments, trading, remittance, or an account with a balance — ALSO read `verticals/fintech-mobile.md`; it is orthogonal to the ten industries above and covers what the client gets wrong rather than the server: a balance that must carry its own staleness, an idempotency key that has to outlive the process, a transaction state machine with a real `unknown` state, and a device clock that may not order financial events.
|
|
505
|
+
4. **Write** `docs/architecture/ARCH-<feature>.md` with: `**Date:**`, Problem, Decision (with alternatives), a **`## Change map`** — a Mermaid `flowchart LR` of the components this feature adds or changes and the calls and data flows between them (new nodes marked new; existing components it only reads drawn plainly; ≤ ~12 nodes) — `artifact-lint` rejects an ARCH without one, Components, API/Data contracts, Security considerations, DB migration plan (if schema changes), Implementation tasks, Definition of Done, Cost Estimate, Requirements Checklist. **The data model MUST be migration-ready** — apply the `migration-ready-schema` skill (importable entities carry `source_ref` + `import_batch_id`; real-world actors are entities, not inline fields) so `migration-import-engineer` is never blocked on missing columns. **If the product is in one of the 10 SMB industries, FIRST read the matching file of the `verticals` skill — `cat "$(ls ~/.claude/plugins/cache/*/great_cto/*/skills/verticals/<industry>.md 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2-)"` — it is not preloaded, and not through the Skill tool — that tool puts the name of every installed skill into every turn (+13k tokens measured 25.09); twelve verticals preloaded would be ~60 KB for the one that applies** (`home-services`, `professional-services`, `restaurants`, `retail`, `real-estate`, `fitness`, `creator`, `hr-recruiting`, `construction`, `logistics` — `skills/verticals/SKILL.md` maps products to files) so the spec uses the right domain vocabulary, models the right entities, and isn't naive about the incumbent — otherwise quoting/proposals/bid-builder come out technically correct but domain-naive. **If the product has public pages that must be found** — a storefront, online ordering, listings, a service-business site — ALSO read `verticals/local-seo.md` and put its structured data and on-page signals in the spec. **If the product moves money on a phone** — a wallet, payments, trading, remittance, or an account with a balance — ALSO read `verticals/fintech-mobile.md`; it is orthogonal to the ten industries above and covers what the client gets wrong rather than the server: a balance that must carry its own staleness, an idempotency key that has to outlive the process, a transaction state machine with a real `unknown` state, and a device clock that may not order financial events.
|
|
506
506
|
|
|
507
507
|
**Anti-patterns to avoid** (see `skills/great_cto/references/anti-patterns.md`, ARCH rules A1–A8). Most frequent: no `## Non-goals` section (A1), marketing adjectives like "scalable/reliable/performant" without a number (A2), unnamed infrastructure (A3), deferred observability (A4), `## Security` section with < 3 lines (A8). These are flagged by `/audit lint`.
|
|
508
508
|
|
|
@@ -722,13 +722,28 @@ Mark each item `- [ ]` so senior-dev and security-officer can tick off during re
|
|
|
722
722
|
| Check | Rule |
|
|
723
723
|
|-------|------|
|
|
724
724
|
| File scope | >8 files or >1 new service? State it explicitly in the ARCH doc. |
|
|
725
|
-
| Data flow |
|
|
725
|
+
| Data flow | Draw it in `## Change map` (below) and check it for cycles. |
|
|
726
726
|
| Rollback | State the rollback procedure for BOTH surfaces: (a) compute/deploy (e.g. instant revert to previous deployment) AND (b) schema/data (down-migration or forward-fix). Answer both — a deploy that reverts but leaves a one-way migration is not rolled back. |
|
|
727
727
|
| Credentials | List every API key, token, third-party account the plan requires. No credential requests mid-implementation. |
|
|
728
728
|
| External deps | Every external API, MCP server, third-party CLI — verify reachable before handoff. |
|
|
729
729
|
| Test paths | Happy path, error cases, edge cases — all listed in Requirements Checklist. |
|
|
730
730
|
|
|
731
|
-
8. **
|
|
731
|
+
8. **Draw the Change map** in `ARCH-<feature>.md` — the picture the approver sees
|
|
732
|
+
on the gate brief. Every ARCH has one, whatever the size; when you answer
|
|
733
|
+
without writing the file, put it in the answer. Components this feature adds
|
|
734
|
+
are marked `:::new`; existing ones it calls or reads are drawn plainly; nothing
|
|
735
|
+
it does not touch. ≤ ~12 nodes. `artifact-lint` rejects an ARCH without it.
|
|
736
|
+
```markdown
|
|
737
|
+
## Change map
|
|
738
|
+
```mermaid
|
|
739
|
+
flowchart LR
|
|
740
|
+
classDef new stroke-width:3px
|
|
741
|
+
Retry[RetryScheduler]:::new -->|reads failed| Deliveries[(deliveries)]
|
|
742
|
+
Retry -->|re-sends| Sender[WebhookSender]
|
|
743
|
+
```
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
9. **Write Requirements Checklist** at the end of `ARCH-<feature>.md`:
|
|
732
747
|
```markdown
|
|
733
748
|
## Requirements Checklist
|
|
734
749
|
> Derived from CTO request. qa-engineer will verify each item at QA step.
|
|
@@ -766,7 +781,7 @@ Mark each item `- [ ]` so senior-dev and security-officer can tick off during re
|
|
|
766
781
|
|
|
767
782
|
**If bd unavailable**: skip silently — the REQ checklist in the ARCH doc is the fallback trace.
|
|
768
783
|
|
|
769
|
-
|
|
784
|
+
10. **Report**:
|
|
770
785
|
```
|
|
771
786
|
Architecture ready → docs/architecture/ARCH-<feature>.md
|
|
772
787
|
Key decisions:
|
|
@@ -165,9 +165,15 @@ sets the verdict, and the human at the gate sees both.
|
|
|
165
165
|
|
|
166
166
|
1. For each P0/P1 Finding, file a Beads bug:
|
|
167
167
|
`bd create "<finding> (<file:line>)" --type bug --priority <0-2>`
|
|
168
|
-
2. Write the review to `docs/reviews/REVIEW-<feature-slug>.md` —
|
|
169
|
-
(severity · file:line · evidence · fix), Observations,
|
|
170
|
-
well
|
|
168
|
+
2. Write the review to `docs/reviews/REVIEW-<feature-slug>.md` — `**Date:**`,
|
|
169
|
+
Findings table (severity · file:line · evidence · fix), Observations, what the
|
|
170
|
+
change does well, and a **`## Change map`**: a Mermaid `flowchart LR` of what
|
|
171
|
+
the diff touches, drawn from the diff itself — one node per changed file or
|
|
172
|
+
component (label = its path), an edge per call or data flow between them that
|
|
173
|
+
the diff adds or changes, a P0/P1 Finding marked on its node. At most ~12
|
|
174
|
+
nodes; group by directory beyond that. It is what the approver sees on the
|
|
175
|
+
gate brief instead of the log, so it shows the change, not the whole system.
|
|
176
|
+
`artifact-lint` rejects a review without one.
|
|
171
177
|
3. Emit the verdict (see `agents/_shared/verdict-format.md`):
|
|
172
178
|
`scripts/log-verdict.sh code-reviewer <APPROVED|BLOCKED> auto feature=<slug> review=docs/reviews/REVIEW-<slug>.md need=<implementer|decision> finding=<id>`
|
|
173
179
|
— `BLOCKED` if any P0 (or unresolved P1) Finding exists; else `APPROVED`.
|
|
@@ -33,7 +33,7 @@ import { bdCacheInvalidate, checkBeadsAvailable, bdWriteSerialised, bd, bdErr, g
|
|
|
33
33
|
import { getMetrics } from './metrics.mjs';
|
|
34
34
|
import { agentActivity, agentActivitySince } from './agent-activity.mjs';
|
|
35
35
|
import { issueTokensAsync, checkToken, checkBinding, consumeToken, approvalSnapshot, beginGateDecision, readTokens } from './gate-tokens.mjs';
|
|
36
|
-
import { gateBrief, settle, readVerdictLogs } from '../../../scripts/lib/gate-brief.mjs';
|
|
36
|
+
import { gateBrief, settle, readVerdictLogs, mapDocPaths } from '../../../scripts/lib/gate-brief.mjs';
|
|
37
37
|
import { readTurns, turnPatch } from '../../../scripts/lib/turn-snapshot.mjs';
|
|
38
38
|
import { appendEvent } from '../../../scripts/lib/agent-events.mjs';
|
|
39
39
|
|
|
@@ -954,12 +954,22 @@ async function dispatch(req, res, url, cwd) {
|
|
|
954
954
|
// inbox poll may hold one.
|
|
955
955
|
const touches = await settle(readTouches(c));
|
|
956
956
|
const secondOpinion = await settle(readSecondOpinion(c));
|
|
957
|
+
const verdictLogs = await settle(() => readVerdictLogs(c));
|
|
958
|
+
// The documents the latest reviewer/architect verdicts point at, for the
|
|
959
|
+
// change map. mapDocPaths admits only paths under docs/ with no `..`.
|
|
960
|
+
const docs = {};
|
|
961
|
+
if (verdictLogs.ok) {
|
|
962
|
+
for (const { path: rel } of mapDocPaths(verdictLogs.value)) {
|
|
963
|
+
docs[rel] = await settle(() => fs.readFileSync(path.join(c, rel), 'utf8'));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
957
966
|
const brief = gateBrief({
|
|
958
967
|
gate,
|
|
959
968
|
token: readTokens(c).tokens[id] || null,
|
|
960
969
|
binding: touches.ok ? { ok: true, value: touches.value.binding } : touches,
|
|
961
970
|
tests: touches.ok ? touches.value.tests : touches,
|
|
962
|
-
verdictLogs
|
|
971
|
+
verdictLogs,
|
|
972
|
+
docs,
|
|
963
973
|
secondOpinion,
|
|
964
974
|
tier: await settle(() => getChangeTier(c)),
|
|
965
975
|
});
|
|
@@ -2151,6 +2151,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
2151
2151
|
.gate-brief .brief-sec { display: grid; grid-template-columns: 110px 1fr; gap: 10px; align-items: baseline; }
|
|
2152
2152
|
.gate-brief .brief-k { color: var(--text3); }
|
|
2153
2153
|
.gate-brief .brief-v { min-width: 0; overflow-wrap: anywhere; }
|
|
2154
|
+
.gate-brief .brief-map { margin: 4px 0 0; padding: 6px 8px; background: var(--bg-card); border-radius: 4px; font-size: var(--fs-small, 12px); white-space: pre-wrap; overflow-x: auto; }
|
|
2154
2155
|
.gate-brief .brief-moved { color: var(--status-blocked); margin-bottom: 4px; }
|
|
2155
2156
|
.inbox-row {
|
|
2156
2157
|
display: grid;
|
|
@@ -4761,7 +4762,7 @@ function renderGateBrief(d) {
|
|
|
4761
4762
|
const row = (label, s, body) => `<div class="brief-sec"><span class="brief-k">${label}</span>`
|
|
4762
4763
|
+ `<span class="brief-v" style="color:${BRIEF_TONE[s.state] || 'inherit'}">${s.state === 'unknown' ? `unknown — ${esc(s.why || '')}` : body}</span></div>`;
|
|
4763
4764
|
const by = Object.fromEntries(d.sections.map((s) => [s.key, s]));
|
|
4764
|
-
const t = by.tree, c = by.change, r = by.risk, v = by.reviews, o = by.second, x = by.touches;
|
|
4765
|
+
const t = by.tree, c = by.change, r = by.risk, v = by.reviews, o = by.second, x = by.touches, mp = by.map;
|
|
4765
4766
|
const head = t.moved
|
|
4766
4767
|
? `<div class="brief-moved"><b>The tree moved after this gate was shown</b> — ${[
|
|
4767
4768
|
[...(t.changed || []), ...(t.removed || [])].length ? `changed: ${briefList([...(t.changed || []), ...(t.removed || [])])}` : '',
|
|
@@ -4781,6 +4782,9 @@ function renderGateBrief(d) {
|
|
|
4781
4782
|
+ row('Change', c, `${c.total} file(s)${c.tier ? ` · tier ${esc(c.tier)}` : ` · tier unknown${c.tierWhy ? ` — ${esc(c.tierWhy)}` : ''}`}${c.files?.length ? `<details><summary class="muted">files</summary>${briefList(c.files, 100)}</details>` : ''}`)
|
|
4782
4783
|
+ row('Undo', r, `${esc(r.reversibility)}${r.categories?.length ? ` · ${esc(r.categories.join(', '))}` : ''}`)
|
|
4783
4784
|
+ row('Reviews', v, reviews)
|
|
4785
|
+
+ (mp ? row('Change map', mp, mp.state === 'empty'
|
|
4786
|
+
? `<span class="muted">${esc(mp.why || 'none')}</span>`
|
|
4787
|
+
: `<span class="${mp.onThisTree ? '' : 'muted'}">${esc(mp.agent)} · ${esc(mp.path)}${mp.onThisTree ? ' ✓ this tree' : ' · other tree'}</span><pre class="brief-map">${esc(mp.source)}</pre>`) : '')
|
|
4784
4788
|
+ row('Second model', o, second)
|
|
4785
4789
|
+ row('Touches', x, touches);
|
|
4786
4790
|
}
|
|
@@ -114,6 +114,52 @@ function touchesSection(binding, tests) {
|
|
|
114
114
|
return out;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// ── change map (great_cto-2m2l) ──────────────────────────────────────────────
|
|
118
|
+
// code-reviewer and architect draw what a change touches as a Mermaid flowchart
|
|
119
|
+
// in their documents; the brief shows the one the latest verdict points at. The
|
|
120
|
+
// board ships no diagram library, so the page shows the source, which reads as a
|
|
121
|
+
// list of edges and pastes into any Mermaid viewer.
|
|
122
|
+
const MAP_AGENTS = [['code-reviewer', 'review'], ['architect', 'arch']];
|
|
123
|
+
|
|
124
|
+
/** The flowchart source in a document's `## Change map`, or null. */
|
|
125
|
+
export function changeMapSource(text) {
|
|
126
|
+
const lines = String(text || '').split('\n');
|
|
127
|
+
const start = lines.findIndex((l) => /^##\s+change map\b/i.test(l));
|
|
128
|
+
if (start < 0) return null;
|
|
129
|
+
let end = lines.length;
|
|
130
|
+
for (let i = start + 1; i < lines.length; i++) if (/^##\s/.test(lines[i])) { end = i; break; }
|
|
131
|
+
const m = lines.slice(start + 1, end).join('\n').match(/```mermaid\s*\n([\s\S]*?)```/);
|
|
132
|
+
return m && /^\s*(flowchart|graph)\b/m.test(m[1]) ? m[1].replace(/\s+$/, '') : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Latest verdict per map agent → the document it points at; only paths under docs/. */
|
|
136
|
+
export function mapDocPaths(verdictLogs) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const [agent, key] of MAP_AGENTS) {
|
|
139
|
+
const { records } = parseVerdictLog(verdictLogs?.[agent] || '', { agent });
|
|
140
|
+
const latest = records.filter((r) => r.meta?.[key]).sort((a, b) => String(b.ts).localeCompare(String(a.ts)))[0];
|
|
141
|
+
const p = latest?.meta?.[key];
|
|
142
|
+
if (p && /^docs\/[A-Za-z0-9._/-]+\.md$/.test(p) && !p.split('/').includes('..')) out.push({ agent, path: p });
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function mapSection(verdictLogs, docs, binding, digest) {
|
|
148
|
+
if (!verdictLogs.ok) return { key: 'map', state: 'unknown', why: verdictLogs.why };
|
|
149
|
+
const targets = mapDocPaths(verdictLogs.value);
|
|
150
|
+
if (!targets.length) return { key: 'map', state: 'empty', why: 'no reviewer or architect verdict points at a document with a change map' };
|
|
151
|
+
const { agent, path } = targets[0];
|
|
152
|
+
const doc = docs?.[path];
|
|
153
|
+
if (!doc || !doc.ok) return { key: 'map', state: 'unknown', agent, path, why: doc ? doc.why : 'the document was not read' };
|
|
154
|
+
const source = changeMapSource(doc.value);
|
|
155
|
+
if (!source) return { key: 'map', state: 'empty', agent, path, why: `${path} has no Change map section with a flowchart` };
|
|
156
|
+
const { records } = parseVerdictLog(verdictLogs.value[agent] || '', { agent });
|
|
157
|
+
const rec = records.filter((r) => r.meta?.review === path || r.meta?.arch === path).sort((a, b) => String(b.ts).localeCompare(String(a.ts)))[0];
|
|
158
|
+
const current = binding.ok ? binding.value : null;
|
|
159
|
+
const onThisTree = Boolean(rec?.receipt && current && compareReceipts(rec.receipt, current, { digest }).state === 'matches');
|
|
160
|
+
return { key: 'map', state: 'recorded', agent, path, onThisTree, source };
|
|
161
|
+
}
|
|
162
|
+
|
|
117
163
|
/**
|
|
118
164
|
* @param {object} p
|
|
119
165
|
* @param {object} p.gate pending gate as the inbox returns it
|
|
@@ -123,9 +169,10 @@ function touchesSection(binding, tests) {
|
|
|
123
169
|
* @param {{ok:boolean}} p.secondOpinion
|
|
124
170
|
* @param {{ok:boolean}} p.tier
|
|
125
171
|
* @param {{ok:boolean}} p.tests affectedTests() result
|
|
172
|
+
* @param {object} [p.docs] {path: {ok, value|why}} documents the verdicts point at
|
|
126
173
|
* @param {Function} [p.digest] file digest for compareReceipts (injected in tests)
|
|
127
174
|
*/
|
|
128
|
-
export function gateBrief({ gate, token = null, binding, verdictLogs, secondOpinion, tier, tests, digest = undefined }) {
|
|
175
|
+
export function gateBrief({ gate, token = null, binding, verdictLogs, secondOpinion, tier, tests, docs = {}, digest = undefined }) {
|
|
129
176
|
const tree = treeSection(token, binding, digest);
|
|
130
177
|
return {
|
|
131
178
|
gate: { id: gate?.id || null, title: gate?.title || '', gate: gate?.gate || null },
|
|
@@ -135,6 +182,7 @@ export function gateBrief({ gate, token = null, binding, verdictLogs, secondOpin
|
|
|
135
182
|
changeSection(binding, tier),
|
|
136
183
|
riskSection(gate),
|
|
137
184
|
reviewsSection(verdictLogs, binding, digest),
|
|
185
|
+
mapSection(verdictLogs, docs, binding, digest),
|
|
138
186
|
secondSection(secondOpinion),
|
|
139
187
|
touchesSection(binding, tests),
|
|
140
188
|
],
|
package/package.json
CHANGED