@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>Knowledge Freshness</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-this-system-does">What this system does</a></li><li class="toc-3"><a href="#how-a-gap-closes">How a gap closes</a></li><li class="toc-2"><a href="#system-map">System map</a></li><li class="toc-2"><a href="#frontmatter-signals-and-resolution">Frontmatter, signals, and resolution</a></li><li class="toc-3"><a href="#frontmatter-schema">Frontmatter schema</a></li><li class="toc-3"><a href="#cadence-model">Cadence model</a></li><li class="toc-3"><a href="#adding-a-new-entry-to-the-kb">Adding a new entry to the KB</a></li><li class="toc-3"><a href="#gap-signal-payload">Gap-signal payload</a></li><li class="toc-3"><a href="#knowledgerootresolution-shape">KnowledgeRootResolution shape</a></li><li class="toc-2"><a href="#from-candidate-to-merged-pr">From candidate to merged PR</a></li><li class="toc-3"><a href="#prefilter">Prefilter</a></li><li class="toc-3"><a href="#audit-verdicts">Audit verdicts</a></li><li class="toc-3"><a href="#pr-generation">PR generation</a></li><li class="toc-3"><a href="#mmr-corroboration-manual">MMR corroboration (manual)</a></li><li class="toc-2"><a href="#the-five-pr-gates">The five PR gates</a></li><li class="toc-2"><a href="#lens-i-gap-detection-suppression">Lens I — gap detection + suppression</a></li><li class="toc-3"><a href="#threshold-matrix">Threshold matrix</a></li><li class="toc-3"><a href="#topic-normalization">Topic normalization</a></li><li class="toc-3"><a href="#what-the-lessonsmd-scanner-sees">What the lessons.md scanner sees</a></li><li class="toc-3"><a href="#3-tier---knowledge-root-resolution">3-tier --knowledge-root resolution</a></li><li class="toc-3"><a href="#warning-policy">Warning policy</a></li><li class="toc-3"><a href="#what-a-lens-i-finding-looks-like">What a Lens I finding looks like</a></li><li class="toc-2"><a href="#the-allowlist">The allowlist</a></li><li class="toc-3"><a href="#most-cited-hosts">Most-cited hosts</a></li><li class="toc-3"><a href="#the-full-allowlist">The full allowlist</a></li><li class="toc-3"><a href="#kb-inventory">KB inventory</a></li><li class="toc-3"><a href="#how-to-expand-the-allowlist">How to expand the allowlist</a></li><li class="toc-2"><a href="#anthropic-vs-deepseek-cron-uses-deepseek">Anthropic vs DeepSeek (cron uses DeepSeek)</a></li><li class="toc-2"><a href="#every-command-that-touches-the-system">Every command that touches the system</a></li><li class="toc-3"><a href="#refresh-arm-commands">Refresh-arm commands</a></li><li class="toc-3"><a href="#gap-arm-commands">Gap-arm commands</a></li><li class="toc-3"><a href="#gate-side-subcommands-also-runnable-locally-for-triage">Gate-side subcommands (also runnable locally for triage)</a></li><li class="toc-2"><a href="#operations-cheat-sheet">Operations cheat sheet</a></li><li class="toc-3"><a href="#an-entrys-audit-failed-in-the-cron">An entry's audit failed in the cron</a></li><li class="toc-3"><a href="#lens-i-keeps-surfacing-a-topic-the-kb-already-covers">Lens I keeps surfacing a topic the KB already covers</a></li><li class="toc-3"><a href="#downstream-auto-detect-cant-find-the-kb">Downstream auto-detect can't find the KB</a></li><li class="toc-3"><a href="#yaml-knowledge-root-stops-working-after-an-upgrade">Yaml knowledge_root stops working after an upgrade</a></li><li class="toc-3"><a href="#a-source-url-fetches-in-curl-but-the-cron-rejects-it">A source URL fetches in curl but the cron rejects it</a></li><li class="toc-3"><a href="#--knowledge-root-resolves-to-a-path-you-didnt-expect">--knowledge-root resolves to a path you didn't expect</a></li><li class="toc-2"><a href="#config-reference">Config reference</a></li><li class="toc-2"><a href="#roadmap-and-known-divergences">Roadmap and known divergences</a></li><li class="toc-3"><a href="#phase-5-planned">Phase 5 (planned)</a></li><li class="toc-3"><a href="#known-divergences">Known divergences</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-this-system-does">What this system does</a></li><li class="toc-3"><a href="#how-a-gap-closes">How a gap closes</a></li><li class="toc-2"><a href="#system-map">System map</a></li><li class="toc-2"><a href="#frontmatter-signals-and-resolution">Frontmatter, signals, and resolution</a></li><li class="toc-3"><a href="#frontmatter-schema">Frontmatter schema</a></li><li class="toc-3"><a href="#cadence-model">Cadence model</a></li><li class="toc-3"><a href="#adding-a-new-entry-to-the-kb">Adding a new entry to the KB</a></li><li class="toc-3"><a href="#gap-signal-payload">Gap-signal payload</a></li><li class="toc-3"><a href="#knowledgerootresolution-shape">KnowledgeRootResolution shape</a></li><li class="toc-2"><a href="#from-candidate-to-merged-pr">From candidate to merged PR</a></li><li class="toc-3"><a href="#prefilter">Prefilter</a></li><li class="toc-3"><a href="#audit-verdicts">Audit verdicts</a></li><li class="toc-3"><a href="#pr-generation">PR generation</a></li><li class="toc-3"><a href="#mmr-corroboration-manual">MMR corroboration (manual)</a></li><li class="toc-2"><a href="#the-five-pr-gates">The five PR gates</a></li><li class="toc-2"><a href="#lens-i-gap-detection-suppression">Lens I — gap detection + suppression</a></li><li class="toc-3"><a href="#threshold-matrix">Threshold matrix</a></li><li class="toc-3"><a href="#topic-normalization">Topic normalization</a></li><li class="toc-3"><a href="#what-the-lessonsmd-scanner-sees">What the lessons.md scanner sees</a></li><li class="toc-3"><a href="#3-tier---knowledge-root-resolution">3-tier --knowledge-root resolution</a></li><li class="toc-3"><a href="#warning-policy">Warning policy</a></li><li class="toc-3"><a href="#what-a-lens-i-finding-looks-like">What a Lens I finding looks like</a></li><li class="toc-2"><a href="#the-allowlist">The allowlist</a></li><li class="toc-3"><a href="#most-cited-hosts">Most-cited hosts</a></li><li class="toc-3"><a href="#the-full-allowlist">The full allowlist</a></li><li class="toc-3"><a href="#kb-inventory">KB inventory</a></li><li class="toc-3"><a href="#how-to-expand-the-allowlist">How to expand the allowlist</a></li><li class="toc-2"><a href="#anthropic-vs-deepseek-cron-uses-deepseek">Anthropic vs DeepSeek (cron uses DeepSeek)</a></li><li class="toc-2"><a href="#every-command-that-touches-the-system">Every command that touches the system</a></li><li class="toc-3"><a href="#refresh-arm-commands">Refresh-arm commands</a></li><li class="toc-3"><a href="#gap-arm-commands">Gap-arm commands</a></li><li class="toc-3"><a href="#gate-side-subcommands-also-runnable-locally-for-triage">Gate-side subcommands (also runnable locally for triage)</a></li><li class="toc-2"><a href="#operations-cheat-sheet">Operations cheat sheet</a></li><li class="toc-3"><a href="#an-entrys-audit-failed-in-the-cron">An entry's audit failed in the cron</a></li><li class="toc-3"><a href="#lens-i-keeps-surfacing-a-topic-the-kb-already-covers">Lens I keeps surfacing a topic the KB already covers</a></li><li class="toc-3"><a href="#downstream-auto-detect-cant-find-the-kb">Downstream auto-detect can't find the KB</a></li><li class="toc-3"><a href="#yaml-knowledge-root-stops-working-after-an-upgrade">Yaml knowledge_root stops working after an upgrade</a></li><li class="toc-3"><a href="#a-source-url-fetches-in-curl-but-the-cron-rejects-it">A source URL fetches in curl but the cron rejects it</a></li><li class="toc-3"><a href="#--knowledge-root-resolves-to-a-path-you-didnt-expect">--knowledge-root resolves to a path you didn't expect</a></li><li class="toc-2"><a href="#config-reference">Config reference</a></li><li class="toc-2"><a href="#roadmap-and-known-divergences">Roadmap and known divergences</a></li><li class="toc-3"><a href="#phase-5-planned">Phase 5 (planned)</a></li><li class="toc-3"><a href="#known-divergences">Known divergences</a></li></ul></nav>
1372
+ </aside>
1092
1373
  <main class="content"><h2 id="what-this-system-does">What this system does</h2>
