Files
Kilo-Org_kilocode/script/extract-source-links.ts
Igor Šćekić 8aaa62c794 Session export capture (#10611)
* feat(session-export): scaffold module with config constants

* feat(session-export): add zstd compression wrapper

* feat(session-export): event and envelope type definitions

* feat(session-export): eligibility check with kill-switch

* feat(session-export): org signal collector with auth resolver

* feat(session-export): worker SQLite schema and storage helpers

* feat(session-export): content-addressed chunker with zstd dedup

* feat(session-export): client-side light scrubber

* feat(session-export): IPC contract and inbox with back-pressure

* feat(session-export): persist scrubbed events with chunked payloads

* feat(session-export): worker entry point with inbox drain loop

* feat(session-export): main-thread capture module

* feat(session-export): workspace baseline and delta fibers

* feat(session-export): sync subscriber and tool io chunking

* feat(session-export): bootstrap wiring and compaction hook

* feat(session-export): wire capture hooks into sessions

* feat(session-export): uploader and buffer cap

* chore(session-export): annotate shared session hooks

* changeset(session-export): add release note

* fix(session-export): keep bootstrap non-blocking without instance context

* feat(session-export): respawn worker after failures

* test(session-export): add performance budget assertions

* test(session-export): add worker end-to-end smoke test

* fix(session-export): strip identity and high-risk baseline paths

* test(session-export): gate perf assertions for stable sweeps

* fix(session-export): bundle worker in single-file builds

* fix(session-export): make capture envelopes cloneable

* fix(session-export): drain worker on CLI shutdown

* feat(session-export): capture workspace baseline and deltas

* fix(session-export): preserve request metadata

* test(session-export): cover request metadata capture

* feat(cli): send indexed session export batches

* feat(cli): authorize session export uploads

* fix(cli): flush session export shutdown uploads

* feat(cli): optimize session export replay payloads

* fix(cli): include session export surface metadata

* fix(session-export): decrement chunk refs after upload

decRefChunks was never called, so chunk ref_count stayed at 1 (or
higher with dedup) and DELETE FROM chunk WHERE ref_count <= 0 never
matched. Chunks accumulated in the local SQLite buffer until the 50 GB
cap. Call decRefChunks alongside markUploaded so deleteUploaded can
reclaim the rows.

* fix(session-export): add periodic uploader flush timer

flushIntervalMs and retryBackoffMaxMs were both defined in config and
neither was referenced anywhere; scheduleFlush only ran on inbound
events or reconnect. After a 5xx or network failure the row was backed
off for 1 s, but if no further event arrived the retry never fired and
the events stranded until the CLI restarted. Drive a periodic flush
from a setInterval, unref the handle so it doesn't pin the process, and
clear it from a new dispose() hook the worker calls on shutdown.

* fix(session-export): stop emitting absolute workspace root in baseline

CaptureMetadata.root was the literal absolute filesystem path
(/Users/<name>/Projects/<repo>), shipped in every
workspace_baseline_completed event and not stripped by handlers'
identity filter. The field was set but never read anywhere downstream
— file paths in the baseline are already relative, so the root added
no replay signal. Remove the field outright.

* fix(session-export): cap pendingEvents result set

The SELECT had no LIMIT clause, so under a backlog (network outage,
crashed receiver) it would materialize the full pending table into a
JavaScript array before the byte-limit truncation applied. With a
50 GB buffer cap that is hundreds of MB of heap inside the worker per
drain. Add LIMIT 500 — the drain loop already re-queries until empty,
so no events are missed.

* fix(session-export): exponential retry backoff up to retryBackoffMaxMs

Both the 5xx and network-error branches always retried after the floor
delay regardless of how many attempts had already failed, and
retryBackoffMaxMs was unreferenced. During a sustained outage every
session re-tried at roughly 1 Hz against the dead receiver. Surface
upload_attempts on EventRow and compute the next delay as
min * 2^attempts capped at max.

* fix(session-export): evict superseded workspace snapshots on remember

createWorkspaceProvider retained every captured snapshot — in-memory
and inside the persisted state file — even after the session moved on
to a newer one. For a 1k-file repo over a 100-turn session that is
~2 GB of unreachable heap plus a state file that grows monotonically.
Drop the previous snapshot for the session on remember() when no other
session still references it.

* fix(session-export): anchor aws_secret_key scrubber to key name

The bare 40-char base64 pattern matched every 40-character hex string,
including all git commit SHAs. Tool outputs, diffs, and conversation
messages were silently rewritten as <<REDACTED:aws_secret_key>>,
destroying lineage information in training data. Require the key name
context — naked secrets in unstructured text are rare and the .env /
.aws/credentials high-risk path strip already covers the common case.

* fix(session-export): preserve root linkage in SyncSubscriber events

SyncSubscriber hardcoded rootSessionId = sessionId on every tool,
permission, and feedback event, so sub-agent sessions lost their root
linkage and a future training pipeline could not reconstruct the
agent topology from these side-channel events. Expose the rootSessionId
mapping from Capture and plumb it through the same pattern as
getTurnId.

* fix(session-export): atomic chunk GC after upload

markUploaded + decRefChunks + deleteUploaded were three separate SQL
statements; a crash between markUploaded and decRefChunks would leave
events flagged uploaded (never retried) and chunks with stale
ref_count (never reclaimed by deleteUploaded). Bundle the three calls
into a single transactional commitUploaded helper so either all three
land or none of them do.

* fix(session-export): wait on transient sqlite locks

* fix(session-export): snapshot current workspace directory

* fix(cli): preserve Kilo model export metadata

* fix(cli): send anon id for session export

* fix(cli): fallback to telemetry anon id

* chore(cli): annotate session export config test

* chore: remove session export docs

* fix(cli): harden session export uploads

* fix(cli): preserve stream lifecycle for session export

* fix(kilo-docs): exclude session export ingest link

* fix(cli): restrict session export workspace sync to git repos

* chore: remove session export changeset

* fix(cli): tighten session export payload types

* fix(cli): type session export model payloads

* fix(cli): simplify session export cleanup

* fix(cli): avoid session export shutdown race

* refactor(cli): use drizzle for session export storage

* fix(cli): finalize session export sqlite statements

* fix(cli): link compaction exports to root sessions

* fix(cli): stop exporting raw stream parts

* fix(cli): prune stale workspace snapshots

* refactor(cli): clarify chunk ref counting

* test(cli): clarify dropped upload assertions

* ci: avoid visual path filter action failure

* fix(cli): preserve in-flight workspace snapshots

* fix(cli): stop uploading baseline start events

* fix(cli): trim redundant export metadata

* fix(cli): trim workspace export bookkeeping

* fix(cli): fold terminal outcome into tool exports

* fix(cli): dedupe request context in export batches

* fix(cli): normalize compaction export payloads

* fix(cli): avoid duplicate tool result exports

* test(cli): align session export expectations

* fix(cli): run secretlint during session export scrubbing

* fix(cli): keep retried export batches contiguous

* fix(cli): include agent info in session exports

* fix(cli): flush pending session exports on serve startup

* fix(cli): drop exports when scrubbing fails

* fix(cli): narrow secretlint value extraction

* fix(cli): limit exported agent info

* test(cli): remove brittle agent export source assertion

* fix(cli): ignore corrupt session export workspace state

* fix(cli): keep session export close best effort

* fix(cli): avoid following session export symlinks

* fix(cli): preserve session export chunk ref counts

* fix(cli): retry transient session export uploads

* fix(cli): validate session export ingest endpoint

* fix(cli): tolerate missing export token details

* fix(cli): fail closed on session export org lookup

* fix(cli): decode session export permission replies

* fix(cli): finalize session export on stream close

* fix(cli): bound session export baseline timeout

* fix(cli): avoid persisting workspace file contents

* fix(cli): bound workspace snapshot capture

* fix(cli): validate session export worker messages

* fix(cli): revoke stale session export eligibility

* fix(cli): scope session export workspaces

* fix(cli): extend session export shutdown flush

* fix(cli): infer free Kilo models for export

* fix(cli): avoid duplicate chunk refs

* fix(cli): throttle session export uploads

* fix(cli): harden session export capture

* feat: disclose free model data collection (#10767)

* feat: disclose free model data collection

* chore(cli): document free model footer sorting

* fix(vscode): add free model data translations

* fix: simplify free model data label

* fix: simplify data collection badges

* fix: remove duplicate model info disclosure

* fix: restore composer data tooltip

* fix: align jetbrains data collection indicator

* fix: align free model data indicators

* fix(vscode): show data collection in model preview

* fix: limit data indicators to kilo gateway

* test(cli): relax prompt cancel timeout

* test(cli): annotate prompt cancel timeout

* test(cli): classify prompt queue runtime test

* fix(cli): defer session export startup
2026-06-02 13:58:42 +00:00

192 lines
6.3 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* Extracts user-facing URLs from the VS Code extension and CLI source code,
* then writes them to a markdown file that the docs link-checker validates.
*
* Usage:
* bun run script/extract-source-links.ts # Generate / update the committed file
* bun run script/extract-source-links.ts --check # CI mode — exit 1 if the file is stale
*/
import { Glob } from "bun"
import path from "path"
const ROOT = path.resolve(import.meta.dir, "..")
const OUTPUT = path.join(ROOT, "packages/kilo-docs/source-links.md")
const check = process.argv.includes("--check")
const DIRS = [
path.join(ROOT, "packages/kilo-vscode/src"),
path.join(ROOT, "packages/kilo-vscode/webview-ui"),
path.join(ROOT, "packages/opencode/src"),
]
const EXTENSIONS = ["ts", "tsx", "js", "jsx"]
// Matches http:// and https:// URLs in string literals or comments
const URL_RE = /https?:\/\/[^\s"'`)\]},;*\\<>]+/g
// URLs to exclude — only genuinely non-checkable URLs (API endpoints, localhost,
// examples, dynamic templates, namespaces). Real external URLs should be extracted
// and validated by lychee; add lychee.toml exclusions for sites that block bots.
const EXCLUDE_PATTERNS = [
// Localhost and internal
/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)/,
/^https?:\/\/kilo\.internal/,
/^https?:\/\/dev\.kilo\.ai/,
/^https?:\/\/tauri\.localhost/,
// Example/placeholder URLs
/^https?:\/\/example\.com/,
/^https?:\/\/api\.example\.com/,
/^https?:\/\/api\.myprovider\.com/,
/^https?:\/\/synthetic\.new/,
// API endpoints (not user-facing)
/^https?:\/\/api\.kilo\.ai\/api\//,
/^https?:\/\/supermassive-black-hole\.kiloapps\.io\/v1\/session-export\//, // kilocode_change
/^https?:\/\/ingest\.kilosessions\.ai/,
/^https?:\/\/api\.openai\.com/,
/^https?:\/\/api\.github\.com/,
/^https?:\/\/api\.githubcopilot\.com/,
/^https?:\/\/[^/]+\.openai\.azure\.com\/openai/, // kilocode_change
/^https?:\/\/api\.cloudflare\.com/,
/^https?:\/\/api\.releases\.hashicorp\.com/,
/^https?:\/\/auth\.openai\.com/,
/^https?:\/\/chatgpt\.com\/backend-api/,
/^https?:\/\/mcp\.exa\.ai/,
/^https?:\/\/registry\.npmjs\.org/,
/^https?:\/\/formulae\.brew\.sh\/api/,
/^https?:\/\/community\.chocolatey\.org\/api/,
/^https?:\/\/download-cdn\.jetbrains\.com/,
/^https?:\/\/raw\.githubusercontent\.com/,
// XML/SVG namespace URIs
/^https?:\/\/www\.w3\.org\//,
// URLs that are templates with interpolation (contain ${ after stripping)
/\$\{/,
// Truncated/placeholder URLs (e.g., https://…) or bare protocols
/^https?:\/\/[\W]*$/,
// GHE example domains
/^https?:\/\/company\.ghe\.com/,
// Example/placeholder GitHub URLs used in docs/comments
/^https?:\/\/github\.com\/owner\//,
/^https?:\/\/github\.com\/\.extraheader/,
/^https?:\/\/github\.com\/user-attachments\/assets\/xxxx/,
/^https?:\/\/github\.com\/user-attachments\/files\/\d+\/api\.json/,
// Example/template session URLs with placeholders
/\/s\/abc123$/,
// Truncated URL paths (e.g., /s/ with no ID)
/\/s\/$/,
]
// Directories to skip entirely
const SKIP_DIRS = ["node_modules", ".storybook", "stories", "test", "tests", "__tests__", "__mocks__"]
// Subdirectories containing vendored/third-party code
const SKIP_PATH_SEGMENTS = ["continuedev"]
// Individual files to skip (data files full of non-user-facing URLs)
const SKIP_FILES = ["models-snapshot.ts", "models-snapshot.js", "check-forbidden-strings.ts"] // kilocode_change
function shouldExclude(url: string): boolean {
return EXCLUDE_PATTERNS.some((re) => re.test(url))
}
function shouldSkipFile(filepath: string): boolean {
const rel = path.relative(ROOT, filepath)
const parts = rel.split(path.sep)
if (parts.some((p) => SKIP_DIRS.includes(p))) return true
if (SKIP_PATH_SEGMENTS.some((seg) => rel.includes(seg))) return true
if (/\.test\.[jt]sx?$/.test(filepath)) return true
if (/\.spec\.[jt]sx?$/.test(filepath)) return true
if (/\.stories\.[jt]sx?$/.test(filepath)) return true
if (parts.includes("i18n") && path.basename(filepath) !== "en.ts") return true // kilocode_change
const basename = path.basename(filepath)
if (SKIP_FILES.includes(basename)) return true
return false
}
// kilocode_change start
function source(filepath: string): string {
return path.relative(ROOT, filepath).replaceAll(path.sep, "/")
}
// kilocode_change end
function clean(url: string): string {
return url.replace(/[.),:;]+$/, "").replace(/<\/?\w+>$/, "")
}
async function extract(): Promise<Map<string, Set<string>>> {
const links = new Map<string, Set<string>>()
for (const dir of DIRS) {
for (const ext of EXTENSIONS) {
const glob = new Glob(`**/*.${ext}`)
for await (const entry of glob.scan({ cwd: dir, absolute: true })) {
// kilocode_change start
const file = source(entry)
if (shouldSkipFile(file)) continue
const content = await Bun.file(entry).text()
for (const line of content.split("\n")) {
for (const match of line.matchAll(URL_RE)) {
const url = clean(match[0])
if (shouldExclude(url)) continue
if (!links.has(url)) links.set(url, new Set())
links.get(url)!.add(file)
}
}
// kilocode_change end
}
}
}
return links
}
function render(sorted: [string, Set<string>][]): string {
const parts = [
"# Source Code Links",
"",
"<!-- Auto-generated by script/extract-source-links.ts — DO NOT EDIT -->",
"",
]
for (const [url, files] of sorted) {
parts.push(`- <${url}>`)
for (const file of [...files].sort()) {
parts.push(` <!-- ${file} -->`)
}
}
parts.push("")
return parts.join("\n")
}
const links = await extract()
const sorted = [...links.entries()].sort(([a], [b]) => a.localeCompare(b))
const output = render(sorted)
if (check) {
const committed = await Bun.file(OUTPUT)
.text()
.catch(() => "")
if (committed === output) {
console.log("packages/kilo-docs/source-links.md is up to date.")
process.exit(0)
}
console.error(
[
"ERROR: packages/kilo-docs/source-links.md is out of date.",
"",
"Run the following command locally and commit the result:",
"",
" bun run script/extract-source-links.ts",
"",
].join("\n"),
)
process.exit(1)
}
await Bun.write(OUTPUT, output)
console.log(`Wrote ${sorted.length} unique URLs to packages/kilo-docs/source-links.md`)