@zigrivers/scaffold 3.30.0 → 3.31.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 (69) hide show
  1. package/content/guides/AUTHORING.md +8 -5
  2. package/content/guides/cli/index.html +367 -14
  3. package/content/guides/concepts/index.html +367 -14
  4. package/content/guides/dashboard/index.html +367 -14
  5. package/content/guides/index.html +368 -15
  6. package/content/guides/install/index.html +373 -20
  7. package/content/guides/install/index.md +6 -6
  8. package/content/guides/knowledge/index.html +367 -14
  9. package/content/guides/knowledge-freshness/index.html +369 -16
  10. package/content/guides/knowledge-freshness/index.md +2 -2
  11. package/content/guides/mmr/index.html +373 -20
  12. package/content/guides/multi-agent/index.html +369 -16
  13. package/content/guides/multi-agent/index.md +2 -2
  14. package/content/guides/observability/index.html +367 -14
  15. package/content/guides/pipeline/index.html +378 -37
  16. package/content/guides/pipeline/index.md +8 -8
  17. package/content/guides/review-workflow/index.html +367 -14
  18. package/dist/cli/commands/info.test.js +3 -0
  19. package/dist/cli/commands/info.test.js.map +1 -1
  20. package/dist/cli/commands/list.test.js +6 -0
  21. package/dist/cli/commands/list.test.js.map +1 -1
  22. package/dist/cli/commands/status.d.ts.map +1 -1
  23. package/dist/cli/commands/status.js +38 -4
  24. package/dist/cli/commands/status.js.map +1 -1
  25. package/dist/cli/commands/status.test.js +66 -2
  26. package/dist/cli/commands/status.test.js.map +1 -1
  27. package/dist/e2e/commands.test.js +3 -0
  28. package/dist/e2e/commands.test.js.map +1 -1
  29. package/dist/e2e/knowledge.test.js +5 -0
  30. package/dist/e2e/knowledge.test.js.map +1 -1
  31. package/dist/guides/build.d.ts +1 -1
  32. package/dist/guides/build.d.ts.map +1 -1
  33. package/dist/guides/build.js +14 -7
  34. package/dist/guides/build.js.map +1 -1
  35. package/dist/guides/build.test.js +39 -0
  36. package/dist/guides/build.test.js.map +1 -1
  37. package/dist/guides/chrome.d.ts.map +1 -1
  38. package/dist/guides/chrome.js +83 -12
  39. package/dist/guides/chrome.js.map +1 -1
  40. package/dist/guides/cli-guides.test.js +3 -0
  41. package/dist/guides/cli-guides.test.js.map +1 -1
  42. package/dist/guides/dashboard-theme.css +8 -0
  43. package/dist/guides/directives-tabs.test.js +47 -0
  44. package/dist/guides/directives-tabs.test.js.map +1 -1
  45. package/dist/guides/directives.d.ts.map +1 -1
  46. package/dist/guides/directives.js +14 -0
  47. package/dist/guides/directives.js.map +1 -1
  48. package/dist/guides/guides.css +268 -0
  49. package/dist/guides/index-page.d.ts.map +1 -1
  50. package/dist/guides/index-page.js +41 -8
  51. package/dist/guides/index-page.js.map +1 -1
  52. package/dist/guides/sanitize.d.ts.map +1 -1
  53. package/dist/guides/sanitize.js +4 -0
  54. package/dist/guides/sanitize.js.map +1 -1
  55. package/dist/guides/template.d.ts.map +1 -1
  56. package/dist/guides/template.js +7 -2
  57. package/dist/guides/template.js.map +1 -1
  58. package/dist/state/ensure-v3-migration.d.ts.map +1 -1
  59. package/dist/state/ensure-v3-migration.js +4 -2
  60. package/dist/state/ensure-v3-migration.js.map +1 -1
  61. package/dist/utils/fs.d.ts +6 -30
  62. package/dist/utils/fs.d.ts.map +1 -1
  63. package/dist/utils/fs.js +66 -58
  64. package/dist/utils/fs.js.map +1 -1
  65. package/dist/utils/fs.test.js +53 -6
  66. package/dist/utils/fs.test.js.map +1 -1
  67. package/dist/validation/index.test.js +3 -0
  68. package/dist/validation/index.test.js.map +1 -1
  69. package/package.json +2 -2
@@ -48,9 +48,13 @@
48
48
  --yellow: #d97706;
49
49
  --yellow-bg: #fffbeb;
50
50
  --yellow-border:#fde68a;
51
+ --red: #dc2626;
52
+ --red-bg: #fef2f2;
53
+ --red-border: #fecaca;
51
54
  --gray: #9ca3af;
52
55
  --gray-bg: #f3f4f6;
53
56
  --gray-border: #e5e7eb;
57
+ --scrim: rgba(15, 17, 23, 0.45);
54
58
 
55
59
  /* Semantic: Next Banner */
56
60
  --next-bg: #eef2ff;
@@ -133,9 +137,13 @@
133
137
  --yellow: #fbbf24;
134
138
  --yellow-bg: rgba(120, 53, 15, 0.25);
135
139
  --yellow-border:rgba(251, 191, 36, 0.20);
140
+ --red: #f87171;
141
+ --red-bg: rgba(127, 29, 29, 0.25);
142
+ --red-border: rgba(248, 113, 113, 0.22);
136
143
  --gray: #6b7294;
137
144
  --gray-bg: #252940;
138
145
  --gray-border: #363c58;
146
+ --scrim: rgba(0, 0, 0, 0.6);
139
147
 
140
148
  /* Semantic: Next Banner */
141
149
  --next-bg: rgba(30, 27, 75, 0.50);
@@ -1078,17 +1086,290 @@ figure.mermaid svg .marker {
1078
1086
  }
1079
1087
  figure.mermaid svg .edgeLabel,
1080
1088
  figure.mermaid svg .edgeLabel text { fill: var(--text-muted); color: var(--text-muted); }
1089
+
1090
+ /* ============================================================================
1091
+ * guides.css — component + layout styles for `scaffold guides` reference pages.
1092
+ *
1093
+ * Pairs with lib/dashboard-theme.css (the token source) and src/guides/chrome.ts
1094
+ * (the behavior). Styles the guide CHROME (.topbar, .layout, .rail, nav.toc,
1095
+ * .content) and the markdown DIRECTIVES (callouts, sev chips, filter-tables,
1096
+ * charts, tabs, citations) plus base prose typography.
1097
+ *
1098
+ * DESIGN SYSTEM: all COLORS come from dashboard-theme.css tokens, and spacing
1099
+ * uses the --sp-* scale wherever it maps. The few structural layout constants
1100
+ * (topbar height, rail/drawer width, chart label column, card min) are declared
1101
+ * as local custom properties below; a handful of sub-scale UI values (chip/bar
1102
+ * sizing, em-based inline-code padding) and the responsive breakpoint are
1103
+ * literal because no token expresses them. Both themes are covered because every
1104
+ * color is a token.
1105
+ * ==========================================================================*/
1106
+
1107
+ :root {
1108
+ --topbar-h: 52px; /* sticky topbar height; rail sticky offset keys off it */
1109
+ --rail-w: 260px; /* desktop TOC sidebar column */
1110
+ --drawer-w: 280px; /* mobile off-canvas TOC drawer */
1111
+ --card-min: 260px; /* index card min track width */
1112
+ --chart-label-w: 90px; /* chart row label column min */
1113
+ }
1114
+
1115
+ /* ── Base / reset on top of the token base in dashboard-theme.css ─────────── */
1116
+ .content a { color: var(--accent); text-decoration: none; }
1117
+ .content a:hover { text-decoration: underline; }
1118
+ .content strong { font-weight: var(--fw-semi); }
1119
+ .content hr { border: 0; border-top: 1px solid var(--border-light); margin: var(--sp-6) 0; }
1120
+
1121
+ /* Consistent keyboard focus for every interactive control (a11y). */
1122
+ .topbar button:focus-visible,
1123
+ .copy-btn:focus-visible,
1124
+ .tab-btn:focus-visible,
1125
+ .filter-input:focus-visible,
1126
+ nav.toc a:focus-visible,
1127
+ .guide-card:focus-visible,
1128
+ .content a:focus-visible {
1129
+ outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm);
1130
+ }
1131
+
1132
+ /* ── Topbar ────────────────────────────────────────────────────────────────*/
1133
+ .topbar {
1134
+ position: sticky; top: 0; z-index: 60; height: var(--topbar-h);
1135
+ display: flex; align-items: center; gap: var(--sp-3);
1136
+ padding: 0 var(--page-pad);
1137
+ background: var(--bg-card); border-bottom: 1px solid var(--border);
1138
+ }
1139
+ .topbar h1 {
1140
+ flex: 1; min-width: 0; margin: 0;
1141
+ font-size: var(--text-lg); font-weight: var(--fw-bold);
1142
+ letter-spacing: var(--ls-tight);
1143
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1144
+ }
1145
+ .topbar button {
1146
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text);
1147
+ border-radius: var(--radius-sm); padding: var(--sp-1) var(--sp-3); font-size: var(--text-base);
1148
+ line-height: 1; cursor: pointer; font-family: inherit;
1149
+ }
1150
+ .topbar button:hover { background: var(--bg-hover); border-color: var(--accent); }
1151
+ .nav-toggle { display: none; }
1152
+
1153
+ /* ── Layout: sticky sidebar TOC + reading-width content ──────────────────── */
1154
+ .layout {
1155
+ max-width: var(--max-w); margin: 0 auto;
1156
+ display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr);
1157
+ gap: var(--sp-8); padding: 0 var(--page-pad);
1158
+ }
1159
+ .rail {
1160
+ position: sticky; top: var(--topbar-h); align-self: start;
1161
+ height: calc(100vh - var(--topbar-h)); overflow-y: auto;
1162
+ padding: var(--sp-5) 0; border-right: 1px solid var(--border-light);
1163
+ }
1164
+ .content { min-width: 0; padding: var(--sp-6) 0 var(--sp-10); }
1165
+
1166
+ /* Backdrop behind the mobile drawer (toggled with the rail via chrome.ts). */
1167
+ .rail-backdrop { display: none; }
1168
+ /* In-drawer close button — hidden on desktop (the rail is a static sidebar). */
1169
+ .rail-close { display: none; }
1170
+
1171
+ /* ── Table of contents (scrollspy marks a.active) ────────────────────────── */
1172
+ nav.toc ul { list-style: none; margin: 0; padding: 0; }
1173
+ nav.toc li { margin: 0; }
1174
+ nav.toc a {
1175
+ display: block; padding: var(--sp-1) var(--sp-3); line-height: 1.35;
1176
+ color: var(--text-muted); font-size: var(--text-sm);
1177
+ text-decoration: none; border-left: 2px solid transparent;
1178
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
1179
+ }
1180
+ nav.toc a:hover { color: var(--text); background: var(--bg-hover); }
1181
+ nav.toc a.active {
1182
+ color: var(--accent); border-left-color: var(--accent);
1183
+ background: var(--accent-glow); font-weight: var(--fw-medium);
1184
+ }
1185
+ nav.toc li.toc-3 a { padding-left: var(--sp-6); font-size: var(--text-xs); }
1186
+
1187
+ /* ── Prose typography ──────────────────────────────────────────────────────*/
1188
+ .content h2 {
1189
+ font-size: var(--text-xl); letter-spacing: var(--ls-tight);
1190
+ margin: var(--sp-8) 0 var(--sp-3); padding-bottom: var(--sp-2);
1191
+ border-bottom: 1px solid var(--border-light); scroll-margin-top: calc(var(--topbar-h) + var(--sp-3));
1192
+ }
1193
+ .content h3 {
1194
+ font-size: var(--text-lg); margin: var(--sp-5) 0 var(--sp-2);
1195
+ scroll-margin-top: calc(var(--topbar-h) + var(--sp-3));
1196
+ }
1197
+ .content p { margin: var(--sp-3) 0; line-height: var(--lh-relaxed); }
1198
+ .content ul, .content ol { margin: var(--sp-3) 0; padding-left: var(--sp-6); }
1199
+ .content li { margin: var(--sp-1) 0; line-height: var(--lh-relaxed); }
1200
+ .content blockquote {
1201
+ margin: var(--sp-3) 0; padding: var(--sp-1) var(--sp-4);
1202
+ border-left: 3px solid var(--border); color: var(--text-muted);
1203
+ }
1204
+
1205
+ /* ── Inline code + code blocks (chrome.ts wraps <pre> in .code + .copy-btn) ──*/
1206
+ .content code {
1207
+ font-family: var(--font-mono); font-size: 0.85em;
1208
+ background: var(--bg-inset); padding: 0.12em 0.4em; border-radius: var(--radius-sm);
1209
+ }
1210
+ .content .code { position: relative; margin: var(--sp-3) 0; }
1211
+ .content .code pre {
1212
+ margin: 0; padding: var(--sp-3) var(--sp-4); overflow-x: auto;
1213
+ background: var(--bg-inset); border: 1px solid var(--border-light);
1214
+ border-radius: var(--radius-sm); font-family: var(--font-mono);
1215
+ font-size: var(--text-sm); line-height: var(--lh-relaxed);
1216
+ }
1217
+ .content .code pre code { background: none; padding: 0; font-size: inherit; }
1218
+ .copy-btn {
1219
+ position: absolute; top: var(--sp-1); right: var(--sp-1);
1220
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text-muted);
1221
+ border-radius: var(--radius-sm); font-size: var(--text-xs); padding: var(--sp-1) var(--sp-2);
1222
+ cursor: pointer; opacity: 0.85; font-family: inherit;
1223
+ }
1224
+ .copy-btn:hover { color: var(--accent); border-color: var(--accent); opacity: 1; }
1225
+
1226
+ /* ── Callouts ─ (border-color BEFORE border-left-color so the accent wins) ── */
1227
+ .callout {
1228
+ margin: var(--sp-4) 0; padding: var(--sp-3) var(--sp-4);
1229
+ border: 1px solid var(--border); border-left-width: 3px;
1230
+ border-radius: var(--radius-sm); background: var(--bg-card);
1231
+ }
1232
+ .callout > :first-child { margin-top: 0; }
1233
+ .callout > :last-child { margin-bottom: 0; }
1234
+ .callout-note, .callout-info { background: var(--blue-bg); border-color: var(--blue-border); border-left-color: var(--blue); }
1235
+ .callout-tip { background: var(--green-bg); border-color: var(--green-border); border-left-color: var(--green); }
1236
+ .callout-warning { background: var(--yellow-bg); border-color: var(--yellow-border); border-left-color: var(--yellow); }
1237
+ .callout-danger { background: var(--red-bg); border-color: var(--red-border); border-left-color: var(--red); }
1238
+
1239
+ /* ── Severity chips (:sev) — tight pill, sub-scale vertical padding ───────── */
1240
+ .sev {
1241
+ display: inline-block; font-size: var(--text-xs); font-weight: var(--fw-semi);
1242
+ padding: 1px var(--sp-2); border-radius: 999px; line-height: 1.5;
1243
+ border: 1px solid var(--border); background: var(--bg-inset); color: var(--text-muted);
1244
+ white-space: nowrap;
1245
+ }
1246
+ .sev-p0 { color: var(--sev-p0); border-color: var(--sev-p0); }
1247
+ .sev-p1 { color: var(--sev-p1); border-color: var(--sev-p1); }
1248
+ .sev-p2 { color: var(--sev-p2); border-color: var(--sev-p2); }
1249
+ .sev-p3 { color: var(--sev-p3); border-color: var(--sev-p3); }
1250
+ .sev-pass { color: var(--sev-pass); border-color: var(--sev-pass); }
1251
+
1252
+ /* ── Citations (:cite) — inline provenance refs ──────────────────────────── */
1253
+ .fp, .cite-advisory {
1254
+ font-family: var(--font-mono); font-size: 0.82em;
1255
+ padding: 0.05em 0.35em; border-radius: var(--radius-sm);
1256
+ background: var(--bg-inset); border: 1px solid var(--border-light);
1257
+ }
1258
+ .fp { color: var(--accent); }
1259
+ .cite-advisory { color: var(--text-faint); border-style: dashed; }
1260
+
1261
+ /* ── Tables + filter-tables ──────────────────────────────────────────────── */
1262
+ .content table { width: 100%; border-collapse: collapse; margin: var(--sp-3) 0; font-size: var(--text-sm); }
1263
+ .content th, .content td { text-align: left; padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border-light); vertical-align: top; }
1264
+ .content th {
1265
+ color: var(--text-muted); font-weight: var(--fw-semi); font-size: var(--text-xs);
1266
+ text-transform: uppercase; letter-spacing: var(--ls-wide); border-bottom-color: var(--border);
1267
+ }
1268
+ .content tbody tr:hover { background: var(--bg-hover); }
1269
+ .content td code { white-space: nowrap; }
1270
+ .filter-table { margin: var(--sp-4) 0; }
1271
+ .filter-input {
1272
+ width: 100%; max-width: 320px; margin-bottom: var(--sp-2);
1273
+ padding: var(--sp-2) var(--sp-3); font-family: inherit; font-size: var(--text-sm);
1274
+ color: var(--text); background: var(--bg-card);
1275
+ border: 1px solid var(--border); border-radius: var(--radius-sm);
1276
+ }
1277
+ .filter-input:focus { border-color: var(--accent); }
1278
+
1279
+ /* ── Charts (:::chart) — label + proportional bar (fill carries inline width%) */
1280
+ .chart-block { margin: var(--sp-4) 0; }
1281
+ .chart-row {
1282
+ display: grid; grid-template-columns: minmax(var(--chart-label-w), 24%) 1fr;
1283
+ align-items: center; gap: var(--sp-3); margin: var(--sp-1) 0;
1284
+ }
1285
+ .chart-label {
1286
+ font-size: var(--text-sm); color: var(--text-muted); text-align: right;
1287
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1288
+ }
1289
+ .chart-row .chart-bar {
1290
+ height: 0.9em; min-width: 2px; background: var(--accent);
1291
+ border-radius: var(--radius-sm);
1292
+ }
1293
+
1294
+ /* ── Tabs (::::tabs / :::tab) ─────────────────────────────────────────────── */
1295
+ .tabs { margin: var(--sp-4) 0; }
1296
+ .tablist { display: flex; flex-wrap: wrap; gap: var(--sp-1); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-3); }
1297
+ .tab-btn {
1298
+ background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
1299
+ padding: var(--sp-2) var(--sp-3); color: var(--text-muted);
1300
+ font-family: inherit; font-size: var(--text-sm); font-weight: var(--fw-medium); cursor: pointer;
1301
+ }
1302
+ .tab-btn:hover { color: var(--text); }
1303
+ .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
1304
+ .tabpane { display: none; }
1305
+ .tabpane.active { display: block; }
1306
+
1307
+ /* ── Mermaid diagrams ────────────────────────────────────────────────────── */
1308
+ .content figure { margin: var(--sp-4) 0; text-align: center; }
1309
+ .content figure svg, .content > svg, .content .mermaid svg { max-width: 100%; height: auto; }
1310
+
1311
+ /* ── Index page: category card grid ──────────────────────────────────────── */
1312
+ .content .lead { color: var(--text-muted); font-size: var(--text-base); max-width: 60ch; margin-top: var(--sp-2); }
1313
+ .guide-cards {
1314
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(min(var(--card-min), 100%), 1fr));
1315
+ gap: var(--sp-4); margin: var(--sp-4) 0 var(--sp-6);
1316
+ }
1317
+ .guide-card {
1318
+ display: flex; flex-direction: column; gap: var(--sp-2);
1319
+ padding: var(--sp-4); background: var(--bg-card);
1320
+ border: 1px solid var(--border); border-radius: var(--radius);
1321
+ color: inherit; text-decoration: none;
1322
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1323
+ }
1324
+ .guide-card:hover { border-color: var(--accent); box-shadow: var(--shadow-md); text-decoration: none; }
1325
+ .guide-card-title { font-weight: var(--fw-semi); color: var(--accent); font-size: var(--text-base); }
1326
+ .guide-card-desc { color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-normal); }
1327
+
1328
+ /* ── Responsive: TOC becomes an off-canvas drawer (chrome.ts toggles .open) ──*/
1329
+ /* 860px is literal — media queries cannot read custom properties. Revisit it if
1330
+ --topbar-h / --rail-w / --drawer-w change (the drawer sticky offsets key off them). */
1331
+ @media (max-width: 860px) {
1332
+ .nav-toggle { display: inline-flex; align-items: center; }
1333
+ .layout { grid-template-columns: 1fr; gap: 0; }
1334
+ .rail {
1335
+ position: fixed; top: var(--topbar-h); left: 0; bottom: 0; width: var(--drawer-w); z-index: 50;
1336
+ height: auto; background: var(--bg-card); border-right: 1px solid var(--border);
1337
+ padding: var(--sp-4); box-shadow: var(--shadow-lg);
1338
+ transform: translateX(-100%); transition: transform 0.2s ease, visibility 0.2s ease;
1339
+ /* Closed drawer is off-screen AND removed from tab order / pointer events. */
1340
+ visibility: hidden; pointer-events: none;
1341
+ }
1342
+ .rail.open { transform: translateX(0); visibility: visible; pointer-events: auto; }
1343
+ .rail-close {
1344
+ display: block; margin-left: auto; margin-bottom: var(--sp-2);
1345
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text);
1346
+ border-radius: var(--radius-sm); padding: var(--sp-1) var(--sp-3); font-size: var(--text-base);
1347
+ line-height: 1; cursor: pointer; font-family: inherit;
1348
+ }
1349
+ .rail-close:hover { background: var(--bg-hover); border-color: var(--accent); }
1350
+ .rail-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1351
+ .rail-backdrop {
1352
+ display: block; position: fixed; inset: var(--topbar-h) 0 0 0;
1353
+ background: var(--scrim); z-index: 49;
1354
+ opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
1355
+ }
1356
+ .rail.open ~ .rail-backdrop { opacity: 1; pointer-events: auto; }
1357
+ }
1081
1358
  </style>
