@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,809 @@
|
|
|
1
|
+
/** The served application shell. All data is client-fetched from /api/*; this template has
|
|
2
|
+
* zero server-side interpolation. Client JS avoids backticks entirely (template-literal
|
|
3
|
+
* safety inside this TS string). */
|
|
4
|
+
export const appHtml = () => `<!doctype html>
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="utf-8">
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
9
|
+
<title>OVERSTORY</title>
|
|
10
|
+
<meta name="description" content="Ask your codebase. Every citation is a stamped receipt.">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,450;9..144,560&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg:#FAF9F5; --surface:#FFFFFF; --subtle:#F1EFE9; --line:#E4E1D8;
|
|
17
|
+
--text:#1A1D1B; --text2:#5C6660; --text3:#98A19A;
|
|
18
|
+
--accent:#1F7A5C; --accent-hover:#196349; --accent-soft:rgba(31,122,92,.09);
|
|
19
|
+
--verified:#1F7A5C; --stale:#B8860B; --missing:#C0453B; --unchecked:#98A19A;
|
|
20
|
+
--shadow-sm:0 1px 2px rgba(20,24,22,.06); --shadow-md:0 1px 3px rgba(20,24,22,.08), 0 4px 16px rgba(20,24,22,.05);
|
|
21
|
+
--serif:"Fraunces",Georgia,serif; --sans:"Inter",system-ui,sans-serif; --mono:"JetBrains Mono",ui-monospace,Consolas,monospace;
|
|
22
|
+
--ease:cubic-bezier(0.2,0,0,1);
|
|
23
|
+
}
|
|
24
|
+
[data-theme="dark"] {
|
|
25
|
+
--bg:#101312; --surface:#171B19; --subtle:#1D2220; --line:#2A302C;
|
|
26
|
+
--text:#F0F2EF; --text2:#A3ACA5; --text3:#6E7770;
|
|
27
|
+
--accent:#34A47C; --accent-hover:#3FB98D; --accent-soft:rgba(52,164,124,.14);
|
|
28
|
+
--verified:#34A47C; --stale:#D9A441; --missing:#D06258; --unchecked:#6E7770;
|
|
29
|
+
--shadow-sm:none; --shadow-md:none;
|
|
30
|
+
}
|
|
31
|
+
* { box-sizing:border-box; margin:0; padding:0; }
|
|
32
|
+
html,body { height:100%; }
|
|
33
|
+
body { background:var(--bg); color:var(--text); font:400 15px/1.6 var(--sans); }
|
|
34
|
+
button { font:inherit; color:inherit; background:none; border:none; cursor:pointer; }
|
|
35
|
+
textarea { font:inherit; color:inherit; }
|
|
36
|
+
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:4px; }
|
|
37
|
+
::selection { background:var(--accent-soft); }
|
|
38
|
+
@media (prefers-reduced-motion:reduce) { * { transition:none !important; animation:none !important; } }
|
|
39
|
+
|
|
40
|
+
.shell { display:grid; grid-template-columns:264px 1fr; height:100%; }
|
|
41
|
+
|
|
42
|
+
/* ---------- sidebar ---------- */
|
|
43
|
+
.side { display:flex; flex-direction:column; background:var(--subtle); border-right:1px solid var(--line); padding:16px 12px; gap:6px; min-height:0; }
|
|
44
|
+
.brand { display:flex; align-items:center; gap:9px; padding:6px 10px 14px; }
|
|
45
|
+
.brand svg { flex:none; }
|
|
46
|
+
.brand .word { font:560 15px/1 var(--serif); letter-spacing:.14em; }
|
|
47
|
+
.new-thread { display:flex; align-items:center; gap:8px; background:var(--accent); color:#fff; border-radius:10px; padding:9px 14px; font-weight:500; font-size:13.5px; transition:background 120ms var(--ease); box-shadow:var(--shadow-sm); }
|
|
48
|
+
.new-thread:hover { background:var(--accent-hover); }
|
|
49
|
+
.new-thread .plus { font-size:15px; line-height:1; }
|
|
50
|
+
.nav { margin-top:10px; display:flex; flex-direction:column; gap:2px; }
|
|
51
|
+
.nav button { display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:8px; font-size:13.5px; color:var(--text2); text-align:left; width:100%; transition:background 120ms var(--ease), color 120ms var(--ease); }
|
|
52
|
+
.nav button:hover { background:var(--surface); color:var(--text); }
|
|
53
|
+
.nav button.active { background:var(--surface); color:var(--text); font-weight:500; box-shadow:var(--shadow-sm); }
|
|
54
|
+
.nav .ico { width:16px; text-align:center; flex:none; opacity:.75; }
|
|
55
|
+
.threads-label { margin:16px 10px 6px; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--text3); }
|
|
56
|
+
.threads { flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:1px; min-height:0; }
|
|
57
|
+
.thread-item { display:flex; align-items:center; gap:6px; padding:7px 10px; border-radius:8px; font-size:13px; color:var(--text2); width:100%; text-align:left; }
|
|
58
|
+
.thread-item:hover { background:var(--surface); color:var(--text); }
|
|
59
|
+
.thread-item.active { background:var(--surface); color:var(--text); }
|
|
60
|
+
.thread-item .t-title { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
61
|
+
.thread-item .t-del { opacity:0; flex:none; color:var(--text3); font-size:14px; padding:0 2px; }
|
|
62
|
+
.thread-item:hover .t-del { opacity:1; }
|
|
63
|
+
.thread-item .t-del:hover { color:var(--missing); }
|
|
64
|
+
.side-foot { border-top:1px solid var(--line); padding-top:10px; display:flex; flex-direction:column; gap:8px; }
|
|
65
|
+
.fresh-pill { display:flex; align-items:center; gap:7px; padding:7px 10px; border-radius:8px; background:var(--surface); font-size:12px; color:var(--text2); box-shadow:var(--shadow-sm); }
|
|
66
|
+
.fresh-pill .pct { font:500 12px var(--mono); }
|
|
67
|
+
.fresh-pill.ok .pct { color:var(--verified); }
|
|
68
|
+
.fresh-pill.warn .pct { color:var(--stale); }
|
|
69
|
+
.side-row { display:flex; align-items:center; justify-content:space-between; padding:0 4px; }
|
|
70
|
+
.provider-tag { font:400 10.5px var(--mono); color:var(--text3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:160px; }
|
|
71
|
+
.theme-btn { padding:5px 8px; border-radius:7px; color:var(--text2); font-size:13px; }
|
|
72
|
+
.theme-btn:hover { background:var(--surface); }
|
|
73
|
+
|
|
74
|
+
/* ---------- main ---------- */
|
|
75
|
+
.main { overflow-y:auto; min-width:0; display:flex; flex-direction:column; }
|
|
76
|
+
.view { width:100%; flex:1; display:flex; flex-direction:column; }
|
|
77
|
+
.view > * { flex:1; }
|
|
78
|
+
|
|
79
|
+
/* home */
|
|
80
|
+
.home { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:48px 24px; gap:0; min-height:100%; }
|
|
81
|
+
.home .mark { margin-bottom:18px; }
|
|
82
|
+
.home h1 { font:450 32px/1.25 var(--serif); text-align:center; letter-spacing:-.01em; }
|
|
83
|
+
.home h1 .repo { font-weight:560; }
|
|
84
|
+
.home .stats { margin-top:10px; font-size:13px; color:var(--text2); text-align:center; }
|
|
85
|
+
.home .stats .num { font-family:var(--mono); font-size:12.5px; }
|
|
86
|
+
.askwrap { width:100%; max-width:680px; margin-top:28px; }
|
|
87
|
+
.askbox { position:relative; background:var(--surface); border:1px solid var(--line); border-radius:14px; box-shadow:var(--shadow-md); transition:border-color 120ms var(--ease); }
|
|
88
|
+
.askbox:focus-within { border-color:var(--accent); }
|
|
89
|
+
.askbox textarea { width:100%; resize:none; border:none; outline:none; background:transparent; padding:16px 52px 16px 18px; font-size:15px; line-height:1.5; max-height:160px; }
|
|
90
|
+
.askbox textarea::placeholder { color:var(--text3); }
|
|
91
|
+
.ask-send { position:absolute; right:10px; bottom:10px; width:34px; height:34px; border-radius:9px; background:var(--accent); color:#fff; display:flex; align-items:center; justify-content:center; transition:background 120ms var(--ease), opacity 120ms; }
|
|
92
|
+
.ask-send:hover { background:var(--accent-hover); }
|
|
93
|
+
.ask-send:disabled { opacity:.35; cursor:default; }
|
|
94
|
+
.ask-hint { text-align:center; margin-top:10px; font-size:11.5px; color:var(--text3); }
|
|
95
|
+
.ask-hint kbd { font:400 10.5px var(--mono); border:1px solid var(--line); border-radius:4px; padding:1px 5px; background:var(--surface); }
|
|
96
|
+
.sugg { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin-top:22px; max-width:680px; }
|
|
97
|
+
.sugg button { border:1px solid var(--line); background:var(--surface); border-radius:999px; padding:7px 14px; font-size:13px; color:var(--text2); transition:all 120ms var(--ease); box-shadow:var(--shadow-sm); }
|
|
98
|
+
.sugg button:hover { border-color:var(--accent); color:var(--accent); }
|
|
99
|
+
.honesty { margin-top:40px; font-size:12px; color:var(--text3); text-align:center; max-width:480px; }
|
|
100
|
+
|
|
101
|
+
/* thread */
|
|
102
|
+
.thread-view { max-width:800px; width:100%; margin:0 auto; padding:36px 32px 24px; display:flex; flex-direction:column; }
|
|
103
|
+
.turns { flex:1; }
|
|
104
|
+
.turn { padding:18px 0 30px; }
|
|
105
|
+
.turn + .turn { border-top:1px solid var(--line); }
|
|
106
|
+
.turn .q { font:450 22px/1.35 var(--serif); letter-spacing:-.005em; margin-bottom:14px; }
|
|
107
|
+
.status { display:flex; align-items:center; gap:10px; color:var(--text2); font-size:13.5px; padding:6px 0 10px; }
|
|
108
|
+
.status .orbs { display:inline-flex; gap:4px; }
|
|
109
|
+
.status .orb { width:5px; height:5px; border-radius:50%; background:var(--accent); animation:pulse 1.2s ease-in-out infinite; }
|
|
110
|
+
.status .orb:nth-child(2) { animation-delay:.2s; } .status .orb:nth-child(3) { animation-delay:.4s; }
|
|
111
|
+
@keyframes pulse { 0%,100% { opacity:.25; transform:scale(.85);} 50% { opacity:1; transform:scale(1);} }
|
|
112
|
+
.answer { font-size:15px; line-height:1.7; max-width:68ch; }
|
|
113
|
+
.answer .sent { display:inline; }
|
|
114
|
+
.chip { display:inline-flex; align-items:center; gap:3px; vertical-align:2px; margin:0 3px 0 2px; padding:1px 7px; border-radius:999px; font:500 10.5px var(--mono); border:1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); color:var(--accent); background:var(--surface); transition:all 120ms var(--ease); }
|
|
115
|
+
.chip .seal-dot { width:5px; height:5px; border-radius:50%; }
|
|
116
|
+
.chip:hover { border-color:var(--accent); color:var(--accent); }
|
|
117
|
+
.chip.open { background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }
|
|
118
|
+
.grounding { margin-top:14px; font-size:12px; color:var(--text3); display:flex; align-items:center; gap:6px; }
|
|
119
|
+
.grounding .g-dot { width:6px; height:6px; border-radius:50%; }
|
|
120
|
+
.quarantine { margin-top:12px; }
|
|
121
|
+
.quarantine > button { font-size:12.5px; color:var(--stale); display:flex; align-items:center; gap:6px; }
|
|
122
|
+
.quarantine .q-body { margin-top:8px; border-left:2px solid var(--stale); padding:4px 0 4px 12px; color:var(--text2); font-size:13.5px; }
|
|
123
|
+
.src-label { margin:22px 0 10px; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--text3); display:flex; align-items:center; gap:8px; }
|
|
124
|
+
.cards { display:grid; grid-template-columns:repeat(auto-fill, minmax(210px, 1fr)); gap:10px; }
|
|
125
|
+
.card { text-align:left; background:var(--surface); border:1px solid var(--line); border-radius:11px; padding:11px 13px; transition:all 140ms var(--ease); box-shadow:var(--shadow-sm); display:flex; flex-direction:column; gap:6px; min-width:0; }
|
|
126
|
+
.card:hover { border-color:var(--accent); transform:translateY(-1px); box-shadow:var(--shadow-md); }
|
|
127
|
+
.card .c-top { display:flex; align-items:center; gap:7px; min-width:0; }
|
|
128
|
+
.card .c-n { flex:none; font:500 10px var(--mono); color:var(--text3); border:1px solid var(--line); border-radius:5px; padding:1px 5px; }
|
|
129
|
+
.card .c-file { font:500 12.5px var(--sans); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
130
|
+
.card .c-meta { display:flex; align-items:center; gap:8px; font:400 10.5px var(--mono); color:var(--text3); min-width:0; }
|
|
131
|
+
.card .c-meta .c-path { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
|
|
132
|
+
.card .c-snip { font:400 11px/1.5 var(--mono); color:var(--text2); overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
|
|
133
|
+
.seal { display:inline-flex; align-items:center; gap:4px; font:500 10px var(--mono); }
|
|
134
|
+
.seal .s-dot { width:6px; height:6px; border-radius:50%; }
|
|
135
|
+
.seal.VERIFIED { color:var(--verified); } .seal.VERIFIED .s-dot { background:var(--verified); }
|
|
136
|
+
.seal.VERIFIED .s-dot::after { content:"\\2713"; color:#fff; font-size:5px; line-height:6px; display:block; text-align:center; }
|
|
137
|
+
.seal .s-dot { position:relative; }
|
|
138
|
+
.seal.STALE { color:var(--stale); } .seal.STALE .s-dot { background:var(--stale); }
|
|
139
|
+
.seal.OUT_OF_CORPUS, .seal.UNGROUNDED { color:var(--missing); } .seal.OUT_OF_CORPUS .s-dot, .seal.UNGROUNDED .s-dot { background:var(--missing); }
|
|
140
|
+
.receipt { margin-top:10px; background:var(--surface); border:1px solid var(--line); border-radius:11px; overflow:hidden; box-shadow:var(--shadow-sm); }
|
|
141
|
+
.receipt .r-top { display:flex; flex-wrap:wrap; align-items:center; gap:10px; padding:9px 14px; border-bottom:1px dashed var(--line); font:400 11.5px var(--mono); }
|
|
142
|
+
.receipt .r-file { color:var(--accent); font-weight:500; }
|
|
143
|
+
.receipt .r-hash { margin-left:auto; color:var(--text3); }
|
|
144
|
+
.receipt pre { overflow-x:auto; padding:10px 0; background:var(--subtle); }
|
|
145
|
+
.rline { display:flex; font:400 12px/1.55 var(--mono); }
|
|
146
|
+
.rline .ln { flex:none; width:50px; text-align:right; padding-right:12px; color:var(--text3); user-select:none; }
|
|
147
|
+
.rline .code { white-space:pre; padding-right:16px; color:var(--text2); }
|
|
148
|
+
.followup { position:sticky; bottom:0; padding:14px 0 22px; background:linear-gradient(transparent, var(--bg) 34%); }
|
|
149
|
+
|
|
150
|
+
/* library */
|
|
151
|
+
.lib { display:grid; grid-template-columns:280px 1fr; gap:0; min-height:100%; }
|
|
152
|
+
.lib-rail { border-right:1px solid var(--line); padding:22px 10px 60px; overflow-y:auto; }
|
|
153
|
+
.lib-main { padding:30px 36px 80px; overflow-y:auto; min-width:0; }
|
|
154
|
+
.lib-banner { display:flex; align-items:center; gap:10px; background:var(--surface); border:1px solid var(--line); border-radius:11px; padding:11px 16px; margin-bottom:22px; font-size:13px; color:var(--text2); box-shadow:var(--shadow-sm); }
|
|
155
|
+
.lib-banner .pct { font:500 13px var(--mono); }
|
|
156
|
+
.trow { display:flex; align-items:center; gap:7px; width:100%; text-align:left; padding:5px 9px; border-radius:7px; font:400 12.5px var(--mono); color:var(--text2); }
|
|
157
|
+
.trow:hover { background:var(--subtle); color:var(--text); }
|
|
158
|
+
.trow.active { background:var(--accent-soft); color:var(--text); }
|
|
159
|
+
.trow .caret { width:10px; font-size:8px; color:var(--text3); flex:none; transition:transform 120ms var(--ease); }
|
|
160
|
+
.trow.open .caret { transform:rotate(90deg); }
|
|
161
|
+
.trow .dot { width:6px; height:6px; border-radius:50%; flex:none; }
|
|
162
|
+
.tkids { margin-left:13px; border-left:1px solid var(--line); padding-left:5px; }
|
|
163
|
+
.node-h { font:450 24px/1.3 var(--serif); margin-bottom:4px; }
|
|
164
|
+
.node-sub { color:var(--text2); font-size:12.5px; margin-bottom:20px; display:flex; gap:10px; align-items:center; }
|
|
165
|
+
.claims { display:flex; flex-direction:column; gap:8px; }
|
|
166
|
+
.claim { background:var(--surface); border:1px solid var(--line); border-radius:11px; box-shadow:var(--shadow-sm); }
|
|
167
|
+
.claim > button { display:flex; align-items:flex-start; gap:10px; width:100%; text-align:left; padding:12px 15px; }
|
|
168
|
+
.claim > button:hover { background:var(--subtle); border-radius:11px; }
|
|
169
|
+
.claim .cl-text { flex:1; font-size:13.5px; line-height:1.55; }
|
|
170
|
+
.claim .fl { flex:none; font:400 10px var(--mono); color:var(--text3); border:1px dashed var(--line); border-radius:5px; padding:1px 6px; margin-top:2px; }
|
|
171
|
+
.claim .fl.unsupported { color:var(--missing); border-color:var(--missing); }
|
|
172
|
+
.claim .r-wrap { padding:0 12px 12px; }
|
|
173
|
+
.empty { padding:60px 24px; text-align:center; color:var(--text2); font-size:14px; }
|
|
174
|
+
.fixwrap { max-width:800px; margin:0 auto; padding:30px 32px 80px; }
|
|
175
|
+
.fix-intro { color:var(--text2); font-size:13.5px; max-width:68ch; margin:6px 0 20px; }
|
|
176
|
+
.fixcard { background:var(--surface); border:1px solid var(--line); border-radius:11px; box-shadow:var(--shadow-sm); margin-bottom:10px; display:flex; gap:12px; align-items:flex-start; padding:13px 16px; }
|
|
177
|
+
.fixcard .sev { flex:none; margin-top:2px; font:500 10px var(--mono); padding:1px 8px; border-radius:999px; border:1px solid var(--line); color:var(--text3); }
|
|
178
|
+
.fixcard .sev.s1 { color:var(--missing); border-color:color-mix(in srgb, var(--missing) 45%, transparent); }
|
|
179
|
+
.fixcard .sev.s2 { color:var(--stale); border-color:color-mix(in srgb, var(--stale) 45%, transparent); }
|
|
180
|
+
.fixcard .body { flex:1; min-width:0; }
|
|
181
|
+
.fixcard .t { font:500 13.5px var(--sans); overflow-wrap:anywhere; }
|
|
182
|
+
.fixcard .d { font-size:12.5px; color:var(--text2); margin-top:2px; overflow-wrap:anywhere; }
|
|
183
|
+
.fixcard .m { font:400 10.5px var(--mono); color:var(--text3); margin-top:5px; }
|
|
184
|
+
.fixcard .copy { flex:none; padding:6px 12px; border:1px solid var(--line); border-radius:7px; font-size:12px; color:var(--accent); white-space:nowrap; }
|
|
185
|
+
.fixcard .copy:hover { border-color:var(--accent); }
|
|
186
|
+
.empty .cmd { display:inline-block; margin-top:14px; font:400 13px var(--mono); background:var(--surface); border:1px solid var(--line); padding:8px 14px; border-radius:9px; }
|
|
187
|
+
.errbox { margin:10px 0; border:1px solid var(--missing); border-radius:10px; padding:10px 14px; color:var(--missing); font-size:13px; }
|
|
188
|
+
|
|
189
|
+
@media (max-width:900px) {
|
|
190
|
+
.shell { grid-template-columns:1fr; }
|
|
191
|
+
.side { position:fixed; inset:0 30% 0 0; z-index:30; transform:translateX(-100%); transition:transform 240ms var(--ease); }
|
|
192
|
+
.side.open { transform:none; box-shadow:var(--shadow-md); }
|
|
193
|
+
.topbar { display:flex !important; }
|
|
194
|
+
.lib { grid-template-columns:1fr; } .lib-rail { display:none; }
|
|
195
|
+
.thread-view { padding:24px 18px 120px; }
|
|
196
|
+
}
|
|
197
|
+
.topbar { display:none; align-items:center; gap:10px; padding:12px 16px; border-bottom:1px solid var(--line); position:sticky; top:0; background:var(--bg); z-index:10; }
|
|
198
|
+
.topbar button { border:1px solid var(--line); border-radius:8px; padding:5px 11px; font-size:12.5px; color:var(--text2); }
|
|
199
|
+
</style>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<div class="shell">
|
|
203
|
+
<aside class="side" id="side">
|
|
204
|
+
<div class="brand">
|
|
205
|
+
<svg width="20" height="20" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
|
206
|
+
<circle cx="11" cy="8" r="5.2" stroke="var(--accent)" stroke-width="1.5"/>
|
|
207
|
+
<circle cx="6.6" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.2" opacity=".8"/>
|
|
208
|
+
<circle cx="15.4" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.2" opacity=".5"/>
|
|
209
|
+
<path d="M11 13v6" stroke="var(--text3)" stroke-width="1.5" stroke-linecap="round"/>
|
|
210
|
+
</svg>
|
|
211
|
+
<span class="word">OVERSTORY</span>
|
|
212
|
+
</div>
|
|
213
|
+
<button class="new-thread" id="newThread"><span class="plus">+</span> New thread</button>
|
|
214
|
+
<div class="nav">
|
|
215
|
+
<button id="navAsk" class="active"><span class="ico">⚲</span> Ask</button>
|
|
216
|
+
<button id="navLib"><span class="ico">⤒</span> Library</button>
|
|
217
|
+
<button id="navFix"><span class="ico">✎</span> Fixes</button>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="threads-label">Threads</div>
|
|
220
|
+
<nav class="threads" id="threadList" aria-label="Threads"></nav>
|
|
221
|
+
<div class="side-foot">
|
|
222
|
+
<div class="fresh-pill" id="freshPill"><span class="pct" id="freshPct">…</span><span id="freshLabel">loading</span></div>
|
|
223
|
+
<div class="side-row">
|
|
224
|
+
<span class="provider-tag" id="providerTag"></span>
|
|
225
|
+
<button class="theme-btn" id="themeBtn" title="Toggle theme" aria-label="Toggle theme">☽</button>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</aside>
|
|
229
|
+
<main class="main" id="main">
|
|
230
|
+
<div class="topbar"><button id="menuBtn">Menu</button><span style="font:560 13px var(--serif); letter-spacing:.12em;">OVERSTORY</span></div>
|
|
231
|
+
<div class="view" id="view"></div>
|
|
232
|
+
</main>
|
|
233
|
+
</div>
|
|
234
|
+
<script>
|
|
235
|
+
(function () {
|
|
236
|
+
'use strict';
|
|
237
|
+
var state = { overview: null, threads: [], view: 'home', thread: null, streaming: false, treeData: null, libNode: null, libOpen: {} };
|
|
238
|
+
var SEAL_WORD = { VERIFIED: 'verified', STALE: 'stale', OUT_OF_CORPUS: 'missing', UNGROUNDED: 'ungrounded' };
|
|
239
|
+
var SEAL_TITLE = {
|
|
240
|
+
VERIFIED: 'Receipt checked: the cited lines exist and are unchanged.',
|
|
241
|
+
STALE: 'The cited evidence changed since the tree was built.',
|
|
242
|
+
OUT_OF_CORPUS: 'The citation no longer resolves.',
|
|
243
|
+
UNGROUNDED: 'No valid citation.'
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
function el(tag, cls, text) {
|
|
247
|
+
var n = document.createElement(tag);
|
|
248
|
+
if (cls) n.className = cls;
|
|
249
|
+
if (text !== undefined) n.textContent = text;
|
|
250
|
+
return n;
|
|
251
|
+
}
|
|
252
|
+
function fetchJson(url, opts) { return fetch(url, opts).then(function (r) { return r.json(); }); }
|
|
253
|
+
|
|
254
|
+
/* theme */
|
|
255
|
+
var savedTheme = localStorage.getItem('overstory-theme');
|
|
256
|
+
if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme);
|
|
257
|
+
else if (window.matchMedia('(prefers-color-scheme: dark)').matches) document.documentElement.setAttribute('data-theme', 'dark');
|
|
258
|
+
function themeGlyph() {
|
|
259
|
+
document.getElementById('themeBtn').innerHTML = document.documentElement.getAttribute('data-theme') === 'dark' ? '☼' : '☽';
|
|
260
|
+
}
|
|
261
|
+
document.getElementById('themeBtn').addEventListener('click', function () {
|
|
262
|
+
var next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
263
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
264
|
+
localStorage.setItem('overstory-theme', next);
|
|
265
|
+
themeGlyph();
|
|
266
|
+
});
|
|
267
|
+
themeGlyph();
|
|
268
|
+
|
|
269
|
+
/* boot */
|
|
270
|
+
function boot() {
|
|
271
|
+
fetchJson('/api/overview').then(function (o) {
|
|
272
|
+
state.overview = o;
|
|
273
|
+
renderSidebarMeta();
|
|
274
|
+
render();
|
|
275
|
+
}).catch(function () { state.overview = { error: true }; render(); });
|
|
276
|
+
refreshThreads();
|
|
277
|
+
}
|
|
278
|
+
function refreshThreads() {
|
|
279
|
+
fetchJson('/api/threads').then(function (list) { state.threads = list; renderThreadList(); });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function renderSidebarMeta() {
|
|
283
|
+
var o = state.overview;
|
|
284
|
+
if (!o || o.error) return;
|
|
285
|
+
var pct = Math.round(o.freshness * 100);
|
|
286
|
+
var pill = document.getElementById('freshPill');
|
|
287
|
+
pill.classList.toggle('ok', pct === 100);
|
|
288
|
+
pill.classList.toggle('warn', pct < 100);
|
|
289
|
+
document.getElementById('freshPct').textContent = pct + '%';
|
|
290
|
+
document.getElementById('freshLabel').textContent = pct === 100
|
|
291
|
+
? 'tree is current'
|
|
292
|
+
: (o.total - o.verified) + ' claims out of date';
|
|
293
|
+
pill.title = 'Built ' + new Date(o.builtAt).toLocaleString() + ' \\u00B7 ' + o.verified + ' of ' + o.total + ' claims verified \\u00B7 run overstory build to refresh';
|
|
294
|
+
var prov = o.provider || '';
|
|
295
|
+
var pretty = prov.indexOf('ollama:') === 0 ? 'Local model \\u00B7 ' + prov.slice(7)
|
|
296
|
+
: prov.indexOf('anthropic:') === 0 ? 'Anthropic \\u00B7 ' + prov.slice(10)
|
|
297
|
+
: 'No LLM \\u00B7 evidence only';
|
|
298
|
+
document.getElementById('providerTag').textContent = pretty;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function renderThreadList() {
|
|
302
|
+
var host = document.getElementById('threadList');
|
|
303
|
+
host.textContent = '';
|
|
304
|
+
state.threads.forEach(function (t) {
|
|
305
|
+
var b = el('button', 'thread-item' + (state.thread && state.thread.id === t.id ? ' active' : ''));
|
|
306
|
+
b.appendChild(el('span', 't-title', t.title));
|
|
307
|
+
var del = el('span', 't-del', '\\u00D7');
|
|
308
|
+
del.title = 'Delete thread';
|
|
309
|
+
del.addEventListener('click', function (ev) {
|
|
310
|
+
ev.stopPropagation();
|
|
311
|
+
fetch('/api/thread?id=' + encodeURIComponent(t.id), { method: 'DELETE' }).then(function () {
|
|
312
|
+
if (state.thread && state.thread.id === t.id) { state.thread = null; state.view = 'home'; render(); }
|
|
313
|
+
refreshThreads();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
b.appendChild(del);
|
|
317
|
+
b.addEventListener('click', function () { openThread(t.id); });
|
|
318
|
+
host.appendChild(b);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function openThread(id) {
|
|
323
|
+
fetchJson('/api/thread?id=' + encodeURIComponent(id)).then(function (t) {
|
|
324
|
+
if (t.error) return;
|
|
325
|
+
state.thread = t; state.view = 'thread'; render(); renderThreadList();
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* ask box factory */
|
|
330
|
+
function askBox(placeholder, autofocus) {
|
|
331
|
+
var wrap = el('div', 'askwrap');
|
|
332
|
+
var box = el('div', 'askbox');
|
|
333
|
+
var ta = document.createElement('textarea');
|
|
334
|
+
ta.rows = 1; ta.placeholder = placeholder; ta.setAttribute('aria-label', placeholder);
|
|
335
|
+
var send = el('button', 'ask-send');
|
|
336
|
+
send.innerHTML = '↑'; send.title = 'Ask'; send.setAttribute('aria-label', 'Ask');
|
|
337
|
+
box.appendChild(ta); box.appendChild(send);
|
|
338
|
+
wrap.appendChild(box);
|
|
339
|
+
function submit() {
|
|
340
|
+
var q = ta.value.trim();
|
|
341
|
+
if (!q || state.streaming) return;
|
|
342
|
+
ta.value = '';
|
|
343
|
+
askQuestion(q);
|
|
344
|
+
}
|
|
345
|
+
ta.addEventListener('keydown', function (e) {
|
|
346
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
|
|
347
|
+
});
|
|
348
|
+
ta.addEventListener('input', function () {
|
|
349
|
+
ta.style.height = 'auto'; ta.style.height = Math.min(ta.scrollHeight, 160) + 'px';
|
|
350
|
+
});
|
|
351
|
+
send.addEventListener('click', submit);
|
|
352
|
+
if (autofocus) setTimeout(function () { ta.focus(); }, 40);
|
|
353
|
+
wrap._ta = ta;
|
|
354
|
+
return wrap;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* SSE ask */
|
|
358
|
+
function askQuestion(question) {
|
|
359
|
+
if (!state.thread) state.thread = { id: null, title: question, turns: [] };
|
|
360
|
+
state.view = 'thread';
|
|
361
|
+
state.streaming = true;
|
|
362
|
+
var liveTurn = { question: question, result: null, phase: { phase: 'brainstorm' }, error: null };
|
|
363
|
+
state.thread.turns.push(liveTurn);
|
|
364
|
+
render();
|
|
365
|
+
|
|
366
|
+
fetch('/api/ask', {
|
|
367
|
+
method: 'POST',
|
|
368
|
+
headers: { 'content-type': 'application/json' },
|
|
369
|
+
body: JSON.stringify({ question: question, threadId: state.thread.id || undefined })
|
|
370
|
+
}).then(function (res) {
|
|
371
|
+
var reader = res.body.getReader();
|
|
372
|
+
var dec = new TextDecoder();
|
|
373
|
+
var buf = '';
|
|
374
|
+
function pump() {
|
|
375
|
+
return reader.read().then(function (r) {
|
|
376
|
+
if (r.done) { finish(); return; }
|
|
377
|
+
buf += dec.decode(r.value, { stream: true });
|
|
378
|
+
var parts = buf.split('\\n\\n');
|
|
379
|
+
buf = parts.pop();
|
|
380
|
+
parts.forEach(function (block) {
|
|
381
|
+
var evLine = block.split('\\n').find(function (l) { return l.indexOf('event: ') === 0; });
|
|
382
|
+
var dataLine = block.split('\\n').find(function (l) { return l.indexOf('data: ') === 0; });
|
|
383
|
+
if (!evLine || !dataLine) return;
|
|
384
|
+
var ev = evLine.slice(7).trim();
|
|
385
|
+
var data;
|
|
386
|
+
try { data = JSON.parse(dataLine.slice(6)); } catch (e) { return; }
|
|
387
|
+
if (ev === 'thread' && !state.thread.id) { state.thread.id = data.id; state.thread.title = data.title; refreshThreads(); }
|
|
388
|
+
if (ev === 'phase') { liveTurn.phase = data; updateStatus(liveTurn); }
|
|
389
|
+
if (ev === 'answer') { liveTurn.result = data; liveTurn.phase = null; }
|
|
390
|
+
if (ev === 'error') { liveTurn.error = data.message; liveTurn.phase = null; }
|
|
391
|
+
});
|
|
392
|
+
return pump();
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function finish() { state.streaming = false; render(); refreshThreads(); }
|
|
396
|
+
return pump();
|
|
397
|
+
}).catch(function (err) {
|
|
398
|
+
liveTurn.error = String(err); state.streaming = false; render();
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function phaseText(p, o) {
|
|
403
|
+
if (!p) return '';
|
|
404
|
+
if (p.phase === 'brainstorm') return 'Understanding the question\\u2026';
|
|
405
|
+
if (p.phase === 'search') return 'Searching ' + (o ? o.total : '') + ' claims' + (p.subqueries && p.subqueries.length > 1 ? ' \\u00B7 ' + p.subqueries.slice(1).join(' \\u00B7 ') : '') + '\\u2026';
|
|
406
|
+
if (p.phase === 'write') return 'Writing from ' + (p.evidenceCount || 0) + ' pieces of evidence\\u2026';
|
|
407
|
+
if (p.phase === 'gate') return 'Notarizing citations against the code\\u2026';
|
|
408
|
+
return '';
|
|
409
|
+
}
|
|
410
|
+
function updateStatus(turn) {
|
|
411
|
+
var s = document.getElementById('live-status');
|
|
412
|
+
if (s) s.lastChild.textContent = phaseText(turn.phase, state.overview);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* renderers */
|
|
416
|
+
function render() {
|
|
417
|
+
var view = document.getElementById('view');
|
|
418
|
+
view.textContent = '';
|
|
419
|
+
document.getElementById('navAsk').classList.toggle('active', state.view !== 'lib' && state.view !== 'fix');
|
|
420
|
+
document.getElementById('navLib').classList.toggle('active', state.view === 'lib');
|
|
421
|
+
document.getElementById('navFix').classList.toggle('active', state.view === 'fix');
|
|
422
|
+
if (state.overview && state.overview.error) { view.appendChild(renderNoTree()); return; }
|
|
423
|
+
if (state.view === 'lib') { view.appendChild(renderLibrary()); return; }
|
|
424
|
+
if (state.view === 'fix') { view.appendChild(renderFixView()); return; }
|
|
425
|
+
if (state.view === 'thread' && state.thread) { view.appendChild(renderThread()); return; }
|
|
426
|
+
view.appendChild(renderHome());
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function renderFixView() {
|
|
430
|
+
var wrap = el('div', 'fixwrap');
|
|
431
|
+
if (!state.treeData) {
|
|
432
|
+
fetchJson('/api/tree').then(function (d) {
|
|
433
|
+
if (d.error) return;
|
|
434
|
+
state.treeData = d;
|
|
435
|
+
Object.keys(d.nodes).forEach(function (id) { if (d.nodes[id].kind !== 'leaf') state.libOpen[id] = true; });
|
|
436
|
+
render();
|
|
437
|
+
});
|
|
438
|
+
wrap.appendChild(el('div', 'empty', 'Scanning the tree\\u2026'));
|
|
439
|
+
return wrap;
|
|
440
|
+
}
|
|
441
|
+
var findings = state.treeData.findings || [];
|
|
442
|
+
wrap.appendChild(el('h2', 'node-h', 'Fix prompts'));
|
|
443
|
+
wrap.appendChild(el('div', 'fix-intro', 'Paste-ready prompts for your coding agent, generated from the verified tree. Each is grounded in receipts, scoped to one bounded change, and ends with machine-checkable acceptance criteria \\u2014 including rebuilding this tree to prove the fix landed. One prompt per session; smallest diff wins.'));
|
|
444
|
+
if (!findings.length) { wrap.appendChild(el('div', 'empty', 'No findings \\u2014 clean by every deterministic check.')); return wrap; }
|
|
445
|
+
var sevWord = { 1: 'fix first', 2: 'soon', 3: 'later' };
|
|
446
|
+
findings.forEach(function (f) {
|
|
447
|
+
var card = el('div', 'fixcard');
|
|
448
|
+
card.appendChild(el('span', 'sev s' + f.severity, sevWord[f.severity]));
|
|
449
|
+
var body = el('div', 'body');
|
|
450
|
+
body.appendChild(el('div', 't', f.title));
|
|
451
|
+
body.appendChild(el('div', 'd', f.detail));
|
|
452
|
+
body.appendChild(el('div', 'm', f.kind + ' \\u00B7 ' + f.receipts + ' receipt' + (f.receipts === 1 ? '' : 's')));
|
|
453
|
+
card.appendChild(body);
|
|
454
|
+
var copy = el('button', 'copy', 'Copy prompt');
|
|
455
|
+
copy.addEventListener('click', function () {
|
|
456
|
+
navigator.clipboard.writeText(f.prompt).then(function () {
|
|
457
|
+
copy.textContent = 'Copied \\u2713';
|
|
458
|
+
setTimeout(function () { copy.textContent = 'Copy prompt'; }, 1500);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
card.appendChild(copy);
|
|
462
|
+
wrap.appendChild(card);
|
|
463
|
+
});
|
|
464
|
+
return wrap;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function renderNoTree() {
|
|
468
|
+
var box = el('div', 'empty');
|
|
469
|
+
box.appendChild(el('div', '', 'No knowledge tree yet for this folder.'));
|
|
470
|
+
box.appendChild(el('div', 'cmd', 'npx @northtek/overstory build'));
|
|
471
|
+
box.appendChild(el('div', '', 'Then refresh this page.')).style.marginTop = '10px';
|
|
472
|
+
return box;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderHome() {
|
|
476
|
+
var o = state.overview;
|
|
477
|
+
var home = el('div', 'home');
|
|
478
|
+
var mark = el('div', 'mark');
|
|
479
|
+
mark.innerHTML = '<svg width="34" height="34" viewBox="0 0 22 22" fill="none"><circle cx="11" cy="8" r="5.2" stroke="var(--accent)" stroke-width="1.4"/><circle cx="6.6" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.1" opacity=".7"/><circle cx="15.4" cy="11.4" r="4" stroke="var(--text3)" stroke-width="1.1" opacity=".45"/><path d="M11 13v6" stroke="var(--text3)" stroke-width="1.4" stroke-linecap="round"/></svg>';
|
|
480
|
+
home.appendChild(mark);
|
|
481
|
+
var h1 = el('h1');
|
|
482
|
+
h1.appendChild(document.createTextNode('Ask '));
|
|
483
|
+
h1.appendChild(el('span', 'repo', o ? o.name : 'your codebase'));
|
|
484
|
+
home.appendChild(h1);
|
|
485
|
+
if (o) {
|
|
486
|
+
var stats = el('div', 'stats');
|
|
487
|
+
stats.innerHTML = '<span class="num">' + o.verified + '</span> verified claims across <span class="num">' + o.nodes + '</span> nodes \\u00B7 every answer cites lines you can check';
|
|
488
|
+
home.appendChild(stats);
|
|
489
|
+
}
|
|
490
|
+
var ab = askBox('Ask about this codebase\\u2026', true);
|
|
491
|
+
home.appendChild(ab);
|
|
492
|
+
var hint = el('div', 'ask-hint');
|
|
493
|
+
hint.innerHTML = '<kbd>/</kbd> to focus \\u00B7 <kbd>Enter</kbd> to ask \\u00B7 answers are notarized, not just generated';
|
|
494
|
+
ab.appendChild(hint);
|
|
495
|
+
if (o && o.suggestions) {
|
|
496
|
+
var sg = el('div', 'sugg');
|
|
497
|
+
o.suggestions.forEach(function (s) {
|
|
498
|
+
var b = el('button', '', s);
|
|
499
|
+
b.addEventListener('click', function () { askQuestion(s); });
|
|
500
|
+
sg.appendChild(b);
|
|
501
|
+
});
|
|
502
|
+
home.appendChild(sg);
|
|
503
|
+
}
|
|
504
|
+
home.appendChild(el('div', 'honesty', 'Every citation is checked against the code, not just displayed. Statements without verifiable evidence are withheld and say so.'));
|
|
505
|
+
return home;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function renderThread() {
|
|
509
|
+
var wrap = el('div', 'thread-view');
|
|
510
|
+
var turns = el('div', 'turns');
|
|
511
|
+
state.thread.turns.forEach(function (turn, i) {
|
|
512
|
+
turns.appendChild(renderTurn(turn, i === state.thread.turns.length - 1));
|
|
513
|
+
});
|
|
514
|
+
wrap.appendChild(turns);
|
|
515
|
+
var fu = el('div', 'followup');
|
|
516
|
+
fu.appendChild(askBox('Ask a follow-up\\u2026', !state.streaming));
|
|
517
|
+
wrap.appendChild(fu);
|
|
518
|
+
return wrap;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function renderTurn(turn, isLast) {
|
|
522
|
+
var t = el('div', 'turn');
|
|
523
|
+
t.appendChild(el('div', 'q', turn.question));
|
|
524
|
+
if (turn.phase) {
|
|
525
|
+
var st = el('div', 'status');
|
|
526
|
+
st.id = 'live-status';
|
|
527
|
+
var orbs = el('span', 'orbs');
|
|
528
|
+
orbs.appendChild(el('span', 'orb')); orbs.appendChild(el('span', 'orb')); orbs.appendChild(el('span', 'orb'));
|
|
529
|
+
st.appendChild(orbs);
|
|
530
|
+
st.appendChild(document.createTextNode(phaseText(turn.phase, state.overview)));
|
|
531
|
+
t.appendChild(st);
|
|
532
|
+
return t;
|
|
533
|
+
}
|
|
534
|
+
if (turn.error) { t.appendChild(el('div', 'errbox', turn.error)); return t; }
|
|
535
|
+
var r = turn.result;
|
|
536
|
+
if (!r) return t;
|
|
537
|
+
|
|
538
|
+
var evidenceByClaim = {};
|
|
539
|
+
(r.evidence || []).forEach(function (e) { evidenceByClaim[e.claimId] = e; });
|
|
540
|
+
var citedIds = [];
|
|
541
|
+
(r.sentences || []).forEach(function (s) {
|
|
542
|
+
(s.refs || []).forEach(function (ref) { if (citedIds.indexOf(ref) < 0) citedIds.push(ref); });
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (!r.sentences || !r.sentences.length) {
|
|
546
|
+
var none = el('div', '');
|
|
547
|
+
none.style.cssText = 'border:1px solid var(--line);border-radius:11px;padding:14px 16px;background:var(--surface);font-size:13.5px;color:var(--text2);box-shadow:var(--shadow-sm)';
|
|
548
|
+
none.textContent = 'The tree\\u2019s evidence couldn\\u2019t support a verifiable answer to this question. Try rephrasing, or browse the Library to see what the tree knows.';
|
|
549
|
+
t.appendChild(none);
|
|
550
|
+
}
|
|
551
|
+
var ans = el('div', 'answer');
|
|
552
|
+
(r.sentences || []).forEach(function (s) {
|
|
553
|
+
var span = el('span', 'sent', s.text + ' ');
|
|
554
|
+
ans.appendChild(span);
|
|
555
|
+
(s.refs || []).forEach(function (ref) {
|
|
556
|
+
var n = citedIds.indexOf(ref) + 1;
|
|
557
|
+
var ev = evidenceByClaim[ref];
|
|
558
|
+
var chip = el('button', 'chip');
|
|
559
|
+
chip.appendChild(document.createTextNode(String(n)));
|
|
560
|
+
var dot = el('span', 'seal-dot');
|
|
561
|
+
dot.style.background = 'var(--' + (s.verdict === 'VERIFIED' ? 'verified' : s.verdict === 'STALE' ? 'stale' : 'missing') + ')';
|
|
562
|
+
chip.appendChild(dot);
|
|
563
|
+
chip.title = (ev ? ev.text + ' \\u2014 ' : '') + SEAL_TITLE[s.verdict];
|
|
564
|
+
chip.addEventListener('click', function () { toggleReceiptCard(t, ref, chip); });
|
|
565
|
+
ans.appendChild(chip);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
t.appendChild(ans);
|
|
569
|
+
|
|
570
|
+
if (r.sentences && r.sentences.length) {
|
|
571
|
+
var g = el('div', 'grounding');
|
|
572
|
+
var gd = el('span', 'g-dot');
|
|
573
|
+
var pct = Math.round((r.grounding || 0) * 100);
|
|
574
|
+
gd.style.background = pct === 100 ? 'var(--verified)' : pct >= 50 ? 'var(--stale)' : 'var(--missing)';
|
|
575
|
+
g.appendChild(gd);
|
|
576
|
+
g.appendChild(document.createTextNode('Grounded ' + pct + '% \\u00B7 mode: ' + r.mode + ' \\u00B7 every chip opens its receipt'));
|
|
577
|
+
t.appendChild(g);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (r.unverifiable && r.unverifiable.length) {
|
|
581
|
+
var qr = el('div', 'quarantine');
|
|
582
|
+
var qb = el('button', '', '\\u25B8 ' + r.unverifiable.length + ' statement' + (r.unverifiable.length === 1 ? '' : 's') + ' withheld (no verifiable citation)');
|
|
583
|
+
var qbody = el('div', 'q-body');
|
|
584
|
+
qbody.hidden = true;
|
|
585
|
+
r.unverifiable.forEach(function (u) { qbody.appendChild(el('div', '', u)); });
|
|
586
|
+
qb.addEventListener('click', function () { qbody.hidden = !qbody.hidden; });
|
|
587
|
+
qr.appendChild(qb); qr.appendChild(qbody);
|
|
588
|
+
t.appendChild(qr);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (citedIds.length) {
|
|
592
|
+
var lbl = el('div', 'src-label');
|
|
593
|
+
lbl.appendChild(document.createTextNode('Receipts'));
|
|
594
|
+
lbl.appendChild(el('span', '', '\\u00B7 ' + citedIds.length));
|
|
595
|
+
t.appendChild(lbl);
|
|
596
|
+
var cards = el('div', 'cards');
|
|
597
|
+
citedIds.forEach(function (ref, idx) {
|
|
598
|
+
var ev = evidenceByClaim[ref];
|
|
599
|
+
if (!ev) return;
|
|
600
|
+
var card = el('button', 'card');
|
|
601
|
+
card.setAttribute('data-ref', ref);
|
|
602
|
+
var top = el('div', 'c-top');
|
|
603
|
+
top.appendChild(el('span', 'c-n', String(idx + 1)));
|
|
604
|
+
var fileName = (ev.spans && ev.spans[0]) ? ev.spans[0].file.split('/').pop() : ev.nodePath || 'roll-up claim';
|
|
605
|
+
top.appendChild(el('span', 'c-file', fileName));
|
|
606
|
+
card.appendChild(top);
|
|
607
|
+
var meta = el('div', 'c-meta');
|
|
608
|
+
var seal = el('span', 'seal ' + (ev.verdict || 'VERIFIED'));
|
|
609
|
+
seal.appendChild(el('span', 's-dot'));
|
|
610
|
+
seal.appendChild(document.createTextNode(SEAL_WORD[ev.verdict] || 'verified'));
|
|
611
|
+
seal.title = SEAL_TITLE[ev.verdict] || '';
|
|
612
|
+
meta.appendChild(seal);
|
|
613
|
+
if (ev.spans && ev.spans[0]) {
|
|
614
|
+
var pathEl = el('span', 'c-path', ev.spans[0].file + ':' + ev.spans[0].startLine + '-' + ev.spans[0].endLine);
|
|
615
|
+
pathEl.title = ev.spans[0].file + ':' + ev.spans[0].startLine + '-' + ev.spans[0].endLine;
|
|
616
|
+
meta.appendChild(pathEl);
|
|
617
|
+
}
|
|
618
|
+
card.appendChild(meta);
|
|
619
|
+
card.appendChild(el('div', 'c-snip', ev.text));
|
|
620
|
+
card.addEventListener('click', function () { toggleReceiptCard(t, ref, card); });
|
|
621
|
+
cards.appendChild(card);
|
|
622
|
+
});
|
|
623
|
+
t.appendChild(cards);
|
|
624
|
+
var rslot = el('div', 'r-slot');
|
|
625
|
+
rslot.id = 'rslot-' + Math.random().toString(36).slice(2, 8);
|
|
626
|
+
t.appendChild(rslot);
|
|
627
|
+
t._evidence = evidenceByClaim;
|
|
628
|
+
t._rslot = rslot;
|
|
629
|
+
}
|
|
630
|
+
return t;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function toggleReceiptCard(turnEl, ref, trigger) {
|
|
634
|
+
var slot = turnEl._rslot;
|
|
635
|
+
if (!slot) return;
|
|
636
|
+
var openRef = slot.getAttribute('data-open');
|
|
637
|
+
turnEl.querySelectorAll('.chip.open').forEach(function (c) { c.classList.remove('open'); });
|
|
638
|
+
if (openRef === ref) { slot.textContent = ''; slot.removeAttribute('data-open'); return; }
|
|
639
|
+
var ev = turnEl._evidence[ref];
|
|
640
|
+
if (!ev) return;
|
|
641
|
+
slot.textContent = '';
|
|
642
|
+
slot.setAttribute('data-open', ref);
|
|
643
|
+
if (trigger.classList.contains('chip')) trigger.classList.add('open');
|
|
644
|
+
slot.appendChild(receiptEl(ev));
|
|
645
|
+
slot.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function receiptEl(ev) {
|
|
649
|
+
var box = el('div', 'receipt');
|
|
650
|
+
(ev.spans || []).forEach(function (span) {
|
|
651
|
+
var top = el('div', 'r-top');
|
|
652
|
+
top.appendChild(el('span', 'r-file', span.file + ':' + span.startLine + '-' + span.endLine));
|
|
653
|
+
var seal = el('span', 'seal ' + (ev.verdict || 'VERIFIED'));
|
|
654
|
+
seal.appendChild(el('span', 's-dot'));
|
|
655
|
+
seal.appendChild(document.createTextNode(SEAL_WORD[ev.verdict] || 'verified'));
|
|
656
|
+
top.appendChild(seal);
|
|
657
|
+
if (span.hash) top.appendChild(el('span', 'r-hash', 'sha256 ' + String(span.hash).slice(0, 12)));
|
|
658
|
+
box.appendChild(top);
|
|
659
|
+
var pre = el('pre');
|
|
660
|
+
String(span.text).split('\\n').forEach(function (line, i) {
|
|
661
|
+
var lr = el('div', 'rline');
|
|
662
|
+
lr.appendChild(el('span', 'ln', String(span.startLine + i)));
|
|
663
|
+
lr.appendChild(el('span', 'code', line === '' ? ' ' : line));
|
|
664
|
+
pre.appendChild(lr);
|
|
665
|
+
});
|
|
666
|
+
box.appendChild(pre);
|
|
667
|
+
});
|
|
668
|
+
if (!ev.spans || !ev.spans.length) {
|
|
669
|
+
var top2 = el('div', 'r-top');
|
|
670
|
+
top2.appendChild(el('span', 'r-file', ev.nodePath || 'tree claim'));
|
|
671
|
+
box.appendChild(top2);
|
|
672
|
+
var body = el('div', '');
|
|
673
|
+
body.style.cssText = 'padding:10px 14px;font-size:13px;color:var(--text2)';
|
|
674
|
+
body.textContent = 'Grounded in tree claim: "' + ev.text + '" \\u2014 open the Library to descend to its source lines.';
|
|
675
|
+
box.appendChild(body);
|
|
676
|
+
}
|
|
677
|
+
return box;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* library */
|
|
681
|
+
function renderLibrary() {
|
|
682
|
+
var lib = el('div', 'lib');
|
|
683
|
+
var rail = el('div', 'lib-rail');
|
|
684
|
+
var main = el('div', 'lib-main');
|
|
685
|
+
lib.appendChild(rail); lib.appendChild(main);
|
|
686
|
+
if (!state.treeData) {
|
|
687
|
+
fetchJson('/api/tree').then(function (d) {
|
|
688
|
+
if (d.error) return;
|
|
689
|
+
state.treeData = d;
|
|
690
|
+
state.libNode = state.libNode || d.root;
|
|
691
|
+
Object.keys(d.nodes).forEach(function (id) {
|
|
692
|
+
if (d.nodes[id].kind !== 'leaf') state.libOpen[id] = true; // populated by default, never two lonely rows
|
|
693
|
+
});
|
|
694
|
+
render();
|
|
695
|
+
});
|
|
696
|
+
main.appendChild(el('div', 'empty', 'Loading the tree\\u2026'));
|
|
697
|
+
return lib;
|
|
698
|
+
}
|
|
699
|
+
var d = state.treeData;
|
|
700
|
+
rail.appendChild(treeNodeEl(d.root));
|
|
701
|
+
|
|
702
|
+
var node = d.nodes[state.libNode] || d.nodes[d.root];
|
|
703
|
+
var pct = Math.round(d.freshness * 100);
|
|
704
|
+
var banner = el('div', 'lib-banner');
|
|
705
|
+
var bp = el('span', 'pct', pct + '%');
|
|
706
|
+
bp.style.color = pct === 100 ? 'var(--verified)' : 'var(--stale)';
|
|
707
|
+
banner.appendChild(bp);
|
|
708
|
+
banner.appendChild(document.createTextNode(
|
|
709
|
+
(pct === 100 ? 'Every claim verified against the code' : (d.total - d.verified) + ' of ' + d.total + ' claims need attention') +
|
|
710
|
+
' \\u00B7 built ' + new Date(d.builtAt).toLocaleString() +
|
|
711
|
+
' \\u00B7 verified = receipt checked \\u00B7 unchecked = awaiting semantic critique'
|
|
712
|
+
));
|
|
713
|
+
main.appendChild(banner);
|
|
714
|
+
main.appendChild(el('h2', 'node-h', node.kind === 'root' ? node.title : node.title));
|
|
715
|
+
var sub = el('div', 'node-sub');
|
|
716
|
+
sub.appendChild(el('span', '', node.kind + (node.path ? ' \\u00B7 ' + node.path : '')));
|
|
717
|
+
sub.appendChild(el('span', '', node.verified + ' of ' + node.total + ' verified in this subtree'));
|
|
718
|
+
main.appendChild(sub);
|
|
719
|
+
var claims = el('div', 'claims');
|
|
720
|
+
if (!node.claims.length) claims.appendChild(el('div', 'empty', 'No claims for this node.'));
|
|
721
|
+
node.claims.forEach(function (c) {
|
|
722
|
+
var cl = el('div', 'claim');
|
|
723
|
+
var row = el('button', '');
|
|
724
|
+
var seal = el('span', 'seal ' + c.verdict);
|
|
725
|
+
seal.appendChild(el('span', 's-dot'));
|
|
726
|
+
seal.appendChild(document.createTextNode(SEAL_WORD[c.verdict]));
|
|
727
|
+
seal.title = SEAL_TITLE[c.verdict];
|
|
728
|
+
row.appendChild(seal);
|
|
729
|
+
row.appendChild(el('span', 'cl-text', c.text));
|
|
730
|
+
if (c.faithfulness === 'unsupported') { var f = el('span', 'fl unsupported', 'flagged'); f.title = 'The build-time critique judged the cited lines do not support this claim.'; row.appendChild(f); }
|
|
731
|
+
else if (c.faithfulness === 'unchecked') { var f2 = el('span', 'fl', 'unchecked'); f2.title = 'No semantic critique ran for this claim.'; row.appendChild(f2); }
|
|
732
|
+
var rwrap = el('div', 'r-wrap');
|
|
733
|
+
rwrap.hidden = true;
|
|
734
|
+
row.addEventListener('click', function () {
|
|
735
|
+
if (rwrap.hidden && !rwrap.firstChild) {
|
|
736
|
+
rwrap.appendChild(receiptEl({ spans: c.spans, verdict: c.verdict, text: c.text, nodePath: node.path }));
|
|
737
|
+
(c.refs || []).forEach(function (ref) {
|
|
738
|
+
var target = state.treeData.nodes[ref.nodeId];
|
|
739
|
+
if (!target) return;
|
|
740
|
+
var line = el('button', '');
|
|
741
|
+
line.style.cssText = 'display:block;margin-top:6px;font:400 12px var(--mono);color:var(--accent);';
|
|
742
|
+
line.textContent = '\\u2192 grounded in ' + (target.path || target.title);
|
|
743
|
+
line.addEventListener('click', function (evn) { evn.stopPropagation(); state.libNode = ref.nodeId; render(); });
|
|
744
|
+
rwrap.appendChild(line);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
rwrap.hidden = !rwrap.hidden;
|
|
748
|
+
});
|
|
749
|
+
cl.appendChild(row); cl.appendChild(rwrap);
|
|
750
|
+
claims.appendChild(cl);
|
|
751
|
+
});
|
|
752
|
+
main.appendChild(claims);
|
|
753
|
+
return lib;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function treeNodeEl(id) {
|
|
757
|
+
var d = state.treeData;
|
|
758
|
+
var node = d.nodes[id];
|
|
759
|
+
var wrap = el('div', '');
|
|
760
|
+
var hasKids = node.childIds.length > 0;
|
|
761
|
+
var row = el('button', 'trow' + (state.libNode === id ? ' active' : '') + (state.libOpen[id] ? ' open' : ''));
|
|
762
|
+
row.appendChild(el('span', 'caret', hasKids ? '\\u25B6' : ''));
|
|
763
|
+
var dot = el('span', 'dot');
|
|
764
|
+
dot.style.background = node.worst === 'VERIFIED' ? 'var(--verified)' : node.worst === 'STALE' ? 'var(--stale)' : 'var(--missing)';
|
|
765
|
+
row.appendChild(dot);
|
|
766
|
+
row.appendChild(el('span', '', node.kind === 'root' ? node.title : node.title + (node.kind === 'dir' ? '/' : '')));
|
|
767
|
+
row.title = node.verified + ' of ' + node.total + ' claims verified in this subtree';
|
|
768
|
+
row.addEventListener('click', function () {
|
|
769
|
+
if (hasKids) state.libOpen[id] = !state.libOpen[id];
|
|
770
|
+
state.libNode = id;
|
|
771
|
+
render();
|
|
772
|
+
});
|
|
773
|
+
wrap.appendChild(row);
|
|
774
|
+
if (hasKids && state.libOpen[id]) {
|
|
775
|
+
var kids = el('div', 'tkids');
|
|
776
|
+
node.childIds.forEach(function (cid) { kids.appendChild(treeNodeEl(cid)); });
|
|
777
|
+
wrap.appendChild(kids);
|
|
778
|
+
}
|
|
779
|
+
return wrap;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/* nav + shortcuts */
|
|
783
|
+
document.getElementById('newThread').addEventListener('click', function () {
|
|
784
|
+
state.thread = null; state.view = 'home'; render(); renderThreadList();
|
|
785
|
+
});
|
|
786
|
+
document.getElementById('navAsk').addEventListener('click', function () {
|
|
787
|
+
state.view = state.thread ? 'thread' : 'home'; render();
|
|
788
|
+
});
|
|
789
|
+
document.getElementById('navLib').addEventListener('click', function () { state.view = 'lib'; render(); });
|
|
790
|
+
document.getElementById('navFix').addEventListener('click', function () { state.view = 'fix'; render(); });
|
|
791
|
+
document.getElementById('menuBtn') && document.getElementById('menuBtn').addEventListener('click', function () {
|
|
792
|
+
document.getElementById('side').classList.toggle('open');
|
|
793
|
+
});
|
|
794
|
+
document.addEventListener('keydown', function (e) {
|
|
795
|
+
var tag = document.activeElement && document.activeElement.tagName;
|
|
796
|
+
if ((e.key === '/' || ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k')) && tag !== 'TEXTAREA' && tag !== 'INPUT') {
|
|
797
|
+
e.preventDefault();
|
|
798
|
+
var ta = document.querySelector('.askbox textarea');
|
|
799
|
+
if (ta) ta.focus();
|
|
800
|
+
else { state.view = state.thread ? 'thread' : 'home'; render(); }
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
boot();
|
|
805
|
+
})();
|
|
806
|
+
</script>
|
|
807
|
+
</body>
|
|
808
|
+
</html>
|
|
809
|
+
`;
|