webctx 0.2.2 → 0.2.4

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.
@@ -0,0 +1,149 @@
1
+ package app
2
+
3
+ import (
4
+ "sort"
5
+ "strings"
6
+ )
7
+
8
+ const backtickFence = "\x60\x60\x60"
9
+
10
+ type evidenceBlock struct {
11
+ text string
12
+ code bool
13
+ score int
14
+ order int
15
+ }
16
+
17
+ // A code fence is indivisible evidence: do not silently truncate a command,
18
+ // request body or SDK example and then invent a closing fence.
19
+ func splitEvidenceBlocks(markdown, question string) []evidenceBlock {
20
+ var blocks []evidenceBlock
21
+ var content strings.Builder
22
+ terms := queryTerms(question)
23
+ inCode := false
24
+ fence := ""
25
+ flush := func(code bool) {
26
+ text := strings.TrimSpace(content.String())
27
+ content.Reset()
28
+ if text == "" {
29
+ return
30
+ }
31
+ score := termScore(text, terms)
32
+ if code {
33
+ score *= 3
34
+ if strings.Contains(strings.ToLower(question), "rest") &&
35
+ (strings.Contains(text, "curl") || strings.Contains(text, "POST ") || strings.Contains(text, "GET ")) {
36
+ score += 4
37
+ }
38
+ }
39
+ blocks = append(blocks, evidenceBlock{text: text, code: code, score: score, order: len(blocks)})
40
+ }
41
+ for _, line := range strings.Split(markdown, "\n") {
42
+ trimmed := strings.TrimSpace(line)
43
+ opening := !inCode && (strings.HasPrefix(trimmed, backtickFence) || strings.HasPrefix(trimmed, "~~~"))
44
+ if opening {
45
+ flush(false)
46
+ inCode = true
47
+ fence = trimmed[:3]
48
+ }
49
+ content.WriteString(line)
50
+ content.WriteByte('\n')
51
+ if inCode && !opening && strings.TrimSpace(strings.TrimPrefix(trimmed, fence)) == "" &&
52
+ strings.HasPrefix(trimmed, fence) {
53
+ flush(true)
54
+ inCode = false
55
+ fence = ""
56
+ }
57
+ }
58
+ // Unclosed code blocks are not valid examples; treat them as source prose.
59
+ flush(false)
60
+ return blocks
61
+ }
62
+
63
+ // Select complete, high-relevance examples and explanatory prose from long
64
+ // sections, preserving original order and the hard context budget.
65
+ func atomicFocusedSection(markdown, question string, budget int) string {
66
+ markdown = strings.TrimSpace(markdown)
67
+ if len(markdown) <= budget {
68
+ return markdown
69
+ }
70
+ if budget < 180 {
71
+ return "*[Section exceeds excerpt budget; follow the source URL.]*"
72
+ }
73
+ blocks := splitEvidenceBlocks(markdown, question)
74
+ const omitted = "\n\n*[Other source text omitted. Follow the original URL for full details.]*"
75
+ remaining := budget - len(omitted) - 8
76
+ if remaining < 80 {
77
+ return "*[Section exceeds excerpt budget; follow the source URL.]*"
78
+ }
79
+ selected := make(map[int]string)
80
+ var codes []evidenceBlock
81
+ var prose []evidenceBlock
82
+ for _, block := range blocks {
83
+ if block.code {
84
+ codes = append(codes, block)
85
+ } else {
86
+ prose = append(prose, block)
87
+ }
88
+ }
89
+ sort.SliceStable(codes, func(i, j int) bool {
90
+ if codes[i].score == codes[j].score {
91
+ return codes[i].order < codes[j].order
92
+ }
93
+ return codes[i].score > codes[j].score
94
+ })
95
+ // Reserve a little space for the section explanation.
96
+ codeAllowance := remaining * 85 / 100
97
+ for _, block := range codes {
98
+ size := len(block.text) + 2
99
+ if size <= codeAllowance {
100
+ selected[block.order] = block.text
101
+ codeAllowance -= size
102
+ remaining -= size
103
+ }
104
+ }
105
+ sort.SliceStable(prose, func(i, j int) bool {
106
+ if prose[i].score == prose[j].score {
107
+ return prose[i].order < prose[j].order
108
+ }
109
+ return prose[i].score > prose[j].score
110
+ })
111
+ for _, block := range prose {
112
+ if remaining < 100 {
113
+ break
114
+ }
115
+ if len(block.text)+2 <= remaining {
116
+ selected[block.order] = block.text
117
+ remaining -= len(block.text) + 2
118
+ } else if part := completeProseLines(block.text, remaining-4); part != "" {
119
+ selected[block.order] = part
120
+ remaining -= len(part) + 2
121
+ }
122
+ }
123
+ var output []string
124
+ for _, block := range blocks {
125
+ if text := selected[block.order]; text != "" {
126
+ output = append(output, text)
127
+ }
128
+ }
129
+ if len(output) == 0 {
130
+ return "*[Code examples exceed the excerpt budget; inspect source URL for the full examples.]*"
131
+ }
132
+ return strings.Join(output, "\n\n") + omitted
133
+ }
134
+
135
+ func completeProseLines(text string, maxBytes int) string {
136
+ if maxBytes <= 0 {
137
+ return ""
138
+ }
139
+ var lines []string
140
+ size := 0
141
+ for _, line := range strings.Split(text, "\n") {
142
+ if size+len(line)+1 > maxBytes {
143
+ break
144
+ }
145
+ lines = append(lines, line)
146
+ size += len(line) + 1
147
+ }
148
+ return strings.TrimSpace(strings.Join(lines, "\n"))
149
+ }
@@ -0,0 +1,47 @@
1
+ package app
2
+
3
+ import (
4
+ "strings"
5
+ "testing"
6
+ )
7
+
8
+ func TestFocusedExcerptPreservesWholeCurlExamples(t *testing.T) {
9
+ code := backtickFence + "bash\ncurl -X POST https://api.example.com/v2/scrape \\\n" +
10
+ " -H 'Content-Type: application/json' \\\n" +
11
+ " -d '{\"alexandria\":{\"capability\":\"find-tools\"}}'\n" + backtickFence
12
+ doc := "# Documentation\n\n" + strings.Repeat("Background prose unrelated to endpoints.\n", 200) +
13
+ "\n## Inspect tools over REST\n" + strings.Repeat("Introductory filler.\n", 70) +
14
+ "\n" + code + "\n" + strings.Repeat("Appendix filler.\n", 200)
15
+ got := focusExcerpt(doc, "How do I inspect Alexandria tools with a REST request example?", 1600)
16
+ if !strings.Contains(got, code) {
17
+ t.Fatalf("the complete REST example must survive extraction:\n%s", got)
18
+ }
19
+ if strings.Count(got, backtickFence) != 2 {
20
+ t.Fatalf("expected exactly one balanced code fence: %s", got)
21
+ }
22
+ if len(got) > 1800 {
23
+ t.Fatalf("excerpt exceeded budget: %d", len(got))
24
+ }
25
+ }
26
+
27
+ func TestOversizedCodeBlocksAreNeverPartiallyReturned(t *testing.T) {
28
+ huge := backtickFence + "json\n{\"payload\":\"" + strings.Repeat("a", 3000) + "\"}\n" + backtickFence
29
+ doc := "# API\n\n" + huge + "\n\n" + strings.Repeat("Further explanation.\n", 200)
30
+ got := atomicFocusedSection(doc, "What is the API payload?", 700)
31
+ if strings.Contains(got, "\"payload\"") || strings.Contains(got, backtickFence) {
32
+ t.Fatalf("oversized example should be omitted, not truncated:\n%s", got)
33
+ }
34
+ if !strings.Contains(got, "omitted") && !strings.Contains(got, "source URL") {
35
+ t.Fatalf("omission should be explicitly marked: %s", got)
36
+ }
37
+ }
38
+
39
+ func TestPlainCodeFenceOpeningNotTreatedAsClosing(t *testing.T) {
40
+ doc := "# API\n" + strings.Repeat("Preliminary text.\n", 80) + "\n" +
41
+ backtickFence + "\nPOST /v2/scrape\n{}\n" + backtickFence + "\n" +
42
+ strings.Repeat("End notes.\n", 150)
43
+ got := atomicFocusedSection(doc, "scrape request", 650)
44
+ if !strings.Contains(got, backtickFence+"\nPOST /v2/scrape\n{}\n"+backtickFence) {
45
+ t.Fatalf("plain opening fence was not handled atomically: %s", got)
46
+ }
47
+ }
@@ -0,0 +1,237 @@
1
+ package app
2
+
3
+ import (
4
+ "net/url"
5
+ "regexp"
6
+ "sort"
7
+ "strings"
8
+ )
9
+
10
+ var documentationIndexPattern = regexp.MustCompile("https?://[^\\s<>\"')\\]]+/llms\\.txt")
11
+
12
+ func documentIndexURLs(base *url.URL, primary string) []string {
13
+ var urls []string
14
+ seen := map[string]bool{}
15
+ for _, raw := range documentationIndexPattern.FindAllString(primary, 5) {
16
+ target, err := url.Parse(raw)
17
+ if err != nil || validateSourceURL(raw) != nil ||
18
+ !strings.EqualFold(base.Hostname(), target.Hostname()) || target.Port() != base.Port() {
19
+ continue
20
+ }
21
+ if !seen[raw] {
22
+ urls = append(urls, raw)
23
+ seen[raw] = true
24
+ }
25
+ }
26
+ fallback := base.Scheme + "://" + base.Host + "/llms.txt"
27
+ if !seen[fallback] {
28
+ urls = append(urls, fallback)
29
+ }
30
+ return urls
31
+ }
32
+
33
+ func implementationQuestion(question string) bool {
34
+ text := strings.ToLower(question)
35
+ for _, signal := range []string{
36
+ "implement", "integrat", "runnable", "configuration",
37
+ "code example", "complete", "how to", "set up", "using a", "creating",
38
+ } {
39
+ if strings.Contains(text, signal) {
40
+ return true
41
+ }
42
+ }
43
+ return false
44
+ }
45
+
46
+ func implementationLinkBonus(candidate sourceCandidate, question string) int {
47
+ lower := strings.ToLower(candidate.url + " " + candidate.label)
48
+ q := strings.ToLower(question)
49
+ score := 0
50
+ if implementationQuestion(question) {
51
+ switch {
52
+ case strings.Contains(lower, "/get-started/"), strings.Contains(lower, "/quickstart"),
53
+ strings.Contains(lower, "getting started"):
54
+ score += 64 // Publisher's end-to-end guide, not isolated snippets.
55
+ case strings.Contains(lower, "tutorial"), strings.Contains(lower, "integration"),
56
+ strings.Contains(lower, "how-to"):
57
+ score += 16
58
+ case strings.Contains(lower, "example"):
59
+ score += 6
60
+ }
61
+ }
62
+ if strings.Contains(q, "sql") || strings.Contains(q, "sqlite") {
63
+ if strings.Contains(lower, "sqlite") || strings.Contains(lower, "storage") {
64
+ score += 26
65
+ } else if strings.Contains(lower, "/examples/") {
66
+ score -= 16
67
+ }
68
+ }
69
+ if strings.Contains(q, "webhook") || strings.Contains(q, "fulfill") || strings.Contains(q, "fulfil") {
70
+ if strings.Contains(lower, "webhook") || strings.Contains(lower, "fulfill") || strings.Contains(lower, "fulfil") {
71
+ score += 30
72
+ }
73
+ }
74
+ if strings.Contains(q, "checkout") && (strings.Contains(lower, "checkout") || strings.Contains(lower, "session")) {
75
+ score += 6
76
+ }
77
+ if strings.Contains(q, "signature") && strings.Contains(lower, "signature") {
78
+ score += 24
79
+ }
80
+ if strings.Contains(q, "curl") || strings.Contains(q, "rest") || strings.Contains(q, "endpoint") {
81
+ if strings.Contains(lower, "/api/") || strings.Contains(lower, "api-reference") {
82
+ score += 10
83
+ }
84
+ }
85
+ if strings.Contains(q, "session") && strings.Contains(q, "creat") &&
86
+ (strings.Contains(lower, "/sessions/create") || strings.Contains(lower, "create a checkout session")) {
87
+ score += 38
88
+ }
89
+ if strings.Contains(q, "checkout") && strings.Contains(lower, "/checkout/quickstart") {
90
+ score += 20
91
+ }
92
+ if strings.Contains(q, "fulfill") || strings.Contains(q, "fulfil") {
93
+ if strings.Contains(lower, "/checkout/fulfillment") {
94
+ score += 24
95
+ }
96
+ }
97
+ if strings.Contains(lower, "/alarms/") && !strings.Contains(q, "alarm") {
98
+ score -= 24
99
+ }
100
+ if strings.Contains(lower, "/concepts/") && implementationQuestion(question) {
101
+ score -= 12
102
+ }
103
+ if strings.Contains(lower, "legacy") && (strings.Contains(q, "new ") || strings.Contains(q, "current")) && !strings.Contains(q, "legacy") {
104
+ score -= 25
105
+ }
106
+ return score
107
+ }
108
+
109
+ // Search indices on a large documentation host often contain links for many
110
+ // unrelated products. Prefer links in the original section or with a product
111
+ // named in the question. This prevents "webhooks for Climate Orders" from
112
+ // outranking generic Checkout webhooks solely because both contain "webhook".
113
+ func sourceScopeBonus(candidate sourceCandidate, rawURL, question string) int {
114
+ base, err1 := url.Parse(rawURL)
115
+ target, err2 := url.Parse(candidate.url)
116
+ if err1 != nil || err2 != nil {
117
+ return 0
118
+ }
119
+ q := strings.ToLower(question)
120
+ sourceSegments := strings.Split(strings.Trim(base.Path, "/"), "/")
121
+ targetSegments := strings.Split(strings.Trim(target.Path, "/"), "/")
122
+ if len(targetSegments) == 0 {
123
+ return 0
124
+ }
125
+ score := 0
126
+ if len(sourceSegments) > 0 && targetSegments[0] == sourceSegments[0] {
127
+ score += 8
128
+ }
129
+ if len(targetSegments) > 0 && targetSegments[0] != "" &&
130
+ !strings.Contains(q, strings.TrimSuffix(targetSegments[0], "s")) &&
131
+ !strings.Contains(q, targetSegments[0]) &&
132
+ targetSegments[0] != "api" && targetSegments[0] != "webhooks" &&
133
+ targetSegments[0] != "events" &&
134
+ (len(sourceSegments) == 0 || targetSegments[0] != sourceSegments[0]) {
135
+ score -= 24
136
+ }
137
+ // A general Worker overview is less relevant than Durable Objects'
138
+ // focused setup instructions even if both mention "Worker".
139
+ if strings.Contains(q, "durable object") && strings.Contains(target.Path, "/workers/") &&
140
+ !strings.Contains(target.Path, "/durable-objects/") {
141
+ score -= 14
142
+ }
143
+ return score
144
+ }
145
+
146
+ func normalizedDocumentationURL(raw string) string {
147
+ u, err := url.Parse(raw)
148
+ if err != nil {
149
+ return raw
150
+ }
151
+ p := strings.TrimSuffix(u.Path, "/index.md")
152
+ p = strings.TrimSuffix(p, ".md")
153
+ p = strings.TrimSuffix(p, "/")
154
+ u.Path = p
155
+ u.Fragment = ""
156
+ u.RawFragment = ""
157
+ u.RawPath = ""
158
+ return u.String()
159
+ }
160
+
161
+ func researchCandidates(rawURL, primary, question string, limit int) []sourceCandidate {
162
+ baseCandidates := relatedSourceLinks(primary, rawURL, question, limit*4)
163
+ indexCandidates := linksFromDocsIndexWithPrimary(rawURL, primary, question, limit*5)
164
+ seen := map[string]bool{normalizedDocumentationURL(rawURL): true}
165
+ var candidates []sourceCandidate
166
+ for _, c := range append(baseCandidates, indexCandidates...) {
167
+ key := normalizedDocumentationURL(c.url)
168
+ if seen[key] {
169
+ continue
170
+ }
171
+ seen[key] = true
172
+ c.score += implementationLinkBonus(c, question)
173
+ c.score += sourceScopeBonus(c, rawURL, question)
174
+ candidates = append(candidates, c)
175
+ }
176
+ sort.SliceStable(candidates, func(i, j int) bool {
177
+ if candidates[i].score == candidates[j].score {
178
+ return candidates[i].order < candidates[j].order
179
+ }
180
+ return candidates[i].score > candidates[j].score
181
+ })
182
+ if len(candidates) > limit {
183
+ return candidates[:limit]
184
+ }
185
+ return candidates
186
+ }
187
+
188
+ type evidenceNeed struct {
189
+ name string
190
+ alternatives []string
191
+ }
192
+
193
+ // Check for explicit source evidence; do not claim that a plausible guess
194
+ // supplies missing request fields or code.
195
+ func evidenceNeeds(question string) []evidenceNeed {
196
+ q := strings.ToLower(question)
197
+ var needs []evidenceNeed
198
+ for _, literal := range []string{"line_items", "success_url", "cancel_url", "sql.exec", "wrangler", "sqlite"} {
199
+ if strings.Contains(q, literal) {
200
+ needs = append(needs, evidenceNeed{literal, []string{literal}})
201
+ }
202
+ }
203
+ if strings.Contains(q, "webhook") {
204
+ needs = append(needs, evidenceNeed{"webhook events", []string{"webhook", "checkout.session.completed"}})
205
+ if strings.Contains(q, "signature") || strings.Contains(q, "secure") {
206
+ needs = append(needs, evidenceNeed{"webhook signature verification", []string{"stripe-signature", "constructevent", "signature verification", "signatureverification"}})
207
+ }
208
+ }
209
+ if strings.Contains(q, "sql table") || strings.Contains(q, "creating a sql") {
210
+ needs = append(needs, evidenceNeed{"SQL table creation", []string{"create table"}})
211
+ }
212
+ if strings.Contains(q, "accesses the binding") || strings.Contains(q, "access the binding") {
213
+ needs = append(needs, evidenceNeed{"Worker binding access", []string{"getbyname", "env.my_durable_object", "env.durable_object"}})
214
+ }
215
+ if strings.Contains(q, "checkout") && (strings.Contains(q, "fulfill") || strings.Contains(q, "fulfil")) {
216
+ needs = append(needs, evidenceNeed{"Checkout fulfillment event", []string{"checkout.session.completed"}})
217
+ }
218
+ return needs
219
+ }
220
+
221
+ func missingResearchEvidence(question, text string) []string {
222
+ lower := strings.ToLower(text)
223
+ var missing []string
224
+ for _, need := range evidenceNeeds(question) {
225
+ found := false
226
+ for _, candidate := range need.alternatives {
227
+ if strings.Contains(lower, candidate) {
228
+ found = true
229
+ break
230
+ }
231
+ }
232
+ if !found {
233
+ missing = append(missing, need.name)
234
+ }
235
+ }
236
+ return missing
237
+ }
@@ -0,0 +1,72 @@
1
+ package app
2
+
3
+ import (
4
+ "net/http"
5
+ "strings"
6
+ "testing"
7
+ )
8
+
9
+ func TestStripeFocusedLinksPreferCheckoutAndWebhookDocs(t *testing.T) {
10
+ old := http.DefaultClient
11
+ t.Cleanup(func() { http.DefaultClient = old })
12
+ index := "# Documentation\n\n" +
13
+ "- [Climate order webhooks](https://docs.stripe.com/climate/orders/webhooks.md)\n" +
14
+ "- [Create a Checkout Session](https://docs.stripe.com/api/checkout/sessions/create.md)\n" +
15
+ "- [Build a Stripe-hosted checkout page](https://docs.stripe.com/checkout/quickstart.md)\n" +
16
+ "- [Set up and deploy a webhook](https://docs.stripe.com/webhooks/quickstart.md)\n" +
17
+ "- [Fulfill orders](https://docs.stripe.com/checkout/fulfillment.md)\n" +
18
+ "- [Verify webhook signatures](https://docs.stripe.com/webhooks.md#verify-signature)\n"
19
+ http.DefaultClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
20
+ if req.URL.Path == "/llms.txt" {
21
+ return testHTTPResponse(req, http.StatusOK, index, nil), nil
22
+ }
23
+ return testHTTPResponse(req, http.StatusNotFound, "", nil), nil
24
+ })}
25
+ question := "Implement Stripe-hosted Checkout: create a Checkout Session REST request and securely fulfill orders using webhooks, including signature verification"
26
+ primary := "# Stripe Checkout\n\n[Start building your checkout integration](https://docs.stripe.com/checkout/quickstart.md)"
27
+ got := researchCandidates("https://docs.stripe.com/payments/checkout", primary, question, 5)
28
+ var links []string
29
+ for _, c := range got {
30
+ links = append(links, c.url)
31
+ }
32
+ output := strings.Join(links, "\n")
33
+ for _, want := range []string{
34
+ "/checkout/quickstart.md",
35
+ "/api/checkout/sessions/create.md",
36
+ "/webhooks/quickstart.md",
37
+ "/checkout/fulfillment.md",
38
+ } {
39
+ if !strings.Contains(output, want) {
40
+ t.Errorf("expected %q among selected source links: %s", want, output)
41
+ }
42
+ }
43
+ if strings.Contains(output, "/climate/") {
44
+ t.Errorf("irrelevant Climate-specific webhook outranked Checkout sources: %s", output)
45
+ }
46
+ }
47
+
48
+ func TestCloudflareNewSQLiteQuestionPrefersGettingStarted(t *testing.T) {
49
+ old := http.DefaultClient
50
+ t.Cleanup(func() { http.DefaultClient = old })
51
+ http.DefaultClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
52
+ return testHTTPResponse(req, http.StatusForbidden, "", nil), nil
53
+ })}
54
+ primary := "# Cloudflare Durable Objects\n" +
55
+ "[Durable Object alarms](https://developers.cloudflare.com/durable-objects/api/alarms/)\n" +
56
+ "[SQLite storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/)\n" +
57
+ "[Get started](https://developers.cloudflare.com/durable-objects/get-started/)\n" +
58
+ "> Documentation Index https://developers.cloudflare.com/durable-objects/llms.txt\n"
59
+ question := "For a new Cloudflare Worker using a SQLite-backed Durable Object, show complete wrangler configuration, Worker binding, and SQL table code."
60
+ got := researchCandidates("https://developers.cloudflare.com/durable-objects/", primary, question, 2)
61
+ if len(got) != 2 {
62
+ t.Fatalf("unexpected number of selected references: %+v", got)
63
+ }
64
+ var links []string
65
+ for _, c := range got {
66
+ links = append(links, c.url)
67
+ }
68
+ output := strings.Join(links, "\n")
69
+ if !strings.Contains(output, "/get-started/") || strings.Contains(output, "/alarms/") {
70
+ t.Fatalf("unrelated alarm material displaced getting-started guide: %s", output)
71
+ }
72
+ }