diff --git a/AGENTS.md b/AGENTS.md index 20f9bcdf..6f6dec86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,25 @@ When rules conflict, follow this order: 4. Code quality: types, readable boundaries, focused modules 5. Performance where it matters +## Opinionated Product Defaults + +- **MUST**: Prefer one canonical behavior over configurable alternatives. A new + flag, config field, environment variable, mode, strategy option, adapter hook, + or fallback path is a product feature and must be justified by an explicit + user request or a real correctness requirement. +- **MUST NOT**: Add speculative flexibility for imagined users, migrations, + review preferences, local workflows, or "just in case" scenarios. If the + requested behavior can work with one solid default, implement that default. +- **MUST NOT**: Add boolean switches that create two runtime paths unless both + paths are essential and the user explicitly asked for the choice. Boolean + policy knobs are especially suspect because they double the state space and + test surface. +- **MUST**: When a design seems to need a new option, first try to remove the + need by choosing the stronger default, tightening the invariant, or failing + clearly. Ask the user before adding the option if it still seems necessary. +- **MUST**: Delete obsolete branches, tests, docs, and config when removing a + behavior. Do not preserve dormant compatibility paths. + ## Repository Shape **ktx** is a pnpm + uv workspace. @@ -192,6 +211,19 @@ autonomously — without being asked the leading question — is the bar. next stack. The only acceptable static patterns are genuinely universal invariants (e.g. DB-engine system catalogs) and ktx's own self-emitted signatures. +- **MUST**: Give each capability one implementation and route every caller + through it. When some behavior — running a query, resolving a credential or + config reference, authenticating, selecting a dialect, loading config — + already has a working implementation that some call sites use, make new or + divergent call sites depend on that path instead of standing up a second one. + Parallel implementations of one capability drift apart silently: a fix, a + newly supported input, or an added case lands on one path and not the other, + so one entry point (a CLI command, an MCP tool, an ingest stage) succeeds + while another fails on the same input. When two paths already do the same + job, collapse onto the shared one and delete the duplicate instead of + keeping both. When fixing a defect that lives on one path, fix the shared + implementation; do not patch the symptom on a forked branch, which preserves + the divergence you set out to remove. - **SHOULD**: Before inventing an abstraction or hand-rolling structural logic, search for what already exists and reuse it — the codebase's canonical representation (a structured ref/key type) instead of a parallel string scheme, @@ -212,12 +244,45 @@ Before presenting a design, answer these explicitly: instead of building or parsing my own? 5. Am I discarding the better option on a weak or misapplied constraint (one-time vs recurring cost, "more surface area", "more work now")? +6. Does another entry point already perform this operation through a shared + implementation? If so, am I routing through that path instead of forking a + parallel one — and if I'm fixing a bug, am I fixing the shared layer rather + than one branch? +7. Am I adding a user-visible option or alternate runtime path that the user did + not ask for? If yes, can one opinionated default solve the problem instead? +8. Does this option multiply behavior by caller path, config value, or local + state? If yes, remove it unless it is explicitly required. A user question that nudges toward an alternative ("would X help?", "should I always do Y?", "will you hardcode Z?") is a signal that a better option exists. Investigate the implied direction and reason it through *before* defending the original proposal — and prefer to have asked yourself the question first. +Example: If generated context changes should be saved, choose one save policy +and route ingest, setup, memory, indexing, and docs through it. Do not add an +`auto_commit`-style switch unless the user explicitly asks for staged-only runs +and accepts the extra runtime path. + +## Code Comments + +Code must be self-explanatory. A comment exists only to state a constraint the +code cannot show; everything else belongs in the PR description or nowhere. + +- **MUST**: Keep each comment to 1-3 lines stating only what the code cannot + show: a cross-file invariant ("error-severity issues never reach here — the + doctor exits on them first"), a required ordering ("ktx.yaml is written + before git init, so a crash cannot leave a bare `.git`"), or a library quirk + ("zod reports unknown record keys as `invalid_key`"). +- **MUST**: State each invariant once, at the public entry point. Do not repeat + the same guarantee across a helper, its wrapper, and the call site. +- **MUST NOT**: Write prose comment blocks — design rationale, alternatives + considered, change narration ("is now written before…"), caller enumerations + ("shared by X, Y, and Z"), or restatements of what the code already shows. + That is the author addressing the reviewer, and it rots once merged. +- **MAY**: Open a regression test with a 1-3 line comment stating the scenario + it guards when the test name cannot carry it. Omit design history and + references to removed designs. + ## TypeScript Standards - Use Node 22+ and pnpm workspace commands. @@ -337,7 +402,8 @@ use `PascalCase` without the suffix. ## Telemetry -**ktx** ships PostHog usage telemetry. When adding commands or events: +**ktx** ships PostHog usage telemetry. Catalog telemetry events use strict +schemas. When adding commands or events: - **MUST NOT**: Add fields that carry user data — file paths, hostnames, environment values, SQL text, schema/table/column names, error messages, @@ -354,6 +420,24 @@ use `PascalCase` without the suffix. of collected data changes. Adding another event with no new field types needs no docs change. +### Error reports + +**ktx** also sends PostHog Error Tracking `$exception` events when telemetry is +enabled. This channel is separate from the strict catalog event schema and is +used only for exception diagnostics. + +`$exception` events may include stack frames, error class names, raw error +messages, cause chains, `source`, `handled`, `fatal`, runtime version fields, +OS/runtime fields, and the hashed `projectId` when known. Stack frames may +include local file paths and the local username when those appear in paths. + +`$exception` events must never intentionally include secrets, credentials, +database URLs, auth headers, raw argv, raw environment values, SQL text, +schema/table/column names as explicit properties, customer row data, user prompt +text, or raw MCP arguments. Reporters must redact call-site-provided secret +snapshots and common static credential patterns before the SDK serializes the +exception. + ## Documentation and Specs - Keep public documentation in `README.md`, package READMEs, example READMEs, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4fb3040..212dd9e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to KTX +# Contributing to ktx -Thanks for your interest in KTX. This page covers **how to contribute** and +Thanks for your interest in **ktx**. This page covers **how to contribute** and the **contributor rewards program**. For development setup, repository layout, and verification commands, see the [Contributing guide in the docs](https://docs.kaelio.com/ktx/docs/community/contributing). @@ -23,7 +23,7 @@ layout, and verification commands, see the ## Contributor rewards program We send merch to contributors whose pull requests get merged. The goal is -to thank the people building KTX with us, not to drive volume. +to thank the people building **ktx** with us, not to drive volume. ### How it works @@ -76,7 +76,7 @@ See the [Community & Support](https://docs.kaelio.com/ktx/docs/community/support page for the full guide. The short version: - **Questions, "how do I...", setup help, sharing patterns**: join the - [KTX Slack](https://join.slack.com/t/ktxcommunity/shared_invite/zt-3y9b44m1x-LVyNNJD5nwaZHq4XS29LMQ). + [**ktx** Slack](https://join.slack.com/t/ktxcommunity/shared_invite/zt-3y9b44m1x-LVyNNJD5nwaZHq4XS29LMQ). - **Bugs**: use the [Bug report](.github/ISSUE_TEMPLATE/bug_report.yml) template. - **Feature requests**: use the @@ -87,7 +87,7 @@ page for the full guide. The short version: ## Code of conduct -KTX follows the +**ktx** follows the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). Be respectful, assume good intent, and keep discussion focused on the project. Report concerns to the maintainers in Slack or by email at diff --git a/README.md b/README.md index 2c433e0d..3f704f04 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,20 @@ Documentation Join the ktx Slack community License - Y Combinator P25 + Y Combinator P25