1082
1359
  <script>(function(){try{var t=localStorage.getItem('guide-theme');if(!t&&window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches)t='dark';if(t)document.documentElement.setAttribute('data-theme',t);}catch(e){}})();</script>
1083
1360
  </head>
1084
1361
  <body>
1085
1362
  <header class="topbar">
1086
- <button data-action="nav" class="nav-toggle" aria-label="Toggle navigation">☰</button>
1363
+ <button data-action="nav" class="nav-toggle" aria-label="Toggle navigation"
1364
+ aria-expanded="false" aria-controls="guide-toc">☰</button>
1087
1365
  <h1>MMR Reference</h1>
1088
1366
  <button data-action="theme" class="theme-toggle" aria-label="Toggle theme">◐</button>
1089
1367
  </header>
1090
1368
  <div class="layout">
1091
- <aside class="rail"><nav class="toc" aria-label="Table of contents"><ul><li class="toc-2"><a href="#what-mmr-is">What MMR is</a></li><li class="toc-3"><a href="#the-core-idea-in-five-moves">The core idea in five moves</a></li><li class="toc-2"><a href="#end-to-end-flow">End-to-end flow</a></li><li class="toc-2"><a href="#the-mmr-review-command">The mmr review command</a></li><li class="toc-3"><a href="#copy-paste-commands-by-target">Copy-paste commands by target</a></li><li class="toc-2"><a href="#other-subcommands">Other subcommands</a></li><li class="toc-2"><a href="#channel-architecture">Channel architecture</a></li><li class="toc-3"><a href="#the-channel-config-shape">The channel config shape</a></li><li class="toc-3"><a href="#built-in-channels">Built-in channels</a></li><li class="toc-3"><a href="#the-dispatcher">The dispatcher</a></li><li class="toc-2"><a href="#scaffold-wrappers">Scaffold wrappers</a></li><li class="toc-2"><a href="#findings-reconciliation-verdicts">Findings, reconciliation &amp; verdicts</a></li><li class="toc-3"><a href="#the-finding-shape">The Finding shape</a></li><li class="toc-3"><a href="#stable-identity-finding-key">Stable identity (finding_key)</a></li><li class="toc-3"><a href="#agreement-confidence">Agreement &amp; confidence</a></li><li class="toc-3"><a href="#the-gate-the-four-verdicts">The gate &amp; the four verdicts</a></li><li class="toc-2"><a href="#degraded-mode-compensation-auth">Degraded mode, compensation &amp; auth</a></li><li class="toc-2"><a href="#configuration-mmryaml">Configuration (.mmr.yaml)</a></li></ul></nav></aside>
1369
+ <aside class="rail" id="guide-toc">
1370
+ <button class="rail-close" data-action="nav" aria-label="Close navigation">✕</button>
1371
+ <nav class="toc" aria-label="Table of contents"><ul><li class="toc-2"><a href="#what-mmr-is">What MMR is</a></li><li class="toc-3"><a href="#the-core-idea-in-five-moves">The core idea in five moves</a></li><li class="toc-2"><a href="#end-to-end-flow">End-to-end flow</a></li><li class="toc-2"><a href="#the-mmr-review-command">The mmr review command</a></li><li class="toc-3"><a href="#copy-paste-commands-by-target">Copy-paste commands by target</a></li><li class="toc-2"><a href="#other-subcommands">Other subcommands</a></li><li class="toc-2"><a href="#channel-architecture">Channel architecture</a></li><li class="toc-3"><a href="#the-channel-config-shape">The channel config shape</a></li><li class="toc-3"><a href="#built-in-channels">Built-in channels</a></li><li class="toc-3"><a href="#the-dispatcher">The dispatcher</a></li><li class="toc-2"><a href="#scaffold-wrappers">Scaffold wrappers</a></li><li class="toc-2"><a href="#findings-reconciliation-verdicts">Findings, reconciliation &amp; verdicts</a></li><li class="toc-3"><a href="#the-finding-shape">The Finding shape</a></li><li class="toc-3"><a href="#stable-identity-finding-key">Stable identity (finding_key)</a></li><li class="toc-3"><a href="#agreement-confidence">Agreement &amp; confidence</a></li><li class="toc-3"><a href="#the-gate-the-four-verdicts">The gate &amp; the four verdicts</a></li><li class="toc-2"><a href="#degraded-mode-compensation-auth">Degraded mode, compensation &amp; auth</a></li><li class="toc-2"><a href="#configuration-mmryaml">Configuration (.mmr.yaml)</a></li></ul></nav>
1372
+ </aside>
1092
1373
  <main class="content"><h2 id="what-mmr-is">What MMR is</h2>
