writingstylescope

package
v0.0.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package writingstylescope is a keelson app for finding shared writing between two documents. Paste two Markdown documents; the app splits each into sections (one per heading), measures the compression distance of every A-section against every B-section, and shows the result three ways: a document-level headline, the cross-matrix as a heatmap, and the empirical distribution of all the pairwise distances.

The measurement is normalized compression distance (NCD): compress the two texts together and see how much smaller that is than compressing them apart. A pair that shares wording compresses well together. The engine is public/analytics/similarity/compression and its stylometry sub-package.

The app returns no verdict, and it asserts no threshold — NCD has no absolute scale that survives a change of subject, language, or section length. What it shows instead is where each pair sits in the background distribution formed by every other pair of the same two documents. A section that was copied is the point that does not belong to that distribution. Deciding what that means is the reader's job. The decision and its alternatives are ADR-0175.

Three dock tabs: "Documents" (paste, sweep, headline), "Matrix" (the section-by-section heatmap and the closest pairs) and "Distribution" (the ECDF that calibrates them). The pairs table hands the whole cross-matrix to the SQL playground as an ephemeral dataset — see writingstylescope_handover.go.

Lifecycle: Mount captures the id stack, logger and bus; Frame renders inside the host-owned window and runs the sweep when the user asks for one; Unmount cancels any confidence-band warm-up still in flight and retracts the published dataset.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Analysis

type Analysis struct {
	// SecA / SecB are the sections that were actually swept — the ones that
	// cleared MinSectionBytes. DroppedA / DroppedB count those that did not.
	SecA     []Section
	SecB     []Section
	DroppedA int
	DroppedB int

	// Ncd is the cross-matrix, row-major: cell (i, j) is at i*len(SecB)+j.
	Ncd []float64
	// Sorted is Ncd ascending — the ECDF sample and the ranking source.
	Sorted []float64
	// Pairs is the closest maxRankedPairs cells, ascending by Ncd.
	Pairs []Pair

	// Document-level readings from stylometry's own measurement modes, taken
	// with document A (its kept sections, concatenated) as the fixed
	// reference. Profile mode truncates both sides to a common length;
	// instance mode streams B's sections past the reference and may stop
	// early — InstConverged and InstCount say whether it did.
	ProfileNcd    float64
	ProfileCcc    float64
	InstCount     int64
	InstMin       float64
	InstMean      float64
	InstMax       float64
	InstStdDev    float64
	InstConverged bool

	MinSectionBytes int
	Elapsed         time.Duration
}

Analysis is one completed sweep. It is immutable once returned and is held by the app until either pane's text changes.

func (*Analysis) At

func (inst *Analysis) At(i int, j int) (ncd float64)

At returns the NCD of A-section i against B-section j. Out-of-range coordinates return NaN rather than panicking — a hover readout computes cell coordinates from cursor position and may briefly be off the grid.

func (*Analysis) Cols

func (inst *Analysis) Cols() (n int)

func (*Analysis) Max

func (inst *Analysis) Max() (v float64)

func (*Analysis) Min

func (inst *Analysis) Min() (v float64)

Min and Max bound the matrix. Both are NaN for an empty matrix.

func (*Analysis) Quantile

func (inst *Analysis) Quantile(ncd float64) (q float64)

Quantile returns the fraction of the matrix at or below ncd — where a cell sits in this document pair's own background distribution. This is the number the app reports instead of a threshold verdict (§SD3): "closer than all but 0.1% of pairs" is a statement about these two documents, where "below 0.4" would be a statement about a corpus nobody measured.

func (*Analysis) Rows

func (inst *Analysis) Rows() (n int)

Rows and Cols are the matrix dimensions.

type App

type App struct {
	// contains filtered or unexported fields
}

App is the per-window writingstylescope instance.

func (*App) Frame

func (inst *App) Frame(ctx app.FrameContextI) (err error)

Frame renders the app body. The host has already pre-pushed a window-unique salt onto inst.ids (ADR-0026 §SD9), so the app must not Reset() the stack or wrap the body in its own instance salt.

func (*App) Manifest

func (inst *App) Manifest() (m app.Manifest)

func (*App) Mount

func (inst *App) Mount(ctx app.MountContextI) (err error)

func (*App) Unmount

func (inst *App) Unmount(ctx app.MountContextI) (err error)

Unmount cancels a confidence-band solve still running for this window and retracts the published pairs dataset. A band that already finished stays in the shared ecdfbands cache, so a reopen still renders instantly.

type Pair

type Pair struct {
	I   int
	J   int
	Ncd float64
}

Pair is one cell of the cross-matrix: section I of document A against section J of document B.

type Section

type Section struct {
	// Title is the heading's flattened plain text — inline styling dropped,
	// any `{#anchor}` suffix already stripped by the parser. Empty for the
	// preamble section.
	Title string
	// Level is the ATX heading depth, 1..6. Zero for the preamble.
	Level uint8
	// Start and End are byte offsets into the source the section was sliced
	// from; Text is that slice.
	Start int
	End   int
	Text  string
}

Section is one heading's own prose: the span from that heading's line start to the next heading's line start, at whatever level the next heading sits (ADR-0175 §SD1). Sliced that way a section never contains a descendant section's text, so the same prose is never measured twice, and the sections that carry substantial text are the deep ones — which is the granularity the app compares at.

The preamble (text before the first heading, after any frontmatter) is a Section with an empty Title and Level 0.

func (Section) Bytes

func (inst Section) Bytes() (n int)

Bytes is the section's length in the source.

func (Section) Label

func (inst Section) Label() (label string)

Label names the section for an axis tick, a table cell, or a readout.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL