@zigrivers/scaffold 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/guides/AUTHORING.md +143 -0
- package/content/guides/cli/index.html +1502 -0
- package/content/guides/cli/index.md +206 -0
- package/content/guides/concepts/index.html +1617 -0
- package/content/guides/concepts/index.md +347 -0
- package/content/guides/dashboard/index.html +1560 -0
- package/content/guides/dashboard/index.md +264 -0
- package/content/guides/index.html +1 -1
- package/content/guides/install/.diagrams/diagram-0.svg +1 -0
- package/content/guides/install/.diagrams/manifest.json +3 -0
- package/content/guides/install/index.html +1300 -0
- package/content/guides/install/index.md +186 -0
- package/content/guides/knowledge/.diagrams/diagram-0.svg +1 -0
- package/content/guides/knowledge/.diagrams/manifest.json +3 -0
- package/content/guides/knowledge/index.html +1412 -0
- package/content/guides/knowledge/index.md +209 -0
- package/content/guides/knowledge-freshness/.diagrams/diagram-0.svg +1 -0
- package/content/guides/knowledge-freshness/.diagrams/manifest.json +3 -0
- package/content/guides/knowledge-freshness/index.html +2442 -0
- package/content/guides/knowledge-freshness/index.md +893 -0
- package/content/guides/mmr/index.html +35 -17
- package/content/guides/mmr/index.md +39 -16
- package/content/guides/multi-agent/.diagrams/diagram-0.svg +1 -0
- package/content/guides/multi-agent/.diagrams/manifest.json +3 -0
- package/content/guides/multi-agent/index.html +1362 -0
- package/content/guides/multi-agent/index.md +243 -0
- package/content/guides/observability/.diagrams/diagram-0.svg +1 -0
- package/content/guides/observability/.diagrams/diagram-1.svg +1 -0
- package/content/guides/observability/.diagrams/diagram-2.svg +1 -0
- package/content/guides/observability/.diagrams/diagram-3.svg +1 -0
- package/content/guides/observability/.diagrams/manifest.json +6 -0
- package/content/guides/observability/index.html +2904 -0
- package/content/guides/observability/index.md +1097 -0
- package/content/guides/pipeline/.diagrams/diagram-0.svg +1 -0
- package/content/guides/pipeline/.diagrams/diagram-1.svg +1 -0
- package/content/guides/pipeline/.diagrams/manifest.json +4 -0
- package/content/guides/pipeline/index.html +1632 -0
- package/content/guides/pipeline/index.md +387 -0
- package/content/guides/review-workflow/.diagrams/diagram-0.svg +1 -0
- package/content/guides/review-workflow/.diagrams/diagram-1.svg +1 -0
- package/content/guides/review-workflow/.diagrams/manifest.json +4 -0
- package/content/guides/review-workflow/index.html +1437 -0
- package/content/guides/review-workflow/index.md +248 -0
- package/dist/guides/build.d.ts.map +1 -1
- package/dist/guides/build.js +7 -2
- package/dist/guides/build.js.map +1 -1
- package/dist/guides/build.test.js +8 -0
- package/dist/guides/build.test.js.map +1 -1
- package/dist/guides/directives-cite.test.d.ts +2 -0
- package/dist/guides/directives-cite.test.d.ts.map +1 -0
- package/dist/guides/directives-cite.test.js +26 -0
- package/dist/guides/directives-cite.test.js.map +1 -0
- package/dist/guides/directives.d.ts +1 -0
- package/dist/guides/directives.d.ts.map +1 -1
- package/dist/guides/directives.js +24 -0
- package/dist/guides/directives.js.map +1 -1
- package/dist/guides/links.d.ts +14 -0
- package/dist/guides/links.d.ts.map +1 -0
- package/dist/guides/links.js +56 -0
- package/dist/guides/links.js.map +1 -0
- package/dist/guides/links.test.d.ts +2 -0
- package/dist/guides/links.test.d.ts.map +1 -0
- package/dist/guides/links.test.js +72 -0
- package/dist/guides/links.test.js.map +1 -0
- package/dist/guides/render.d.ts +1 -0
- package/dist/guides/render.d.ts.map +1 -1
- package/dist/guides/render.js +1 -1
- package/dist/guides/render.js.map +1 -1
- package/dist/guides/sanitize.d.ts.map +1 -1
- package/dist/guides/sanitize.js +1 -0
- package/dist/guides/sanitize.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,2442 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-chrome-version="1">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Knowledge Freshness</title>
|
|
7
|
+
<!-- scaffold:chrome v1 -->
|
|
8
|
+
<style>/* Scaffold Dashboard Theme
|
|
9
|
+
* All CSS for the generated pipeline dashboard.
|
|
10
|
+
* Embedded into HTML by scripts/generate-dashboard.sh.
|
|
11
|
+
* Design system reference: docs/design-system.md
|
|
12
|
+
*
|
|
13
|
+
* Aesthetic: "Precision Industrial" — Swiss-typographic control room.
|
|
14
|
+
* Deep navy dark mode with indigo accents, clean cool-white light mode.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* ─── Design Tokens (Light Mode) ──────────────── */
|
|
18
|
+
:root {
|
|
19
|
+
/* Surface */
|
|
20
|
+
--bg: #f5f6fa;
|
|
21
|
+
--bg-card: #ffffff;
|
|
22
|
+
--bg-hover: #eef0f6;
|
|
23
|
+
--bg-inset: #e8eaf2;
|
|
24
|
+
|
|
25
|
+
/* Text */
|
|
26
|
+
--text: #1a1d2e;
|
|
27
|
+
--text-muted: #6b7294;
|
|
28
|
+
--text-faint: #9ba1c0;
|
|
29
|
+
|
|
30
|
+
/* Borders & Structure */
|
|
31
|
+
--border: #dde0ed;
|
|
32
|
+
--border-light: #eceef5;
|
|
33
|
+
--radius: 10px;
|
|
34
|
+
--radius-sm: 6px;
|
|
35
|
+
|
|
36
|
+
/* Accent */
|
|
37
|
+
--accent: #4f46e5;
|
|
38
|
+
--accent-hover: #4338ca;
|
|
39
|
+
--accent-glow: rgba(79, 70, 229, 0.10);
|
|
40
|
+
|
|
41
|
+
/* Semantic: Status */
|
|
42
|
+
--green: #059669;
|
|
43
|
+
--green-bg: #ecfdf5;
|
|
44
|
+
--green-border: #a7f3d0;
|
|
45
|
+
--blue: #2563eb;
|
|
46
|
+
--blue-bg: #eff6ff;
|
|
47
|
+
--blue-border: #bfdbfe;
|
|
48
|
+
--yellow: #d97706;
|
|
49
|
+
--yellow-bg: #fffbeb;
|
|
50
|
+
--yellow-border:#fde68a;
|
|
51
|
+
--gray: #9ca3af;
|
|
52
|
+
--gray-bg: #f3f4f6;
|
|
53
|
+
--gray-border: #e5e7eb;
|
|
54
|
+
|
|
55
|
+
/* Semantic: Next Banner */
|
|
56
|
+
--next-bg: #eef2ff;
|
|
57
|
+
--next-border: #4f46e5;
|
|
58
|
+
--next-glow: rgba(79, 70, 229, 0.06);
|
|
59
|
+
|
|
60
|
+
/* Semantic: Progress */
|
|
61
|
+
--progress-bg: #e5e7eb;
|
|
62
|
+
--progress-h: 10px;
|
|
63
|
+
|
|
64
|
+
/* Depth */
|
|
65
|
+
--shadow-sm: 0 1px 2px rgba(30, 34, 60, 0.04);
|
|
66
|
+
--shadow: 0 1px 3px rgba(30, 34, 60, 0.07), 0 1px 2px rgba(30, 34, 60, 0.04);
|
|
67
|
+
--shadow-md: 0 4px 12px rgba(30, 34, 60, 0.08), 0 1px 3px rgba(30, 34, 60, 0.05);
|
|
68
|
+
--shadow-lg: 0 8px 24px rgba(30, 34, 60, 0.10), 0 2px 6px rgba(30, 34, 60, 0.04);
|
|
69
|
+
|
|
70
|
+
/* Spacing scale (4px base) */
|
|
71
|
+
--sp-1: 4px;
|
|
72
|
+
--sp-2: 8px;
|
|
73
|
+
--sp-3: 12px;
|
|
74
|
+
--sp-4: 16px;
|
|
75
|
+
--sp-5: 20px;
|
|
76
|
+
--sp-6: 24px;
|
|
77
|
+
--sp-8: 32px;
|
|
78
|
+
--sp-10: 40px;
|
|
79
|
+
|
|
80
|
+
/* Typography */
|
|
81
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
82
|
+
--font-mono: "SF Mono", "Cascadia Code", "Fira Code", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
83
|
+
--text-xs: 0.75rem;
|
|
84
|
+
--text-sm: 0.8125rem;
|
|
85
|
+
--text-base: 0.9375rem;
|
|
86
|
+
--text-lg: 1.125rem;
|
|
87
|
+
--text-xl: 1.375rem;
|
|
88
|
+
--text-2xl: 1.75rem;
|
|
89
|
+
--lh-tight: 1.25;
|
|
90
|
+
--lh-normal: 1.5;
|
|
91
|
+
--lh-relaxed: 1.625;
|
|
92
|
+
--ls-tight: -0.01em;
|
|
93
|
+
--ls-wide: 0.025em;
|
|
94
|
+
--fw-normal: 400;
|
|
95
|
+
--fw-medium: 500;
|
|
96
|
+
--fw-semi: 600;
|
|
97
|
+
--fw-bold: 700;
|
|
98
|
+
|
|
99
|
+
/* Layout */
|
|
100
|
+
--max-w: 960px;
|
|
101
|
+
--page-pad: 24px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ─── Design Tokens (Dark Mode) ───────────────── */
|
|
105
|
+
[data-theme="dark"] {
|
|
106
|
+
/* Surface */
|
|
107
|
+
--bg: #0f1117;
|
|
108
|
+
--bg-card: #1a1d2e;
|
|
109
|
+
--bg-hover: #252940;
|
|
110
|
+
--bg-inset: #141724;
|
|
111
|
+
|
|
112
|
+
/* Text */
|
|
113
|
+
--text: #e2e5f0;
|
|
114
|
+
--text-muted: #7c82a8;
|
|
115
|
+
--text-faint: #555c80;
|
|
116
|
+
|
|
117
|
+
/* Borders & Structure */
|
|
118
|
+
--border: #2a2f45;
|
|
119
|
+
--border-light: #21253a;
|
|
120
|
+
|
|
121
|
+
/* Accent */
|
|
122
|
+
--accent: #818cf8;
|
|
123
|
+
--accent-hover: #a5b4fc;
|
|
124
|
+
--accent-glow: rgba(129, 140, 248, 0.12);
|
|
125
|
+
|
|
126
|
+
/* Semantic: Status */
|
|
127
|
+
--green: #34d399;
|
|
128
|
+
--green-bg: rgba(6, 78, 59, 0.25);
|
|
129
|
+
--green-border: rgba(52, 211, 153, 0.25);
|
|
130
|
+
--blue: #60a5fa;
|
|
131
|
+
--blue-bg: rgba(30, 58, 95, 0.30);
|
|
132
|
+
--blue-border: rgba(96, 165, 250, 0.25);
|
|
133
|
+
--yellow: #fbbf24;
|
|
134
|
+
--yellow-bg: rgba(120, 53, 15, 0.25);
|
|
135
|
+
--yellow-border:rgba(251, 191, 36, 0.20);
|
|
136
|
+
--gray: #6b7294;
|
|
137
|
+
--gray-bg: #252940;
|
|
138
|
+
--gray-border: #363c58;
|
|
139
|
+
|
|
140
|
+
/* Semantic: Next Banner */
|
|
141
|
+
--next-bg: rgba(30, 27, 75, 0.50);
|
|
142
|
+
--next-border: #818cf8;
|
|
143
|
+
--next-glow: rgba(129, 140, 248, 0.08);
|
|
144
|
+
|
|
145
|
+
/* Semantic: Progress */
|
|
146
|
+
--progress-bg: #1f2337;
|
|
147
|
+
|
|
148
|
+
/* Depth */
|
|
149
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.20);
|
|
150
|
+
--shadow: 0 1px 3px rgba(0, 0, 0, 0.30), 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
151
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.20);
|
|
152
|
+
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.40), 0 2px 6px rgba(0, 0, 0, 0.20);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ─── Theme Toggle ───────────────────────────── */
|
|
156
|
+
.theme-toggle {
|
|
157
|
+
background: var(--bg-inset);
|
|
158
|
+
border: 1px solid var(--border);
|
|
159
|
+
border-radius: var(--radius-sm);
|
|
160
|
+
padding: var(--sp-1) var(--sp-2);
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
font-size: var(--text-base);
|
|
163
|
+
line-height: 1;
|
|
164
|
+
color: var(--text-muted);
|
|
165
|
+
transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
margin-left: auto;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.theme-toggle:hover {
|
|
172
|
+
border-color: var(--accent);
|
|
173
|
+
color: var(--accent);
|
|
174
|
+
background: var(--accent-glow);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ─── Base ────────────────────────────────────── */
|
|
178
|
+
*, *::before, *::after {
|
|
179
|
+
margin: 0;
|
|
180
|
+
padding: 0;
|
|
181
|
+
box-sizing: border-box;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
body {
|
|
185
|
+
font-family: var(--font-sans);
|
|
186
|
+
font-size: var(--text-base);
|
|
187
|
+
line-height: var(--lh-normal);
|
|
188
|
+
color: var(--text);
|
|
189
|
+
background: var(--bg);
|
|
190
|
+
-webkit-font-smoothing: antialiased;
|
|
191
|
+
-moz-osx-font-smoothing: grayscale;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ─── Layout ──────────────────────────────────── */
|
|
195
|
+
.wrap {
|
|
196
|
+
max-width: var(--max-w);
|
|
197
|
+
margin: 0 auto;
|
|
198
|
+
padding: var(--sp-8) var(--page-pad);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ─── Header ──────────────────────────────────── */
|
|
202
|
+
.header {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: baseline;
|
|
205
|
+
gap: var(--sp-3);
|
|
206
|
+
margin-bottom: var(--sp-2);
|
|
207
|
+
flex-wrap: wrap;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
h1 {
|
|
211
|
+
font-size: var(--text-2xl);
|
|
212
|
+
font-weight: var(--fw-bold);
|
|
213
|
+
letter-spacing: var(--ls-tight);
|
|
214
|
+
line-height: var(--lh-tight);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
h2 {
|
|
218
|
+
font-size: var(--text-lg);
|
|
219
|
+
font-weight: var(--fw-semi);
|
|
220
|
+
letter-spacing: var(--ls-tight);
|
|
221
|
+
line-height: var(--lh-tight);
|
|
222
|
+
margin-bottom: var(--sp-3);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.header-meta {
|
|
226
|
+
font-size: var(--text-xs);
|
|
227
|
+
color: var(--text-faint);
|
|
228
|
+
margin-bottom: var(--sp-6);
|
|
229
|
+
letter-spacing: var(--ls-wide);
|
|
230
|
+
text-transform: uppercase;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ─── Badge ───────────────────────────────────── */
|
|
234
|
+
.badge {
|
|
235
|
+
display: inline-block;
|
|
236
|
+
padding: 2px var(--sp-2);
|
|
237
|
+
border-radius: 99px;
|
|
238
|
+
font-size: var(--text-xs);
|
|
239
|
+
font-weight: var(--fw-semi);
|
|
240
|
+
letter-spacing: var(--ls-wide);
|
|
241
|
+
background: var(--accent);
|
|
242
|
+
color: #fff;
|
|
243
|
+
text-transform: uppercase;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.badge-optional {
|
|
247
|
+
background: var(--yellow-bg);
|
|
248
|
+
color: var(--yellow);
|
|
249
|
+
border: 1px solid var(--yellow-border);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ─── Progress Bar ────────────────────────────── */
|
|
253
|
+
.progress-bar {
|
|
254
|
+
width: 100%;
|
|
255
|
+
height: var(--progress-h);
|
|
256
|
+
background: var(--progress-bg);
|
|
257
|
+
border-radius: 99px;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
margin-bottom: var(--sp-6);
|
|
260
|
+
display: flex;
|
|
261
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.progress-bar .seg-done {
|
|
265
|
+
background: linear-gradient(135deg, var(--green), #10b981);
|
|
266
|
+
box-shadow: 0 0 8px rgba(5, 150, 105, 0.3);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.progress-bar .seg-likely {
|
|
270
|
+
background: linear-gradient(135deg, var(--blue), #3b82f6);
|
|
271
|
+
box-shadow: 0 0 8px rgba(37, 99, 235, 0.25);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.progress-bar .seg-skip {
|
|
275
|
+
background: var(--gray);
|
|
276
|
+
opacity: 0.7;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ─── Summary Cards ───────────────────────────── */
|
|
280
|
+
.cards {
|
|
281
|
+
display: grid;
|
|
282
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
283
|
+
gap: var(--sp-3);
|
|
284
|
+
margin-bottom: var(--sp-6);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.card {
|
|
288
|
+
background: var(--bg-card);
|
|
289
|
+
border: 1px solid var(--border);
|
|
290
|
+
border-radius: var(--radius);
|
|
291
|
+
padding: var(--sp-4) var(--sp-5);
|
|
292
|
+
box-shadow: var(--shadow);
|
|
293
|
+
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.card:hover {
|
|
297
|
+
box-shadow: var(--shadow-md);
|
|
298
|
+
transform: translateY(-1px);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.card-num {
|
|
302
|
+
font-size: var(--text-2xl);
|
|
303
|
+
font-weight: var(--fw-bold);
|
|
304
|
+
font-family: var(--font-mono);
|
|
305
|
+
letter-spacing: var(--ls-tight);
|
|
306
|
+
line-height: 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.card-lbl {
|
|
310
|
+
font-size: var(--text-xs);
|
|
311
|
+
color: var(--text-muted);
|
|
312
|
+
margin-top: var(--sp-1);
|
|
313
|
+
letter-spacing: var(--ls-wide);
|
|
314
|
+
text-transform: uppercase;
|
|
315
|
+
font-weight: var(--fw-medium);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* ─── What's Next Banner ──────────────────────── */
|
|
319
|
+
.next-banner {
|
|
320
|
+
background: var(--next-bg);
|
|
321
|
+
border: 1px solid var(--next-border);
|
|
322
|
+
border-left: 4px solid var(--next-border);
|
|
323
|
+
border-radius: var(--radius);
|
|
324
|
+
padding: var(--sp-5) var(--sp-6);
|
|
325
|
+
margin-bottom: var(--sp-6);
|
|
326
|
+
box-shadow: 0 0 0 1px var(--next-glow), var(--shadow);
|
|
327
|
+
position: relative;
|
|
328
|
+
overflow: hidden;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.next-banner::before {
|
|
332
|
+
content: "";
|
|
333
|
+
position: absolute;
|
|
334
|
+
top: 0;
|
|
335
|
+
left: 0;
|
|
336
|
+
width: 4px;
|
|
337
|
+
height: 100%;
|
|
338
|
+
background: var(--next-border);
|
|
339
|
+
animation: pulse-border 2.5s ease-in-out infinite;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@keyframes pulse-border {
|
|
343
|
+
0%, 100% { opacity: 1; }
|
|
344
|
+
50% { opacity: 0.5; }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.next-banner h2 {
|
|
348
|
+
color: var(--accent);
|
|
349
|
+
margin-bottom: var(--sp-1);
|
|
350
|
+
font-size: var(--text-base);
|
|
351
|
+
font-weight: var(--fw-semi);
|
|
352
|
+
letter-spacing: var(--ls-wide);
|
|
353
|
+
text-transform: uppercase;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.next-banner p {
|
|
357
|
+
color: var(--text);
|
|
358
|
+
font-size: var(--text-base);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.next-cmd {
|
|
362
|
+
font-family: var(--font-mono);
|
|
363
|
+
background: var(--bg-card);
|
|
364
|
+
padding: var(--sp-1) var(--sp-3);
|
|
365
|
+
border-radius: var(--radius-sm);
|
|
366
|
+
font-size: var(--text-sm);
|
|
367
|
+
display: inline-flex;
|
|
368
|
+
align-items: center;
|
|
369
|
+
gap: var(--sp-2);
|
|
370
|
+
margin-top: var(--sp-3);
|
|
371
|
+
border: 1px solid var(--border);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* ─── Phase Headers (Collapsible) ─────────────── */
|
|
375
|
+
.phase {
|
|
376
|
+
margin-bottom: var(--sp-6);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.phase-hdr {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
gap: var(--sp-2);
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
padding: var(--sp-2) 0;
|
|
385
|
+
user-select: none;
|
|
386
|
+
border-bottom: 2px solid var(--border);
|
|
387
|
+
margin-bottom: var(--sp-3);
|
|
388
|
+
transition: border-color 0.15s ease;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.phase-hdr:hover {
|
|
392
|
+
border-bottom-color: var(--accent);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.phase-hdr:hover h2 {
|
|
396
|
+
color: var(--accent);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.phase-hdr h2 {
|
|
400
|
+
transition: color 0.15s ease;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.phase-hdr .arr {
|
|
404
|
+
transition: transform 0.2s ease;
|
|
405
|
+
font-size: var(--text-xs);
|
|
406
|
+
color: var(--text-muted);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.phase-hdr.closed .arr {
|
|
410
|
+
transform: rotate(-90deg);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.phase-cnt {
|
|
414
|
+
font-size: var(--text-xs);
|
|
415
|
+
font-family: var(--font-mono);
|
|
416
|
+
color: var(--text-faint);
|
|
417
|
+
margin-left: auto;
|
|
418
|
+
letter-spacing: var(--ls-wide);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* ─── Prompt List ─────────────────────────────── */
|
|
422
|
+
.plist {
|
|
423
|
+
display: flex;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
gap: var(--sp-2);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* ─── Prompt Cards ────────────────────────────── */
|
|
429
|
+
.pcard {
|
|
430
|
+
background: var(--bg-card);
|
|
431
|
+
border: 1px solid var(--border);
|
|
432
|
+
border-radius: var(--radius);
|
|
433
|
+
padding: var(--sp-3) var(--sp-4);
|
|
434
|
+
box-shadow: var(--shadow-sm);
|
|
435
|
+
display: grid;
|
|
436
|
+
grid-template-columns: auto 1fr auto;
|
|
437
|
+
gap: var(--sp-2) var(--sp-3);
|
|
438
|
+
align-items: start;
|
|
439
|
+
transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.pcard:hover {
|
|
443
|
+
box-shadow: var(--shadow);
|
|
444
|
+
transform: translateY(-1px);
|
|
445
|
+
border-color: var(--accent-glow);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ─── Status Badges ──────────────────────────── */
|
|
449
|
+
.status-badge {
|
|
450
|
+
display: inline-flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
gap: var(--sp-1);
|
|
453
|
+
font-size: var(--text-xs);
|
|
454
|
+
font-weight: var(--fw-medium);
|
|
455
|
+
padding: 2px var(--sp-2);
|
|
456
|
+
border-radius: 99px;
|
|
457
|
+
white-space: nowrap;
|
|
458
|
+
flex-shrink: 0;
|
|
459
|
+
letter-spacing: var(--ls-wide);
|
|
460
|
+
line-height: var(--lh-tight);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.st-completed {
|
|
464
|
+
background: var(--green-bg);
|
|
465
|
+
color: var(--green);
|
|
466
|
+
border: 1px solid var(--green-border);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.st-likely-completed {
|
|
470
|
+
background: var(--blue-bg);
|
|
471
|
+
color: var(--blue);
|
|
472
|
+
border: 1px solid var(--blue-border);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.st-skipped {
|
|
476
|
+
background: var(--gray-bg);
|
|
477
|
+
color: var(--gray);
|
|
478
|
+
border: 1px solid var(--gray-border);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.st-pending {
|
|
482
|
+
background: var(--bg-inset);
|
|
483
|
+
color: var(--text-faint);
|
|
484
|
+
border: 1px solid var(--border);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* ─── Status Legend ──────────────────────────── */
|
|
488
|
+
.status-legend {
|
|
489
|
+
display: flex;
|
|
490
|
+
flex-wrap: wrap;
|
|
491
|
+
gap: var(--sp-2);
|
|
492
|
+
margin-bottom: var(--sp-4);
|
|
493
|
+
padding: var(--sp-2) 0;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.status-legend .status-badge {
|
|
497
|
+
cursor: default;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* ─── Prompt Card Inner ───────────────────────── */
|
|
501
|
+
.pinfo {
|
|
502
|
+
min-width: 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.pname {
|
|
506
|
+
font-weight: var(--fw-semi);
|
|
507
|
+
font-size: var(--text-base);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.pstep {
|
|
511
|
+
font-size: var(--text-xs);
|
|
512
|
+
font-family: var(--font-mono);
|
|
513
|
+
color: var(--text-faint);
|
|
514
|
+
letter-spacing: var(--ls-wide);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.pdesc {
|
|
518
|
+
font-size: var(--text-sm);
|
|
519
|
+
color: var(--text-muted);
|
|
520
|
+
margin-top: 2px;
|
|
521
|
+
line-height: var(--lh-relaxed);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.pdesc-long {
|
|
525
|
+
font-size: var(--text-xs);
|
|
526
|
+
color: var(--text-faint);
|
|
527
|
+
margin-top: 2px;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.pdeps {
|
|
531
|
+
font-size: var(--text-xs);
|
|
532
|
+
color: var(--yellow);
|
|
533
|
+
margin-top: var(--sp-1);
|
|
534
|
+
font-weight: var(--fw-medium);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* ─── Copy Command Button ─────────────────────── */
|
|
538
|
+
.pcmd {
|
|
539
|
+
font-family: var(--font-mono);
|
|
540
|
+
font-size: var(--text-xs);
|
|
541
|
+
background: var(--bg-inset);
|
|
542
|
+
padding: 3px var(--sp-2);
|
|
543
|
+
border-radius: var(--radius-sm);
|
|
544
|
+
cursor: pointer;
|
|
545
|
+
border: 1px solid var(--border);
|
|
546
|
+
white-space: nowrap;
|
|
547
|
+
align-self: center;
|
|
548
|
+
color: var(--text-muted);
|
|
549
|
+
transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
|
|
550
|
+
letter-spacing: var(--ls-wide);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.pcmd:hover {
|
|
554
|
+
border-color: var(--accent);
|
|
555
|
+
color: var(--accent);
|
|
556
|
+
background: var(--accent-glow);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.pcmd.copied {
|
|
560
|
+
border-color: var(--green);
|
|
561
|
+
color: var(--green);
|
|
562
|
+
background: var(--green-bg);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* ─── Prompt Modal ────────────────────────────── */
|
|
566
|
+
.modal-overlay {
|
|
567
|
+
position: fixed;
|
|
568
|
+
inset: 0;
|
|
569
|
+
background: rgba(0, 0, 0, 0.6);
|
|
570
|
+
display: flex;
|
|
571
|
+
align-items: center;
|
|
572
|
+
justify-content: center;
|
|
573
|
+
z-index: 1000;
|
|
574
|
+
padding: var(--sp-4);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.modal {
|
|
578
|
+
background: var(--bg-card);
|
|
579
|
+
border: 1px solid var(--border);
|
|
580
|
+
border-radius: var(--radius);
|
|
581
|
+
box-shadow: var(--shadow-lg);
|
|
582
|
+
max-width: 720px;
|
|
583
|
+
width: 100%;
|
|
584
|
+
max-height: 85vh;
|
|
585
|
+
display: flex;
|
|
586
|
+
flex-direction: column;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.modal-header {
|
|
590
|
+
display: flex;
|
|
591
|
+
align-items: center;
|
|
592
|
+
gap: var(--sp-3);
|
|
593
|
+
padding: var(--sp-4) var(--sp-5);
|
|
594
|
+
border-bottom: 1px solid var(--border);
|
|
595
|
+
flex-shrink: 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.modal-header h3 {
|
|
599
|
+
font-size: var(--text-lg);
|
|
600
|
+
font-weight: var(--fw-semi);
|
|
601
|
+
flex: 1;
|
|
602
|
+
min-width: 0;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.modal-close {
|
|
606
|
+
background: var(--bg-inset);
|
|
607
|
+
border: 1px solid var(--border);
|
|
608
|
+
border-radius: var(--radius-sm);
|
|
609
|
+
padding: var(--sp-1) var(--sp-2);
|
|
610
|
+
cursor: pointer;
|
|
611
|
+
font-size: var(--text-base);
|
|
612
|
+
color: var(--text-muted);
|
|
613
|
+
line-height: 1;
|
|
614
|
+
transition: border-color 0.15s ease, color 0.15s ease;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.modal-close:hover {
|
|
618
|
+
border-color: var(--accent);
|
|
619
|
+
color: var(--accent);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.modal-body {
|
|
623
|
+
padding: var(--sp-5);
|
|
624
|
+
overflow-y: auto;
|
|
625
|
+
flex: 1;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.modal-body pre {
|
|
629
|
+
font-family: var(--font-mono);
|
|
630
|
+
font-size: var(--text-sm);
|
|
631
|
+
line-height: var(--lh-relaxed);
|
|
632
|
+
white-space: pre-wrap;
|
|
633
|
+
word-break: break-word;
|
|
634
|
+
color: var(--text);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.modal-body pre .md-heading {
|
|
638
|
+
font-weight: var(--fw-bold);
|
|
639
|
+
color: var(--accent);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.modal-body pre .md-code {
|
|
643
|
+
background: var(--bg-inset);
|
|
644
|
+
padding: 1px 4px;
|
|
645
|
+
border-radius: 3px;
|
|
646
|
+
font-size: var(--text-xs);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.modal-footer {
|
|
650
|
+
display: flex;
|
|
651
|
+
gap: var(--sp-2);
|
|
652
|
+
padding: var(--sp-3) var(--sp-5);
|
|
653
|
+
border-top: 1px solid var(--border);
|
|
654
|
+
flex-shrink: 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.modal-copy-btn {
|
|
658
|
+
background: var(--accent);
|
|
659
|
+
color: #fff;
|
|
660
|
+
border: none;
|
|
661
|
+
border-radius: var(--radius-sm);
|
|
662
|
+
padding: var(--sp-2) var(--sp-4);
|
|
663
|
+
font-size: var(--text-sm);
|
|
664
|
+
font-weight: var(--fw-medium);
|
|
665
|
+
cursor: pointer;
|
|
666
|
+
transition: background 0.15s ease;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.modal-copy-btn:hover {
|
|
670
|
+
background: var(--accent-hover);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.modal-copy-btn.copied {
|
|
674
|
+
background: var(--green);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* ─── Beads Task Section ─────────────────────── */
|
|
678
|
+
.beads-section {
|
|
679
|
+
margin-top: var(--sp-8);
|
|
680
|
+
margin-bottom: var(--sp-6);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.beads-filters {
|
|
684
|
+
display: flex;
|
|
685
|
+
gap: var(--sp-2);
|
|
686
|
+
margin-bottom: var(--sp-3);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.beads-filter {
|
|
690
|
+
background: var(--bg-inset);
|
|
691
|
+
border: 1px solid var(--border);
|
|
692
|
+
border-radius: 99px;
|
|
693
|
+
padding: var(--sp-1) var(--sp-3);
|
|
694
|
+
font-size: var(--text-xs);
|
|
695
|
+
font-weight: var(--fw-medium);
|
|
696
|
+
color: var(--text-muted);
|
|
697
|
+
cursor: pointer;
|
|
698
|
+
transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
|
|
699
|
+
letter-spacing: var(--ls-wide);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.beads-filter:hover {
|
|
703
|
+
border-color: var(--accent);
|
|
704
|
+
color: var(--accent);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.beads-filter.active {
|
|
708
|
+
background: var(--accent);
|
|
709
|
+
color: #fff;
|
|
710
|
+
border-color: var(--accent);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/* ─── Beads Status Badges ────────────────────── */
|
|
714
|
+
.st-bead-open {
|
|
715
|
+
background: var(--accent-glow);
|
|
716
|
+
color: var(--accent);
|
|
717
|
+
border: 1px solid var(--accent);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.st-bead-progress {
|
|
721
|
+
background: var(--blue-bg);
|
|
722
|
+
color: var(--blue);
|
|
723
|
+
border: 1px solid var(--blue-border);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.st-bead-blocked {
|
|
727
|
+
background: var(--yellow-bg);
|
|
728
|
+
color: var(--yellow);
|
|
729
|
+
border: 1px solid var(--yellow-border);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.st-bead-deferred {
|
|
733
|
+
background: var(--gray-bg);
|
|
734
|
+
color: var(--gray);
|
|
735
|
+
border: 1px solid var(--gray-border);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.st-bead-closed {
|
|
739
|
+
background: var(--green-bg);
|
|
740
|
+
color: var(--green);
|
|
741
|
+
border: 1px solid var(--green-border);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* ─── Beads Filter Separator ─────────────────── */
|
|
745
|
+
.beads-filter-sep {
|
|
746
|
+
width: 1px;
|
|
747
|
+
background: var(--border);
|
|
748
|
+
align-self: stretch;
|
|
749
|
+
margin: 0 var(--sp-1);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/* ─── Beads Priority Filter ──────────────────── */
|
|
753
|
+
.beads-prio-filter {
|
|
754
|
+
background: var(--bg-inset);
|
|
755
|
+
border: 1px solid var(--border);
|
|
756
|
+
border-radius: 99px;
|
|
757
|
+
padding: var(--sp-1) var(--sp-3);
|
|
758
|
+
font-size: var(--text-xs);
|
|
759
|
+
font-weight: var(--fw-medium);
|
|
760
|
+
color: var(--text-muted);
|
|
761
|
+
cursor: pointer;
|
|
762
|
+
transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
|
|
763
|
+
letter-spacing: var(--ls-wide);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.beads-prio-filter:hover {
|
|
767
|
+
border-color: var(--accent);
|
|
768
|
+
color: var(--accent);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.beads-prio-filter.active {
|
|
772
|
+
background: var(--accent);
|
|
773
|
+
color: #fff;
|
|
774
|
+
border-color: var(--accent);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* ─── Beads Task Detail Modal ────────────────── */
|
|
778
|
+
.bead-meta-grid {
|
|
779
|
+
display: grid;
|
|
780
|
+
grid-template-columns: 1fr 1fr;
|
|
781
|
+
gap: var(--sp-3);
|
|
782
|
+
padding: var(--sp-4) 0;
|
|
783
|
+
border-bottom: 1px solid var(--border-light);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.bead-meta-item {
|
|
787
|
+
display: flex;
|
|
788
|
+
flex-direction: column;
|
|
789
|
+
gap: 2px;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.bead-meta-label {
|
|
793
|
+
font-size: var(--text-xs);
|
|
794
|
+
color: var(--text-faint);
|
|
795
|
+
text-transform: uppercase;
|
|
796
|
+
letter-spacing: var(--ls-wide);
|
|
797
|
+
font-weight: var(--fw-medium);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.bead-meta-value {
|
|
801
|
+
font-size: var(--text-sm);
|
|
802
|
+
font-weight: var(--fw-medium);
|
|
803
|
+
color: var(--text);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.bead-description {
|
|
807
|
+
padding: var(--sp-4) 0;
|
|
808
|
+
border-bottom: 1px solid var(--border-light);
|
|
809
|
+
white-space: pre-wrap;
|
|
810
|
+
font-size: var(--text-sm);
|
|
811
|
+
line-height: var(--lh-relaxed);
|
|
812
|
+
color: var(--text-muted);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.bead-deps {
|
|
816
|
+
padding: var(--sp-4) 0;
|
|
817
|
+
border-bottom: 1px solid var(--border-light);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.bead-dep-group {
|
|
821
|
+
display: flex;
|
|
822
|
+
flex-wrap: wrap;
|
|
823
|
+
align-items: center;
|
|
824
|
+
gap: var(--sp-2);
|
|
825
|
+
margin-bottom: var(--sp-2);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.bead-dep-group:last-child {
|
|
829
|
+
margin-bottom: 0;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.bead-dep-label {
|
|
833
|
+
font-size: var(--text-xs);
|
|
834
|
+
color: var(--text-faint);
|
|
835
|
+
text-transform: uppercase;
|
|
836
|
+
letter-spacing: var(--ls-wide);
|
|
837
|
+
font-weight: var(--fw-medium);
|
|
838
|
+
min-width: 80px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.bead-dep-link {
|
|
842
|
+
display: inline-block;
|
|
843
|
+
font-family: var(--font-mono);
|
|
844
|
+
font-size: var(--text-xs);
|
|
845
|
+
padding: 2px var(--sp-2);
|
|
846
|
+
border-radius: 99px;
|
|
847
|
+
background: var(--accent-glow);
|
|
848
|
+
color: var(--accent);
|
|
849
|
+
border: 1px solid var(--accent);
|
|
850
|
+
cursor: pointer;
|
|
851
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
852
|
+
text-decoration: none;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.bead-dep-link:hover {
|
|
856
|
+
background: var(--accent);
|
|
857
|
+
color: #fff;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.bead-timestamps {
|
|
861
|
+
display: flex;
|
|
862
|
+
flex-wrap: wrap;
|
|
863
|
+
gap: var(--sp-4);
|
|
864
|
+
padding: var(--sp-4) 0;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.bead-ts-item {
|
|
868
|
+
display: flex;
|
|
869
|
+
flex-direction: column;
|
|
870
|
+
gap: 2px;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.bead-ts-label {
|
|
874
|
+
font-size: var(--text-xs);
|
|
875
|
+
color: var(--text-faint);
|
|
876
|
+
text-transform: uppercase;
|
|
877
|
+
letter-spacing: var(--ls-wide);
|
|
878
|
+
font-weight: var(--fw-medium);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.bead-ts-value {
|
|
882
|
+
font-size: var(--text-sm);
|
|
883
|
+
color: var(--text-muted);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.bead-ts-value[title] {
|
|
887
|
+
border-bottom: 1px dotted var(--text-faint);
|
|
888
|
+
cursor: help;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/* ─── Standalone Commands Section ─────────────── */
|
|
892
|
+
.ongoing {
|
|
893
|
+
margin-top: var(--sp-10);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.ongoing h2 {
|
|
897
|
+
letter-spacing: var(--ls-wide);
|
|
898
|
+
text-transform: uppercase;
|
|
899
|
+
font-size: var(--text-sm);
|
|
900
|
+
color: var(--text-muted);
|
|
901
|
+
margin-bottom: var(--sp-4);
|
|
902
|
+
border-bottom: 2px solid var(--border);
|
|
903
|
+
padding-bottom: var(--sp-2);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/* ─── Footer ──────────────────────────────────── */
|
|
907
|
+
.footer {
|
|
908
|
+
text-align: center;
|
|
909
|
+
font-size: var(--text-xs);
|
|
910
|
+
color: var(--text-faint);
|
|
911
|
+
margin-top: var(--sp-10);
|
|
912
|
+
padding-top: var(--sp-4);
|
|
913
|
+
border-top: 1px solid var(--border-light);
|
|
914
|
+
letter-spacing: var(--ls-wide);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/* ─── Utilities ───────────────────────────────── */
|
|
918
|
+
.hidden {
|
|
919
|
+
display: none;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/* Build-observability severity + verdict tokens (Plan 4) */
|
|
923
|
+
:root {
|
|
924
|
+
--sev-p0: #dc2626; /* red 600 */
|
|
925
|
+
--sev-p1: #ea580c; /* orange 600 */
|
|
926
|
+
--sev-p2: #ca8a04; /* yellow 600 */
|
|
927
|
+
--sev-p3: #2563eb; /* blue 600 */
|
|
928
|
+
--sev-pass: #16a34a; /* green 600 */
|
|
929
|
+
}
|
|
930
|
+
[data-theme="dark"] {
|
|
931
|
+
--sev-p0: #f87171;
|
|
932
|
+
--sev-p1: #fb923c;
|
|
933
|
+
--sev-p2: #facc15;
|
|
934
|
+
--sev-p3: #60a5fa;
|
|
935
|
+
--sev-pass: #4ade80;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/* Build-observability panel layout */
|
|
939
|
+
.panel {
|
|
940
|
+
background: var(--bg-card);
|
|
941
|
+
border: 1px solid var(--border);
|
|
942
|
+
border-radius: var(--radius);
|
|
943
|
+
padding: var(--sp-4) var(--sp-6);
|
|
944
|
+
margin-bottom: var(--sp-6);
|
|
945
|
+
}
|
|
946
|
+
.panel > header {
|
|
947
|
+
display: flex;
|
|
948
|
+
align-items: center;
|
|
949
|
+
gap: var(--sp-3);
|
|
950
|
+
margin-bottom: var(--sp-4);
|
|
951
|
+
flex-wrap: wrap;
|
|
952
|
+
}
|
|
953
|
+
.panel > header h2 {
|
|
954
|
+
margin: 0;
|
|
955
|
+
font-size: var(--text-base);
|
|
956
|
+
font-weight: var(--fw-semi);
|
|
957
|
+
}
|
|
958
|
+
.panel .meta {
|
|
959
|
+
color: var(--text-muted);
|
|
960
|
+
font-size: var(--text-sm);
|
|
961
|
+
}
|
|
962
|
+
.grid { display: grid; gap: var(--sp-4); }
|
|
963
|
+
.grid-2 { grid-template-columns: repeat(2, 1fr); }
|
|
964
|
+
@media (max-width: 640px) { .grid-2 { grid-template-columns: 1fr; } }
|
|
965
|
+
|
|
966
|
+
/* Finding filters */
|
|
967
|
+
.finding-filters {
|
|
968
|
+
display: flex;
|
|
969
|
+
gap: var(--sp-2);
|
|
970
|
+
flex-wrap: wrap;
|
|
971
|
+
margin-bottom: var(--sp-4);
|
|
972
|
+
}
|
|
973
|
+
.finding-filters button {
|
|
974
|
+
padding: var(--sp-1) var(--sp-3);
|
|
975
|
+
border: 1px solid var(--border);
|
|
976
|
+
border-radius: var(--radius-sm);
|
|
977
|
+
background: var(--bg-inset);
|
|
978
|
+
color: var(--text);
|
|
979
|
+
font-size: var(--text-sm);
|
|
980
|
+
cursor: pointer;
|
|
981
|
+
}
|
|
982
|
+
.finding-filters button:hover,
|
|
983
|
+
.finding-filters button.active {
|
|
984
|
+
background: var(--accent);
|
|
985
|
+
border-color: var(--accent);
|
|
986
|
+
color: #fff;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/* Findings list */
|
|
990
|
+
.findings {
|
|
991
|
+
list-style: none;
|
|
992
|
+
padding: 0;
|
|
993
|
+
margin: 0;
|
|
994
|
+
display: flex;
|
|
995
|
+
flex-direction: column;
|
|
996
|
+
gap: var(--sp-3);
|
|
997
|
+
}
|
|
998
|
+
.finding {
|
|
999
|
+
background: var(--bg-inset);
|
|
1000
|
+
border: 1px solid var(--border-light);
|
|
1001
|
+
border-radius: var(--radius-sm);
|
|
1002
|
+
padding: var(--sp-3) var(--sp-4);
|
|
1003
|
+
}
|
|
1004
|
+
.finding header {
|
|
1005
|
+
display: flex;
|
|
1006
|
+
align-items: center;
|
|
1007
|
+
gap: var(--sp-2);
|
|
1008
|
+
flex-wrap: wrap;
|
|
1009
|
+
margin-bottom: var(--sp-2);
|
|
1010
|
+
}
|
|
1011
|
+
.finding-id {
|
|
1012
|
+
font-family: var(--font-mono, monospace);
|
|
1013
|
+
font-size: var(--text-xs);
|
|
1014
|
+
color: var(--text-muted);
|
|
1015
|
+
background: var(--bg-card);
|
|
1016
|
+
border: 1px solid var(--border);
|
|
1017
|
+
border-radius: 4px;
|
|
1018
|
+
padding: 1px var(--sp-1);
|
|
1019
|
+
}
|
|
1020
|
+
.finding .lens {
|
|
1021
|
+
font-size: var(--text-xs);
|
|
1022
|
+
color: var(--text-muted);
|
|
1023
|
+
}
|
|
1024
|
+
.finding .title {
|
|
1025
|
+
font-size: var(--text-sm);
|
|
1026
|
+
font-weight: var(--fw-semi);
|
|
1027
|
+
flex: 1;
|
|
1028
|
+
}
|
|
1029
|
+
.finding p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
|
|
1030
|
+
.empty { color: var(--text-muted); font-size: var(--text-sm); text-align: center; padding: var(--sp-4); }
|
|
1031
|
+
|
|
1032
|
+
/* ── Mermaid diagrams ─────────────────────────────────────────────────────────
|
|
1033
|
+
The build renders mermaid to inline SVG via mmdc, then sanitizeSvg() +
|
|
1034
|
+
rehype-sanitize strip the SVG's own <script>, <foreignObject>, AND <style>
|
|
1035
|
+
for security. Stripping <style> means the diagram arrives unstyled (nodes
|
|
1036
|
+
default to a black fill). These theme-token rules restyle the SVG so nodes,
|
|
1037
|
+
edges, arrowheads, and labels render correctly — and follow light/dark mode.
|
|
1038
|
+
Authors must render with htmlLabels:false (the generator forces this) so node
|
|
1039
|
+
labels are native <text>/<tspan> rather than stripped <foreignObject> HTML. */
|
|
1040
|
+
figure.mermaid { margin: var(--sp-5) 0; text-align: center; }
|
|
1041
|
+
figure.mermaid svg { max-width: 100%; height: auto; }
|
|
1042
|
+
/* Node shapes */
|
|
1043
|
+
figure.mermaid svg .node rect,
|
|
1044
|
+
figure.mermaid svg .node circle,
|
|
1045
|
+
figure.mermaid svg .node ellipse,
|
|
1046
|
+
figure.mermaid svg .node polygon,
|
|
1047
|
+
figure.mermaid svg .node path {
|
|
1048
|
+
fill: var(--bg-inset);
|
|
1049
|
+
stroke: var(--border);
|
|
1050
|
+
stroke-width: 1px;
|
|
1051
|
+
}
|
|
1052
|
+
/* Background helper rects mermaid emits behind labels */
|
|
1053
|
+
figure.mermaid svg .node .label-container { fill: var(--bg-inset); stroke: var(--border); }
|
|
1054
|
+
figure.mermaid svg rect.background { fill: none; stroke: none; }
|
|
1055
|
+
/* Labels (rendered as <text>/<tspan> when htmlLabels:false) */
|
|
1056
|
+
figure.mermaid svg .nodeLabel,
|
|
1057
|
+
figure.mermaid svg .node text,
|
|
1058
|
+
figure.mermaid svg text.nodeLabel,
|
|
1059
|
+
figure.mermaid svg .label text,
|
|
1060
|
+
figure.mermaid svg span.nodeLabel {
|
|
1061
|
+
fill: var(--text);
|
|
1062
|
+
color: var(--text);
|
|
1063
|
+
font-family: var(--font-sans);
|
|
1064
|
+
}
|
|
1065
|
+
/* Edges: thin strokes, not filled blobs */
|
|
1066
|
+
figure.mermaid svg .edgePath path,
|
|
1067
|
+
figure.mermaid svg path.flowchart-link,
|
|
1068
|
+
figure.mermaid svg .flowchart-link {
|
|
1069
|
+
fill: none;
|
|
1070
|
+
stroke: var(--text-faint);
|
|
1071
|
+
stroke-width: 1.5px;
|
|
1072
|
+
}
|
|
1073
|
+
/* Arrowheads */
|
|
1074
|
+
figure.mermaid svg marker path,
|
|
1075
|
+
figure.mermaid svg .marker {
|
|
1076
|
+
fill: var(--text-faint);
|
|
1077
|
+
stroke: var(--text-faint);
|
|
1078
|
+
}
|
|
1079
|
+
figure.mermaid svg .edgeLabel,
|
|
1080
|
+
figure.mermaid svg .edgeLabel text { fill: var(--text-muted); color: var(--text-muted); }
|
|
1081
|
+
</style>
|
|
1082
|
+
<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
|
+
</head>
|
|
1084
|
+
<body>
|
|
1085
|
+
<header class="topbar">
|
|
1086
|
+
<button data-action="nav" class="nav-toggle" aria-label="Toggle navigation">☰</button>
|
|
1087
|
+
<h1>Knowledge Freshness</h1>
|
|
1088
|
+
<button data-action="theme" class="theme-toggle" aria-label="Toggle theme">◐</button>
|
|
1089
|
+
</header>
|
|
1090
|
+
<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>
|
|
1092
|
+
<main class="content"><h2 id="what-this-system-does">What this system does</h2>
|
|
1093
|
+
<p>Knowledge entries under <code>content/knowledge/</code> declare a <code>volatility</code> tier and a
|
|
1094
|
+
list of <code>sources</code>. A daily cron prefilters at most ten entries that are <em>due</em> —
|
|
1095
|
+
by cadence or by a changed source hash — runs a grounded LLM audit against the
|
|
1096
|
+
prefetched source bodies, opens one PR per drifted entry, and gates that PR on
|
|
1097
|
+
five checks. In parallel, downstream agents emit <code>knowledge_gap_signal</code> events
|
|
1098
|
+
when they hit a topic the KB does not cover; <strong>Lens I</strong> aggregates those signals
|
|
1099
|
+
into P1/P2 audit findings, suppressing any topic an entry already covers.</p>
|
|
1100
|
+
<p>Two arms, two outcomes:</p>
|
|
1101
|
+
<ul>
|
|
1102
|
+
<li>The <strong>refresh arm</strong> chases <em>known</em> sources for drift. It ends in a PR that
|
|
1103
|
+
<em>updates</em> an entry.</li>
|
|
1104
|
+
<li>The <strong>gap arm</strong> surfaces <em>unknown</em> topics. It ends in a PR that <em>creates</em> an
|
|
1105
|
+
entry.</li>
|
|
1106
|
+
</ul>
|
|
1107
|
+
<p>Both terminate in a human-merged PR.</p>
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
<table><thead><tr><th>Surface</th><th>Value</th><th>Notes</th></tr></thead><tbody><tr><td>Volatility tiers</td><td>3</td><td><code>fast-moving</code> / <code>evolving</code> / <code>stable</code></td></tr><tr><td>Audit verdicts</td><td>4</td><td><code>current</code> / <code>minor-drift</code> / <code>major-drift</code> / <code>superseded</code></td></tr><tr><td>Daily audit ceiling</td><td>10</td><td>set by <code>--max=10</code> in the cron workflow; not a yaml knob</td></tr><tr><td>PR gates</td><td>5</td><td>4 blocking + 1 advisory</td></tr><tr><td>Signal window</td><td>90 days</td><td>rolling; drives Lens I aggregation</td></tr></tbody></table>
|
|
1144
|
+
<div class="callout callout-note"><p><strong>Two subsystems, one config file.</strong> Knowledge Freshness and the separate
|
|
1145
|
+
<a href="../observability/index.md">Build Observability</a> system both read
|
|
1146
|
+
<code>.scaffold/observability.yaml</code>. This guide documents Knowledge Freshness;
|
|
1147
|
+
Lens I is the seam where the two meet (it lives in the observability audit but
|
|
1148
|
+
reasons about the KB).</p></div>
|
|
1149
|
+
<h3 id="how-a-gap-closes">How a gap closes</h3>
|
|
1150
|
+
<p>The full lifecycle, end to end:</p>
|
|
1151
|
+
<ol>
|
|
1152
|
+
<li>Downstream agents emit signals; they accumulate in the rolling 90-day window.</li>
|
|
1153
|
+
<li>A topic's signal count and distinct-project count cross the threshold.</li>
|
|
1154
|
+
<li>Lens I emits a P1/P2 finding.</li>
|
|
1155
|
+
<li>An operator adds <code>content/knowledge/<category>/<slug>.md</code>.</li>
|
|
1156
|
+
<li>The next audit's knowledge index covers the slug and Lens I <strong>suppresses</strong> the
|
|
1157
|
+
bucket — the finding disappears.</li>
|
|
1158
|
+
</ol>
|
|
1159
|
+
<p>Signals are <em>not</em> purged when the entry is added. The window is rolling, so
|
|
1160
|
+
yesterday's signals still aggregate tomorrow; suppression filters the <em>emit</em>
|
|
1161
|
+
step, not the aggregation step (<span class="fp" data-path="src/observability/checks/lens-i-knowledge-gaps.ts:155">src/observability/checks/lens-i-knowledge-gaps.ts:155</span>).
|
|
1162
|
+
Signals only fade as they age out of the 90-day window naturally.</p>
|
|
1163
|
+
<h2 id="system-map">System map</h2>
|
|
1164
|
+
<figure class="mermaid"><svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" class="flowchart" style="max-width: 951.219px; background-color: transparent;" viewBox="0 0 951.21875 940.3999633789062" role="graphics-document document">#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:#552222;}#my-svg .error-text{fill:#552222;stroke:#552222;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#666;stroke:#666;}#my-svg .marker.cross{stroke:#666;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#000000;}#my-svg .cluster-label text{fill:#333;}#my-svg .cluster-label span{color:#333;}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:#000000;color:#000000;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#eee;stroke:#999;stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:#666!important;stroke-width:0;stroke:#666;}#my-svg .arrowheadPath{fill:#333333;}#my-svg .edgePath .path{stroke:#666;stroke-width:1px;}#my-svg .flowchart-link{stroke:#666;fill:none;}#my-svg .edgeLabel{background-color:white;text-align:center;}#my-svg .edgeLabel p{background-color:white;}#my-svg .edgeLabel rect{opacity:0.5;background-color:white;fill:white;}#my-svg .labelBkg{background-color:rgba(255, 255, 255, 0.5);}#my-svg .cluster rect{fill:hsl(0, 0%, 98.9215686275%);stroke:#707070;stroke-width:1px;}#my-svg .cluster text{fill:#333;}#my-svg .cluster span{color:#333;}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(-160, 0%, 93.3333333333%);border:1px solid #707070;border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#000000;}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:white;text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:white;padding:2px;}#my-svg .icon-shape .label rect,#my-svg .image-shape .label rect{opacity:0.5;background-color:white;fill:white;}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg .node .neo-node{stroke:#999;}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:1px;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node .neo-line path{stroke:#999;filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node circle .state-start{fill:#000000;}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}<g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="my-svg_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"></path></marker><marker id="my-svg_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"></polygon></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="my-svg_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="my-svg_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="my-svg_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5;"></path></marker><marker id="my-svg_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"><g class="cluster" id="my-svg-gap"><rect style="" x="390.40625" y="8" width="552.8125" height="625.5999908447266"></rect><g class="cluster-label" transform="translate(636.375, 8)"><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">Gap</tspan><tspan class="text-inner-tspan"> arm</tspan></tspan></text></g></g></g><g class="cluster" id="my-svg-refresh"><rect style="" x="8" y="149.5999984741211" width="362.40625" height="782.7999877929688"></rect><g class="cluster-label" transform="translate(146.1015625, 149.5999984741211)"><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">Refresh</tspan><tspan class="text-inner-tspan"> arm</tspan></tspan></text></g></g></g></g><g class="edgePaths"><path d="M132.684,241.2L132.684,245.367C132.684,249.533,132.684,257.867,132.684,267C132.684,276.133,132.684,286.067,132.684,291.033L132.684,296" id="my-svg-L_CRON_PF_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M132.684,366.6L132.684,372.233C132.684,377.867,132.684,389.133,132.684,398.267C132.684,407.4,132.684,414.4,132.684,417.9L132.684,421.4" id="my-svg-L_PF_RUN_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M132.684,492L132.684,496.167C132.684,500.333,132.684,508.667,132.684,516.333C132.684,524,132.684,531,132.684,534.5L132.684,538" id="my-svg-L_RUN_APPLY_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M132.684,608.6L132.684,612.767C132.684,616.933,132.684,625.267,132.684,638.45C132.684,651.633,132.684,669.667,138.932,687.163C145.18,704.66,157.676,721.62,163.925,730.1L170.173,738.58" id="my-svg-L_APPLY_GATES_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M190.598,790.8L190.598,794.967C190.598,799.133,190.598,807.467,190.598,815.133C190.598,822.8,190.598,829.8,190.598,833.3L190.598,836.8" id="my-svg-L_GATES_MERGE_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M536.594,99.6L536.594,103.767C536.594,107.933,536.594,116.267,536.594,124.6C536.594,132.933,536.594,141.267,536.594,148.933C536.594,156.6,536.594,163.6,536.594,167.1L536.594,170.6" id="my-svg-L_TAIL_EVENT_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M536.594,241.2L536.594,245.367C536.594,249.533,536.594,257.867,536.594,267C536.594,276.133,536.594,286.067,536.594,291.033L536.594,296" id="my-svg-L_EVENT_LEDGER_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M536.594,366.6L536.594,372.233C536.594,377.867,536.594,389.133,544.883,398.65C553.172,408.168,569.751,415.935,578.04,419.819L586.329,423.703" id="my-svg-L_LEDGER_LENSI_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M661.023,492L661.023,496.167C661.023,500.333,661.023,508.667,661.023,516.333C661.023,524,661.023,531,661.023,534.5L661.023,538" id="my-svg-L_LENSI_FINDING_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M785.453,375.4L785.453,379.567C785.453,383.733,785.453,392.067,777.164,400.117C768.875,408.168,752.296,415.935,744.007,419.819L735.718,423.703" id="my-svg-L_RESOLVER_LENSI_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path><path d="M661.023,608.6L661.023,612.767C661.023,616.933,661.023,625.267,592.271,638.45C523.52,651.633,386.016,669.667,311.015,687.163C236.015,704.66,223.519,721.62,217.271,730.1L211.023,738.58" id="my-svg-L_FINDING_GATES_0" class="edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" style=";" marker-end="url(#my-svg_flowchart-v2-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><text y="-10.1" text-anchor="middle"><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"></tspan></text></g></g><g><rect class="background" style="stroke: none"></rect></g><g class="edgeLabel" transform="translate(433.84693, 663.39369)"><g class="label" transform="translate(0, -28.099998474121094)"><g><rect class="background" style="" x="-95.828125" y="-1" width="191.65625" height="58.19999694824219"></rect><text y="-10.1" text-anchor="middle" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em" text-anchor="middle"><tspan class="text-inner-tspan">operator</tspan><tspan class="text-inner-tspan"> adds</tspan><tspan class="text-inner-tspan"> an</tspan><tspan class="text-inner-tspan"> entry</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em" text-anchor="middle"><tspan class="text-inner-tspan">whose</tspan><tspan class="text-inner-tspan"> name:</tspan><tspan class="text-inner-tspan"> matches</tspan><tspan class="text-inner-tspan"> the</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="2.1em" text-anchor="middle"><tspan class="text-inner-tspan">bucket</tspan><tspan class="text-inner-tspan"> →</tspan><tspan class="text-inner-tspan"> PR</tspan></tspan></text></g></g></g></g><g class="nodes"><g class="node default" id="my-svg-flowchart-CRON-0" transform="translate(132.68359375, 207.89999771118164)"><rect class="basic label-container" style="" x="-86.40625" y="-33.29999923706055" width="172.8125" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">cron</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">09:00</tspan><tspan class="text-inner-tspan"> UTC</tspan><tspan class="text-inner-tspan"> daily</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-PF-1" transform="translate(132.68359375, 333.2999954223633)"><rect class="basic label-container" style="" x="-81.59375" y="-33.29999923706055" width="163.1875" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">audit-prefilter</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">--max=10</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-RUN-3" transform="translate(132.68359375, 458.6999931335449)"><rect class="basic label-container" style="" x="-85.171875" y="-33.29999923706055" width="170.34375" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">audit-run-entry</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">grounded</tspan><tspan class="text-inner-tspan"> LLM</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-APPLY-5" transform="translate(132.68359375, 575.299991607666)"><rect class="basic label-container" style="" x="-70.84375" y="-33.29999923706055" width="141.6875" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">audit-apply</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">--open-pr</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-GATES-7" transform="translate(190.59765625, 766.2999877929688)"><rect class="basic label-container" style="" x="-67.1328125" y="-24.5" width="134.265625" height="49"></rect><g class="label" style="" transform="translate(0, -9.5)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">5</tspan><tspan class="text-inner-tspan"> PR</tspan><tspan class="text-inner-tspan"> gates</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-MERGE-9" transform="translate(190.59765625, 874.0999870300293)"><rect class="basic label-container" style="" x="-92.9609375" y="-33.29999923706055" width="185.921875" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">human</tspan><tspan class="text-inner-tspan"> merge</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">→</tspan><tspan class="text-inner-tspan"> VERSION</tspan><tspan class="text-inner-tspan"> bump</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-TAIL-10" transform="translate(536.59375, 66.29999923706055)"><rect class="basic label-container" style="" x="-90.6171875" y="-33.29999923706055" width="181.234375" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">gap-signal-tail</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">89</tspan><tspan class="text-inner-tspan"> pipeline</tspan><tspan class="text-inner-tspan"> steps</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-EVENT-11" transform="translate(536.59375, 207.89999771118164)"><rect class="basic label-container" style="" x="-111.1875" y="-33.29999923706055" width="222.375" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">scaffold</tspan><tspan class="text-inner-tspan"> observe</tspan><tspan class="text-inner-tspan"> event</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">knowledge_gap_signal</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-LEDGER-13" transform="translate(536.59375, 333.2999954223633)"><rect class="basic label-container" style="" x="-76.09375" y="-33.29999923706055" width="152.1875" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">ledger</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">activity.jsonl</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-LENSI-15" transform="translate(661.0234375, 458.6999931335449)"><rect class="basic label-container" style="" x="-83.65625" y="-33.29999923706055" width="167.3125" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">Lens</tspan><tspan class="text-inner-tspan"> I</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">90-day</tspan><tspan class="text-inner-tspan"> window</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-FINDING-17" transform="translate(661.0234375, 575.299991607666)"><rect class="basic label-container" style="" x="-56.3359375" y="-33.29999923706055" width="112.671875" height="66.5999984741211"></rect><g class="label" style="" transform="translate(0, -18.299999237060547)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">finding</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">P1</tspan><tspan class="text-inner-tspan"> /</tspan><tspan class="text-inner-tspan"> P2</tspan></tspan></text></g></g></g><g class="node default" id="my-svg-flowchart-RESOLVER-18" transform="translate(785.453125, 333.2999954223633)"><rect class="basic label-container" style="" x="-122.765625" y="-42.099998474121094" width="245.53125" height="84.19999694824219"></rect><g class="label" style="" transform="translate(0, -27.099998474121094)"><rect></rect><g><rect class="background" style="stroke: none"></rect><text y="-10.1" style=""><tspan class="text-outer-tspan row" x="0" y="-0.1em"><tspan class="text-inner-tspan">3-tier</tspan><tspan class="text-inner-tspan"> --knowledge-root</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="1em"><tspan class="text-inner-tspan">resolver</tspan></tspan><tspan class="text-outer-tspan row" x="0" y="2.1em"><tspan class="text-inner-tspan">suppresses</tspan><tspan class="text-inner-tspan"> covered</tspan><tspan class="text-inner-tspan"> topics</tspan></tspan></text></g></g></g></g></g></g><defs></defs><defs></defs></svg></figure>
|
|
1165
|
+
<p>Three real hooks sit beside the two arms: the <strong>phase-audit hook</strong> (runs Lens H
|
|
1166
|
+
only, never Lens I), the <strong>doc-conformance MMR channel</strong> (routes Lens I findings
|
|
1167
|
+
into MMR), and the <strong><code>--fix</code> flow</strong> (initial + verifier + postfix audit). They
|
|
1168
|
+
are covered below.</p>
|
|
1169
|
+
<div class="callout callout-warning"><p><strong>Doc drift on MMR-in-cron.</strong> Three docs frame MMR-in-cron differently. The
|
|
1170
|
+
parent spec's locked decision #3 is authoritative: a native
|
|
1171
|
+
<code>knowledge-freshness</code> MMR channel is deferred to Phase 5. The cron today runs
|
|
1172
|
+
only inline gates. Two interim paths give reviewers MMR signal on a freshness
|
|
1173
|
+
PR: (1) the built-in <code>doc-conformance</code> MMR channel (disabled by default; enable
|
|
1174
|
+
with <code>mmr review --channels=doc-conformance</code>); (2) the manual <code>mmr review --diff -</code> command in <a href="#from-candidate-to-merged-pr">From candidate to merged PR</a>.</p></div>
|
|
1175
|
+
<h2 id="frontmatter-signals-and-resolution">Frontmatter, signals, and resolution</h2>
|
|
1176
|
+
<h3 id="frontmatter-schema">Frontmatter schema</h3>
|
|
1177
|
+
<p>Every knowledge entry's frontmatter is a Zod-validated object with four
|
|
1178
|
+
freshness-relevant fields. The schema is the source of truth and runs as Gate 1
|
|
1179
|
+
of the PR CI (<span class="fp" data-path="src/validation/knowledge-frontmatter-validator.ts:42-50">src/validation/knowledge-frontmatter-validator.ts:42-50</span>);
|
|
1180
|
+
runtime readers tolerate missing optional fields.</p>
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
<table><thead><tr><th>Field</th><th>Type</th><th>Default</th><th>Validation</th><th>Read by</th></tr></thead><tbody><tr><td><code>name</code></td><td>string</td><td>required</td><td>regex <code>/^[a-z][a-z0-9-]*$/</code></td><td>assembly-loader, Lens I suppression</td></tr><tr><td><code>description</code></td><td>string</td><td>required</td><td>warns if > 200 chars</td><td>assembly-loader (TOC), audit prompt</td></tr><tr><td><code>topics</code></td><td>string[]</td><td><code>[]</code></td><td>any string</td><td>assembly-loader (auto-selection)</td></tr><tr><td><code>volatility</code></td><td>enum</td><td><code>evolving</code></td><td><code>stable|evolving|fast-moving</code></td><td>prefilter cadence</td></tr><tr><td><code>last-reviewed</code></td><td>ISO date</td><td><code>null</code></td><td><code>YYYY-MM-DD</code> & real calendar date</td><td>prefilter cadence</td></tr><tr><td><code>version-pin</code></td><td>string</td><td><code>null</code></td><td>any string (e.g. <code>"OWASP Top 10 2021"</code>)</td><td>audit prompt; <code>superseded</code> verdict signals it must advance manually</td></tr><tr><td><code>sources[]</code></td><td>object[]</td><td><code>[]</code></td><td>each: <code>url</code> (SSRF-checked at fetch), <code>anchor</code> (optional, starts with <code>#</code>), <code>retrieved</code> (ISO date), <code>hash</code> (sha256)</td><td>prefilter (hash + cadence), audit runner (prefetch)</td></tr></tbody></table>
|
|
1243
|
+
<div class="callout callout-warning"><p><strong><code>name</code> vs. gap-topic regex.</strong> An entry <code>name</code> must start with a letter
|
|
1244
|
+
(<code>/^[a-z][a-z0-9-]*$/</code>), but Lens I gap <em>topics</em> allow a leading digit
|
|
1245
|
+
(<code>/^[a-z0-9]+(-[a-z0-9]+)*$/</code>). So a gap signalled for a topic like <code>3d-rendering</code>
|
|
1246
|
+
cannot be suppressed by an entry of the same name — pick a letter-leading <code>name</code>
|
|
1247
|
+
(and list the numeric form under <code>topics</code>) when closing such a gap.</p></div>
|
|
1248
|
+
<div class="callout callout-note"><p><strong>Anchor semantics.</strong> Put fragments in <code>anchor</code>, never inside <code>url</code>. The audit
|
|
1249
|
+
fetches <code>url + (anchor ?? '')</code> and hashes that body; the coverage check
|
|
1250
|
+
(<span class="fp" data-path="src/knowledge-freshness/audit-apply.ts:82-101">src/knowledge-freshness/audit-apply.ts:82-101</span>) matches the same combined
|
|
1251
|
+
string. Splitting prevents hash drift from spurious URL re-encodings and lets two
|
|
1252
|
+
sources at the same base URL with different <code>#anchor</code>s be tracked independently.</p></div>
|
|
1253
|
+
<h3 id="cadence-model">Cadence model</h3>
|
|
1254
|
+
<p>Three tiers, three windows — <strong>14 / 60 / 180</strong> days for <code>fast-moving</code> /
|
|
1255
|
+
<code>evolving</code> / <code>stable</code> (<span class="fp" data-path="src/knowledge-freshness/audit-prefilter.ts:5-7">src/knowledge-freshness/audit-prefilter.ts:5-7</span>).
|
|
1256
|
+
An entry with no <code>last-reviewed</code> always counts as due. Sources with a changed
|
|
1257
|
+
hash also become candidates regardless of age, but the hash check only runs for
|
|
1258
|
+
entries still <em>inside</em> their cadence window.</p>
|
|
1259
|
+
<h4>Which tier does an entry belong in?</h4>
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
<table><thead><tr><th>Provenance</th><th>Change frequency</th><th>Recommended tier</th></tr></thead><tbody><tr><td>vendor SDK / API docs</td><td>quarterly or faster</td><td><code>fast-moving</code></td></tr><tr><td>standards / RFCs, vendor docs</td><td>yearly-ish</td><td><code>evolving</code></td></tr><tr><td>canonical pattern reference</td><td>multi-year</td><td><code>stable</code></td></tr></tbody></table>
|
|
1286
|
+
<p>Rule of thumb: if a version bump <em>often breaks</em> downstream guidance, lean
|
|
1287
|
+
<code>fast-moving</code>; if drift is <em>extremely rare</em>, <code>stable</code>; otherwise <code>evolving</code>
|
|
1288
|
+
(the default).</p>
|
|
1289
|
+
<h3 id="adding-a-new-entry-to-the-kb">Adding a new entry to the KB</h3>
|
|
1290
|
+
<ol>
|
|
1291
|
+
<li><strong>Choose a category directory</strong> under <code>content/knowledge/<category>/</code>. Many
|
|
1292
|
+
categories exist today (<code>backend</code>, <code>core</code>, <code>cli</code>, <code>research</code>, <code>web-app</code>,
|
|
1293
|
+
<code>web3</code>, …); prefer placing into an existing one. Creating a new category is a
|
|
1294
|
+
separate PR.</li>
|
|
1295
|
+
<li><strong>File name = entry slug + <code>.md</code>.</strong> The basename must match the <code>name:</code> field
|
|
1296
|
+
(e.g. <code>retry-with-jitter.md</code> ↔ <code>name: retry-with-jitter</code>). Lens I's
|
|
1297
|
+
suppression match reads <code>name:</code> only, not the filename — a mismatch silently
|
|
1298
|
+
breaks suppression.</li>
|
|
1299
|
+
<li><strong>Required frontmatter:</strong> <code>name</code>, <code>description</code>. Add <code>volatility</code> + <code>sources[]</code>
|
|
1300
|
+
if you want the cron to audit it — an entry with no <code>sources[]</code> is skipped by
|
|
1301
|
+
the prefilter (<span class="fp" data-path="src/knowledge-freshness/audit-prefilter.ts:17">src/knowledge-freshness/audit-prefilter.ts:17</span>).</li>
|
|
1302
|
+
<li><strong>Validate locally:</strong> <code>make validate-knowledge</code>.</li>
|
|
1303
|
+
<li><strong>Confirm the prefilter will pick it up.</strong> A fresh entry has no
|
|
1304
|
+
<code>last-reviewed</code>, so it should appear at priority 100:
|
|
1305
|
+
<pre><code class="language-bash">node dist/index.js knowledge-freshness audit-prefilter --max=10 \
|
|
1306
|
+
| jq '.[] | select(.name=="<your-new-slug>")'
|
|
1307
|
+
</code></pre>
|
|
1308
|
+
The daily ceiling is 10, so a flood of new entries may queue past the first day.</li>
|
|
1309
|
+
</ol>
|
|
1310
|
+
<h3 id="gap-signal-payload">Gap-signal payload</h3>
|
|
1311
|
+
<p>A gap signal is a ledger event validated by
|
|
1312
|
+
<span class="fp" data-path="src/observability/engine/event-schemas.ts:191-220">src/observability/engine/event-schemas.ts:191-220</span> (payload allow-list at
|
|
1313
|
+
<span class="fp" data-path="src/observability/engine/event-schemas.ts:12">src/observability/engine/event-schemas.ts:12</span>):</p>
|
|
1314
|
+
<pre><code class="language-json">{
|
|
1315
|
+
"event_id": "<uuid>",
|
|
1316
|
+
"worktree_id": "<sha>",
|
|
1317
|
+
"actor_label": "agent | bot | …",
|
|
1318
|
+
"branch": "<branch>",
|
|
1319
|
+
"task_id": null,
|
|
1320
|
+
"type": "knowledge_gap_signal",
|
|
1321
|
+
"ts": "<ISO-8601>",
|
|
1322
|
+
"payload": {
|
|
1323
|
+
"topic": "<kebab-slug>",
|
|
1324
|
+
"source": "agent_search",
|
|
1325
|
+
"project_id": "<sha256-hex>",
|
|
1326
|
+
"step_name": "tech-stack",
|
|
1327
|
+
"agent_excerpt": "…"
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
</code></pre>
|
|
1331
|
+
<p><code>topic</code> is ≤80 chars matching <code>/^[a-z0-9]+(-[a-z0-9]+)*$/</code>; <code>source</code> ∈
|
|
1332
|
+
{<code>agent_search</code>, <code>lessons</code>, <code>manual</code>}; <code>project_id</code> is 64-char sha256 hex (or the
|
|
1333
|
+
literal <code>lessons</code> when <code>source=lessons</code>); <code>step_name</code> and <code>agent_excerpt</code> (≤200
|
|
1334
|
+
chars) are optional.</p>
|
|
1335
|
+
<div class="callout callout-tip"><p><strong>Suppressing emission in tests/CI.</strong> Set <code>SCAFFOLD_GAP_SIGNAL_QUIET=1</code>. The
|
|
1336
|
+
assembly-time tail (<code>src/core/assembly/gap-signal-tail.ts</code>) then renders no
|
|
1337
|
+
emission template into the pipeline step. Default is always-on (locked decision
|
|
1338
|
+
#9) — catch gaps everywhere they occur.</p></div>
|
|
1339
|
+
<h3 id="knowledgerootresolution-shape">KnowledgeRootResolution shape</h3>
|
|
1340
|
+
<p>The resolver returns a three-field record that threads through the audit run
|
|
1341
|
+
(<span class="fp" data-path="src/observability/knowledge-index.ts:275-291">src/observability/knowledge-index.ts:275-291</span>):</p>
|
|
1342
|
+
<pre><code class="language-ts">export interface KnowledgeRootResolution {
|
|
1343
|
+
/** Validated absolute path to a knowledge directory, or null. */
|
|
1344
|
+
root: string | null
|
|
1345
|
+
/** Pre-loaded index Set, populated by the validator. Null when root is null.
|
|
1346
|
+
Lens I reads this directly — no re-walk. */
|
|
1347
|
+
index: Set<string> | null
|
|
1348
|
+
/** Audit trail of what was tried. Lens I uses this to compose a precise
|
|
1349
|
+
warn-once message when root is null. */
|
|
1350
|
+
attempts: KnowledgeRootAttempt[]
|
|
1351
|
+
}
|
|
1352
|
+
</code></pre>
|
|
1353
|
+
<h2 id="from-candidate-to-merged-pr">From candidate to merged PR</h2>
|
|
1354
|
+
<p>The cron is a thin bash loop — the brains live in three CLI subcommands and a
|
|
1355
|
+
meta-prompt that runs a grounded LLM against pre-fetched source bodies.</p>
|
|
1356
|
+
<h3 id="prefilter">Prefilter</h3>
|
|
1357
|
+
<p>An entry becomes a candidate when (1) it has at least one source, AND (2) either
|
|
1358
|
+
its <code>last-reviewed</code> is older than the cadence window, OR a source's prefetched
|
|
1359
|
+
hash differs from the stored one. Priority orders highest-score first: unreviewed
|
|
1360
|
+
entries (100), then overdue entries (<code>50 + ageDays</code>, so the oldest rank highest),
|
|
1361
|
+
with in-window hash changes at 75; the top <code>--max</code>
|
|
1362
|
+
win (<span class="fp" data-path="src/knowledge-freshness/audit-prefilter.ts:14-72">src/knowledge-freshness/audit-prefilter.ts:14-72</span>):</p>
|
|
1363
|
+
<pre><code class="language-ts">for (const e of entries) {
|
|
1364
|
+
if (e.sources.length === 0) continue // no sources = no audit
|
|
1365
|
+
if (!e.lastReviewed) { select = true; priority = 100 }
|
|
1366
|
+
else if (ageDays > window) { select = true; priority = 50 + ageDays }
|
|
1367
|
+
else {
|
|
1368
|
+
// hash check — Promise.all over a small per-entry list (1-3 sources)
|
|
1369
|
+
if (anyHashChanged) { select = true; priority = 75 }
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
candidates.sort((a, b) => b.priority - a.priority)
|
|
1373
|
+
return candidates.slice(0, max)
|
|
1374
|
+
</code></pre>
|
|
1375
|
+
<p>The hash check is a tiebreaker, not a baseline. Entries already <em>past</em> their
|
|
1376
|
+
window are selected immediately at priority <code>50 + ageDays</code> — no network cost.
|
|
1377
|
+
The hash check only runs in the <code>else</code> branch (still <em>inside</em> the window), runs
|
|
1378
|
+
<code>Promise.all</code> over the entry's 1–3 sources, and swallows fetch errors so a slow
|
|
1379
|
+
upstream doesn't crash the cron.</p>
|
|
1380
|
+
<h3 id="audit-verdicts">Audit verdicts</h3>
|
|
1381
|
+
<p>The meta-prompt at <code>content/tools/knowledge-audit-entry.md</code> instructs the LLM to
|
|
1382
|
+
read pre-fetched source bodies (no web tool available) and emit one of four
|
|
1383
|
+
verdicts. <strong>Every verdict opens a PR</strong> — the dry-run apply runs first so gates
|
|
1384
|
+
can inspect the proposed diff, then <code>--open-pr</code> creates the branch.</p>
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
<table><thead><tr><th>Verdict</th><th>What the PR contains</th></tr></thead><tbody><tr><td><code>current</code></td><td>Frontmatter-only: bumps <code>last-reviewed</code>, <code>sources[*].hash</code>, <code>sources[*].retrieved</code> so the entry exits the queue.</td></tr><tr><td><code>minor-drift</code></td><td>Frontmatter persistence + findings table as commentary. <code>applyVerdictToEntry</code> refuses any <code>proposed_changes</code> on this verdict (<span class="fp" data-path="src/knowledge-freshness/audit-apply.ts:54-58">src/knowledge-freshness/audit-apply.ts:54-58</span>); no body edits.</td></tr><tr><td><code>major-drift</code></td><td>Body edits land via <code>proposed_changes</code> (H2-heading-anchored splices). Gate 4 blocks if a stable entry's diff exceeds 20% churn without the override label.</td></tr><tr><td><code>superseded</code></td><td>A new edition shipped; <code>version-pin</code> must advance. <code>last-reviewed</code> does <strong>not</strong> advance (<span class="fp" data-path="src/knowledge-freshness/audit-apply.ts:103-118">src/knowledge-freshness/audit-apply.ts:103-118</span>) — only <code>hash</code>/<code>retrieved</code> update, so the entry stays due until a human re-audits. Prevents a known-stale entry from looking fresh.</td></tr></tbody></table>
|
|
1411
|
+
<h3 id="pr-generation">PR generation</h3>
|
|
1412
|
+
<p>Branch: <code>knowledge-freshness/<entry>-<YYYY-MM-DD></code>. <code>renderPrBody</code> renders a
|
|
1413
|
+
summary, the verdict fields, a findings table, the sources, and any preserve
|
|
1414
|
+
warnings (it does not embed the raw verdict JSON). Each candidate gets
|
|
1415
|
+
its own PR off <code>origin/main</code> — the cron <code>git checkout main</code> between iterations
|
|
1416
|
+
and restores the entry between the dry-run apply (for gates) and the final
|
|
1417
|
+
<code>--open-pr</code> call. PRs do not stack; failures isolate per-candidate.</p>
|
|
1418
|
+
<h4>VERSION bump on merge</h4>
|
|
1419
|
+
<p>A dedicated workflow
|
|
1420
|
+
(<span class="fp" data-path=".github/workflows/knowledge-freshness-version-bump.yml:16">.github/workflows/knowledge-freshness-version-bump.yml:16</span>) fires on PR
|
|
1421
|
+
<code>closed</code> (merged-only) when the source branch starts with <code>knowledge-freshness/</code>
|
|
1422
|
+
<em>or</em> the PR carries the <code>knowledge-freshness</code> label. It computes the next SemVer
|
|
1423
|
+
from the PR title and body, writes <code>content/knowledge/VERSION</code>, commits with the
|
|
1424
|
+
prefix <code>chore(knowledge):</code> (deliberately not <code>knowledge-freshness/*</code>) so the
|
|
1425
|
+
commit doesn't re-trigger itself, then <code>git pull --rebase</code> before pushing. Bump
|
|
1426
|
+
rules (<span class="fp" data-path="src/knowledge-freshness/bump-version.ts:26-45">src/knowledge-freshness/bump-version.ts:26-45</span>):</p>
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
<table><thead><tr><th>Match</th><th>Bump</th><th>Notes</th></tr></thead><tbody><tr><td><code>BREAKING CHANGE:</code> anywhere in title, or start-of-line in body</td><td>major</td><td>Wins over every other prefix</td></tr><tr><td><code>feat(knowledge):</code> / <code>feat(knowledge-freshness):</code> title prefix</td><td>minor</td><td>Case-sensitive</td></tr><tr><td><code>chore(knowledge):</code> / <code>chore(knowledge-freshness):</code> title prefix</td><td>patch</td><td>Used by the bump commit itself</td></tr><tr><td>Anything else (including <code>fix(knowledge):</code>)</td><td>patch</td><td>Logs a <code>::notice::</code> for unrecognized prefixes</td></tr></tbody></table>
|
|
1458
|
+
<p>The start-of-line anchor on the BREAKING CHANGE body match (<code>/^BREAKING CHANGE:/m</code>) is deliberate — a freshness PR's body embeds an LLM-generated
|
|
1459
|
+
findings table whose evidence excerpts could otherwise mention "BREAKING CHANGE:"
|
|
1460
|
+
and trigger an accidental major bump.</p>
|
|
1461
|
+
<h3 id="mmr-corroboration-manual">MMR corroboration (manual)</h3>
|
|
1462
|
+
<p>The cron does <em>not</em> dispatch MMR today — the workflow only runs inline gates. To
|
|
1463
|
+
corroborate a freshness PR locally:</p>
|
|
1464
|
+
<pre><code class="language-bash">git diff origin/main...HEAD -- 'content/knowledge/**/*.md' \
|
|
1465
|
+
| mmr review --diff - --focus knowledge-freshness --sync --format json
|
|
1466
|
+
</code></pre>
|
|
1467
|
+
<p>A native <code>knowledge-freshness</code> MMR channel is the Phase 5 plan. See the
|
|
1468
|
+
<a href="../mmr/index.md">MMR guide</a> for the channel architecture.</p>
|
|
1469
|
+
<h2 id="the-five-pr-gates">The five PR gates</h2>
|
|
1470
|
+
<p>The cron's <code>GITHUB_TOKEN</code>-opened PRs don't fire downstream workflows, so the
|
|
1471
|
+
cron also runs the gate code inline (same CLI surface). Human-opened freshness
|
|
1472
|
+
PRs get gated by the workflow at
|
|
1473
|
+
<span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:17">.github/workflows/knowledge-freshness-gates.yml:17</span>.</p>
|
|
1474
|
+
<div class="filter-table"><input type="text" class="filter-input" placeholder="Filter…" aria-label="Filter table rows" disabled>
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
<table><thead><tr><th>#</th><th>Gate</th><th>What it checks</th><th>Mode</th><th>Source</th></tr></thead><tbody><tr><td>1</td><td>Frontmatter validator</td><td>Zod schema parse over every entry (excludes README). Strict calendar-date refinement; SSRF guard on source URLs.</td><td><span class="sev sev-p0">blocking</span></td><td><span class="fp" data-path="src/validation/knowledge-frontmatter-validator.ts:42-50">src/validation/knowledge-frontmatter-validator.ts:42-50</span></td></tr><tr><td>2</td><td>Source link-check</td><td>Every <code>sources[*].url</code> returns 2xx. Operates on the changed-files list via <code>--files-from</code>.</td><td><span class="sev sev-p0">blocking</span></td><td><span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:117-123">.github/workflows/knowledge-freshness-gates.yml:117-123</span></td></tr><tr><td>3</td><td>Unsourced-claims lint</td><td>New normative claims must have a <code>sources[]</code> entry. Runs even when 1/2 failed.</td><td><span class="sev sev-p3">advisory</span></td><td><span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:126-135">.github/workflows/knowledge-freshness-gates.yml:126-135</span></td></tr><tr><td>4</td><td>Anti-over-rewrite</td><td>Stable entries reject diffs deleting >20% of lines unless the <code>override:anti-over-rewrite</code> label is applied. Cron-opened <code>knowledge-freshness/*</code> branches only.</td><td><span class="sev sev-p1">blocking</span></td><td><span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:137-152">.github/workflows/knowledge-freshness-gates.yml:137-152</span></td></tr><tr><td>5</td><td>Deep Guidance preserved</td><td>Literal <code>## Deep Guidance</code> heading must survive — the assembly engine pulls just that section.</td><td><span class="sev sev-p0">blocking</span></td><td><span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:154-160">.github/workflows/knowledge-freshness-gates.yml:154-160</span></td></tr></tbody></table></div>
|
|
1522
|
+
<div class="callout callout-warning"><p><strong>Spec drift on the Gate 4 override.</strong> The parent spec describes the override as
|
|
1523
|
+
a marker in the PR <em>description</em>; the shipped mechanism
|
|
1524
|
+
(<span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:148-152">.github/workflows/knowledge-freshness-gates.yml:148-152</span>) reads a
|
|
1525
|
+
maintainer-applied PR <em>label</em> (<code>override:anti-over-rewrite</code>) via <code>--pr-labels</code>.
|
|
1526
|
+
The shipped behavior is authoritative; the spec text is stale.</p></div>
|
|
1527
|
+
<div class="callout callout-note"><p><strong>Anti-tamper checkout (known gap).</strong> The gate workflow builds the gate code from
|
|
1528
|
+
HEAD, not from <code>origin/main</code>
|
|
1529
|
+
(<span class="fp" data-path=".github/workflows/knowledge-freshness-gates.yml:42-53">.github/workflows/knowledge-freshness-gates.yml:42-53</span>). The hardening —
|
|
1530
|
+
build from base, overlay only PR HEAD's <code>content/knowledge/</code> — is deferred
|
|
1531
|
+
because the bootstrap PR introduced the gate code itself. Risk is mitigated by
|
|
1532
|
+
mandatory PR review until a follow-up flips the checkout strategy.</p></div>
|
|
1533
|
+
<h2 id="lens-i-gap-detection-suppression">Lens I — gap detection + suppression</h2>
|
|
1534
|
+
<p>Lens I runs under <code>--scope=docs</code> and <code>--scope=all</code>
|
|
1535
|
+
(<span class="fp" data-path="src/observability/checks/lens-i-knowledge-gaps.ts:43">src/observability/checks/lens-i-knowledge-gaps.ts:43</span>). It collects
|
|
1536
|
+
signals from the ledger (rolling 90-day window,
|
|
1537
|
+
<span class="fp" data-path="src/observability/checks/lens-i-knowledge-gaps.ts:52">src/observability/checks/lens-i-knowledge-gaps.ts:52</span>) plus synthetic
|
|
1538
|
+
signals from <code>tasks/lessons.md</code>, buckets them by normalized topic, applies the
|
|
1539
|
+
threshold matrix, and suppresses buckets whose topic an entry already covers.</p>
|
|
1540
|
+
<div class="callout callout-note"><p><strong>Where Lens I sits in the taxonomy.</strong> "Lens" is scaffold's name for an audit
|
|
1541
|
+
check function inside <code>scaffold observe audit</code>. The full set is A–I; Lens I
|
|
1542
|
+
(<code>I-knowledge-gaps</code>) is this one. The other seven plus Lens H are documented in
|
|
1543
|
+
the <a href="../observability/index.md">Build Observability guide</a>.</p></div>
|
|
1544
|
+
<h3 id="threshold-matrix">Threshold matrix</h3>
|
|
1545
|
+
<p>The rules (<span class="fp" data-path="src/observability/checks/lens-i-knowledge-gaps.ts:148-149">src/observability/checks/lens-i-knowledge-gaps.ts:148-149</span>):</p>
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
<table><thead><tr><th>signal_count</th><th>distinct_projects</th><th>Severity</th></tr></thead><tbody><tr><td>≥ 5</td><td>≥ 3</td><td><span class="sev sev-p1">P1</span></td></tr><tr><td>≥ 3</td><td>≥ 2</td><td><span class="sev sev-p2">P2</span></td></tr><tr><td>below both</td><td>—</td><td>no finding</td></tr></tbody></table>
|
|
1572
|
+
<h3 id="topic-normalization">Topic normalization</h3>
|
|
1573
|
+
<p>Lens I normalizes the raw topic before bucketing, then validates the result.
|
|
1574
|
+
Two distinct steps: <code>normalizeTopic</code>
|
|
1575
|
+
(<span class="fp" data-path="src/observability/checks/lens-i-lessons-scanner.ts:32-38">src/observability/checks/lens-i-lessons-scanner.ts:32-38</span>) always
|
|
1576
|
+
produces a (possibly empty) string; <code>isValidTopic</code>
|
|
1577
|
+
(<span class="fp" data-path="src/observability/checks/lens-i-lessons-scanner.ts:114-116">src/observability/checks/lens-i-lessons-scanner.ts:114-116</span>) decides
|
|
1578
|
+
whether to accept it. Normalization lowercases, strips apostrophes, replaces
|
|
1579
|
+
every other non-slug run with a single hyphen, collapses repeats, and trims. The
|
|
1580
|
+
validator additionally enforces ≤ 80 chars and <code>/^[a-z0-9]+(-[a-z0-9]+)*$/</code>.
|
|
1581
|
+
So <code>Agent Eval Harnesses!</code> → <code>agent-eval-harnesses</code> (valid), but <code>!!!</code> → `` (rejected).</p>
|
|
1582
|
+
<h3 id="what-the-lessonsmd-scanner-sees">What the lessons.md scanner sees</h3>
|
|
1583
|
+
<p>Lens I synthesizes signals from <code>tasks/lessons.md</code> at audit time (read inline, no
|
|
1584
|
+
ledger writes) via two passes per non-fenced line — code-fenced blocks are
|
|
1585
|
+
skipped (<span class="fp" data-path="src/observability/checks/lens-i-lessons-scanner.ts:4">src/observability/checks/lens-i-lessons-scanner.ts:4</span>):</p>
|
|
1586
|
+
<ol>
|
|
1587
|
+
<li><strong>Explicit marker</strong> — <code><!-- gap-topic: <slug> --></code> (slug must already be
|
|
1588
|
+
kebab-case; the marker regex enforces it).</li>
|
|
1589
|
+
<li><strong>Heuristic phrases</strong> (case-insensitive): <em>"would have helped to have a guide
|
|
1590
|
+
on X"</em>, <em>"missing knowledge entry for X"</em>, <em>"no knowledge entry for X"</em> /
|
|
1591
|
+
<em>"no kb entry for X"</em>, <em>"missing knowledge: X"</em>.</li>
|
|
1592
|
+
</ol>
|
|
1593
|
+
<p>Captured topics run through the same <code>normalizeTopic</code> / <code>isValidTopic</code>.
|
|
1594
|
+
Synthetic signals carry <code>project_id: "lessons"</code> and are <strong>excluded</strong> from the
|
|
1595
|
+
distinct-projects count by the aggregator's <code>delete('lessons')</code> rule (decision
|
|
1596
|
+
#6) — they corroborate but don't independently satisfy the threshold.</p>
|
|
1597
|
+
<h3 id="3-tier---knowledge-root-resolution">3-tier <code>--knowledge-root</code> resolution</h3>
|
|
1598
|
+
<p>Lens I must know where the KB lives to skip already-covered topics. The resolver
|
|
1599
|
+
(<code>resolveKnowledgeRoot</code> at <span class="fp" data-path="src/observability/knowledge-index.ts:326-379">src/observability/knowledge-index.ts:326-379</span>)
|
|
1600
|
+
tries three tiers in order:</p>
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
<table><thead><tr><th>Tier</th><th>Source</th><th>On failure</th></tr></thead><tbody><tr><td>1</td><td><code>--knowledge-root</code> CLI flag (resolved against <code>process.cwd()</code>)</td><td><strong>hard error</strong> before any lens runs (<code>KnowledgeRootCliInvalidError</code>)</td></tr><tr><td>2</td><td><code>lenses.I-knowledge-gaps.knowledge_root</code> in yaml (resolved against cwd)</td><td>soft-fail; records <code>{outcome: 'invalid', reason}</code> in the attempts trail</td></tr><tr><td>3</td><td>auto-detect — <code>findScaffoldKnowledgeRoot</code> walks parents for <code>package.json#name === '@zigrivers/scaffold'</code> (<span class="fp" data-path="src/observability/knowledge-index.ts:164-178">src/observability/knowledge-index.ts:164-178</span>)</td><td>returns <code>null</code> if no install is found</td></tr></tbody></table>
|
|
1627
|
+
<p>The sharp asymmetry is intentional: an operator who <em>typed</em> a <code>--knowledge-root</code>
|
|
1628
|
+
gets a hard error on a bad path; yaml and auto-detect soft-fail so suppression
|
|
1629
|
+
degrades gracefully. The most instructive case: yaml invalid + auto-detect found
|
|
1630
|
+
— the trail records the yaml failure <em>and</em> the auto-detect success, root is the
|
|
1631
|
+
auto-detect path, and a one-line stderr note points at the stale yaml. This is
|
|
1632
|
+
what an operator sees when <code>npm update -g @zigrivers/scaffold</code> moved the install
|
|
1633
|
+
out from under a pinned yaml path.</p>
|
|
1634
|
+
<h3 id="warning-policy">Warning policy</h3>
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
<table><thead><tr><th>Key</th><th>Status</th><th>When emitted</th></tr></thead><tbody><tr><td><code>lens-i:no-root</code></td><td>active</td><td>Lens I runs, no root resolved, lens enabled. Per-audit deduped via <code>warnedKeys: Set<string></code>. If yaml failed validation, the message gains a clause quoting the bad path + reason.</td></tr><tr><td><code>lens-i:index-load-failed</code></td><td>reserved</td><td>Never emitted today — <code>validateKnowledgeRoot</code> exercises the loader at resolution time, foreclosing this path.</td></tr><tr><td>(none)</td><td>no-warn</td><td>Lens I disabled — resolver runs but no warning surfaces (decisions #4 / #11).</td></tr></tbody></table>
|
|
1661
|
+
<p><code>emitOnceForAudit</code> (<span class="fp" data-path="src/observability/knowledge-index.ts:251-259">src/observability/knowledge-index.ts:251-259</span>) reads a
|
|
1662
|
+
caller-provided <code>Set</code> created fresh in each <code>runAudit</code>
|
|
1663
|
+
(<span class="fp" data-path="src/observability/engine/api.ts:114">src/observability/engine/api.ts:114</span>), so the <code>--fix</code> flow's three
|
|
1664
|
+
internal audits each get their own dedup scope.</p>
|
|
1665
|
+
<h3 id="what-a-lens-i-finding-looks-like">What a Lens I finding looks like</h3>
|
|
1666
|
+
<p>A single finding excerpt from the audit sidecar (<code>docs/audits/<id>.json</code>):</p>
|
|
1667
|
+
<pre><code class="language-json">{
|
|
1668
|
+
"id": "a3f2c1d4...",
|
|
1669
|
+
"lens_id": "I-knowledge-gaps",
|
|
1670
|
+
"severity": "P2",
|
|
1671
|
+
"title": "Knowledge base lacks coverage for \"agent-eval-harnesses\" — 4 signals across 2 projects",
|
|
1672
|
+
"source_doc": "",
|
|
1673
|
+
"evidence": {
|
|
1674
|
+
"kind": "knowledge_gap",
|
|
1675
|
+
"topic": "agent-eval-harnesses",
|
|
1676
|
+
"signal_count": 4,
|
|
1677
|
+
"distinct_project_count": 2,
|
|
1678
|
+
"distinct_projects": ["a3f2...", "1c4e..."],
|
|
1679
|
+
"first_seen": "2026-04-12T09:00:00Z",
|
|
1680
|
+
"last_seen": "2026-05-21T14:30:00Z",
|
|
1681
|
+
"example_excerpts": ["No knowledge entry for agent eval harnesses"]
|
|
1682
|
+
},
|
|
1683
|
+
"confidence": "medium",
|
|
1684
|
+
"fix_hint": {
|
|
1685
|
+
"kind": "edit_doc",
|
|
1686
|
+
"target": "content/knowledge/<category>/agent-eval-harnesses.md",
|
|
1687
|
+
"prompt": "Propose a new knowledge entry for \"agent-eval-harnesses\". Evidence: 4 signals from 2 projects in the last 90 days."
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
</code></pre>
|
|
1691
|
+
<div class="callout callout-warning"><p><strong>Phase audits don't trigger Lens I.</strong> The phase-boundary hook
|
|
1692
|
+
(<code>StateManager.markCompleted</code> → <code>runPhaseAudit</code> at
|
|
1693
|
+
<span class="fp" data-path="src/observability/engine/phase-audit.ts:63">src/observability/engine/phase-audit.ts:63</span>) fires only Lens H-cross-doc
|
|
1694
|
+
(<code>lensIds: ['H-cross-doc']</code> at <span class="fp" data-path="src/observability/engine/phase-audit.ts:77">src/observability/engine/phase-audit.ts:77</span>).
|
|
1695
|
+
Lens I never runs at phase boundaries. A phase-audit run that surfaces zero
|
|
1696
|
+
findings does <strong>not</strong> mean Lens I is happy — it means Lens I never ran. To see
|
|
1697
|
+
Lens I findings, invoke <code>scaffold observe audit --scope=docs</code> (or <code>--scope=all</code>)
|
|
1698
|
+
explicitly, or run it through <code>--fix</code>.</p></div>
|
|
1699
|
+
<h2 id="the-allowlist">The allowlist</h2>
|
|
1700
|
+
<p>Out-of-allowlist sources warn but don't block (decision #4). Bare hostnames
|
|
1701
|
+
match subdomains; <code>host/path</code> entries additionally require the URL path to start
|
|
1702
|
+
with the prefix; <code>github_repos</code> is locked to specific <code>owner/repo</code>.</p>
|
|
1703
|
+
<p>The off-allowlist warning is <strong>advisory</strong> and is surfaced by the
|
|
1704
|
+
frontmatter-validation path (<code>validateKnowledgeFile</code>), not by a gate. Gate 3
|
|
1705
|
+
(<code>lint-unsourced</code>) is a separate advisory check that flags nearby links not
|
|
1706
|
+
covered by the entry's declared <code>sources[]</code> domains. Off-allowlist sources still
|
|
1707
|
+
get fetched, hashed, and audited — they just warn.
|
|
1708
|
+
It is <strong>not</strong> a security boundary: the SSRF guard
|
|
1709
|
+
(<code>src/knowledge-freshness/source-url-validator.ts</code>) runs independently, so a new
|
|
1710
|
+
host never unlocks private-IP fetches. The editorial bar is: "would the
|
|
1711
|
+
maintainers want this URL to be the verbatim grounding for a P0/P1 finding?"</p>
|
|
1712
|
+
<h3 id="most-cited-hosts">Most-cited hosts</h3>
|
|
1713
|
+
<p>Counted live from every entry's <code>sources[*].url</code> at build time.</p>
|
|
1714
|
+
|
|
1715
|
+
<div class="chart-block"><div class="chart chart-bar"><div class="chart-row" aria-label="martinfowler.com: 37"><span class="chart-label">martinfowler.com</span><div class="chart-bar" style="width:100%"></div></div><div class="chart-row" aria-label="developer.mozilla.org: 24"><span class="chart-label">developer.mozilla.org</span><div class="chart-bar" style="width:65%"></div></div><div class="chart-row" aria-label="owasp.org: 17"><span class="chart-label">owasp.org</span><div class="chart-bar" style="width:46%"></div></div><div class="chart-row" aria-label="developer.android.com: 15"><span class="chart-label">developer.android.com</span><div class="chart-bar" style="width:41%"></div></div><div class="chart-row" aria-label="the-turing-way.netlify.app: 15"><span class="chart-label">the-turing-way.netlify.app</span><div class="chart-bar" style="width:41%"></div></div><div class="chart-row" aria-label="developer.apple.com: 14"><span class="chart-label">developer.apple.com</span><div class="chart-bar" style="width:38%"></div></div><div class="chart-row" aria-label="developer.chrome.com: 14"><span class="chart-label">developer.chrome.com</span><div class="chart-bar" style="width:38%"></div></div><div class="chart-row" aria-label="sre.google: 12"><span class="chart-label">sre.google</span><div class="chart-bar" style="width:32%"></div></div><div class="chart-row" aria-label="w3.org: 12"><span class="chart-label">w3.org</span><div class="chart-bar" style="width:32%"></div></div><div class="chart-row" aria-label="microservices.io: 11"><span class="chart-label">microservices.io</span><div class="chart-bar" style="width:30%"></div></div><div class="chart-row" aria-label="ethereum.org: 10"><span class="chart-label">ethereum.org</span><div class="chart-bar" style="width:27%"></div></div><div class="chart-row" aria-label="rfc-editor.org: 10"><span class="chart-label">rfc-editor.org</span><div class="chart-bar" style="width:27%"></div></div><div class="chart-row" aria-label="consensys.github.io: 9"><span class="chart-label">consensys.github.io</span><div class="chart-bar" style="width:24%"></div></div><div class="chart-row" aria-label="docs.openzeppelin.com: 9"><span class="chart-label">docs.openzeppelin.com</span><div class="chart-bar" style="width:24%"></div></div><div class="chart-row" aria-label="opentelemetry.io: 9"><span class="chart-label">opentelemetry.io</span><div class="chart-bar" style="width:24%"></div></div></div>
|
|
1716
|
+
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
|
|
1731
|
+
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
|
|
1746
|
+
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
|
|
1750
|
+
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
<table><thead><tr><th>Host</th><th>Citations</th></tr></thead><tbody><tr><td>martinfowler.com</td><td>37</td></tr><tr><td>developer.mozilla.org</td><td>24</td></tr><tr><td>owasp.org</td><td>17</td></tr><tr><td>developer.android.com</td><td>15</td></tr><tr><td>the-turing-way.netlify.app</td><td>15</td></tr><tr><td>developer.apple.com</td><td>14</td></tr><tr><td>developer.chrome.com</td><td>14</td></tr><tr><td>sre.google</td><td>12</td></tr><tr><td>w3.org</td><td>12</td></tr><tr><td>microservices.io</td><td>11</td></tr><tr><td>ethereum.org</td><td>10</td></tr><tr><td>rfc-editor.org</td><td>10</td></tr><tr><td>consensys.github.io</td><td>9</td></tr><tr><td>docs.openzeppelin.com</td><td>9</td></tr><tr><td>opentelemetry.io</td><td>9</td></tr></tbody></table></div>
|
|
1785
|
+
|
|
1786
|
+
<h3 id="the-full-allowlist">The full allowlist</h3>
|
|
1787
|
+
<p>Every host plus its category, and the pinned GitHub repos.</p>
|
|
1788
|
+
|
|
1789
|
+
<p>47 allowlisted hosts and 3 GitHub repos. Out-of-list sources warn (they do not block).</p>
|
|
1790
|
+
<div class="filter-table"><input type="text" class="filter-input" placeholder="Filter…" aria-label="Filter table rows" disabled>
|
|
1791
|
+
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
|
|
1798
|
+
|
|
1799
|
+
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
|
|
1806
|
+
|
|
1807
|
+
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
|
|
1835
|
+
|
|
1836
|
+
|
|
1837
|
+
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
|
|
1869
|
+
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
|
|
1888
|
+
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
|
|
1917
|
+
|
|
1918
|
+
|
|
1919
|
+
|
|
1920
|
+
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
|
|
1931
|
+
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
|
|
1935
|
+
|
|
1936
|
+
|
|
1937
|
+
|
|
1938
|
+
|
|
1939
|
+
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
|
|
1951
|
+
|
|
1952
|
+
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
|
|
1957
|
+
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
|
|
1972
|
+
|
|
1973
|
+
|
|
1974
|
+
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
|
|
1982
|
+
|
|
1983
|
+
|
|
1984
|
+
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
<table><thead><tr><th>Host</th><th>Category</th></tr></thead><tbody><tr><td><code>ai.google.dev</code></td><td>ai-ml</td></tr><tr><td><code>anthropic.com</code></td><td>ai-ml</td></tr><tr><td><code>docs.wandb.ai</code></td><td>ai-ml</td></tr><tr><td><code>mlflow.org</code></td><td>ai-ml</td></tr><tr><td><code>modelcontextprotocol.io</code></td><td>ai-ml</td></tr><tr><td><code>platform.openai.com</code></td><td>ai-ml</td></tr><tr><td><code>spec.graphql.org</code></td><td>api</td></tr><tr><td><code>spec.openapis.org</code></td><td>api</td></tr><tr><td><code>developer.chrome.com</code></td><td>browser-ext</td></tr><tr><td><code>docs.aws.amazon.com</code></td><td>cloud-ops</td></tr><tr><td><code>opentelemetry.io</code></td><td>cloud-ops</td></tr><tr><td><code>sre.google</code></td><td>cloud-ops</td></tr><tr><td><code>aicpa-cima.com</code></td><td>compliance</td></tr><tr><td><code>aicpa.org</code></td><td>compliance</td></tr><tr><td><code>eur-lex.europa.eu</code></td><td>compliance</td></tr><tr><td><code>pcisecuritystandards.org</code></td><td>compliance</td></tr><tr><td><code>www.finra.org</code></td><td>compliance</td></tr><tr><td><code>www.sec.gov</code></td><td>compliance</td></tr><tr><td><code>developer.android.com</code></td><td>mobile</td></tr><tr><td><code>developer.apple.com</code></td><td>mobile</td></tr><tr><td><code>adr.github.io</code></td><td>patterns</td></tr><tr><td><code>agilealliance.org</code></td><td>patterns</td></tr><tr><td><code>conventionalcommits.org</code></td><td>patterns</td></tr><tr><td><code>google.github.io</code></td><td>patterns</td></tr><tr><td><code>martinfowler.com</code></td><td>patterns</td></tr><tr><td><code>microservices.io</code></td><td>patterns</td></tr><tr><td><code>thoughtworks.com</code></td><td>patterns</td></tr><tr><td><code>the-turing-way.netlify.app</code></td><td>research</td></tr><tr><td><code>nist.gov</code></td><td>security</td></tr><tr><td><code>openid.net</code></td><td>security</td></tr><tr><td><code>owasp.org</code></td><td>security</td></tr><tr><td><code>consensys.github.io</code></td><td>smart-contracts</td></tr><tr><td><code>docs.openzeppelin.com</code></td><td>smart-contracts</td></tr><tr><td><code>docs.safe.global</code></td><td>smart-contracts</td></tr><tr><td><code>ethereum.org</code></td><td>smart-contracts</td></tr><tr><td><code>swcregistry.io</code></td><td>smart-contracts</td></tr><tr><td><code>ietf.org/rfc</code></td><td>standards</td></tr><tr><td><code>www.iso.org</code></td><td>standards</td></tr><tr><td><code>www.rfc-editor.org</code></td><td>standards</td></tr><tr><td><code>docs.pact.io</code></td><td>testing</td></tr><tr><td><code>docs.astral.sh</code></td><td>tooling</td></tr><tr><td><code>git-scm.com</code></td><td>tooling</td></tr><tr><td><code>peps.python.org</code></td><td>tooling</td></tr><tr><td><code>www.postgresql.org</code></td><td>tooling</td></tr><tr><td><code>developer.mozilla.org</code></td><td>web-standards</td></tr><tr><td><code>tr.designtokens.org</code></td><td>web-standards</td></tr><tr><td><code>www.w3.org</code></td><td>web-standards</td></tr></tbody></table></div>
|
|
1988
|
+
<p><strong>GitHub repos:</strong> <code>modelcontextprotocol/specification</code>, <code>steveyegge/beads</code>, <code>joelparkerhenderson/architecture-decision-record</code></p>
|
|
1989
|
+
|
|
1990
|
+
<h3 id="kb-inventory">KB inventory</h3>
|
|
1991
|
+
<p>Totals over <code>content/knowledge/</code>, broken down per category.</p>
|
|
1992
|
+
|
|
1993
|
+
<p><strong>266 entries</strong> across 19 categories:</p>
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
|
|
2005
|
+
|
|
2006
|
+
|
|
2007
|
+
|
|
2008
|
+
|
|
2009
|
+
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
|
|
2019
|
+
|
|
2020
|
+
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
|
|
2024
|
+
|
|
2025
|
+
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
|
|
2029
|
+
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
|
|
2054
|
+
|
|
2055
|
+
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
|
|
2060
|
+
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
|
|
2079
|
+
<table><thead><tr><th>Category</th><th>Entries</th></tr></thead><tbody><tr><td>core</td><td>35</td></tr><tr><td>game</td><td>25</td></tr><tr><td>research</td><td>25</td></tr><tr><td>backend</td><td>22</td></tr><tr><td>review</td><td>20</td></tr><tr><td>web-app</td><td>17</td></tr><tr><td>web3</td><td>14</td></tr><tr><td>data-science</td><td>13</td></tr><tr><td>browser-extension</td><td>12</td></tr><tr><td>data-pipeline</td><td>12</td></tr><tr><td>library</td><td>12</td></tr><tr><td>ml</td><td>12</td></tr><tr><td>mobile-app</td><td>12</td></tr><tr><td>cli</td><td>10</td></tr><tr><td>validation</td><td>7</td></tr><tr><td>product</td><td>6</td></tr><tr><td>execution</td><td>5</td></tr><tr><td>tools</td><td>4</td></tr><tr><td>finalization</td><td>3</td></tr></tbody></table>
|
|
2080
|
+
|
|
2081
|
+
<h3 id="how-to-expand-the-allowlist">How to expand the allowlist</h3>
|
|
2082
|
+
<p>Adding a host is a one-line PR to
|
|
2083
|
+
<code>docs/knowledge-freshness/authoritative-sources.yaml</code>:</p>
|
|
2084
|
+
<pre><code class="language-diff"> hosts:
|
|
2085
|
+
- owasp.org
|
|
2086
|
+
+ - developers.cloudflare.com
|
|
2087
|
+
- nist.gov
|
|
2088
|
+
</code></pre>
|
|
2089
|
+
<ol>
|
|
2090
|
+
<li><strong>Pick the form.</strong> Bare hostname for vendor docs whose path layout changes;
|
|
2091
|
+
<code>host/path</code> prefix for shared-tenancy hosts where you only trust a sub-path;
|
|
2092
|
+
<code>owner/repo</code> under <code>github_repos:</code> for specific GitHub repos. Skip <code>www.</code>
|
|
2093
|
+
(bare entries auto-match subdomains).</li>
|
|
2094
|
+
<li><strong>Verify the host is live</strong> — <code>curl -sI https://<host>/<path></code> should return
|
|
2095
|
+
2xx (or a 3xx that ultimately resolves).</li>
|
|
2096
|
+
<li><strong>Mirror the category</strong> in <code>CATEGORY_MAP</code> in
|
|
2097
|
+
<code>scripts/build-freshness-reference.mjs</code> — otherwise the regenerated allowlist
|
|
2098
|
+
table shows the new host as <code>other</code>.</li>
|
|
2099
|
+
<li><strong>Open a normal PR.</strong> Allowlist additions are not a separate trust delegation;
|
|
2100
|
+
any maintainer can review.</li>
|
|
2101
|
+
</ol>
|
|
2102
|
+
<h2 id="anthropic-vs-deepseek-cron-uses-deepseek">Anthropic vs DeepSeek (cron uses DeepSeek)</h2>
|
|
2103
|
+
<p>The cron switched to DeepSeek HTTP to remove the local <code>claude</code> CLI dependency
|
|
2104
|
+
from CI. Local audits keep using whichever provider is configured. Precedence is
|
|
2105
|
+
resolved by <code>resolveProvider</code> (<span class="fp" data-path="src/knowledge-freshness/providers/index.ts:36">src/knowledge-freshness/providers/index.ts:36</span>):</p>
|
|
2106
|
+
<ol>
|
|
2107
|
+
<li><code>--provider <name></code> — explicit flag, operator override</li>
|
|
2108
|
+
<li><code>KNOWLEDGE_FRESHNESS_PROVIDER</code> env var</li>
|
|
2109
|
+
<li>A single API key in env — inferred</li>
|
|
2110
|
+
<li>Both API keys present → error (ambiguous)</li>
|
|
2111
|
+
<li>No env, <code>claude</code> on PATH → anthropic (subprocess uses keychain)</li>
|
|
2112
|
+
<li>Nothing → error (no provider configured)</li>
|
|
2113
|
+
</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
|
|
2115
|
+
can only read the prefetched bodies). <strong>Requires the <code>claude</code> CLI on PATH
|
|
2116
|
+
regardless of how the provider was chosen</strong> — the resolver throws
|
|
2117
|
+
(<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
|
+
picked via flag, env, or API-key inference and <code>claude</code> isn't installed.
|
|
2119
|
+
<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>
|
|
2121
|
+
<li><strong>Auth:</strong> requires <code>DEEPSEEK_API_KEY</code>.</li>
|
|
2122
|
+
<li><strong>Default model:</strong> <code>deepseek-v4-flash</code>.</li>
|
|
2123
|
+
<li><strong>Override:</strong> set <code>KNOWLEDGE_FRESHNESS_DEEPSEEK_MODEL</code> to <code>deepseek-v4-pro</code>.
|
|
2124
|
+
Other values throw at dispatcher-build time
|
|
2125
|
+
(<span class="fp" data-path="src/knowledge-freshness/providers/deepseek.ts:54-58">src/knowledge-freshness/providers/deepseek.ts:54-58</span>).</li>
|
|
2126
|
+
<li><strong>Thinking mode:</strong> hardcoded <code>thinking: { type: 'disabled' }</code>.</li>
|
|
2127
|
+
<li><strong>URL:</strong> hardcoded to <code>https://api.deepseek.com/chat/completions</code>;
|
|
2128
|
+
project-local config cannot redirect (decision #7 invariant).</li>
|
|
2129
|
+
</ul></div></div>
|
|
2130
|
+
<div class="callout callout-danger"><p><strong>Why the DeepSeek URL is hardcoded.</strong> An untrusted project's
|
|
2131
|
+
<code>.scaffold/observability.yaml</code> could otherwise redirect the LLM dispatcher at an
|
|
2132
|
+
attacker-controlled host that captures <code>DEEPSEEK_API_KEY</code> from request headers.
|
|
2133
|
+
Hardcoding closes that exfiltration path — the same threat model that hardcodes
|
|
2134
|
+
Lens H's <code>claude -p</code> command in the Build Observability audit.</p></div>
|
|
2135
|
+
<p>The cron wires DeepSeek explicitly
|
|
2136
|
+
(<span class="fp" data-path=".github/workflows/knowledge-freshness-audit.yml:70">.github/workflows/knowledge-freshness-audit.yml:70</span>):</p>
|
|
2137
|
+
<pre><code class="language-yaml">env:
|
|
2138
|
+
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
|
2139
|
+
KNOWLEDGE_FRESHNESS_PROVIDER: deepseek
|
|
2140
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
2141
|
+
</code></pre>
|
|
2142
|
+
<p>A missing <code>DEEPSEEK_API_KEY</code> fails the run loudly at preflight rather than
|
|
2143
|
+
silently exiting 0 with zero PRs.</p>
|
|
2144
|
+
<h2 id="every-command-that-touches-the-system">Every command that touches the system</h2>
|
|
2145
|
+
<p>All commands ship in the published CLI.</p>
|
|
2146
|
+
<h3 id="refresh-arm-commands">Refresh-arm commands</h3>
|
|
2147
|
+
|
|
2148
|
+
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
|
|
2152
|
+
|
|
2153
|
+
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
|
|
2160
|
+
|
|
2161
|
+
|
|
2162
|
+
|
|
2163
|
+
|
|
2164
|
+
|
|
2165
|
+
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
|
|
2169
|
+
|
|
2170
|
+
|
|
2171
|
+
|
|
2172
|
+
<table><thead><tr><th>Command</th><th>Purpose</th></tr></thead><tbody><tr><td><code>scaffold knowledge-freshness audit-prefilter [--max=N]</code></td><td>Walk <code>content/knowledge/</code>, apply cadence + hash check, print a JSON candidate array. <code>--max</code> default 10 (<span class="fp" data-path="src/cli/commands/knowledge-freshness-audit-prefilter.ts:18">src/cli/commands/knowledge-freshness-audit-prefilter.ts:18</span>); the CLI emits only <code>{ name, path }</code> per candidate (<span class="fp" data-path="src/cli/commands/knowledge-freshness-audit-prefilter.ts:43">src/cli/commands/knowledge-freshness-audit-prefilter.ts:43</span>).</td></tr><tr><td><code>scaffold knowledge-freshness audit-run-entry <path></code></td><td>Pre-fetch each source through SSRF guards, dispatch the grounded audit, print verdict JSON. <code>--provider anthropic|deepseek</code> overrides env precedence.</td></tr><tr><td><code>scaffold knowledge-freshness audit-apply <path> <verdict.json> [--open-pr]</code></td><td>Patch frontmatter + apply <code>proposed_changes</code> by H2 heading. The wrapper re-fetches every checked URL and computes its own sha256 (<span class="fp" data-path="src/knowledge-freshness/audit-apply.ts:82-101">src/knowledge-freshness/audit-apply.ts:82-101</span>), so persisted hashes are deterministic, not the LLM's claim. Refuses to advance <code>last-reviewed</code> unless every declared source is covered.</td></tr><tr><td><code>make validate-knowledge</code></td><td>Gate 1 — runs the Zod validator over every entry (README excluded).</td></tr></tbody></table>
|
|
2173
|
+
<h3 id="gap-arm-commands">Gap-arm commands</h3>
|
|
2174
|
+
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
|
|
2181
|
+
|
|
2182
|
+
|
|
2183
|
+
|
|
2184
|
+
|
|
2185
|
+
|
|
2186
|
+
|
|
2187
|
+
|
|
2188
|
+
|
|
2189
|
+
|
|
2190
|
+
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
|
|
2194
|
+
|
|
2195
|
+
<table><thead><tr><th>Command</th><th>Purpose</th></tr></thead><tbody><tr><td><code>scaffold observe audit --lens I-knowledge-gaps [--knowledge-root <path>] [--fix]</code></td><td>Run the gap-detection lens against the local ledger + <code>tasks/lessons.md</code>. <code>--knowledge-root</code> overrides yaml + auto-detect for suppression. <code>--fix</code> dispatches the fix flow; the override threads through all three audits.</td></tr><tr><td><code>scaffold observe event knowledge_gap_signal --topic=<slug> --source=<…> --project-id=<sha> …</code></td><td>Write one validated gap signal to the ledger. Used by the assembly-time tail and by operators backfilling synthetic signals.</td></tr><tr><td><code>scaffold observe ack <prefix-or-id></code></td><td>Acknowledge (or reopen) a finding so it stops surfacing. Use when a Lens I topic is deliberately out of scope.</td></tr></tbody></table>
|
|
2196
|
+
<p>The <code>--fix</code> flow (<code>runFixFlow</code> at <span class="fp" data-path="src/observability/engine/fix-flow.ts:71">src/observability/engine/fix-flow.ts:71</span>)
|
|
2197
|
+
runs a three-audit loop: (1) the initial audit produces a fix plan; (2) for each
|
|
2198
|
+
blocking finding, dispatch a fix agent then re-audit just that finding (the
|
|
2199
|
+
verifier); (3) one postfix audit runs everything for the final report. The
|
|
2200
|
+
<code>--knowledge-root</code> override threads into all three (decision #20) so suppression
|
|
2201
|
+
is consistent throughout.</p>
|
|
2202
|
+
<h3 id="gate-side-subcommands-also-runnable-locally-for-triage">Gate-side subcommands (also runnable locally for triage)</h3>
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
|
|
2209
|
+
|
|
2210
|
+
|
|
2211
|
+
|
|
2212
|
+
|
|
2213
|
+
|
|
2214
|
+
|
|
2215
|
+
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
|
|
2222
|
+
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
|
|
2226
|
+
|
|
2227
|
+
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
|
|
2231
|
+
|
|
2232
|
+
|
|
2233
|
+
|
|
2234
|
+
|
|
2235
|
+
|
|
2236
|
+
|
|
2237
|
+
|
|
2238
|
+
<table><thead><tr><th>Command</th><th>Gate</th><th>Purpose</th></tr></thead><tbody><tr><td><code>knowledge-freshness link-check [<path>] [--files-from <json>]</code></td><td>2</td><td>HTTP-HEAD every <code>sources[*].url</code>; 2xx passes, else exit 1.</td></tr><tr><td><code>knowledge-freshness lint-unsourced [<path>] [--files-from <json>] [--diff <patch>]</code></td><td>3</td><td>Heuristic scan for normative language in new lines without a <code>sources[]</code> reference. Advisory: prints findings but always exits 0.</td></tr><tr><td><code>knowledge-freshness anti-over-rewrite [--files-from <json>] [--diff <patch>] [--pr-labels <csv>]</code></td><td>4</td><td>For each changed <code>stable</code> entry, compare deleted-line count to 20% of the body; exit 1 if crossed without <code>override:anti-over-rewrite</code>. The cron passes <code>--pr-labels ""</code> (it can't self-apply labels).</td></tr><tr><td><code>knowledge-freshness deep-guidance-check [<path>] [--files-from <json>]</code></td><td>5</td><td>Assert each changed entry still contains a <code>## Deep Guidance</code> heading (case-sensitive).</td></tr><tr><td><code>knowledge-freshness bump-version --title <str> --body <str></code></td><td>—</td><td>Pure-function dry-run of <code>deriveBumpKind</code> + <code>bumpSemver</code>; prints <code>bump:</code> and <code>next:</code> lines parsed by the version-bump workflow.</td></tr></tbody></table>
|
|
2239
|
+
<h2 id="operations-cheat-sheet">Operations cheat sheet</h2>
|
|
2240
|
+
<h3 id="an-entrys-audit-failed-in-the-cron">An entry's audit failed in the cron</h3>
|
|
2241
|
+
<p>The cron logs <code>audit failed for <name> — moving on</code> and continues; the entry
|
|
2242
|
+
stays in tomorrow's queue. Causes: provider auth (key rotated), source URL now
|
|
2243
|
+
404s, a fetch/HTTP error or the 5 MiB fetch-and-hash cap, dispatcher error, or
|
|
2244
|
+
LLM timeout. (A source body over the 96 KiB embed cap is <strong>truncated</strong> and
|
|
2245
|
+
flagged <code>truncated: true</code> — it does not fail the audit.) Reproduce locally:</p>
|
|
2246
|
+
<pre><code class="language-bash">DEEPSEEK_API_KEY=sk-… node dist/index.js knowledge-freshness \
|
|
2247
|
+
audit-run-entry content/knowledge/<cat>/<name>.md
|
|
2248
|
+
# read stderr to see if it's a URL issue or a provider issue
|
|
2249
|
+
</code></pre>
|
|
2250
|
+
<h3 id="lens-i-keeps-surfacing-a-topic-the-kb-already-covers">Lens I keeps surfacing a topic the KB already covers</h3>
|
|
2251
|
+
<p>Suppression didn't match. Either the resolver returned <code>root: null</code> (look for
|
|
2252
|
+
<code>[Lens I] knowledge-root not located</code> in stderr) or the entry's <code>name:</code> doesn't
|
|
2253
|
+
normalize to the same slug as the bucket topic — the match is exact and
|
|
2254
|
+
post-normalize.</p>
|
|
2255
|
+
<pre><code class="language-bash">scaffold observe audit --lens I-knowledge-gaps --json \
|
|
2256
|
+
--knowledge-root /path/to/content/knowledge \
|
|
2257
|
+
| jq '.findings[] | select(.lens_id=="I-knowledge-gaps")'
|
|
2258
|
+
grep -A1 "^---" content/knowledge/<cat>/<slug>.md | grep "^name:"
|
|
2259
|
+
</code></pre>
|
|
2260
|
+
<h3 id="downstream-auto-detect-cant-find-the-kb">Downstream auto-detect can't find the KB</h3>
|
|
2261
|
+
<p><code>findScaffoldKnowledgeRoot</code> walks parents from the CLI install's module location
|
|
2262
|
+
looking for <code>package.json#name === '@zigrivers/scaffold'</code>. Symlinked or
|
|
2263
|
+
repackaged installs may miss. Pin it via the tier-2 yaml:</p>
|
|
2264
|
+
<pre><code class="language-yaml">lenses:
|
|
2265
|
+
I-knowledge-gaps:
|
|
2266
|
+
knowledge_root: /opt/homebrew/lib/node_modules/@zigrivers/scaffold/content/knowledge
|
|
2267
|
+
</code></pre>
|
|
2268
|
+
<h3 id="yaml-knowledge-root-stops-working-after-an-upgrade">Yaml <code>knowledge_root</code> stops working after an upgrade</h3>
|
|
2269
|
+
<p>The yaml tier soft-fails and records the reason in the attempts trail; Lens I
|
|
2270
|
+
appends it to the warning. Validation requires all four: the path exists, is a
|
|
2271
|
+
directory, contains a <code><path>/VERSION</code> marker, and <code>loadKnowledgeIndex</code> runs
|
|
2272
|
+
without throwing (an empty index is OK). The usual cause after an upgrade is a
|
|
2273
|
+
moved install path:</p>
|
|
2274
|
+
<pre><code class="language-bash">find / -name VERSION -path '*content/knowledge*' 2>/dev/null
|
|
2275
|
+
</code></pre>
|
|
2276
|
+
<p>Then update <code>lenses.I-knowledge-gaps.knowledge_root</code> to the new path.</p>
|
|
2277
|
+
<h3 id="a-source-url-fetches-in-curl-but-the-cron-rejects-it">A source URL fetches in <code>curl</code> but the cron rejects it</h3>
|
|
2278
|
+
<p>The SSRF guard re-resolves the hostname at fetch time and rejects any IP in a
|
|
2279
|
+
non-globally-routable range (RFC1918, link-local, loopback, CGNAT, ULA,
|
|
2280
|
+
IPv4-mapped IPv6, …). Common cause: an internal DNS view returning a private IP
|
|
2281
|
+
for an outwardly-public hostname.</p>
|
|
2282
|
+
<pre><code class="language-bash">node -e 'require("node:dns").promises.lookup("<host>", { all: true }).then(console.log)'
|
|
2283
|
+
</code></pre>
|
|
2284
|
+
<p>Fix: move the source to a globally-routable host, or remove it. Allowlisting does
|
|
2285
|
+
<strong>not</strong> bypass the SSRF guard.</p>
|
|
2286
|
+
<h3 id="--knowledge-root-resolves-to-a-path-you-didnt-expect"><code>--knowledge-root</code> resolves to a path you didn't expect</h3>
|
|
2287
|
+
<p>Auto-detect may pick a stale npm-global install. The successful-resolution path
|
|
2288
|
+
doesn't log its <code>attempts</code> trail today (only the failure path warns), so pin and
|
|
2289
|
+
compare:</p>
|
|
2290
|
+
<pre><code class="language-bash">scaffold observe audit --lens I-knowledge-gaps --json \
|
|
2291
|
+
--knowledge-root /path/you/expected/content/knowledge \
|
|
2292
|
+
| jq '.findings[] | select(.lens_id=="I-knowledge-gaps") | .evidence.topic'
|
|
2293
|
+
# compare against the unset behavior; if the lists differ, auto-detect picked a different KB
|
|
2294
|
+
</code></pre>
|
|
2295
|
+
<p>Fix: pin <code>lenses.I-knowledge-gaps.knowledge_root</code> in
|
|
2296
|
+
<code>.scaffold/observability.yaml</code>. A pinned yaml path takes precedence over
|
|
2297
|
+
auto-detect.</p>
|
|
2298
|
+
<h2 id="config-reference">Config reference</h2>
|
|
2299
|
+
<p>Everything operator-tunable lives in <code>.scaffold/observability.yaml</code>. Anything
|
|
2300
|
+
outside this list is hardcoded (decision #7 invariant) so an untrusted project
|
|
2301
|
+
can't redirect dispatch commands or LLM URLs.</p>
|
|
2302
|
+
<pre><code class="language-yaml">lenses:
|
|
2303
|
+
I-knowledge-gaps:
|
|
2304
|
+
knowledge_root: /path/to/content/knowledge # tier-2 resolver override
|
|
2305
|
+
|
|
2306
|
+
disabled_lenses: [I-knowledge-gaps] # opt-out
|
|
2307
|
+
|
|
2308
|
+
phase_audit:
|
|
2309
|
+
enabled: true # default
|
|
2310
|
+
timeout_s: 60
|
|
2311
|
+
detached: false # fire-and-forget when true
|
|
2312
|
+
|
|
2313
|
+
fix:
|
|
2314
|
+
dispatcher_command: "claude -p" # default
|
|
2315
|
+
timeout_s: 300
|
|
2316
|
+
per_finding_max_attempts: 3
|
|
2317
|
+
</code></pre>
|
|
2318
|
+
<div class="callout callout-warning"><p><strong>The daily audit ceiling is NOT in yaml.</strong> The parent spec's decision #8 reads
|
|
2319
|
+
"10 grounded audits per day; configurable via <code>.scaffold/observability.yaml</code>",
|
|
2320
|
+
but the yaml knob was never implemented. The ceiling is the <code>--max=10</code> flag in
|
|
2321
|
+
<span class="fp" data-path=".github/workflows/knowledge-freshness-audit.yml:67">.github/workflows/knowledge-freshness-audit.yml:67</span>; the CLI default at
|
|
2322
|
+
<span class="fp" data-path="src/cli/commands/knowledge-freshness-audit-prefilter.ts:18">src/cli/commands/knowledge-freshness-audit-prefilter.ts:18</span> is the only
|
|
2323
|
+
fallback. To lower it for your fork, edit the workflow — nothing in yaml will help.</p></div>
|
|
2324
|
+
<h2 id="roadmap-and-known-divergences">Roadmap and known divergences</h2>
|
|
2325
|
+
<h3 id="phase-5-planned">Phase 5 (planned)</h3>
|
|
2326
|
+
<ul>
|
|
2327
|
+
<li><strong>Native MMR <code>knowledge-freshness</code> channel</strong> — runs automatically on freshness
|
|
2328
|
+
PRs (today the cron dispatches no MMR).</li>
|
|
2329
|
+
<li><strong>Frontier scan</strong> — augment cadence/hash triggers with a model-driven check:
|
|
2330
|
+
"has the underlying technology meaningfully changed since <code>last-reviewed</code>?"</li>
|
|
2331
|
+
<li><strong>Taxonomy cross-reference</strong> — detect when two entries assert contradictory
|
|
2332
|
+
facts and route to a reviewer.</li>
|
|
2333
|
+
</ul>
|
|
2334
|
+
<h3 id="known-divergences">Known divergences</h3>
|
|
2335
|
+
<p>The reference page's own audits surfaced these doc-vs-code mismatches; the code
|
|
2336
|
+
is ground truth:</p>
|
|
2337
|
+
<ul>
|
|
2338
|
+
<li><strong>MMR-in-cron framing</strong> — three docs describe it differently; decision #3
|
|
2339
|
+
(Phase 5 deferral) is authoritative.</li>
|
|
2340
|
+
<li><strong>Gate 4 override</strong> — spec says PR-description marker; code reads a PR <em>label</em>.</li>
|
|
2341
|
+
<li><strong>Daily ceiling</strong> — spec implies a yaml knob; only the <code>--max</code> flag exists.</li>
|
|
2342
|
+
<li><strong><code>operations.md</code> lags</strong> — labels the native MMR channel "Phase 4" and
|
|
2343
|
+
describes suppression in future tense; both have shipped.</li>
|
|
2344
|
+
<li><strong><code>www.</code> prefix inconsistency</strong> (P3) — mixed <code>www.</code> use in the allowlist; bare
|
|
2345
|
+
entries already auto-match subdomains, so the prefix is redundant.</li>
|
|
2346
|
+
</ul></main>
|
|
2347
|
+
</div>
|
|
2348
|
+
<script>(function(){
|
|
2349
|
+
var LS_KEY = 'guide-theme';
|
|
2350
|
+
function applyTheme(t) {
|
|
2351
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
2355
|
+
// ─── Theme toggle ────────────────────────────────────────────────────────
|
|
2356
|
+
document.querySelectorAll('[data-action="theme"]').forEach(function(btn) {
|
|
2357
|
+
btn.addEventListener('click', function() {
|
|
2358
|
+
var current = document.documentElement.getAttribute('data-theme');
|
|
2359
|
+
var next = current === 'dark' ? 'light' : 'dark';
|
|
2360
|
+
applyTheme(next);
|
|
2361
|
+
try { localStorage.setItem(LS_KEY, next); } catch(e) {}
|
|
2362
|
+
});
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
// ─── Mobile nav ──────────────────────────────────────────────────────────
|
|
2366
|
+
document.querySelectorAll('[data-action="nav"]').forEach(function(btn) {
|
|
2367
|
+
btn.addEventListener('click', function() {
|
|
2368
|
+
var rail = document.querySelector('.rail');
|
|
2369
|
+
if (rail) rail.classList.toggle('open');
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
|
|
2373
|
+
// ─── Copy buttons ─────────────────────────────────────────────────────────
|
|
2374
|
+
document.querySelectorAll('pre').forEach(function(pre) {
|
|
2375
|
+
if (!pre.parentNode) return;
|
|
2376
|
+
var wrapper = document.createElement('div');
|
|
2377
|
+
wrapper.className = 'code';
|
|
2378
|
+
pre.parentNode.insertBefore(wrapper, pre);
|
|
2379
|
+
wrapper.appendChild(pre);
|
|
2380
|
+
var btn = document.createElement('button');
|
|
2381
|
+
btn.className = 'copy-btn';
|
|
2382
|
+
btn.textContent = 'Copy';
|
|
2383
|
+
btn.addEventListener('click', function() {
|
|
2384
|
+
var text = pre.textContent || '';
|
|
2385
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2386
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
2387
|
+
btn.textContent = 'Copied';
|
|
2388
|
+
setTimeout(function() { btn.textContent = 'Copy'; }, 1200);
|
|
2389
|
+
}, function() {
|
|
2390
|
+
btn.textContent = 'Copy';
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
});
|
|
2394
|
+
wrapper.insertBefore(btn, pre);
|
|
2395
|
+
});
|
|
2396
|
+
|
|
2397
|
+
// ─── Tabs ─────────────────────────────────────────────────────────────────
|
|
2398
|
+
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
|
+
});
|
|
2408
|
+
});
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
// ─── Filter tables ────────────────────────────────────────────────────────
|
|
2413
|
+
document.querySelectorAll('.filter-input').forEach(function(input) {
|
|
2414
|
+
input.addEventListener('input', function() {
|
|
2415
|
+
var q = input.value.toLowerCase();
|
|
2416
|
+
var container = input.closest('.filter-table');
|
|
2417
|
+
if (!container) return;
|
|
2418
|
+
container.querySelectorAll('tbody tr').forEach(function(row) {
|
|
2419
|
+
var text = (row.textContent || '').toLowerCase();
|
|
2420
|
+
row.style.display = text.includes(q) ? '' : 'none';
|
|
2421
|
+
});
|
|
2422
|
+
});
|
|
2423
|
+
});
|
|
2424
|
+
|
|
2425
|
+
// ─── Scrollspy ────────────────────────────────────────────────────────────
|
|
2426
|
+
if (typeof IntersectionObserver === 'undefined') return;
|
|
2427
|
+
var headings = document.querySelectorAll('h2[id],h3[id]');
|
|
2428
|
+
if (!headings.length) return;
|
|
2429
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
2430
|
+
entries.forEach(function(entry) {
|
|
2431
|
+
if (!entry.isIntersecting) return;
|
|
2432
|
+
var id = entry.target.getAttribute('id');
|
|
2433
|
+
document.querySelectorAll('.toc a').forEach(function(a) {
|
|
2434
|
+
a.classList.toggle('active', a.getAttribute('href') === '#' + id);
|
|
2435
|
+
});
|
|
2436
|
+
});
|
|
2437
|
+
}, { rootMargin: '0px 0px -70% 0px', threshold: 0 });
|
|
2438
|
+
headings.forEach(function(h) { observer.observe(h); });
|
|
2439
|
+
});
|
|
2440
|
+
})();</script>
|
|
2441
|
+
</body>
|
|
2442
|
+
</html>
|