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.
@@ -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.66.0",
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 | >3 components exchanging data? Draw ASCII diagram, check for cycles. |
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. **Write Requirements Checklist** at the end of `ARCH-<feature>.md`:
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
- 9. **Report**:
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` — Findings table
169
- (severity · file:line · evidence · fix), Observations, and what the change does
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: await settle(() => readVerdictLogs(c)),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.66.0",
3
+ "version": "3.67.0",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",