1093
1374
  <p>Multi-Model Review runs your changes past several <strong>independent</strong> AI code
1094
1375
  reviewers ("channels"), then <strong>reconciles</strong> their findings into a single
@@ -1321,7 +1602,7 @@ uses <code>prompt_delivery: prompt-file</code> — the dispatcher writes the pro
1321
1602
  file and passes its path via the <code>{{prompt_file}}</code> placeholder. Grok wraps its
1322
1603
  reply in a JSON <code>.text</code> field, which the parser unwraps before extracting
1323
1604
  findings.</p></div>
1324
- <div class="tabs"><div class="tablist" role="tablist"><button class="tab-btn active" role="tab" data-tab="0">Compare</button><button class="tab-btn" role="tab" data-tab="1">codex</button><button class="tab-btn" role="tab" data-tab="2">gemini</button><button class="tab-btn" role="tab" data-tab="3">claude</button><button class="tab-btn" role="tab" data-tab="4">grok</button><button class="tab-btn" role="tab" data-tab="5">doc-conformance</button></div><div class="tabpane active" data-tab="0"><p>The defaults, commands, and parsers below are the built-in presets <span class="fp" data-path="packages/mmr/src/config/defaults.ts:32">packages/mmr/src/config/defaults.ts:32</span>.</p>
1605
+ <div class="tabs"><div class="tablist" role="tablist"><button id="tab-0-0" class="tab-btn active" role="tab" data-tab="0" aria-controls="tabpane-0-0" aria-selected="true" tabindex="0">Compare</button><button id="tab-0-1" class="tab-btn" role="tab" data-tab="1" aria-controls="tabpane-0-1" aria-selected="false" tabindex="-1">codex</button><button id="tab-0-2" class="tab-btn" role="tab" data-tab="2" aria-controls="tabpane-0-2" aria-selected="false" tabindex="-1">gemini</button><button id="tab-0-3" class="tab-btn" role="tab" data-tab="3" aria-controls="tabpane-0-3" aria-selected="false" tabindex="-1">claude</button><button id="tab-0-4" class="tab-btn" role="tab" data-tab="4" aria-controls="tabpane-0-4" aria-selected="false" tabindex="-1">grok</button><button id="tab-0-5" class="tab-btn" role="tab" data-tab="5" aria-controls="tabpane-0-5" aria-selected="false" tabindex="-1">doc-conformance</button></div><div id="tabpane-0-0" class="tabpane active" role="tabpanel" data-tab="0" aria-labelledby="tab-0-0" tabindex="0"><p>The defaults, commands, and parsers below are the built-in presets <span class="fp" data-path="packages/mmr/src/config/defaults.ts:32">packages/mmr/src/config/defaults.ts:32</span>.</p>
1325
1606
 