1093
1374
  <p>Knowledge entries under <code>content/knowledge/</code> declare a <code>volatility</code> tier and a
1094
1375
  list of <code>sources</code>. A daily cron prefilters at most ten entries that are <em>due</em> —
@@ -2111,13 +2392,13 @@ resolved by <code>resolveProvider</code> (<span class="fp" data-path="src/knowle
2111
2392
  <li>No env, <code>claude</code> on PATH → anthropic (subprocess uses keychain)</li>
2112
2393
  <li>Nothing → error (no provider configured)</li>
2113
2394
  </ol>
2114
- <div class="tabs"><div class="tablist" role="tablist"><button class="tab-btn active" role="tab" data-tab="0">Anthropic</button><button class="tab-btn" role="tab" data-tab="1">DeepSeek</button></div><div class="tabpane active" data-tab="0"><p>Subprocess: <code>claude -p --tools ""</code> (empty-tools disables WebFetch so the model
2395
+ <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">Anthropic</button><button id="tab-0-1" class="tab-btn" role="tab" data-tab="1" aria-controls="tabpane-0-1" aria-selected="false" tabindex="-1">DeepSeek</button></div><div id="tabpane-0-0" class="tabpane active" role="tabpanel" data-tab="0" aria-labelledby="tab-0-0" tabindex="0"><p>Subprocess: <code>claude -p --tools ""</code> (empty-tools disables WebFetch so the model
2115
2396
  can only read the prefetched bodies). <strong>Requires the <code>claude</code> CLI on PATH
2116
2397
  regardless of how the provider was chosen</strong> — the resolver throws
2117
2398
  (<span class="fp" data-path="src/knowledge-freshness/providers/index.ts:44-56">src/knowledge-freshness/providers/index.ts:44-56</span>) if anthropic is
2118
2399
  picked via flag, env, or API-key inference and <code>claude</code> isn't installed.
2119
2400
  <code>ANTHROPIC_API_KEY</code> alone is <em>not</em> sufficient. Source:
2120
- <code>src/knowledge-freshness/providers/anthropic.ts</code>.</p></div><div class="tabpane" data-tab="1"><p>HTTP. No subprocess; works in CI without the Claude CLI.</p><ul>
2401
+ <code>src/knowledge-freshness/providers/anthropic.ts</code>.</p></div><div id="tabpane-0-1" class="tabpane" role="tabpanel" data-tab="1" aria-labelledby="tab-0-1" tabindex="0"><p>HTTP. No subprocess; works in CI without the Claude CLI.</p><ul>
2121
2402
  <li><strong>Auth:</strong> requires <code>DEEPSEEK_API_KEY</code>.</li>
2122
2403
  <li><strong>Default model:</strong> <code>deepseek-v4-flash</code>.</li>
2123
2404
  <li><strong>Override:</strong> set <code>KNOWLEDGE_FRESHNESS_DEEPSEEK_MODEL</code> to <code>deepseek-v4-pro</code>.
@@ -2344,6 +2625,7 @@ describes suppression in future tense; both have shipped.</li>
2344
2625
  <li><strong><code>www.</code> prefix inconsistency</strong> (P3) — mixed <code>www.</code> use in the allowlist; bare
2345
2626
  entries already auto-match subdomains, so the prefix is redundant.</li>
2346
2627
  </ul></main>
2628
+ <div class="rail-backdrop" data-action="nav" aria-hidden="true"></div>
2347
2629
  </div>
2348
2630
  <script>(function(){
2349
2631
  var LS_KEY = 'guide-theme';
@@ -2362,12 +2644,69 @@ entries already auto-match subdomains, so the prefix is redundant.</li>
2362
2644
  });
2363
2645
  });
