Changelog
Surface follows semantic versioning. This page mirrors releases of the
surf CLI. While Surface is pre-1.0 (v0.x),
minor versions may include breaking changes.
v0.8.0
Section titled “v0.8.0”Added.
- Hubs are now conformant Open Knowledge Format (OKF) concepts — “Surface = OKF + freshness.” A
hub’s frontmatter is a superset of an OKF concept: it carries OKF’s
type(defaulted toconceptso existing hubs are byte-unchanged and keep working),title,tags, andtimestamp, and preserves any other frontmatter key verbatim (OKF’sdescription/resource, a doc system’sauthor/created/pinned) in anextramap — the OKF rule that consumers must not drop unknown keys. So a hub drops into any OKF consumer (Google’s Knowledge Catalog, the OKF visualizer, Obsidian, git-backed editors), which read the prose and ignore theanchors:Surface governs.surf newscaffoldstype: concept. - Freshness provenance and stable claim IDs.
surf verifynow records the freshness OKF omits: a stable per-claimid(written once, never regenerated, so it survives prose/anchor edits — the substrate for claim timelines) plusverified_atandverified_commit. The who is deliberately not stored (git blame on the hub records it) — keeping author emails out of tracked files. Provenance is written only when the hash actually changes, so a no-op re-verify stays byte-identical. The--format jsonDivergencegains an additiveidfield (no report-version bump). - OKF bundle layout.
surf.tomlgains abundlesglob: each root is a directory tree of concept files, expanded as<root>/**/*.md. OKF reserved files (index.md,log.md) are recognized and skipped for governance — they hold no claims and never block the gate. surf lintOKF advisories. AWarn(never a block) for an unknown frontmatter key that looks like a typo of a known one (recovering the fail-closed signal below), aWarnwhen an anchored hub has no headline (summary/title/description), and aWarnfor a dangling OKF cross-link in a hub body (OKF tolerates broken links, so this never blocks).
Changed.
- Frontmatter no longer rejects unknown keys (OKF requires consumers to preserve them). This is
a deliberate, narrow relaxation of the gate’s fail-closed posture: a mistyped frontmatter key
that previously hard-blocked now parses and earns a
surf lintwarning instead. Unknown keys inside an anchor item still fail closed — anchor items are Surface’s own data, not OKF’s. summaryis now optional (an OKF concept may carry onlydescription).
v0.7.0
Section titled “v0.7.0”Added.
refshub composition. A hub’srefs:now compose other hubs: a path relative to the referencing hub (./resolve.md), optionally> symbolto address one claim within the target (matched against that claim’sat:anchor).surf lintblocks a ref that doesn’t resolve to a loaded hub, points at its own hub, or names a claim the target lacks — the same fail-on-typo discipline ascovers. Thesurf checkgate then propagates staleness one hop: a hub that directly references a stale hub (or a stale claim within one) inherits areferenced_staledivergence and fails the gate, so the signal that flags a dependency flags everything composed on it. Propagation is one-hop by construction (built only from base divergences), so a chainA → B → Cdoesn’t cascade. The repo’s own hubs now declare their cross-hubrefs, and the two prior doc-pointingrefswere reclassified to prose links.surf lintconsolidation nudges. Two advisory warnings push hubs away from the “claim-log” shape (one claim per function) and toward onboarding docs: a claim-log warning when a hub has several claims and never once uses a multi-siteat:list, and a thin-prose warning when a multi-claim hub’s body is too sparse to onboard a reader. Both are non-blocking (exit 0), mirroring the existing over-/under-anchoring nudges.
Changed.
surf suggestreframed. Human output now reads as a list of undocumented symbols, not a list of claims to write: it groups symbols by file and emits a multi-siteat:skeleton with prose-first scaffolding, steering authors toward coarse, system-level claims.--format jsonis unchanged.surf newscaffold ships a prose-first template (## How it works/## Boundaryheadings and a multi-anchor example claim) so a fresh hub is shaped like an onboarding doc.- Hash recipe v2 — the bound/free split. The canonical hash now alpha-renames only
bound identifiers (a symbol’s own name, parameters, locals, loop/range/comprehension
variables,
with/catchaliases, generic params, destructuring binders) and emits every free identifier verbatim (external members, call targets, types, enum/constant references, object keys, decorators). Re-pointing an anchored span at a different symbol is now loud even when the name occurs once —PointsTier.TIER_1→TIER_2,getHighest→getLowest, a barehelper(x)→other(x), a parameter typeFoo→Bar, an object key{ alpha }→{ beta }— where before it passed the gate silently while the claim’s prose became false. Consistent local/parameter renames stay quiet, as before. This subsumes the earlier member-access-only first cut and the Python decorator special case. A new in-tree differential harness (surf-core/tests/differential_hash.rs) gates the change: zero benign-rename regressions, 100% catch on the semantic free-swap corpus across all four languages. Binding detection is tree-sitter-only and fail-closed; the one accepted approximation (match-arm pattern identifiers are treated as free) is documented in Hash recipes. Covers TypeScript, Go, Rust, and Python. - Versioned stamps. Stored hashes now carry their recipe: a v2 stamp is prefixed
2:, a bare 12-hex stamp is an implicit v1.surf checkverifies each stamp under its own recipe, so existing v1 stamps keep passing (with a one-line nudge) untilsurf verifyre-stamps them as v2 — one pass per repo. An unrecognized stamp prefix fails closed. See Hash recipes. Action on upgrade: runsurf verifyonce.
v0.6.3
Section titled “v0.6.3”Added.
- Hubs may now declare advisory
coversglobs (repo-root-relative, same dialect asconfig.hubs). The field is accepted, stored, and lint-validated — a malformed glob blocks, and a hub whose globs don’t match its own anchored files warns — but the verdict never reads it:surf checkis byte-for-byte identical whether or not a hub declarescovers. The louder coverage pass that consumes these globs stays deferred (#94, part of #5). - TypeScript
suggestnow enumerates exported class methods (Class > method) by default — matching Go and Python — so a class-heavy file no longer reports “no unanchored public symbols found.”suggest --alladditionally proposes the exported class itself and non-callableconst/let/vardeclarations (object/array/scalar). The “Rust/TypeScript--allchanges nothing” note now fires only for Rust. - A failed flat TypeScript anchor that looks like a method now hints the chain spelling:
EffectiveTierService.getForUsersor a baregetForUsersreportsdid you mean `EffectiveTierService > getForUsers`?— theClass > methodsyntax was previously undiscoverable.
Fixed.
- TypeScript exported non-callable
const/let/var(export const TIER_MAP = { ... }) is now anchorable — previously only function-valued declarators resolved, so the const maps most prone to drift had no anchor target. Mirrors #28’s Python fix.
v0.6.2
Section titled “v0.6.2”Added.
surf suggest --allnow proposes Go exported top-levelconst,var, andtypedeclarations (including structs), matching what the resolver already accepted — the Go edition of #52’s Python fix. The “Python-only” note added for #79 now fires only for Rust and TypeScript, where--allstill changes nothing.
Fixed.
- Python
@overloadsets are one anchorable unit: consecutive same-name stubs plus their implementation resolve as a single logical symbol, so the bare name works without@Nand the hashed span covers stubs and impl — a signature change in any overload now trips the gate, where previously only the implementation body was hashed and the typed contract could drift silently.suggest’s proposed path for overloaded functions now lints clean instead of failing its own resolver. Anchors that used the oldname@Nworkaround to pick a stub or the implementation should re-anchor to the bare name and re-verify. - Python symbols bound inside module-level
if/tryblocks —TYPE_CHECKINGguards, version gates,try/except ImportErrorfallbacks — now resolve by name like their unconditional siblings, and are enumerated bysuggestand the lint coverage nudge. A name bound in multiple branches is reported ambiguous and disambiguated with the existing@Nmechanism. surf check --filesnow warns on stderr when a glob matched no anchored files, and exits non-zero when every glob matched nothing — a typo’d pattern no longer scopes the gate to nothing and reports a clean run, mirroringsuggest’s existing zero-match behavior.surf lintcoverage warnings (andcheck’s malformed-hub divergences) no longer print a dangling emptyclaim:line when there is no claim to show.- The release workflow now polls the npm registry after publishing and fails the run if any
package version never becomes fetchable, instead of trusting
npm publish’s exit code — the registry has silently dropped freshly published versions post-publish.
Changed.
surf suggest --allprints a note when the scan includes non-Python files:--allcurrently only proposes non-callable symbols for Python, so its silence on Go/Rust/TS is no longer mistakable for “this file has no non-callables”.
v0.6.1
Section titled “v0.6.1”Changed.
surf statsnow reads the whole history window from a single streamedgit log --name-statusinstead of spawning adiff-tree/rev-parse/ls-treetriple per commit. Hub claim state is propagated incrementally — a hub is re-read (git show) only at commits that touched it — so the git-spawn count scales with hub edits rather than history length. On a 13k-commit repo this is the difference between minutes and seconds; metrics are byte-identical to the previous implementation. Requires git ≥ 2.31 for--diff-merges=first-parent.
Fixed.
- ARM64 Linux is now a first-class install target. Releases publish an
aarch64-unknown-linux-gnubinary, and bothinstall.shand the npm channel (@gradient-tools/surface) resolve it for the host. Previously ARM64 Linux fell through to a missing asset; the installer now succeeds there rather than failing on a 404. surf fordistinguishes a path that is a directory from one that does not exist, instead of reporting both the same way — the error now points at the actual problem.
v0.6.0
Section titled “v0.6.0”Added.
surf suggest --alladditionally proposes the non-callable anchor targetsresolvealready accepts butsuggestpreviously hid: top-level classes, module-level constants and type aliases, and class attributes (Python). The default stays callables-only (functions + methods) to avoid over-anchoring fatigue. Closes the gap where those kinds were anchorable by hand yet undiscoverable viasuggest.- npm distribution: install via
npm install @gradient-tools/surfaceor run ad-hoc withnpx @gradient-tools/surface check. A thin shim resolves the prebuilt binary for the host from per-platformoptionalDependencies(macOS Apple Silicon, Linux x86_64) — nopostinstalldownload step. Published from CI on each release with provenance attestation. - Release integrity: each published binary now ships a
<asset>.tar.gz.sha256checksum, and thecurl | shinstall script (and the GitHub Action that wraps it) verifies the download against it before installing. A missing checksum or a mismatch aborts the install — fail closed — rather than running a corrupted or tampered binary.
Changed.
surf statsis much faster on large histories. It previously recomputed the hub claim set at every commit and its parent, each spawninggit ls-treeplus agit showper hub — roughly2 × commits × hubssubprocesses. The claim set is now memoized by commit SHA, with eachbeforerev canonicalized to its parent’s SHA so it reuses the parent commit’s already-computed state. Metrics are unchanged.
Fixed.
surf lint’s under-coverage nudge is no longer workspace-blind and no longer skips methods. Coverage is now computed across the whole workspace, so splitting one file’s claims across two hubs no longer makes each hub warn about the symbols the other one anchors; and the measured public surface now includes methods (Type > method), not just top-level functions, matching whatsuggestproposes.surf for,surf check --files, andsurf statsnow fail loudly on malformed input instead of returning a falsely-reassuring success.surf forerrors on a nonexistent/mistyped path, a directory, or a trailing slash (instead of “no hubs anchor” + exit 0); invalid--filesglob syntax and invalidsurf.tomlhub globs now error instead of being silently dropped (#53, #38).
v0.5.0
Section titled “v0.5.0”Fixed.
surf checknow fails closed. A hub whose frontmatter can’t be parsed previously slipped through silently (exit 0, unenforced); it now surfaces as anUnresolvabledivergence that blocks the run. A hashing error is likewise reported asUnresolvableinstead of being counted clean.
Changed.
- Hub iteration is unified behind
Workspace::iter_hubs(), shared bycheck,lint,suggest, andfor. Each command carries the per-hub parse result explicitly, so the error case can no longer be handled inconsistently from one command to the next. - CI now runs the test suite and dogfood self-check on macOS (
macos-14) in addition to Linux, matching the Apple-Silicon binary that releases ship. Windows is documented as unsupported .
v0.4.0
Section titled “v0.4.0”Added.
surf for <path> [symbol]— reverse lookup: list every hub and claim anchored into a file, so you can pull up the documentation governing code before you edit it (the inverse ofsuggest). Read-only and always exits 0;--format jsonemits a versioned{version, path, matches}envelope. An optional trailingsymbolnarrows to anchors whose first segment matches.surf stats [--since <date>] [--until <date>]— adoption metrics from git history (advisory, never a gate): the rubber-stamp rate (re-stamps that changed a claim’s stored hash but left its prose untouched) and the in-place update rate (commits touching an anchored file that re-sealed the claim in the same commit). One commit = one PR (merges excluded); heuristics are documented indocs/guides/stats.md. Errors (non-zero) when git history is unavailable.surf suggestnow proposes methods, not just top-level functions: Python class methods and Go methods are enumerated asfile > Type > methodanchors (matched by exported Go receiver type; Python async/sync mirrors de-duped). On method-oriented codebases this surfaces the bulk of the public API that was previously invisible tosuggest. Rustimpl/TS class methods remain top-level-only.
Changed.
surf suggestdistinguishes a glob that matched no files from a clean “nothing to suggest”: it warns on stderr and exits non-zero when every glob was empty, so a typo’d path no longer reads as success. Coverage is now keyed on the fullfile > seg > seganchor path, so anchoring one method no longer suppresses its siblings.
v0.3.2
Section titled “v0.3.2”Documentation.
- The docs site (surface.gradientdev.xyz) is now generated from this repo on release: each
v*tag regenerates the Starlight site’s pages and changelog fromdocs/andCHANGELOG.mdand opens a sync PR, so the site no longer drifts from canonical docs. docs/reference/commands.mdis now governed bysurf checkitself — a hub anchored to the clap command/flag surface blocks the gate when the CLI and its reference drift.- Pinned
Connorrmcd6/surface@vX.Y.ZAction refs in the README and docs are now derived fromCargo.tomlat release, and the Examples page description is quoted so it parses under the site’s strict YAML.
v0.3.1
Section titled “v0.3.1”Documentation.
- Reposition around documentation governed like code for fast-moving codebases, led by the context-rot story (a context file that’s accurate when written and rots as the code moves because nobody knows it exists or where to find it). Dropped the older accusatory framing.
- Slim the README to a pitch + compact quickstart that links out; the full reference now lives in
docs/. Restructureddocs/into a site-ready tree (getting-started/,reference/,guides/) withtitle/descriptionfrontmatter, mirroring the docs site (surface.gradientdev.xyz); this repo’sdocs/is the source of truth. - Bring the docs current with 0.3.0: the versioned
--format jsonenvelope, the per-claimignore_literalsoption, Python non-callable anchors, file-rename--follow, and decorators in the hashed span.
v0.3.0
Section titled “v0.3.0”Added.
- Python:
at:now resolves non-callable symbols — module-level constants and type aliases (X = …,X: T = …, PEP 695type X = …) and class-level attributes (Class > attr, including annotation-only). Previously only functions, methods, and classes resolved. - Per-claim
ignore_literals: truefrontmatter option — excludes string-literal content from a claim’s hash so a copy edit inside the anchored span no longer re-opens the gate. Logic edits (operators, numbers, structure) are still caught. The stored hash is computed in this mode, so it travels with the claim rather than a CLI flag. surf verify --followandsurf lintnow follow file renames via git rename detection: a moved file makeslintwarn (and point at--follow) instead of hard-blocking, and--followrewrites the anchor’s path — only when the code is otherwise unchanged. Best-effort and git-dependent; the deterministicsurf checkverdict never depends on it.
Changed.
- Python decorators are now part of an anchored function/class’s hashed span, and a decorator’s
name is kept verbatim (not alpha-renamed) — so adding/removing a decorator, changing its
arguments, or swapping it (
@cache→@lru_cache,@staticmethod→@classmethod) trips the gate. Previously decorators were excluded from the span entirely. - Breaking (JSON):
surf check --format jsonnow emits a versioned envelope{ "version": 1, "divergences": [...] }instead of a bare array. The contract is additive-only within a major version; a breaking change bumpsversion. Consumers should read.divergencesand tolerate unknown fields.
v0.2.1
Section titled “v0.2.1”Documentation.
- Precisely scope “cosmetic”: a literal value is part of the hashed AST, so editing a string literal — including user-facing copy — inside an anchored span trips the gate. “Cosmetic” means only whitespace, comments, and consistent renames. Added a FAQ entry and a note to anchor the narrowest symbol so unrelated literal edits don’t re-open a claim.
v0.2.0
Section titled “v0.2.0”Added.
surf checkscoping:--files <globs>evaluates only claims whose anchored file(s) match, and an explicit--base <ref>diff-scopes the gate to claims whose files changed since the merge base. Omitting--basekeeps a full check (enrichment falls back toHEAD).--format jsonforsurf lintandsurf verify, mirroringsurf check.lintemits structured findings;verifyemits per-anchor outcomes (stamped/followed/unchanged/skipped) plus counts.- Advisory
surf lintgranularity warnings (never blocking): a near-whole-file anchor span, a hub with too many anchors, and public functions in an anchored file that no claim covers. surf lintenforces theAGENTS.mdpointer block: whenAGENTS.mdcarries a<!-- surf:hubs -->block, it must link the hubs directory (and that directory must exist) so agents are pointed at the hubs to search — never duplicating or enumerating them. Opt-in; blocks on a missing/dangling pointer.surf suggest <globs>— scans source for top-level public functions that no hub anchors yet and prints a copy-pasteable starter hub. Suggestions only: never writes a file or stamps a hash.surf_core::public_fns— enumerates a file’s top-level public functions (backs the under-coverage warning andsurf suggest).
Changed.
- An explicit
surf check --base <ref>now diff-scopes the gate to claims whose files changed, in addition to feeding advisoryold_code/magnitude. Previously--baseonly fed enrichment and every claim was still checked. CI that passes--baseexplicitly will now check fewer claims; omit--basefor a full check.
v0.1.1
Section titled “v0.1.1”Fixed.
resolve: resolve const-bound call-expression functions (TS/JS).verify: skip unchanged anchors instead of re-stamping, so a no-op verify leaves hub files byte-identical.
Changed.
- Releases: dropped Intel macOS (
x86_64-apple-darwin); prebuilt binaries cover macOS (Apple Silicon) and Linux (x86_64). Other targets build from source.
v0.1.0
Section titled “v0.1.0”Initial release — the MVP gate that surfaces docs↔code divergence.
Added.
- AST-canonical hashing via bundled, version-pinned tree-sitter grammars: quiet on cosmetics
(formatting, comments, consistent renames), loud on logic. Advisory tree-edit
magnitude. - Anchor resolution: the
file > A > Bpath grammar with@Ndisambiguation; scope-set resolution soType > methodresolves uniquely. - Hub format (markdown frontmatter),
surf.tomlworkspace discovery, and per-claim combined hashing across multi-siteat:lists. - Commands:
surf lint,surf check(the gate),surf verify(re-seal, with--followfor renames), plussurf initandsurf newfor authoring ergonomics. - Language support: TypeScript/TSX, JavaScript/JSX, Rust, Python, and Go.
- Distribution: GitHub Action, pre-commit hook, and
install.sh; Apache-2.0 license.