1326
1607
 
1327
1608
 
@@ -1368,25 +1649,25 @@ findings.</p></div>
1368
1649
 
1369
1650
 
1370
1651
 
1371
- <table><thead><tr><th>Channel</th><th>Default</th><th>Strength</th><th>Prompt delivery</th><th>Parser</th></tr></thead><tbody><tr><td><code>codex</code></td><td>enabled</td><td>Correctness, security, API contracts</td><td>stdin</td><td><code>default</code></td></tr><tr><td><code>gemini</code></td><td>enabled</td><td>Architecture, broad-context reasoning</td><td>stdin</td><td><code>gemini</code></td></tr><tr><td><code>claude</code></td><td>enabled</td><td>Plan alignment, code quality, testing</td><td>stdin</td><td><code>default</code></td></tr><tr><td><code>grok</code></td><td>enabled</td><td>Independent second opinion (xAI; proprietary)</td><td><strong>prompt-file</strong></td><td><code>unwrap $.text → default</code></td></tr><tr><td><code>doc-conformance</code></td><td>opt-in</td><td>PRD/stories/standards conformance (LLM-graded)</td><td>stdin</td><td><code>doc-conformance</code></td></tr></tbody></table></div><div class="tabpane" data-tab="1"><pre><code class="language-yaml">command: codex exec
1652
+ <table><thead><tr><th>Channel</th><th>Default</th><th>Strength</th><th>Prompt delivery</th><th>Parser</th></tr></thead><tbody><tr><td><code>codex</code></td><td>enabled</td><td>Correctness, security, API contracts</td><td>stdin</td><td><code>default</code></td></tr><tr><td><code>gemini</code></td><td>enabled</td><td>Architecture, broad-context reasoning</td><td>stdin</td><td><code>gemini</code></td></tr><tr><td><code>claude</code></td><td>enabled</td><td>Plan alignment, code quality, testing</td><td>stdin</td><td><code>default</code></td></tr><tr><td><code>grok</code></td><td>enabled</td><td>Independent second opinion (xAI; proprietary)</td><td><strong>prompt-file</strong></td><td><code>unwrap $.text → default</code></td></tr><tr><td><code>doc-conformance</code></td><td>opt-in</td><td>PRD/stories/standards conformance (LLM-graded)</td><td>stdin</td><td><code>doc-conformance</code></td></tr></tbody></table></div><div id="tabpane-0-1" class="tabpane" role="tabpanel" data-tab="1" aria-labelledby="tab-0-1" tabindex="0"><pre><code class="language-yaml">command: codex exec
1372
1653
  flags: [--skip-git-repo-check, -s, read-only, --ephemeral]