2364
2646
 
2365
- // ─── Mobile nav ──────────────────────────────────────────────────────────
2647
+ // ─── Mobile nav (drawer + backdrop; aria-expanded + Escape-to-close) ──────
2648
+ function setNav(open) {
2649
+ var rail = document.querySelector('.rail');
2650
+ if (rail) rail.classList.toggle('open', open);
2651
+ var toggle = document.querySelector('.nav-toggle');
2652
+ if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
2653
+ // Modal-drawer focus containment: while open, make the page content inert
2654
+ // (out of tab order + a11y tree) and move focus into the drawer; on close,
2655
+ // restore content and return focus to the toggle.
2656
+ var main = document.querySelector('.content');
2657
+ if (main) main.inert = open;
2658
+ if (open) {
2659
+ var first = rail && rail.querySelector('a, button, [tabindex]:not([tabindex="-1"])');
2660
+ if (first) first.focus();
2661
+ else if (rail) { rail.setAttribute('tabindex', '-1'); rail.focus(); }
2662
+ } else if (toggle) {
2663
+ toggle.focus();
2664
+ }
2665
+ }
2666
+ // If the viewport grows past the mobile breakpoint while the drawer is open,
2667
+ // the rail becomes the desktop sidebar and the toggle hides — clear the open
2668
+ // state so .content doesn't stay inert with no way to close it.
2669
+ if (window.matchMedia) {
2670
+ var mq = window.matchMedia('(max-width: 860px)');
2671
+ var onMq = function() {
2672
+ if (mq.matches) return;
2673
+ var rail = document.querySelector('.rail');
2674
+ if (rail) rail.classList.remove('open');
2675
+ var toggle = document.querySelector('.nav-toggle');
2676
+ if (toggle) toggle.setAttribute('aria-expanded', 'false');
2677
+ var main = document.querySelector('.content');
2678
+ if (main) main.inert = false;
2679
+ };
2680
+ if (mq.addEventListener) mq.addEventListener('change', onMq);
2681
+ else if (mq.addListener) mq.addListener(onMq);
2682
+ }
2366
2683
  document.querySelectorAll('[data-action="nav"]').forEach(function(btn) {
2367
2684
  btn.addEventListener('click', function() {
2368
2685
  var rail = document.querySelector('.rail');
2369
- if (rail) rail.classList.toggle('open');
2686
+ setNav(!(rail && rail.classList.contains('open')));
2687
+ });
2688
+ });
2689
+ // Selecting a TOC link closes the drawer (so the now-active content isn't
2690
+ // left inert behind the panel) before the anchor navigation scrolls.
2691
+ var drawerRail = document.querySelector('.rail');
2692
+ if (drawerRail) {
2693
+ drawerRail.querySelectorAll('a').forEach(function(a) {
2694
+ a.addEventListener('click', function() {
2695
+ if (drawerRail.classList.contains('open')) setNav(false);
2696
+ });
2370
2697
  });
2698
+ }
2699
+ document.addEventListener('keydown', function(e) {
2700
+ var rail = document.querySelector('.rail');
2701
+ if (!rail || !rail.classList.contains('open')) return;
2702
+ if (e.key === 'Escape') { setNav(false); return; } // setNav restores focus to the toggle
2703
+ // Trap Tab within the open drawer (modal pattern).
2704
+ if (e.key !== 'Tab') return;
2705
+ var f = rail.querySelectorAll('a[href], button, [tabindex]:not([tabindex="-1"])');
2706
+ if (!f.length) return;
2707
+ var first = f[0], last = f[f.length - 1];
2708
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
2709
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
2371
2710
  });
