@northtek/overstory 0.1.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/LICENSE +201 -0
- package/README.md +115 -0
- package/dist/build/aggregate.d.ts +9 -0
- package/dist/build/aggregate.js +74 -0
- package/dist/build/builder.d.ts +52 -0
- package/dist/build/builder.js +223 -0
- package/dist/build/inmemory.d.ts +16 -0
- package/dist/build/inmemory.js +79 -0
- package/dist/build/pool.d.ts +3 -0
- package/dist/build/pool.js +16 -0
- package/dist/build/reflexion.d.ts +15 -0
- package/dist/build/reflexion.js +116 -0
- package/dist/build/summarize.d.ts +25 -0
- package/dist/build/summarize.js +146 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +323 -0
- package/dist/core/bm25.d.ts +19 -0
- package/dist/core/bm25.js +48 -0
- package/dist/core/chunk.d.ts +5 -0
- package/dist/core/chunk.js +91 -0
- package/dist/core/corpus.d.ts +21 -0
- package/dist/core/corpus.js +112 -0
- package/dist/core/gate.d.ts +25 -0
- package/dist/core/gate.js +155 -0
- package/dist/core/hash.d.ts +7 -0
- package/dist/core/hash.js +15 -0
- package/dist/core/store.d.ts +74 -0
- package/dist/core/store.js +66 -0
- package/dist/core/types.d.ts +96 -0
- package/dist/core/types.js +1 -0
- package/dist/fix/prompts.d.ts +4 -0
- package/dist/fix/prompts.js +124 -0
- package/dist/fix/scan.d.ts +18 -0
- package/dist/fix/scan.js +197 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +22 -0
- package/dist/llm/anthropic.d.ts +11 -0
- package/dist/llm/anthropic.js +46 -0
- package/dist/llm/mock.d.ts +5 -0
- package/dist/llm/mock.js +20 -0
- package/dist/llm/ollama.d.ts +11 -0
- package/dist/llm/ollama.js +49 -0
- package/dist/llm/provider.d.ts +16 -0
- package/dist/llm/provider.js +7 -0
- package/dist/llm/resolve.d.ts +14 -0
- package/dist/llm/resolve.js +24 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +136 -0
- package/dist/query/ask.d.ts +53 -0
- package/dist/query/ask.js +127 -0
- package/dist/query/notarize.d.ts +40 -0
- package/dist/query/notarize.js +31 -0
- package/dist/registry/github.d.ts +28 -0
- package/dist/registry/github.js +113 -0
- package/dist/registry/publishClient.d.ts +25 -0
- package/dist/registry/publishClient.js +39 -0
- package/dist/registry/registry.d.ts +35 -0
- package/dist/registry/registry.js +57 -0
- package/dist/registry/repoTree.d.ts +9 -0
- package/dist/registry/repoTree.js +32 -0
- package/dist/serve/app.d.ts +4 -0
- package/dist/serve/app.js +809 -0
- package/dist/serve/server.d.ts +9 -0
- package/dist/serve/server.js +180 -0
- package/dist/serve/threads.d.ts +31 -0
- package/dist/serve/threads.js +69 -0
- package/dist/site/data.d.ts +58 -0
- package/dist/site/data.js +88 -0
- package/dist/site/generate.d.ts +21 -0
- package/dist/site/generate.js +827 -0
- package/package.json +64 -0
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
const esc = (s) => s.replace(/&/gu, '&').replace(/</gu, '<').replace(/>/gu, '>').replace(/"/gu, '"');
|
|
2
|
+
/** Render the self-contained explorer. One HTML file, zero external scripts; data embedded
|
|
3
|
+
* as JSON; fonts degrade gracefully offline (air-gapped is a product promise).
|
|
4
|
+
* `ask` (registry pages only) adds the Ask panel: run-locally instructions plus browser-side
|
|
5
|
+
* BYO-key ask — the visitor's key calls Anthropic directly from their browser; the server
|
|
6
|
+
* never sees keys, questions, or answers. */
|
|
7
|
+
export const generateSiteHtml = (data, opts = {}) => {
|
|
8
|
+
const json = JSON.stringify(data).replace(/</gu, '\\u003c');
|
|
9
|
+
const askJson = JSON.stringify(opts.ask ?? null).replace(/</gu, '\\u003c');
|
|
10
|
+
const shareMeta = opts.share
|
|
11
|
+
? `
|
|
12
|
+
<meta property="og:title" content="${esc(data.name)} — a verified map of this codebase">
|
|
13
|
+
<meta property="og:description" content="${esc(opts.share.status)} Every statement links to the exact lines that prove it — checked mechanically, not just displayed.">
|
|
14
|
+
<meta property="og:url" content="${esc(opts.share.url)}">
|
|
15
|
+
<meta property="og:type" content="website">
|
|
16
|
+
<meta property="og:site_name" content="OVERSTORY">
|
|
17
|
+
<meta name="twitter:card" content="summary">
|
|
18
|
+
<meta name="twitter:title" content="${esc(data.name)} — verified codebase map">
|
|
19
|
+
<meta name="twitter:description" content="${esc(opts.share.status)}">`
|
|
20
|
+
: '';
|
|
21
|
+
return `<!doctype html>
|
|
22
|
+
<html lang="en">
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="utf-8">
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
26
|
+
<title>${esc(data.name)} — OVERSTORY</title>
|
|
27
|
+
<meta name="description" content="Knowledge tree for ${esc(data.name)}: every claim carries a verifiable receipt.">${shareMeta}
|
|
28
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
29
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
30
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
31
|
+
<style>
|
|
32
|
+
:root {
|
|
33
|
+
--bg0:#FAF9F5; --bg1:#FFFFFF; --bg2:#F1EFE9; --bg3:#E9E6DD; --line:#E4E1D8;
|
|
34
|
+
--text:#1A1D1B; --text2:#5C6660; --text3:#98A19A;
|
|
35
|
+
--accent:#1F7A5C;
|
|
36
|
+
--verified:#1F7A5C; --stale:#B8860B; --missing:#C0453B; --unchecked:#98A19A;
|
|
37
|
+
--serif:"Fraunces",Georgia,"Iowan Old Style",serif;
|
|
38
|
+
--sans:"Inter",system-ui,-apple-system,sans-serif;
|
|
39
|
+
--mono:"JetBrains Mono",ui-monospace,"Cascadia Code",Consolas,monospace;
|
|
40
|
+
--ease:cubic-bezier(0.2,0,0,1);
|
|
41
|
+
}
|
|
42
|
+
@media (prefers-color-scheme: dark) {
|
|
43
|
+
:root {
|
|
44
|
+
--bg0:#101312; --bg1:#171B19; --bg2:#1D2220; --bg3:#242A26; --line:#2A302C;
|
|
45
|
+
--text:#F0F2EF; --text2:#A3ACA5; --text3:#6E7770;
|
|
46
|
+
--accent:#34A47C;
|
|
47
|
+
--verified:#34A47C; --stale:#D9A441; --missing:#D06258; --unchecked:#6E7770;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
* { box-sizing:border-box; margin:0; padding:0; }
|
|
51
|
+
html { height:100%; }
|
|
52
|
+
body { height:100%; background:var(--bg0); color:var(--text); font:400 14px/1.5 var(--sans); }
|
|
53
|
+
button { font:inherit; color:inherit; background:none; border:none; cursor:pointer; }
|
|
54
|
+
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:3px; }
|
|
55
|
+
::selection { background:color-mix(in srgb, var(--accent) 22%, transparent); }
|
|
56
|
+
|
|
57
|
+
.app { display:grid; grid-template-rows:56px 1fr; height:100%; max-width:1600px; margin:0 auto; }
|
|
58
|
+
.frame { display:grid; grid-template-columns:300px 1fr; min-height:0; border-top:1px solid var(--line); }
|
|
59
|
+
|
|
60
|
+
/* header */
|
|
61
|
+
.hdr { display:flex; align-items:center; gap:16px; padding:0 20px; }
|
|
62
|
+
.mark { display:flex; align-items:center; gap:10px; }
|
|
63
|
+
.mark svg { display:block; }
|
|
64
|
+
.wordmark { font:600 17px/1 var(--serif); letter-spacing:.16em; }
|
|
65
|
+
.treename { color:var(--text2); font-size:12.5px; padding:3px 8px; background:var(--bg2); border:1px solid var(--line); border-radius:4px; font-family:var(--mono); }
|
|
66
|
+
.hdr-spacer { flex:1; }
|
|
67
|
+
.fresh { display:flex; align-items:baseline; gap:8px; }
|
|
68
|
+
.fresh-num { font:500 17px/1 var(--mono); font-variant-numeric:tabular-nums; color:var(--verified); }
|
|
69
|
+
.fresh-num.warn { color:var(--stale); }
|
|
70
|
+
.fresh-label { font-size:11px; color:var(--text3); letter-spacing:.04em; text-transform:uppercase; }
|
|
71
|
+
.search { position:relative; }
|
|
72
|
+
.search input { width:260px; height:32px; padding:0 30px 0 10px; background:var(--bg1); border:1px solid var(--line); border-radius:5px; color:var(--text); font:400 12.5px var(--sans); }
|
|
73
|
+
.search input::placeholder { color:var(--text3); }
|
|
74
|
+
.search kbd { position:absolute; right:8px; top:7px; font:400 10px var(--mono); color:var(--text3); border:1px solid var(--line); border-radius:3px; padding:1px 5px; background:var(--bg2); }
|
|
75
|
+
.rail-toggle { display:none; }
|
|
76
|
+
|
|
77
|
+
/* tree rail */
|
|
78
|
+
.rail { overflow-y:auto; border-right:1px solid var(--line); background:var(--bg1); padding:12px 8px 40px; }
|
|
79
|
+
.rail-hint { font-size:11px; color:var(--text3); padding:2px 10px 10px; letter-spacing:.04em; }
|
|
80
|
+
.tnode { user-select:none; }
|
|
81
|
+
.trow { display:flex; align-items:center; gap:6px; height:28px; padding:0 8px; border-radius:5px; width:100%; text-align:left; font-size:12.5px; color:var(--text2); }
|
|
82
|
+
.trow:hover { background:var(--bg2); color:var(--text); }
|
|
83
|
+
.trow.active { background:var(--bg3); color:var(--text); }
|
|
84
|
+
.trow .caret { width:10px; flex:none; color:var(--text3); font-size:9px; transition:transform 120ms var(--ease); }
|
|
85
|
+
.trow.open .caret { transform:rotate(90deg); }
|
|
86
|
+
.trow .dot { width:6px; height:6px; border-radius:50%; flex:none; }
|
|
87
|
+
.trow .name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:var(--mono); font-size:12px; }
|
|
88
|
+
.tchildren { margin-left:14px; border-left:1px solid var(--line); padding-left:4px; }
|
|
89
|
+
|
|
90
|
+
/* main */
|
|
91
|
+
.main { overflow-y:auto; padding:28px 36px 80px; min-width:0; }
|
|
92
|
+
.crumbs { display:flex; flex-wrap:wrap; gap:6px; font:400 12px var(--mono); color:var(--text3); margin-bottom:10px; }
|
|
93
|
+
.crumbs button { color:var(--text3); padding:1px 2px; }
|
|
94
|
+
.crumbs button:hover { color:var(--accent); }
|
|
95
|
+
.crumbs .sep { color:var(--line); }
|
|
96
|
+
.node-title { font:500 26px/1.2 var(--serif); margin-bottom:4px; }
|
|
97
|
+
.node-meta { display:flex; gap:10px; align-items:center; color:var(--text3); font-size:12px; margin-bottom:24px; }
|
|
98
|
+
.node-meta .chip { padding:2px 7px; border:1px solid var(--line); border-radius:4px; background:var(--bg1); font-family:var(--mono); font-size:11px; }
|
|
99
|
+
|
|
100
|
+
.ledger { border:1px solid var(--line); border-radius:8px; background:var(--bg1); overflow:hidden; }
|
|
101
|
+
.ledger-head { display:flex; justify-content:space-between; padding:10px 16px; border-bottom:1px solid var(--line); background:var(--bg2); font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--text3); }
|
|
102
|
+
.claim { border-bottom:1px solid var(--line); }
|
|
103
|
+
.claim:last-child { border-bottom:none; }
|
|
104
|
+
.claim-row { display:flex; align-items:flex-start; gap:12px; width:100%; text-align:left; padding:13px 16px; min-height:48px; }
|
|
105
|
+
.claim-row:hover { background:var(--bg2); }
|
|
106
|
+
.claim-row[aria-expanded="true"] { background:var(--bg2); }
|
|
107
|
+
.seal { flex:none; margin-top:1px; display:inline-flex; align-items:center; gap:5px; font:500 10.5px var(--mono); letter-spacing:.05em; padding:2px 8px; border-radius:999px; border:1px solid; }
|
|
108
|
+
.seal.VERIFIED { color:var(--verified); border-color:color-mix(in srgb, var(--verified) 40%, transparent); background:color-mix(in srgb, var(--verified) 9%, transparent); }
|
|
109
|
+
.seal.STALE { color:var(--stale); border-color:color-mix(in srgb, var(--stale) 40%, transparent); background:color-mix(in srgb, var(--stale) 9%, transparent); }
|
|
110
|
+
.seal.OUT_OF_CORPUS, .seal.UNGROUNDED { color:var(--missing); border-color:color-mix(in srgb, var(--missing) 40%, transparent); background:color-mix(in srgb, var(--missing) 9%, transparent); }
|
|
111
|
+
.claim-text { flex:1; min-width:0; font-size:14px; color:var(--text); overflow-wrap:anywhere; }
|
|
112
|
+
.flag { flex:none; font:400 10.5px var(--mono); color:var(--missing); border:1px dashed rgba(196,85,77,.5); padding:1px 6px; border-radius:4px; margin-top:2px; }
|
|
113
|
+
.flag.unchecked { color:var(--unchecked); border-color:rgba(138,148,139,.4); }
|
|
114
|
+
.unfold { flex:none; display:inline-flex; align-items:center; gap:6px; margin-top:3px; font:400 10.5px var(--mono); color:var(--text3); white-space:nowrap; }
|
|
115
|
+
.unfold .u-caret { display:inline-block; font-size:9px; transition:transform 120ms var(--ease); }
|
|
116
|
+
.claim-row[aria-expanded="true"] .u-caret { transform:rotate(90deg); }
|
|
117
|
+
.claim-row:hover .unfold { color:var(--text2); }
|
|
118
|
+
.receipt { overflow:hidden; }
|
|
119
|
+
.receipt-inner { margin:0 16px 14px 16px; border:1px solid var(--line); border-radius:6px; background:var(--bg0); }
|
|
120
|
+
.receipt-top { display:flex; flex-wrap:wrap; gap:10px; align-items:center; padding:8px 12px; border-bottom:1px dashed var(--line); font:400 11px var(--mono); color:var(--text2); }
|
|
121
|
+
.receipt-top .file { color:var(--accent); }
|
|
122
|
+
.receipt-top .hash { color:var(--text3); margin-left:auto; }
|
|
123
|
+
.receipt pre { overflow-x:auto; padding:10px 0; font:400 12px/1.5 var(--mono); }
|
|
124
|
+
.srcline { display:flex; }
|
|
125
|
+
.srcline .ln { flex:none; width:52px; text-align:right; padding-right:12px; color:var(--text3); user-select:none; }
|
|
126
|
+
.srcline .code { white-space:pre; color:var(--text2); padding-right:16px; }
|
|
127
|
+
.refline { padding:9px 12px; font-size:12.5px; }
|
|
128
|
+
.refline button { color:var(--accent); font-family:var(--mono); font-size:12px; }
|
|
129
|
+
.refline button:hover { text-decoration:underline; }
|
|
130
|
+
|
|
131
|
+
.empty { padding:48px 24px; text-align:center; color:var(--text2); }
|
|
132
|
+
.empty .cmd { display:inline-block; margin-top:12px; font:400 12.5px var(--mono); background:var(--bg2); border:1px solid var(--line); padding:6px 12px; border-radius:6px; color:var(--text); }
|
|
133
|
+
|
|
134
|
+
.legend { margin-top:22px; font-size:11.5px; color:var(--text3); line-height:1.7; }
|
|
135
|
+
.legend b { color:var(--text2); font-weight:500; }
|
|
136
|
+
|
|
137
|
+
@media (max-width:720px) {
|
|
138
|
+
.frame { grid-template-columns:1fr; }
|
|
139
|
+
.rail { position:fixed; inset:56px 25% 0 0; z-index:20; transform:translateX(-100%); transition:transform 220ms var(--ease); box-shadow:none; }
|
|
140
|
+
.rail.open { transform:none; border-right:1px solid var(--line); }
|
|
141
|
+
.rail-toggle { display:block; padding:4px 10px; border:1px solid var(--line); border-radius:5px; font-size:12px; color:var(--text2); }
|
|
142
|
+
.search input { width:150px; }
|
|
143
|
+
.main { padding:20px 16px 60px; }
|
|
144
|
+
.treename { display:none; }
|
|
145
|
+
}
|
|
146
|
+
@media (prefers-reduced-motion:reduce) {
|
|
147
|
+
* { transition:none !important; animation:none !important; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ---- first-visit welcome ---- */
|
|
151
|
+
.welcome { border:1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); background:color-mix(in srgb, var(--accent) 6%, var(--bg1)); border-radius:10px; padding:16px 18px; margin-bottom:22px; }
|
|
152
|
+
.welcome .w-title { font:500 15px var(--serif); margin-bottom:6px; }
|
|
153
|
+
.welcome .w-body { font-size:13px; color:var(--text2); line-height:1.6; max-width:72ch; }
|
|
154
|
+
.welcome .w-body b { color:var(--text); font-weight:500; }
|
|
155
|
+
.welcome .w-row { display:flex; gap:14px; align-items:center; margin-top:10px; }
|
|
156
|
+
.welcome .w-got { padding:6px 14px; border-radius:6px; background:var(--accent); color:#fff; font:500 12.5px var(--sans); }
|
|
157
|
+
.welcome .w-num { font:500 12px var(--mono); color:var(--accent); }
|
|
158
|
+
|
|
159
|
+
/* ---- Fixes view ---- */
|
|
160
|
+
.fix-btn { display:none; align-items:center; gap:6px; height:32px; padding:0 12px; border-radius:6px; border:1px solid var(--line); background:var(--bg1); color:var(--text2); font:500 12px var(--sans); }
|
|
161
|
+
.fix-btn:hover { border-color:var(--accent); color:var(--accent); }
|
|
162
|
+
.fix-btn.on { background:var(--bg3); color:var(--text); }
|
|
163
|
+
body[data-fixes] .fix-btn { display:inline-flex; }
|
|
164
|
+
.fix-btn .n { font:500 10.5px var(--mono); color:var(--accent); }
|
|
165
|
+
.fix-intro { color:var(--text2); font-size:13px; max-width:70ch; margin-bottom:18px; }
|
|
166
|
+
.fix-card { border:1px solid var(--line); border-radius:8px; background:var(--bg1); margin-bottom:10px; }
|
|
167
|
+
.fix-card .fc-row { display:flex; align-items:flex-start; gap:10px; padding:12px 15px; }
|
|
168
|
+
.fix-card .fc-sev { flex:none; margin-top:2px; font:500 10px var(--mono); padding:1px 7px; border-radius:999px; border:1px solid; }
|
|
169
|
+
.fix-card .fc-sev.s1 { color:var(--missing); border-color:color-mix(in srgb, var(--missing) 40%, transparent); }
|
|
170
|
+
.fix-card .fc-sev.s2 { color:var(--stale); border-color:color-mix(in srgb, var(--stale) 40%, transparent); }
|
|
171
|
+
.fix-card .fc-sev.s3 { color:var(--text3); border-color:var(--line); }
|
|
172
|
+
.fix-card .fc-main { flex:1; min-width:0; }
|
|
173
|
+
.fix-card .fc-title { font:500 13.5px var(--sans); overflow-wrap:anywhere; }
|
|
174
|
+
.fix-card .fc-detail { font-size:12.5px; color:var(--text2); margin-top:2px; overflow-wrap:anywhere; }
|
|
175
|
+
.fix-card .fc-meta { font:400 10.5px var(--mono); color:var(--text3); margin-top:4px; }
|
|
176
|
+
.fix-card .fc-copy { flex:none; padding:6px 12px; border:1px solid var(--line); border-radius:6px; font-size:12px; color:var(--accent); white-space:nowrap; }
|
|
177
|
+
.fix-card .fc-copy:hover { border-color:var(--accent); }
|
|
178
|
+
|
|
179
|
+
/* ---- Ask panel (registry pages only) ---- */
|
|
180
|
+
.ask-open-btn { display:none; align-items:center; gap:7px; height:32px; padding:0 14px; border-radius:6px; background:var(--accent); color:#fff; font:500 12.5px var(--sans); }
|
|
181
|
+
.ask-open-btn:hover { filter:brightness(1.08); }
|
|
182
|
+
body[data-ask] .ask-open-btn { display:inline-flex; }
|
|
183
|
+
.ask-panel { position:fixed; top:0; right:0; bottom:0; width:min(440px, 92vw); z-index:120; background:var(--bg1); border-left:1px solid var(--line); box-shadow:-8px 0 32px rgba(0,0,0,.12); transform:translateX(100%); transition:transform 220ms var(--ease); display:flex; flex-direction:column; }
|
|
184
|
+
.ask-panel.open { transform:none; }
|
|
185
|
+
.ap-head { display:flex; align-items:center; gap:10px; padding:14px 18px; border-bottom:1px solid var(--line); }
|
|
186
|
+
.ap-head .t { font:500 15px var(--serif); }
|
|
187
|
+
.ap-close { margin-left:auto; color:var(--text3); font-size:16px; padding:2px 8px; }
|
|
188
|
+
.ap-tabs { display:flex; gap:4px; padding:10px 14px 0; }
|
|
189
|
+
.ap-tab { flex:1; padding:8px 10px; border-radius:7px 7px 0 0; font-size:12.5px; color:var(--text2); border:1px solid transparent; }
|
|
190
|
+
.ap-tab.on { background:var(--bg0); border-color:var(--line); border-bottom-color:var(--bg0); color:var(--text); font-weight:500; }
|
|
191
|
+
.ap-body { flex:1; overflow-y:auto; background:var(--bg0); border-top:1px solid var(--line); margin-top:-1px; padding:16px; font-size:13px; }
|
|
192
|
+
.ap-body h4 { font:500 13.5px var(--sans); margin:0 0 6px; }
|
|
193
|
+
.ap-body p { color:var(--text2); margin:0 0 10px; line-height:1.55; }
|
|
194
|
+
.ap-cmds { background:var(--bg1); border:1px solid var(--line); border-radius:8px; padding:10px 12px; font:400 11.5px/1.9 var(--mono); overflow-x:auto; white-space:pre; }
|
|
195
|
+
.ap-copy { margin-top:8px; padding:6px 12px; border:1px solid var(--line); border-radius:6px; font-size:12px; color:var(--text2); }
|
|
196
|
+
.ap-copy:hover { border-color:var(--accent); color:var(--accent); }
|
|
197
|
+
.ap-field { width:100%; box-sizing:border-box; background:var(--bg1); border:1px solid var(--line); border-radius:7px; color:var(--text); font:400 12.5px var(--sans); padding:9px 11px; margin-bottom:8px; }
|
|
198
|
+
.ap-field:focus { outline:none; border-color:var(--accent); }
|
|
199
|
+
.ap-row { display:flex; gap:8px; align-items:center; margin-bottom:10px; color:var(--text3); font-size:11.5px; }
|
|
200
|
+
.ap-go { width:100%; padding:9px; border-radius:7px; background:var(--accent); color:#fff; font:500 13px var(--sans); }
|
|
201
|
+
.ap-go:disabled { opacity:.4; }
|
|
202
|
+
.ap-status { color:var(--text2); font-size:12px; padding:10px 2px; }
|
|
203
|
+
.ap-answer { margin-top:10px; font-size:13px; line-height:1.65; }
|
|
204
|
+
.ap-chip { display:inline-flex; align-items:center; gap:3px; vertical-align:1px; margin:0 2px; padding:0 6px; border-radius:999px; font:500 10px var(--mono); border:1px solid var(--line); color:var(--accent); background:var(--bg1); }
|
|
205
|
+
.ap-chip .d { width:5px; height:5px; border-radius:50%; }
|
|
206
|
+
.ap-withheld { margin-top:10px; border-left:2px solid var(--stale); padding:4px 0 4px 10px; color:var(--text2); font-size:12px; }
|
|
207
|
+
.ap-receipt { margin:8px 0; border:1px solid var(--line); border-radius:7px; background:var(--bg1); overflow:hidden; }
|
|
208
|
+
.ap-receipt .rt { padding:6px 10px; border-bottom:1px dashed var(--line); font:400 10.5px var(--mono); color:var(--accent); }
|
|
209
|
+
.ap-receipt pre { margin:0; padding:8px 0; overflow-x:auto; font:400 11px/1.5 var(--mono); }
|
|
210
|
+
.ap-note { margin-top:12px; padding-top:10px; border-top:1px solid var(--line); color:var(--text3); font-size:11px; line-height:1.5; }
|
|
211
|
+
.ap-err { color:var(--missing); font-size:12px; padding:8px 2px; }
|
|
212
|
+
</style>
|
|
213
|
+
</head>
|
|
214
|
+
<body>
|
|
215
|
+
<script id="overstory-data" type="application/json">${json}</script>
|
|
216
|
+
<script id="overstory-ask-config" type="application/json">${askJson}</script>
|
|
217
|
+
<div class="app">
|
|
218
|
+
<header class="hdr">
|
|
219
|
+
<button class="rail-toggle" id="railToggle" aria-label="Toggle tree">tree</button>
|
|
220
|
+
<div class="mark" aria-hidden="true">
|
|
221
|
+
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
|
|
222
|
+
<circle cx="11" cy="8" r="5.2" stroke="var(--accent)" stroke-width="1.4"/>
|
|
223
|
+
<circle cx="6.6" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.2" opacity=".8"/>
|
|
224
|
+
<circle cx="15.4" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.2" opacity=".5"/>
|
|
225
|
+
<path d="M11 13v6" stroke="var(--text3)" stroke-width="1.4" stroke-linecap="round"/>
|
|
226
|
+
</svg>
|
|
227
|
+
</div>
|
|
228
|
+
<span class="wordmark">OVERSTORY</span>
|
|
229
|
+
<span class="treename" id="treeName"></span>
|
|
230
|
+
<div class="hdr-spacer"></div>
|
|
231
|
+
<div class="fresh" id="fresh" title="Fraction of claims whose receipts verify against the code as of this build">
|
|
232
|
+
<span class="fresh-num" id="freshNum">0%</span>
|
|
233
|
+
<span class="fresh-label" id="freshLabel">verified</span>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="search">
|
|
236
|
+
<input id="searchInput" type="search" placeholder="Search claims" aria-label="Search claims">
|
|
237
|
+
<kbd>/</kbd>
|
|
238
|
+
</div>
|
|
239
|
+
<button class="fix-btn" id="fixesBtn"></button>
|
|
240
|
+
<button class="ask-open-btn" id="askOpen">Ask this codebase</button>
|
|
241
|
+
</header>
|
|
242
|
+
<aside class="ask-panel" id="askPanel" aria-label="Ask this codebase" hidden>
|
|
243
|
+
<div class="ap-head"><span class="t">Ask this codebase</span><button class="ap-close" id="askClose" aria-label="Close">✕</button></div>
|
|
244
|
+
<div class="ap-tabs">
|
|
245
|
+
<button class="ap-tab on" id="tabLocal">Run locally (private)</button>
|
|
246
|
+
<button class="ap-tab" id="tabByok">Ask here (your key)</button>
|
|
247
|
+
</div>
|
|
248
|
+
<div class="ap-body" id="apLocal"></div>
|
|
249
|
+
<div class="ap-body" id="apByok" hidden></div>
|
|
250
|
+
</aside>
|
|
251
|
+
<div class="frame">
|
|
252
|
+
<nav class="rail" id="rail" aria-label="Knowledge tree">
|
|
253
|
+
<div class="rail-hint" title="A green dot means everything inside checks out against the code; amber or red means something in that folder needs attention.">TRUST MAP — every dot is checked against the code</div>
|
|
254
|
+
<div id="treeRoot" role="tree"></div>
|
|
255
|
+
</nav>
|
|
256
|
+
<main class="main" id="main" tabindex="-1"></main>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<script>
|
|
260
|
+
(function () {
|
|
261
|
+
'use strict';
|
|
262
|
+
var DATA = JSON.parse(document.getElementById('overstory-data').textContent);
|
|
263
|
+
var state = { current: DATA.root, open: {}, query: '' };
|
|
264
|
+
state.open[DATA.root] = true;
|
|
265
|
+
|
|
266
|
+
var SEAL_LABEL = { VERIFIED: 'verified', STALE: 'stale', OUT_OF_CORPUS: 'missing', UNGROUNDED: 'ungrounded' };
|
|
267
|
+
var SEAL_TITLE = {
|
|
268
|
+
VERIFIED: 'The cited lines exist and are unchanged.',
|
|
269
|
+
STALE: 'The cited evidence changed since this build. Rebuild to refresh.',
|
|
270
|
+
OUT_OF_CORPUS: 'The citation no longer resolves to a file in the corpus.',
|
|
271
|
+
UNGROUNDED: 'This claim carries no valid citation.'
|
|
272
|
+
};
|
|
273
|
+
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
274
|
+
|
|
275
|
+
function el(tag, cls, text) {
|
|
276
|
+
var node = document.createElement(tag);
|
|
277
|
+
if (cls) node.className = cls;
|
|
278
|
+
if (text !== undefined) node.textContent = text;
|
|
279
|
+
return node;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* header */
|
|
283
|
+
document.getElementById('treeName').textContent = DATA.name;
|
|
284
|
+
document.title = DATA.name + ' — OVERSTORY';
|
|
285
|
+
function renderFresh() {
|
|
286
|
+
var pct = Math.round(DATA.freshness * 100);
|
|
287
|
+
var numEl = document.getElementById('freshNum');
|
|
288
|
+
var label = document.getElementById('freshLabel');
|
|
289
|
+
if (pct < 100) numEl.classList.add('warn');
|
|
290
|
+
label.textContent = pct === 100
|
|
291
|
+
? DATA.total + ' of ' + DATA.total + ' claims verified'
|
|
292
|
+
: (DATA.total - DATA.verified) + ' of ' + DATA.total + ' claims need attention';
|
|
293
|
+
if (reduced) { numEl.textContent = pct + '%'; return; }
|
|
294
|
+
var v = 0, target = pct, vel = 0, start = null;
|
|
295
|
+
function step(ts) {
|
|
296
|
+
if (!start) start = ts;
|
|
297
|
+
vel += (target - v) * 0.12;
|
|
298
|
+
vel *= 0.62;
|
|
299
|
+
v += vel;
|
|
300
|
+
if (Math.abs(target - v) < 0.5 && Math.abs(vel) < 0.5 || ts - start > 1000) {
|
|
301
|
+
numEl.textContent = target + '%';
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
numEl.textContent = Math.round(v) + '%';
|
|
305
|
+
requestAnimationFrame(step);
|
|
306
|
+
}
|
|
307
|
+
requestAnimationFrame(step);
|
|
308
|
+
}
|
|
309
|
+
renderFresh();
|
|
310
|
+
|
|
311
|
+
/* tree rail */
|
|
312
|
+
function dotColor(worst) {
|
|
313
|
+
if (worst === 'VERIFIED') return 'var(--verified)';
|
|
314
|
+
if (worst === 'STALE') return 'var(--stale)';
|
|
315
|
+
return 'var(--missing)';
|
|
316
|
+
}
|
|
317
|
+
function renderTree() {
|
|
318
|
+
var host = document.getElementById('treeRoot');
|
|
319
|
+
host.textContent = '';
|
|
320
|
+
host.appendChild(renderTreeNode(DATA.root, 0));
|
|
321
|
+
}
|
|
322
|
+
function renderTreeNode(id, depth) {
|
|
323
|
+
var node = DATA.nodes[id];
|
|
324
|
+
var wrap = el('div', 'tnode');
|
|
325
|
+
var hasKids = node.childIds.length > 0;
|
|
326
|
+
var row = el('button', 'trow' + (state.current === id ? ' active' : '') + (state.open[id] ? ' open' : ''));
|
|
327
|
+
row.setAttribute('role', 'treeitem');
|
|
328
|
+
row.setAttribute('aria-expanded', hasKids ? String(!!state.open[id]) : 'false');
|
|
329
|
+
row.title = node.verified + ' of ' + node.total + ' claims verified in this subtree';
|
|
330
|
+
row.appendChild(el('span', 'caret', hasKids ? '\\u25B6' : ''));
|
|
331
|
+
var dot = el('span', 'dot');
|
|
332
|
+
dot.style.background = dotColor(node.worst);
|
|
333
|
+
row.appendChild(dot);
|
|
334
|
+
row.appendChild(el('span', 'name', node.kind === 'root' ? node.title : (node.title + (node.kind === 'dir' ? '/' : ''))));
|
|
335
|
+
row.addEventListener('click', function () {
|
|
336
|
+
if (hasKids) state.open[id] = !state.open[id];
|
|
337
|
+
go(id);
|
|
338
|
+
});
|
|
339
|
+
wrap.appendChild(row);
|
|
340
|
+
if (hasKids && state.open[id]) {
|
|
341
|
+
var kids = el('div', 'tchildren');
|
|
342
|
+
kids.setAttribute('role', 'group');
|
|
343
|
+
node.childIds.forEach(function (cid) { kids.appendChild(renderTreeNode(cid, depth + 1)); });
|
|
344
|
+
wrap.appendChild(kids);
|
|
345
|
+
}
|
|
346
|
+
return wrap;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* main */
|
|
350
|
+
function crumbsFor(id) {
|
|
351
|
+
var chain = [];
|
|
352
|
+
var path = DATA.nodes[id].path;
|
|
353
|
+
chain.push({ id: DATA.root, label: DATA.nodes[DATA.root].title });
|
|
354
|
+
if (path) {
|
|
355
|
+
var parts = path.split('/');
|
|
356
|
+
var acc = '';
|
|
357
|
+
for (var i = 0; i < parts.length; i++) {
|
|
358
|
+
acc = acc ? acc + '/' + parts[i] : parts[i];
|
|
359
|
+
var nid = (i === parts.length - 1 && DATA.nodes[id].kind === 'leaf') ? 'leaf:' + acc : 'dir:' + acc;
|
|
360
|
+
if (DATA.nodes[nid]) chain.push({ id: nid, label: parts[i] });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return chain;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function welcomeCard() {
|
|
367
|
+
var pct = Math.round(DATA.freshness * 100);
|
|
368
|
+
var card = el('div', 'welcome');
|
|
369
|
+
card.appendChild(el('div', 'w-title', 'You\\u2019re looking at a verified map of ' + DATA.name));
|
|
370
|
+
var body = el('div', 'w-body');
|
|
371
|
+
body.innerHTML = 'A machine read this codebase and wrote it up as short statements \\u2014 then <b>checked every statement against the actual code</b>. Click any row to unfold its <b>receipt</b>: the exact lines it came from. Green means it still checks out; amber means the code changed since. Nothing here is taken on faith \\u2014 including from us.';
|
|
372
|
+
card.appendChild(body);
|
|
373
|
+
var row = el('div', 'w-row');
|
|
374
|
+
var got = el('button', 'w-got', 'Got it');
|
|
375
|
+
got.addEventListener('click', function () {
|
|
376
|
+
try { localStorage.setItem('overstory-welcomed', '1'); } catch (e) {}
|
|
377
|
+
state.welcomed = true;
|
|
378
|
+
renderMain();
|
|
379
|
+
});
|
|
380
|
+
row.appendChild(got);
|
|
381
|
+
row.appendChild(el('span', 'w-num', pct + '% of ' + DATA.total + ' statements verify right now'));
|
|
382
|
+
card.appendChild(row);
|
|
383
|
+
return card;
|
|
384
|
+
}
|
|
385
|
+
try { state.welcomed = !!localStorage.getItem('overstory-welcomed'); } catch (e) { state.welcomed = true; }
|
|
386
|
+
|
|
387
|
+
function renderMain() {
|
|
388
|
+
var main = document.getElementById('main');
|
|
389
|
+
main.textContent = '';
|
|
390
|
+
var hosted = document.body.hasAttribute('data-ask');
|
|
391
|
+
if (hosted && !state.welcomed && !state.fixes && !state.query) main.appendChild(welcomeCard());
|
|
392
|
+
if (state.fixes) { renderFixes(main); return; }
|
|
393
|
+
if (state.query) { renderSearch(main); return; }
|
|
394
|
+
var node = DATA.nodes[state.current];
|
|
395
|
+
|
|
396
|
+
var crumbs = el('div', 'crumbs');
|
|
397
|
+
crumbsFor(node.id).forEach(function (c, i, arr) {
|
|
398
|
+
var b = el('button', '', c.label);
|
|
399
|
+
b.addEventListener('click', function () { go(c.id); });
|
|
400
|
+
crumbs.appendChild(b);
|
|
401
|
+
if (i < arr.length - 1) crumbs.appendChild(el('span', 'sep', '/'));
|
|
402
|
+
});
|
|
403
|
+
main.appendChild(crumbs);
|
|
404
|
+
|
|
405
|
+
main.appendChild(el('h1', 'node-title', node.kind === 'root' ? node.title : node.title));
|
|
406
|
+
var meta = el('div', 'node-meta');
|
|
407
|
+
meta.appendChild(el('span', 'chip', node.kind));
|
|
408
|
+
meta.appendChild(el('span', 'chip', node.builtWith + (node.provider ? ' · ' + node.provider : '')));
|
|
409
|
+
meta.appendChild(el('span', '', node.verified + ' of ' + node.total + ' claims verified in this subtree'));
|
|
410
|
+
main.appendChild(meta);
|
|
411
|
+
|
|
412
|
+
if (node.claims.length === 0) {
|
|
413
|
+
var emptyBox = el('div', 'empty');
|
|
414
|
+
emptyBox.appendChild(el('div', '', 'No claims for this node. The file may be empty, binary, or skipped during the build.'));
|
|
415
|
+
main.appendChild(emptyBox);
|
|
416
|
+
} else {
|
|
417
|
+
var ledger = el('div', 'ledger');
|
|
418
|
+
var head = el('div', 'ledger-head');
|
|
419
|
+
head.appendChild(el('span', '', 'Claims'));
|
|
420
|
+
head.appendChild(el('span', '', 'Select a claim to unfold its receipt'));
|
|
421
|
+
ledger.appendChild(head);
|
|
422
|
+
node.claims.forEach(function (claim) { ledger.appendChild(renderClaim(claim)); });
|
|
423
|
+
main.appendChild(ledger);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
var legend = el('div', 'legend');
|
|
427
|
+
legend.innerHTML = 'Seals are mechanical — <b>verified</b>: the cited lines exist and are unchanged · <b>stale</b>: the evidence changed since this build · <b>missing</b>: the citation no longer resolves. Faithfulness is the build-time semantic critique: claims <b>flagged</b> by it stay visible, never hidden. This page proves provenance, not truth — every claim is one click from its evidence.';
|
|
428
|
+
main.appendChild(legend);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function renderClaim(claim) {
|
|
432
|
+
var wrap = el('div', 'claim');
|
|
433
|
+
var row = el('button', 'claim-row');
|
|
434
|
+
row.setAttribute('aria-expanded', 'false');
|
|
435
|
+
var seal = el('span', 'seal ' + claim.verdict, SEAL_LABEL[claim.verdict]);
|
|
436
|
+
seal.title = SEAL_TITLE[claim.verdict];
|
|
437
|
+
row.appendChild(seal);
|
|
438
|
+
row.appendChild(el('span', 'claim-text', claim.text));
|
|
439
|
+
if (claim.faithfulness === 'unsupported') {
|
|
440
|
+
var f = el('span', 'flag', 'flagged by critique');
|
|
441
|
+
f.title = 'The build-time critique judged the cited lines do not support this claim. Kept visible on purpose.';
|
|
442
|
+
row.appendChild(f);
|
|
443
|
+
} else if (claim.faithfulness === 'unchecked') {
|
|
444
|
+
var f2 = el('span', 'flag unchecked', 'unchecked');
|
|
445
|
+
f2.title = 'No semantic critique ran for this claim (budget or provider limits).';
|
|
446
|
+
row.appendChild(f2);
|
|
447
|
+
}
|
|
448
|
+
var nReceipts = claim.spans.length + claim.refs.length;
|
|
449
|
+
var unfold = el('span', 'unfold');
|
|
450
|
+
unfold.appendChild(el('span', '', nReceipts + (nReceipts === 1 ? ' receipt' : ' receipts')));
|
|
451
|
+
unfold.appendChild(el('span', 'u-caret', '\\u25B8'));
|
|
452
|
+
row.appendChild(unfold);
|
|
453
|
+
var receipt = el('div', 'receipt');
|
|
454
|
+
receipt.hidden = true;
|
|
455
|
+
row.addEventListener('click', function () {
|
|
456
|
+
var openNow = receipt.hidden;
|
|
457
|
+
receipt.hidden = !openNow;
|
|
458
|
+
row.setAttribute('aria-expanded', String(openNow));
|
|
459
|
+
if (openNow && !receipt.firstChild) receipt.appendChild(renderReceipt(claim));
|
|
460
|
+
});
|
|
461
|
+
wrap.appendChild(row);
|
|
462
|
+
wrap.appendChild(receipt);
|
|
463
|
+
return wrap;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function renderReceipt(claim) {
|
|
467
|
+
var inner = el('div', 'receipt-inner');
|
|
468
|
+
if (claim.spans.length === 0 && claim.refs.length === 0) {
|
|
469
|
+
inner.appendChild(el('div', 'refline', 'No citations. This is what ungrounded looks like — the seal above says so.'));
|
|
470
|
+
return inner;
|
|
471
|
+
}
|
|
472
|
+
claim.spans.forEach(function (span) {
|
|
473
|
+
var top = el('div', 'receipt-top');
|
|
474
|
+
top.appendChild(el('span', 'file', span.file + ':' + span.startLine + '-' + span.endLine));
|
|
475
|
+
top.appendChild(el('span', 'hash', 'sha256 ' + span.hash.slice(0, 12)));
|
|
476
|
+
inner.appendChild(top);
|
|
477
|
+
var pre = el('pre');
|
|
478
|
+
var lines = span.text.split('\\n');
|
|
479
|
+
for (var i = 0; i < lines.length; i++) {
|
|
480
|
+
var lineRow = el('div', 'srcline');
|
|
481
|
+
lineRow.appendChild(el('span', 'ln', String(span.startLine + i)));
|
|
482
|
+
lineRow.appendChild(el('span', 'code', lines[i] === '' ? ' ' : lines[i]));
|
|
483
|
+
pre.appendChild(lineRow);
|
|
484
|
+
}
|
|
485
|
+
inner.appendChild(pre);
|
|
486
|
+
});
|
|
487
|
+
claim.refs.forEach(function (ref) {
|
|
488
|
+
var target = DATA.nodes[ref.nodeId];
|
|
489
|
+
var refClaim = null;
|
|
490
|
+
if (target) target.claims.forEach(function (c) { if (c.id === ref.claimId) refClaim = c; });
|
|
491
|
+
var line = el('div', 'refline');
|
|
492
|
+
line.appendChild(document.createTextNode('Grounded in '));
|
|
493
|
+
var link = el('button', '', target ? (target.path || target.title) : ref.nodeId);
|
|
494
|
+
link.addEventListener('click', function () { go(ref.nodeId); });
|
|
495
|
+
line.appendChild(link);
|
|
496
|
+
if (refClaim) line.appendChild(document.createTextNode(' — "' + refClaim.text + '"'));
|
|
497
|
+
inner.appendChild(line);
|
|
498
|
+
});
|
|
499
|
+
return inner;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* search */
|
|
503
|
+
function renderSearch(main) {
|
|
504
|
+
var q = state.query.toLowerCase();
|
|
505
|
+
var terms = q.split(/\\s+/).filter(Boolean);
|
|
506
|
+
var hits = [];
|
|
507
|
+
Object.keys(DATA.nodes).forEach(function (nid) {
|
|
508
|
+
DATA.nodes[nid].claims.forEach(function (claim) {
|
|
509
|
+
var hay = (claim.text + ' ' + DATA.nodes[nid].path).toLowerCase();
|
|
510
|
+
var score = 0;
|
|
511
|
+
terms.forEach(function (t) { if (hay.indexOf(t) >= 0) score += 1; });
|
|
512
|
+
if (score === terms.length && terms.length > 0) hits.push({ nid: nid, claim: claim, score: score });
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
main.appendChild(el('h1', 'node-title', 'Search'));
|
|
516
|
+
var meta = el('div', 'node-meta');
|
|
517
|
+
meta.appendChild(el('span', '', hits.length + (hits.length === 1 ? ' claim matches "' : ' claims match "') + state.query + '"'));
|
|
518
|
+
main.appendChild(meta);
|
|
519
|
+
if (hits.length === 0) {
|
|
520
|
+
var none = el('div', 'empty');
|
|
521
|
+
none.appendChild(el('div', '', 'No claims match "' + state.query + '". Try a file name or an exported symbol.'));
|
|
522
|
+
main.appendChild(none);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
var ledger = el('div', 'ledger');
|
|
526
|
+
hits.slice(0, 50).forEach(function (hit) {
|
|
527
|
+
var wrap = el('div', 'claim');
|
|
528
|
+
var row = el('button', 'claim-row');
|
|
529
|
+
var seal = el('span', 'seal ' + hit.claim.verdict, SEAL_LABEL[hit.claim.verdict]);
|
|
530
|
+
row.appendChild(seal);
|
|
531
|
+
var txt = el('span', 'claim-text');
|
|
532
|
+
txt.appendChild(el('div', '', hit.claim.text));
|
|
533
|
+
var where = el('div', '');
|
|
534
|
+
where.style.cssText = 'font:400 11px var(--mono);color:var(--text3);margin-top:3px';
|
|
535
|
+
where.textContent = DATA.nodes[hit.nid].path || DATA.nodes[hit.nid].title;
|
|
536
|
+
txt.appendChild(where);
|
|
537
|
+
row.appendChild(txt);
|
|
538
|
+
row.addEventListener('click', function () {
|
|
539
|
+
state.query = '';
|
|
540
|
+
document.getElementById('searchInput').value = '';
|
|
541
|
+
go(hit.nid);
|
|
542
|
+
});
|
|
543
|
+
wrap.appendChild(row);
|
|
544
|
+
ledger.appendChild(wrap);
|
|
545
|
+
});
|
|
546
|
+
main.appendChild(ledger);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* navigation + keyboard */
|
|
550
|
+
function openAncestors(id) {
|
|
551
|
+
var node = DATA.nodes[id];
|
|
552
|
+
if (!node) return;
|
|
553
|
+
var path = node.path;
|
|
554
|
+
state.open[DATA.root] = true;
|
|
555
|
+
if (!path) return;
|
|
556
|
+
var parts = path.split('/');
|
|
557
|
+
var acc = '';
|
|
558
|
+
for (var i = 0; i < parts.length; i++) {
|
|
559
|
+
acc = acc ? acc + '/' + parts[i] : parts[i];
|
|
560
|
+
if (DATA.nodes['dir:' + acc]) state.open['dir:' + acc] = true;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function renderFixes(main) {
|
|
564
|
+
main.appendChild(el('h1', 'node-title', 'Fix prompts'));
|
|
565
|
+
var intro = el('div', 'fix-intro');
|
|
566
|
+
intro.textContent = 'Paste-ready prompts for your coding agent, generated from this tree. Each one is grounded in receipts (the exact lines), scoped to one bounded change, and ends with machine-checkable acceptance criteria. One prompt per agent session; smallest diff wins.';
|
|
567
|
+
main.appendChild(intro);
|
|
568
|
+
var sevWord = { 1: 'fix first', 2: 'soon', 3: 'when convenient' };
|
|
569
|
+
(DATA.findings || []).forEach(function (f) {
|
|
570
|
+
var card = el('div', 'fix-card');
|
|
571
|
+
var row = el('div', 'fc-row');
|
|
572
|
+
row.appendChild(el('span', 'fc-sev s' + f.severity, sevWord[f.severity]));
|
|
573
|
+
var mainCol = el('div', 'fc-main');
|
|
574
|
+
mainCol.appendChild(el('div', 'fc-title', f.title));
|
|
575
|
+
mainCol.appendChild(el('div', 'fc-detail', f.detail));
|
|
576
|
+
mainCol.appendChild(el('div', 'fc-meta', f.kind + ' \\u00B7 ' + f.receipts + ' receipt' + (f.receipts === 1 ? '' : 's') + ' attached'));
|
|
577
|
+
row.appendChild(mainCol);
|
|
578
|
+
var copy = el('button', 'fc-copy', 'Copy prompt');
|
|
579
|
+
copy.addEventListener('click', function () {
|
|
580
|
+
navigator.clipboard.writeText(f.prompt).then(function () {
|
|
581
|
+
copy.textContent = 'Copied \\u2713';
|
|
582
|
+
setTimeout(function () { copy.textContent = 'Copy prompt'; }, 1500);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
row.appendChild(copy);
|
|
586
|
+
card.appendChild(row);
|
|
587
|
+
main.appendChild(card);
|
|
588
|
+
});
|
|
589
|
+
if (!(DATA.findings || []).length) main.appendChild(el('div', 'empty', 'No findings — this tree is clean by every deterministic check.'));
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function go(id) {
|
|
593
|
+
if (!DATA.nodes[id]) return;
|
|
594
|
+
state.current = id;
|
|
595
|
+
state.fixes = false;
|
|
596
|
+
openAncestors(id);
|
|
597
|
+
renderTree();
|
|
598
|
+
renderMain();
|
|
599
|
+
document.getElementById('rail').classList.remove('open');
|
|
600
|
+
document.getElementById('main').scrollTop = 0;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
document.getElementById('searchInput').addEventListener('input', function (e) {
|
|
604
|
+
state.query = e.target.value.trim();
|
|
605
|
+
renderMain();
|
|
606
|
+
});
|
|
607
|
+
document.getElementById('searchInput').addEventListener('keydown', function (e) {
|
|
608
|
+
if (e.key === 'Escape') {
|
|
609
|
+
state.query = '';
|
|
610
|
+
e.target.value = '';
|
|
611
|
+
e.target.blur();
|
|
612
|
+
renderMain();
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
document.getElementById('railToggle').addEventListener('click', function () {
|
|
616
|
+
document.getElementById('rail').classList.toggle('open');
|
|
617
|
+
});
|
|
618
|
+
document.addEventListener('keydown', function (e) {
|
|
619
|
+
if (e.key === '/' && document.activeElement !== document.getElementById('searchInput')) {
|
|
620
|
+
e.preventDefault();
|
|
621
|
+
document.getElementById('searchInput').focus();
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/* ---- Ask panel (present only when the page was rendered by the registry) ---- */
|
|
626
|
+
(function () {
|
|
627
|
+
var cfgEl = document.getElementById('overstory-ask-config');
|
|
628
|
+
var ASK = null;
|
|
629
|
+
try { ASK = JSON.parse(cfgEl.textContent); } catch (e) { ASK = null; }
|
|
630
|
+
if (!ASK) return;
|
|
631
|
+
document.body.setAttribute('data-ask', '1');
|
|
632
|
+
var panel = document.getElementById('askPanel');
|
|
633
|
+
panel.hidden = false;
|
|
634
|
+
var localBody = document.getElementById('apLocal');
|
|
635
|
+
var byokBody = document.getElementById('apByok');
|
|
636
|
+
|
|
637
|
+
function openPanel() { panel.classList.add('open'); }
|
|
638
|
+
function closePanel() { panel.classList.remove('open'); }
|
|
639
|
+
document.getElementById('askOpen').addEventListener('click', openPanel);
|
|
640
|
+
document.getElementById('askClose').addEventListener('click', closePanel);
|
|
641
|
+
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closePanel(); });
|
|
642
|
+
document.getElementById('tabLocal').addEventListener('click', function () { setTab(true); });
|
|
643
|
+
document.getElementById('tabByok').addEventListener('click', function () { setTab(false); });
|
|
644
|
+
function setTab(local) {
|
|
645
|
+
document.getElementById('tabLocal').classList.toggle('on', local);
|
|
646
|
+
document.getElementById('tabByok').classList.toggle('on', !local);
|
|
647
|
+
localBody.hidden = !local;
|
|
648
|
+
byokBody.hidden = local;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* Tab 1: run locally */
|
|
652
|
+
var cmds = 'git clone ' + ASK.cloneUrl + '\\ncd ' + ASK.repoLabel + '\\nnpx @northtek/overstory build # local model or your API key\\nnpx @northtek/overstory serve # opens the ask app';
|
|
653
|
+
localBody.appendChild(el('h4', '', 'The full ask experience runs on your machine'));
|
|
654
|
+
localBody.appendChild(el('p', '', 'Clone the repo and serve it locally: a ChatGPT-style app over this codebase where every answer sentence carries a receipt. Your code, your model, your machine — nothing is uploaded anywhere.'));
|
|
655
|
+
var pre = el('div', 'ap-cmds', cmds);
|
|
656
|
+
localBody.appendChild(pre);
|
|
657
|
+
var copyBtn = el('button', 'ap-copy', 'Copy commands');
|
|
658
|
+
copyBtn.addEventListener('click', function () {
|
|
659
|
+
navigator.clipboard.writeText(cmds).then(function () { copyBtn.textContent = 'Copied'; setTimeout(function () { copyBtn.textContent = 'Copy commands'; }, 1500); });
|
|
660
|
+
});
|
|
661
|
+
localBody.appendChild(copyBtn);
|
|
662
|
+
localBody.appendChild(el('p', 'ap-note', 'Works fully offline with a local model via Ollama. Private repos never touch this site — that is the point.'));
|
|
663
|
+
|
|
664
|
+
/* Tab 2: browser-side ask with the visitor’s own key */
|
|
665
|
+
byokBody.appendChild(el('h4', '', 'Ask right here, with your own API key'));
|
|
666
|
+
byokBody.appendChild(el('p', '', 'Your key is kept in this browser only and calls Anthropic directly from this page — it never touches this site’s server. Neither does your question or the answer.'));
|
|
667
|
+
var keyInput = document.createElement('input');
|
|
668
|
+
keyInput.type = 'password'; keyInput.className = 'ap-field'; keyInput.placeholder = 'sk-ant-… (Anthropic API key)';
|
|
669
|
+
keyInput.value = sessionStorage.getItem('overstory-key') || localStorage.getItem('overstory-key') || '';
|
|
670
|
+
byokBody.appendChild(keyInput);
|
|
671
|
+
var rememberRow = el('div', 'ap-row');
|
|
672
|
+
var remember = document.createElement('input'); remember.type = 'checkbox'; remember.id = 'apRemember';
|
|
673
|
+
remember.checked = !!localStorage.getItem('overstory-key');
|
|
674
|
+
var rlabel = document.createElement('label'); rlabel.htmlFor = 'apRemember'; rlabel.textContent = 'remember on this device (otherwise this tab only)';
|
|
675
|
+
rememberRow.appendChild(remember); rememberRow.appendChild(rlabel);
|
|
676
|
+
byokBody.appendChild(rememberRow);
|
|
677
|
+
var modelSel = document.createElement('select'); modelSel.className = 'ap-field';
|
|
678
|
+
[['claude-haiku-4-5-20251001', 'Claude Haiku — fast, ~a cent per question'], ['claude-sonnet-5', 'Claude Sonnet — stronger answers']].forEach(function (m) {
|
|
679
|
+
var o = document.createElement('option'); o.value = m[0]; o.textContent = m[1]; modelSel.appendChild(o);
|
|
680
|
+
});
|
|
681
|
+
byokBody.appendChild(modelSel);
|
|
682
|
+
var qInput = document.createElement('textarea');
|
|
683
|
+
qInput.className = 'ap-field'; qInput.rows = 2; qInput.placeholder = 'What does this codebase…';
|
|
684
|
+
byokBody.appendChild(qInput);
|
|
685
|
+
var goBtn = el('button', 'ap-go', 'Ask — answers carry receipts');
|
|
686
|
+
byokBody.appendChild(goBtn);
|
|
687
|
+
var statusEl = el('div', 'ap-status', '');
|
|
688
|
+
var answerHost = el('div', '');
|
|
689
|
+
byokBody.appendChild(statusEl); byokBody.appendChild(answerHost);
|
|
690
|
+
byokBody.appendChild(el('p', 'ap-note', 'Every answer sentence must cite claims from this page’s verified tree; sentences that cite nothing verifiable are withheld and listed, never blended in. Manage or revoke keys at console.anthropic.com.'));
|
|
691
|
+
|
|
692
|
+
function evidenceSearch(q) {
|
|
693
|
+
var terms = q.toLowerCase().split(/[^a-z0-9_]+/).filter(function (t) { return t.length >= 2; });
|
|
694
|
+
var scored = [];
|
|
695
|
+
Object.keys(DATA.nodes).forEach(function (nid) {
|
|
696
|
+
DATA.nodes[nid].claims.forEach(function (c) {
|
|
697
|
+
var hay = (c.text + ' ' + DATA.nodes[nid].path).toLowerCase();
|
|
698
|
+
var score = 0;
|
|
699
|
+
terms.forEach(function (t) { if (hay.indexOf(t) >= 0) score += 1; });
|
|
700
|
+
if (c.verdict === 'VERIFIED') score += 0.5;
|
|
701
|
+
if (score > 0.5) scored.push({ claim: c, nid: nid, score: score });
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
scored.sort(function (a, b) { return b.score - a.score; });
|
|
705
|
+
return scored.slice(0, 8);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function renderAskAnswer(parsed, evidence) {
|
|
709
|
+
answerHost.textContent = '';
|
|
710
|
+
var box = el('div', 'ap-answer');
|
|
711
|
+
var withheld = [];
|
|
712
|
+
var used = {};
|
|
713
|
+
(parsed.answer || []).forEach(function (s) {
|
|
714
|
+
var refs = [];
|
|
715
|
+
(s.refs || []).forEach(function (r) {
|
|
716
|
+
var n = typeof r === 'number' ? r : parseInt(String(r).replace(/[^0-9]/g, ''), 10);
|
|
717
|
+
if (n >= 1 && n <= evidence.length) refs.push(n);
|
|
718
|
+
});
|
|
719
|
+
if (!refs.length) { withheld.push(s.text); return; }
|
|
720
|
+
box.appendChild(document.createTextNode(s.text + ' '));
|
|
721
|
+
refs.forEach(function (n) {
|
|
722
|
+
used[n] = true;
|
|
723
|
+
var ev = evidence[n - 1];
|
|
724
|
+
var chip = el('button', 'ap-chip');
|
|
725
|
+
chip.appendChild(document.createTextNode(String(n)));
|
|
726
|
+
var d = el('span', 'd');
|
|
727
|
+
d.style.background = ev.claim.verdict === 'VERIFIED' ? 'var(--verified)' : ev.claim.verdict === 'STALE' ? 'var(--stale)' : 'var(--missing)';
|
|
728
|
+
chip.appendChild(d);
|
|
729
|
+
chip.title = ev.claim.text;
|
|
730
|
+
chip.addEventListener('click', function () { toggleApReceipt(box, n, ev); });
|
|
731
|
+
box.appendChild(chip);
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
answerHost.appendChild(box);
|
|
735
|
+
if (withheld.length) {
|
|
736
|
+
var w = el('div', 'ap-withheld');
|
|
737
|
+
w.appendChild(el('div', '', withheld.length + ' statement' + (withheld.length === 1 ? '' : 's') + ' withheld (no verifiable citation):'));
|
|
738
|
+
withheld.forEach(function (t) { w.appendChild(el('div', '', '• ' + t)); });
|
|
739
|
+
answerHost.appendChild(w);
|
|
740
|
+
}
|
|
741
|
+
var total = (parsed.answer || []).length;
|
|
742
|
+
statusEl.textContent = total ? 'Grounded ' + Math.round(((total - withheld.length) / total) * 100) + '% · chips open receipts · computed in your browser' : 'No answer.';
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function toggleApReceipt(box, n, ev) {
|
|
746
|
+
var existing = box.querySelector('[data-ap-receipt="' + n + '"]');
|
|
747
|
+
if (existing) { existing.remove(); return; }
|
|
748
|
+
var r = el('div', 'ap-receipt');
|
|
749
|
+
r.setAttribute('data-ap-receipt', String(n));
|
|
750
|
+
(ev.claim.spans || []).forEach(function (span) {
|
|
751
|
+
r.appendChild(el('div', 'rt', span.file + ':' + span.startLine + '-' + span.endLine + ' · ' + (ev.claim.verdict || '').toLowerCase()));
|
|
752
|
+
var pre = el('pre', '');
|
|
753
|
+
String(span.text).split('\\n').slice(0, 14).forEach(function (line, i) {
|
|
754
|
+
var lr = el('div', 'srcline');
|
|
755
|
+
lr.appendChild(el('span', 'ln', String(span.startLine + i)));
|
|
756
|
+
lr.appendChild(el('span', 'code', line === '' ? ' ' : line));
|
|
757
|
+
pre.appendChild(lr);
|
|
758
|
+
});
|
|
759
|
+
r.appendChild(pre);
|
|
760
|
+
});
|
|
761
|
+
if (!(ev.claim.spans || []).length) r.appendChild(el('div', 'rt', 'grounded in tree claim — open its node in the explorer'));
|
|
762
|
+
box.appendChild(r);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
goBtn.addEventListener('click', function () {
|
|
766
|
+
var key = keyInput.value.trim();
|
|
767
|
+
var q = qInput.value.trim();
|
|
768
|
+
answerHost.textContent = '';
|
|
769
|
+
if (!key || key.indexOf('sk-') !== 0) { statusEl.textContent = ''; answerHost.appendChild(el('div', 'ap-err', 'Enter your Anthropic API key (starts with sk-ant-).')); return; }
|
|
770
|
+
if (!q) { answerHost.appendChild(el('div', 'ap-err', 'Ask something about this codebase.')); return; }
|
|
771
|
+
if (remember.checked) { localStorage.setItem('overstory-key', key); sessionStorage.removeItem('overstory-key'); }
|
|
772
|
+
else { sessionStorage.setItem('overstory-key', key); localStorage.removeItem('overstory-key'); }
|
|
773
|
+
var evidence = evidenceSearch(q);
|
|
774
|
+
if (!evidence.length) { statusEl.textContent = ''; answerHost.appendChild(el('div', 'ap-err', 'The tree has no claims matching that — try other words, or browse the tree.')); return; }
|
|
775
|
+
statusEl.textContent = 'Searching ' + DATA.total + ' claims → asking ' + (modelSel.value.indexOf('haiku') >= 0 ? 'Haiku' : 'Sonnet') + ' from your browser…';
|
|
776
|
+
goBtn.disabled = true;
|
|
777
|
+
var block = evidence.map(function (ev, i) { return '[' + (i + 1) + '] (' + (DATA.nodes[ev.nid].path || 'root') + ') ' + ev.claim.text; }).join('\\n');
|
|
778
|
+
var prompt = 'Answer the question using ONLY this numbered evidence about the codebase "' + DATA.name + '". Every answer sentence cites the evidence numbers that support it.\\n\\nReturn ONLY JSON: {"answer":[{"text":"one sentence","refs":[1,2]}]}\\n\\nEVIDENCE:\\n' + block + '\\n\\nQUESTION: ' + q;
|
|
779
|
+
fetch('https://api.anthropic.com/v1/messages', {
|
|
780
|
+
method: 'POST',
|
|
781
|
+
headers: {
|
|
782
|
+
'content-type': 'application/json',
|
|
783
|
+
'x-api-key': key,
|
|
784
|
+
'anthropic-version': '2023-06-01',
|
|
785
|
+
'anthropic-dangerous-direct-browser-access': 'true'
|
|
786
|
+
},
|
|
787
|
+
body: JSON.stringify({ model: modelSel.value, max_tokens: 1024, messages: [{ role: 'user', content: prompt }] })
|
|
788
|
+
}).then(function (res) {
|
|
789
|
+
if (res.status === 401) throw new Error('Anthropic rejected the key (401). Check it at console.anthropic.com.');
|
|
790
|
+
if (res.status === 429) throw new Error('Rate limited by Anthropic (429) — wait a moment.');
|
|
791
|
+
if (!res.ok) throw new Error('Anthropic returned HTTP ' + res.status + '.');
|
|
792
|
+
return res.json();
|
|
793
|
+
}).then(function (data) {
|
|
794
|
+
var text = ((data.content || []).find(function (b) { return b.type === 'text'; }) || {}).text || '';
|
|
795
|
+
var first = text.indexOf('{');
|
|
796
|
+
var last = text.lastIndexOf('}');
|
|
797
|
+
var parsed = JSON.parse(first >= 0 ? text.slice(first, last + 1) : text);
|
|
798
|
+
renderAskAnswer(parsed, evidence);
|
|
799
|
+
}).catch(function (err) {
|
|
800
|
+
statusEl.textContent = '';
|
|
801
|
+
answerHost.appendChild(el('div', 'ap-err', String(err && err.message ? err.message : err)));
|
|
802
|
+
}).finally(function () { goBtn.disabled = false; });
|
|
803
|
+
});
|
|
804
|
+
})();
|
|
805
|
+
|
|
806
|
+
/* fixes toggle */
|
|
807
|
+
(function () {
|
|
808
|
+
var n = (DATA.findings || []).length;
|
|
809
|
+
if (!n) return;
|
|
810
|
+
document.body.setAttribute('data-fixes', '1');
|
|
811
|
+
var btn = document.getElementById('fixesBtn');
|
|
812
|
+
btn.innerHTML = 'Fixes <span class="n">' + n + '</span>';
|
|
813
|
+
btn.addEventListener('click', function () {
|
|
814
|
+
state.fixes = !state.fixes;
|
|
815
|
+
btn.classList.toggle('on', state.fixes);
|
|
816
|
+
renderMain();
|
|
817
|
+
});
|
|
818
|
+
})();
|
|
819
|
+
|
|
820
|
+
renderTree();
|
|
821
|
+
renderMain();
|
|
822
|
+
})();
|
|
823
|
+
</script>
|
|
824
|
+
</body>
|
|
825
|
+
</html>
|
|
826
|
+
`;
|
|
827
|
+
};
|