1373
1654
  auth.check: codex login status # local file check (fast, 5s)
1374
1655
  recovery: codex login
1375
1656
  output_parser: default
1376
1657
  stderr: suppress
1377
- </code></pre></div><div class="tabpane" data-tab="2"><pre><code class="language-yaml">command: gemini # NO -p: gemini reads stdin natively
1658
+ </code></pre></div><div id="tabpane-0-2" class="tabpane" role="tabpanel" data-tab="2" aria-labelledby="tab-0-2" tabindex="0"><pre><code class="language-yaml">command: gemini # NO -p: gemini reads stdin natively
1378
1659
  flags: [--output-format, json]
1379
1660
  env: { NO_BROWSER: "true" }
1380
1661
  auth.check: NO_BROWSER=true gemini -p "respond with ok" -o json # LLM round-trip, 20s
1381
1662
  recovery: gemini -p "hello"
1382
1663
  output_parser: gemini # unwraps { "response": "…" }
1383
1664
  timeout: 360
1384
- </code></pre></div><div class="tabpane" data-tab="3"><pre><code class="language-yaml">command: claude -p
1665
+ </code></pre></div><div id="tabpane-0-3" class="tabpane" role="tabpanel" data-tab="3" aria-labelledby="tab-0-3" tabindex="0"><pre><code class="language-yaml">command: claude -p
1385
1666
  flags: [--output-format, json]