2372
2711
 
2373
2712
  // ─── Copy buttons ─────────────────────────────────────────────────────────
@@ -2394,17 +2733,31 @@ entries already auto-match subdomains, so the prefix is redundant.</li>
2394
2733
  wrapper.insertBefore(btn, pre);
2395
2734
  });
2396
2735
 
2397
- // ─── Tabs ─────────────────────────────────────────────────────────────────
2736
+ // ─── Tabs (ARIA pattern: aria-selected + roving tabindex + arrow keys) ────
2737
+ function activateTab(group, btn, focus) {
2738
+ var idx = btn.getAttribute('data-tab');
2739
+ group.querySelectorAll('.tab-btn').forEach(function(b) {
2740
+ var on = b === btn;
2741
+ b.classList.toggle('active', on);
2742
+ b.setAttribute('aria-selected', on ? 'true' : 'false');
2743
+ b.setAttribute('tabindex', on ? '0' : '-1');
2744
+ });
2745
+ group.querySelectorAll('.tabpane').forEach(function(pane) {
2746
+ pane.classList.toggle('active', pane.getAttribute('data-tab') === idx);
2747
+ });
2748
+ if (focus) btn.focus();
2749
+ }
2398
2750
  document.querySelectorAll('.tabs').forEach(function(group) {
2399
- group.querySelectorAll('.tab-btn').forEach(function(btn) {
2400
- btn.addEventListener('click', function() {
2401
- var idx = btn.getAttribute('data-tab');
2402
- group.querySelectorAll('.tab-btn').forEach(function(b) {
2403
- b.classList.toggle('active', b === btn);
2404
- });
2405
- group.querySelectorAll('.tabpane').forEach(function(pane) {
2406
- pane.classList.toggle('active', pane.getAttribute('data-tab') === idx);
2407
- });
2751
+ var btns = [].slice.call(group.querySelectorAll('.tab-btn'));
2752
+ btns.forEach(function(btn, i) {
2753
+ btn.addEventListener('click', function() { activateTab(group, btn, false); });
2754
+ btn.addEventListener('keydown', function(e) {
2755
+ var ni = -1;
2756
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') ni = (i + 1) % btns.length;
2757
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') ni = (i - 1 + btns.length) % btns.length;
2758
+ else if (e.key === 'Home') ni = 0;
2759
+ else if (e.key === 'End') ni = btns.length - 1;
2760
+ if (ni >= 0) { e.preventDefault(); activateTab(group, btns[ni], true); }
2408
2761
  });
2409
2762
  });
2410
2763
  });
@@ -665,7 +665,7 @@ resolved by `resolveProvider` (:cite[src/knowledge-freshness/providers/index.ts:
665
665
  6. Nothing → error (no provider configured)
666
666
 
667
667
  ::::tabs
668
- :::tab{title=Anthropic}
668
+ :::tab{title="Anthropic"}
669
669
  Subprocess: `claude -p --tools ""` (empty-tools disables WebFetch so the model
670
670
  can only read the prefetched bodies). **Requires the `claude` CLI on PATH
671
671
  regardless of how the provider was chosen** — the resolver throws
@@ -674,7 +674,7 @@ picked via flag, env, or API-key inference and `claude` isn't installed.
674
674
  `ANTHROPIC_API_KEY` alone is *not* sufficient. Source:
675
675
  `src/knowledge-freshness/providers/anthropic.ts`.
676
676
  :::
677
- :::tab{title=DeepSeek}
677
+ :::tab{title="DeepSeek"}
678
678
  HTTP. No subprocess; works in CI without the Claude CLI.
679
679
 
680
680
  - **Auth:** requires `DEEPSEEK_API_KEY`.