Quickstart · CLI Reference · - Agent Setup · + Agent Setup · Slack

+

+ Built and maintained by Kaelio +

+ --- **ktx** is a self-improving context layer that teaches agents how to query your @@ -139,6 +143,14 @@ Agent integration ready: yes (codex:project) > If `ktx status` prints `ktx mcp start --project-dir ...`, run it before > opening your agent client. +## Upgrading + +Re-run the global install with the `@latest` tag: + +```bash +npm install -g @kaelio/ktx@latest +``` + ## First commands | Command | Purpose | @@ -197,7 +209,7 @@ then the current directory. Pass `--project-dir ` when scripting. - [The Context Layer](https://docs.kaelio.com/ktx/docs/concepts/the-context-layer) - [Building Context](https://docs.kaelio.com/ktx/docs/guides/building-context) - [CLI Reference](https://docs.kaelio.com/ktx/docs/cli-reference/ktx) -- [Agent Quickstart](https://docs.kaelio.com/ktx/docs/ai-resources/agent-quickstart) +- [AI Resources](https://docs.kaelio.com/ktx/docs/community/ai-resources) - [Community & Support](https://docs.kaelio.com/ktx/docs/community/support) ## Community @@ -247,11 +259,17 @@ uv run pytest -q ## Telemetry -**ktx** collects anonymous usage telemetry from interactive CLI runs to -improve setup, command reliability, and data-agent workflows. No file paths, -hostnames, SQL, schema names, error messages, or argv are recorded. See -[Telemetry](https://docs.kaelio.com/ktx/docs/community/telemetry) for the -event catalog and opt-out options. +**ktx** collects privacy-conscious usage telemetry to understand installs and +improve setup, command reliability, and data-agent workflows. Catalog telemetry +events do not record file paths, hostnames, SQL, schema names, table names, +column names, error messages, raw environment values, or argv. Error reports use +PostHog Error Tracking and can include stack frames and raw error messages, +which may contain local file paths or the local username in those paths. +**ktx** redacts secrets, credentials, database URLs, auth headers, argv, raw +environment values, SQL text, row data, and user-typed prompt or MCP argument +text from the explicit `$exception` payload. See +[Telemetry](https://docs.kaelio.com/ktx/docs/community/telemetry) for the event +catalog and opt-out options. ## License diff --git a/SECURITY.md b/SECURITY.md index da90c1a5..2b9dee1d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,20 +2,20 @@ ## Reporting a vulnerability -If you believe you've found a security vulnerability in KTX, please report it +If you believe you've found a security vulnerability in **ktx**, please report it **privately** through GitHub Security Advisories: [Report a vulnerability](https://github.com/Kaelio/ktx/security/advisories/new) If you cannot use GitHub Security Advisories, email `support@kaelio.com` -instead. Please do **not** open a public issue, post in the KTX Slack, or +instead. Please do **not** open a public issue, post in the **ktx** Slack, or share details elsewhere until we have published a fix. When reporting, please include: - A description of the issue and its impact - Steps to reproduce -- The KTX version affected +- The **ktx** version affected ## What to expect diff --git a/assets/star-history.svg b/assets/star-history.svg index 23016f3e..d22688ef 100644 --- a/assets/star-history.svg +++ b/assets/star-history.svg @@ -1 +1 @@ -star-history.comMay 17May 24May 31 200400600800kaelio/ktxStar HistoryDateGitHub Stars +star-history.comMay 17May 24May 31Jun 07Jun 14 20040060080010001200kaelio/ktxStar HistoryDateGitHub Stars diff --git a/docs-site/app/global.css b/docs-site/app/global.css index 929e06b4..2a50ba81 100644 --- a/docs-site/app/global.css +++ b/docs-site/app/global.css @@ -869,6 +869,97 @@ body::after { 50% { opacity: 0.65; transform: scale(0.9); } } +/* ═══════════════════════════════════════════ + GitHub star widget (sidebar footer pill) + Rendered as the `icon` of a fumadocs icon-link, so it sits in the footer + pill beside the Slack mark and the theme toggle. GitHub mark + star glyph + + live count; the star rotates to coral on hover. The !important sizes win + over fumadocs' `[&_svg]:size-4.5` rule on the wrapping link. + ═══════════════════════════════════════════ */ +.ktx-stars { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-display), var(--font-sans), sans-serif; + font-size: 13px; + line-height: 1; +} + +/* Push the stars to the opposite (right) end of the footer pill, leaving the + Slack mark on the left — like justify-content: space-between. The auto margin + absorbs the pill's free space; we cancel the theme toggle's own ms-auto so + that single gap lands before the stars, not between stars and the toggle. */ +#nd-sidebar a[aria-label="Star ktx on GitHub"] { + margin-inline-start: auto; +} + +#nd-sidebar [data-theme-toggle] { + margin-inline-start: 0; +} + +.ktx-stars-gh { + width: 16px !important; + height: 16px !important; + flex-shrink: 0; +} + +.ktx-stars-count-wrap { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.ktx-stars-star { + width: 12px !important; + height: 12px !important; + flex-shrink: 0; + fill: currentColor; + opacity: 0.7; + transition: + transform 0.3s var(--ktx-ease), + fill 0.3s var(--ktx-ease), + opacity 0.3s var(--ktx-ease); +} + +/* The wrapping fumadocs link owns the hover; rotate + colour the star from it. */ +#nd-sidebar a:hover .ktx-stars-star { + transform: rotate(-14deg) scale(1.12); + fill: var(--ktx-coral); + opacity: 1; +} + +.ktx-stars-count { + font-weight: 600; + font-variant-numeric: tabular-nums; + letter-spacing: -0.01em; +} + +/* Skeleton shown only on the rare cold (uncached) fetch */ +.ktx-stars-skeleton-bar { + display: inline-block; + width: 26px; + height: 10px; + border-radius: 4px; + background: linear-gradient( + 90deg, + var(--color-fd-muted) 25%, + color-mix(in oklch, var(--color-fd-muted-foreground) 28%, var(--color-fd-muted)) 50%, + var(--color-fd-muted) 75% + ); + background-size: 200% 100%; + animation: ktx-stars-shimmer 1.4s ease-in-out infinite; +} + +@keyframes ktx-stars-shimmer { + from { background-position: 200% 0; } + to { background-position: -200% 0; } +} + +@media (prefers-reduced-motion: reduce) { + #nd-sidebar a:hover .ktx-stars-star { transform: none; } + .ktx-stars-skeleton-bar { animation: none; } +} + /* Dot grid */ .dot-grid { background-image: radial-gradient( diff --git a/docs-site/app/layout.config.tsx b/docs-site/app/layout.config.tsx index 3245ab09..09654884 100644 --- a/docs-site/app/layout.config.tsx +++ b/docs-site/app/layout.config.tsx @@ -1,22 +1,22 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; -import { GitHubIcon } from "@/components/github-icon"; import { Logo } from "@/components/logo"; import { SlackIcon } from "@/components/slack-icon"; +import { GitHubStars, GITHUB_REPO_URL } from "@/components/github-stars"; +import { ThemeToggle } from "@/components/theme-toggle"; export const baseOptions: BaseLayoutProps = { nav: { - title: , + title: Logo, transparentMode: "top", }, + // Custom two-icon switcher (light / dark) where each icon selects its own + // theme. The default "light-dark" switcher is a single blind toggle — both + // icons just flip the theme, so clicking the sun while already in light mode + // jumps to dark, which reads as broken. + slots: { + themeSwitch: ThemeToggle, + }, links: [ - { - type: "icon", - label: "GitHub", - icon: , - text: "GitHub", - url: "https://github.com/kaelio/ktx", - external: true, - }, { type: "icon", label: "Join the ktx Slack community", @@ -25,5 +25,13 @@ export const baseOptions: BaseLayoutProps = { url: "https://join.slack.com/t/ktxcommunity/shared_invite/zt-3y9b44m1x-LVyNNJD5nwaZHq4XS29LMQ", external: true, }, + { + type: "icon", + label: "Star ktx on GitHub", + icon: , + text: "GitHub", + url: GITHUB_REPO_URL, + external: true, + }, ], }; diff --git a/docs-site/components/diagram-studio/flows.ts b/docs-site/components/diagram-studio/flows.ts index cddf75cb..e63cc512 100644 --- a/docs-site/components/diagram-studio/flows.ts +++ b/docs-site/components/diagram-studio/flows.ts @@ -305,8 +305,8 @@ export const runtimeEdges: Edge[] = [ sourceHandle: "to-context", target: "context", targetHandle: "in", - type: "default", - label: "search", + type: "smoothstep", + label: "search + read", ...labelBg, style: edgeStyle, markerStart: marker, @@ -318,7 +318,7 @@ export const runtimeEdges: Edge[] = [ sourceHandle: "to-warehouse", target: "warehouse", targetHandle: "in", - type: "default", + type: "smoothstep", label: "read-only", ...labelBg, style: edgeStyle, diff --git a/docs-site/components/github-stars.tsx b/docs-site/components/github-stars.tsx new file mode 100644 index 00000000..b7f62143 --- /dev/null +++ b/docs-site/components/github-stars.tsx @@ -0,0 +1,82 @@ +import { Suspense } from "react"; +import { GitHubIcon } from "@/components/github-icon"; + +const REPO = "kaelio/ktx"; +export const GITHUB_REPO_URL = `https://github.com/${REPO}`; +const API_URL = `https://api.github.com/repos/${REPO}`; + +async function fetchStarCount(): Promise { + try { + const res = await fetch(API_URL, { + headers: { Accept: "application/vnd.github+json" }, + // Revalidate hourly. GitHub's unauthenticated REST limit is 60 req/h per + // IP, so a single cached server-side fetch keeps the count fresh while + // never exposing visitors to rate limits or layout shift. + next: { revalidate: 3600 }, + }); + if (!res.ok) return null; + const data = (await res.json()) as { stargazers_count?: unknown }; + return typeof data.stargazers_count === "number" + ? data.stargazers_count + : null; + } catch { + return null; + } +} + +/** Compact, GitHub-style count: 847 → "847", 1234 → "1.2k", 12345 → "12.3k". */ +function formatStars(count: number): string { + if (count < 1000) return count.toLocaleString("en-US"); + const thousands = count / 1000; + const rounded = + thousands >= 100 ? Math.round(thousands) : Math.round(thousands * 10) / 10; + return `${rounded}k`; +} + +function StarGlyph() { + return ( + + ); +} + +async function StarsInner() { + const count = await fetchStarCount(); + return ( + + + {count !== null ? ( + + + {formatStars(count)} + + ) : ( + Star + )} + + ); +} + +function StarsSkeleton() { + return ( +