1386
1667
  auth.check: claude -p "respond with ok" # LLM round-trip, 20s
1387
1668
  recovery: claude login
1388
1669
  output_parser: default
1389
- </code></pre></div><div class="tabpane" data-tab="4"><pre><code class="language-yaml">command: grok
1670
+ </code></pre></div><div id="tabpane-0-4" class="tabpane" role="tabpanel" data-tab="4" aria-labelledby="tab-0-4" tabindex="0"><pre><code class="language-yaml">command: grok
1390
1671
  prompt_delivery: prompt-file
1391
1672
  flags: [--prompt-file, "{{prompt_file}}", --output-format, json]
1392
1673
  auth.check: grok models # lists models / login state (no round-trip)
@@ -1394,7 +1675,7 @@ recovery: grok login
1394
1675
  output_parser: { kind: unwrap-jsonpath, wrap: "$.text", then: default }
1395
1676
  </code></pre><p>Grok is proprietary (xAI), not open-source — it joins the standard set
1396
1677
  mechanically as a 4th CLI channel. Disable it with
1397
- <code>channels_disabled: ["grok"]</code>.</p></div><div class="tabpane" data-tab="5"><pre><code class="language-yaml">enabled: false # opt-in: runs up to 3 LLM calls (~3 min)
1678
+ <code>channels_disabled: ["grok"]</code>.</p></div><div id="tabpane-0-5" class="tabpane" role="tabpanel" data-tab="5" aria-labelledby="tab-0-5" tabindex="0"><pre><code class="language-yaml">enabled: false # opt-in: runs up to 3 LLM calls (~3 min)
1398
1679
  command: scaffold observe audit --profile=full --scope=all --output-mode=mmr-findings
1399
1680
  output_parser: doc-conformance # expects a JSON array of findings
1400
1681
  timeout: 240
@@ -1648,6 +1929,7 @@ be read from the diff's <em>base ref</em>, not the working tree — otherwise a
1648
1929
  add a channel that exfiltrates secrets or self-acknowledge its own findings. Use
1649
1930
  <code>--config-base-ref</code> / the <code>--trust-project-*</code> flags to control this in untrusted
1650
1931
  (e.g. CI) contexts.</p></div></main>
1932
+ <div class="rail-backdrop" data-action="nav" aria-hidden="true"></div>
1651
1933
  </div>
1652
1934
  <script>(function(){
1653
1935
  var LS_KEY = 'guide-theme';
@@ -1666,12 +1948,69 @@ add a channel that exfiltrates secrets or self-acknowledge its own findings. Use
1666
1948
  });
1667
1949
  });
1668
1950
 
1669
- // ─── Mobile nav ──────────────────────────────────────────────────────────
1951
+ // ─── Mobile nav (drawer + backdrop; aria-expanded + Escape-to-close) ──────
1952
+ function setNav(open) {
1953
+ var rail = document.querySelector('.rail');
1954
+ if (rail) rail.classList.toggle('open', open);
1955
+ var toggle = document.querySelector('.nav-toggle');
1956
+ if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
1957
+ // Modal-drawer focus containment: while open, make the page content inert
1958
+ // (out of tab order + a11y tree) and move focus into the drawer; on close,
1959
+ // restore content and return focus to the toggle.
1960
+ var main = document.querySelector('.content');
1961
+ if (main) main.inert = open;
1962
+ if (open) {
1963
+ var first = rail && rail.querySelector('a, button, [tabindex]:not([tabindex="-1"])');
1964
+ if (first) first.focus();
1965
+ else if (rail) { rail.setAttribute('tabindex', '-1'); rail.focus(); }
1966
+ } else if (toggle) {
1967
+ toggle.focus();
1968
+ }
1969
+ }
1970
+ // If the viewport grows past the mobile breakpoint while the drawer is open,
1971
+ // the rail becomes the desktop sidebar and the toggle hides — clear the open
1972
+ // state so .content doesn't stay inert with no way to close it.
1973
+ if (window.matchMedia) {
1974
+ var mq = window.matchMedia('(max-width: 860px)');
1975
+ var onMq = function() {
1976
+ if (mq.matches) return;
1977
+ var rail = document.querySelector('.rail');
1978
+ if (rail) rail.classList.remove('open');
1979
+ var toggle = document.querySelector('.nav-toggle');
1980
+ if (toggle) toggle.setAttribute('aria-expanded', 'false');
1981
+ var main = document.querySelector('.content');
1982
+ if (main) main.inert = false;
1983
+ };
1984
+ if (mq.addEventListener) mq.addEventListener('change', onMq);
1985
+ else if (mq.addListener) mq.addListener(onMq);
1986
+ }
1670
1987
  document.querySelectorAll('[data-action="nav"]').forEach(function(btn) {
1671
1988
  btn.addEventListener('click', function() {
1672
1989
  var rail = document.querySelector('.rail');
1673
- if (rail) rail.classList.toggle('open');
1990
+ setNav(!(rail && rail.classList.contains('open')));
1991
+ });
1992
+ });
1993
+ // Selecting a TOC link closes the drawer (so the now-active content isn't
1994
+ // left inert behind the panel) before the anchor navigation scrolls.
1995
+ var drawerRail = document.querySelector('.rail');
1996
+ if (drawerRail) {
1997
+ drawerRail.querySelectorAll('a').forEach(function(a) {
1998
+ a.addEventListener('click', function() {
1999
+ if (drawerRail.classList.contains('open')) setNav(false);
2000
+ });
1674
2001
  });
2002
+ }
2003
+ document.addEventListener('keydown', function(e) {
2004
+ var rail = document.querySelector('.rail');
2005
+ if (!rail || !rail.classList.contains('open')) return;
2006
+ if (e.key === 'Escape') { setNav(false); return; } // setNav restores focus to the toggle
2007
+ // Trap Tab within the open drawer (modal pattern).
2008
+ if (e.key !== 'Tab') return;
2009
+ var f = rail.querySelectorAll('a[href], button, [tabindex]:not([tabindex="-1"])');
2010
+ if (!f.length) return;
2011
+ var first = f[0], last = f[f.length - 1];
2012
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
2013
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
1675
2014
  });
1676
2015
 
1677
2016
  // ─── Copy buttons ─────────────────────────────────────────────────────────
@@ -1698,17 +2037,31 @@ add a channel that exfiltrates secrets or self-acknowledge its own findings. Use
1698
2037
  wrapper.insertBefore(btn, pre);
1699
2038
  });
1700
2039
 
1701
- // ─── Tabs ─────────────────────────────────────────────────────────────────
2040
+ // ─── Tabs (ARIA pattern: aria-selected + roving tabindex + arrow keys) ────
2041
+ function activateTab(group, btn, focus) {
2042
+ var idx = btn.getAttribute('data-tab');
2043
+ group.querySelectorAll('.tab-btn').forEach(function(b) {
2044
+ var on = b === btn;
2045
+ b.classList.toggle('active', on);
2046
+ b.setAttribute('aria-selected', on ? 'true' : 'false');
2047
+ b.setAttribute('tabindex', on ? '0' : '-1');
2048
+ });
2049
+ group.querySelectorAll('.tabpane').forEach(function(pane) {
2050
+ pane.classList.toggle('active', pane.getAttribute('data-tab') === idx);
2051
+ });
2052
+ if (focus) btn.focus();
2053
+ }
1702
2054
  document.querySelectorAll('.tabs').forEach(function(group) {
1703
- group.querySelectorAll('.tab-btn').forEach(function(btn) {
1704
- btn.addEventListener('click', function() {
1705
- var idx = btn.getAttribute('data-tab');
1706
- group.querySelectorAll('.tab-btn').forEach(function(b) {
1707
- b.classList.toggle('active', b === btn);
1708
- });
1709
- group.querySelectorAll('.tabpane').forEach(function(pane) {
1710
- pane.classList.toggle('active', pane.getAttribute('data-tab') === idx);
1711
- });
2055
+ var btns = [].slice.call(group.querySelectorAll('.tab-btn'));
2056
+ btns.forEach(function(btn, i) {
2057
+ btn.addEventListener('click', function() { activateTab(group, btn, false); });
2058
+ btn.addEventListener('keydown', function(e) {
2059
+ var ni = -1;
2060
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') ni = (i + 1) % btns.length;
2061
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') ni = (i - 1 + btns.length) % btns.length;
2062
+ else if (e.key === 'Home') ni = 0;
2063
+ else if (e.key === 'End') ni = btns.length - 1;
2064
+ if (ni >= 0) { e.preventDefault(); activateTab(group, btns[ni], true); }
1712
2065
  });
1713
2066
  });
1714
2067
  });