Documentation
¶
Overview ¶
Package cli is the `ds` command: every subcommand is a thin call into github.com/ubgo/docsync (docs/SPEC.md §37.5). It owns what the library refuses to: paths, files, git, printing, and exit codes.
Embed it to ship a custom binary with extra extractors and verbs:
func main() {
cli.Main(cli.WithName("pds"), cli.WithExtractor(mytier.Kotlin()), cli.WithVerb("ticket"))
}
Index ¶
- Constants
- Variables
- func ConfigTOML(c config.Config) string
- func Main(opts ...Option)
- func Run(args []string, opts ...Option) int
- type App
- type BuildInfo
- type CommitCounter
- type Git
- func (g Git) Branch() (string, error)
- func (g Git) Churn() (map[string]int, error)
- func (g Git) Clone(url, dir string) error
- func (g Git) CommitIndex(dir, message string) (bool, error)
- func (g Git) CommitsBetween(from, to string) (int, error)
- func (g Git) Exists(commit string) bool
- func (g Git) Head() (string, error)
- func (g Git) Introduced(path, text string) (string, error)
- func (g Git) Message(commit string) (string, error)
- func (g Git) Pull(dir string) error
- func (g Git) Push(dir, message string) error
- func (g Git) Reachable(url string) error
- func (g Git) RemoteURL() string
- func (g Git) Resolve(commit string) (string, error)
- func (g Git) Show(commit, path string) ([]byte, error)
- func (g Git) Staged() ([]string, error)
- func (g Git) Tree(commit string) (map[string]int64, error)
- func (g Git) User() string
- type IndexCommitter
- type MCPTarget
- type Notified
- type NotifyState
- type Option
- func Standard() Option
- func WithClock(f func() time.Time) Option
- func WithConfigDefaults(f func(*config.Config)) Option
- func WithDefaults(o Option) Option
- func WithDir(dir string) Option
- func WithExtractor(es ...extract.Extractor) Option
- func WithHTTPClient(c *http.Client) Option
- func WithIO(in io.Reader, out, errw io.Writer) Option
- func WithName(name string) Option
- func WithNotifyState(s NotifyState) Option
- func WithPicker(ps ...docsync.Picker) Option
- func WithPluginLookup(f func(name string) (string, error)) Option
- func WithRegistry(r *extract.Registry) Option
- func WithVCS(v VCS) Option
- func WithVerb(names ...string) Option
- type Prober
- type RunRecord
- type StaleBlock
- type StaleRepo
- type Staleness
- type Store
- func (s *Store) AddMetrics(served, source int64) error
- func (s *Store) AppendAcks(rows []ledger.Ack) error
- func (s *Store) ApplyEdit(e docsync.Edit) error
- func (s *Store) BodyAt(hash string) (string, bool)
- func (s *Store) Exists() bool
- func (s *Store) Journal(kind string, edits []docsync.Edit) error
- func (s *Store) Load(context.Context) (ledger.Ledger, ledger.Refs, ledger.Acks, error)
- func (s *Store) LoadCache(inputs string, now func() time.Time) (*extractCache, error)
- func (s *Store) LoadConfig() (config.Config, error)
- func (s *Store) LoadConfigOnto(base config.Config) (config.Config, error)
- func (s *Store) LoadForeign() (ledger.Foreign, bool, error)
- func (s *Store) LoadHashes() (map[string]string, error)
- func (s *Store) LoadMetrics() (served, source int64, err error)
- func (s *Store) LoadNotified() (map[string]Notified, error)
- func (s *Store) LoadRuns() (map[string]RunRecord, error)
- func (s *Store) LoadState() (ledger.Ledger, ledger.Refs, ledger.Acks, error)
- func (s *Store) LoadURLs() (map[string]URLEntry, error)
- func (s *Store) Save(_ context.Context, l ledger.Ledger, r ledger.Refs, a ledger.Acks) error
- func (s *Store) SaveCache(c *extractCache) error
- func (s *Store) SaveForeign(f ledger.Foreign) error
- func (s *Store) SaveHashes(m map[string]string) error
- func (s *Store) SaveLedger(l ledger.Ledger, r ledger.Refs) error
- func (s *Store) SaveLedgerSharded(l ledger.Ledger, r ledger.Refs, shard bool) error
- func (s *Store) SaveNotified(m map[string]Notified) error
- func (s *Store) SaveRuns(runs map[string]RunRecord) error
- func (s *Store) SaveURLs(m map[string]URLEntry) error
- func (s *Store) Undo() ([]docsync.Edit, error)
- func (s *Store) Write(name string, data []byte) error
- func (s *Store) WriteBodies(bodies map[string]string) error
- type TreeVCS
- type URLEntry
- type UpdateReport
- type VCS
Constants ¶
const ( // ClaudeFile and AgentsRootFile are the repo-root instruction files // Claude Code and other agents read. ClaudeFile = "CLAUDE.md" AgentsRootFile = "AGENTS.md" // SkillFile is the Claude Code skill, loadable on demand. SkillFile = ".claude/skills/docsync/SKILL.md" // ClaudeSettingsFile holds Claude Code hooks for the repo. ClaudeSettingsFile = ".claude/settings.json" )
`ds init --agents` (§26.10): the rules of section 25 land where agents read them, `ds mcp` is registered, and a session-start hook loads the map. Every write is idempotent: fragments sit between markers and are replaced in place, JSON configs are merged, so running it again after a tool upgrade refreshes the rules without touching anything else.
const ( CacheDir = "cache" CacheFile = "extract.json" )
The extraction cache (§16 "incremental by default") lives in .ds/cache/extract.json: per file, the content hash and what extracting those bytes produced. A file whose bytes did not change is not re-extracted. Files with problems are never cached (the library rule), so a fixed directive is always re-read.
const ( ExitOK = 0 ExitFindings = 1 ExitError = 2 )
Exit codes. Findings decide `check`'s code (§17); everything else is a usage or runtime failure.
const ( // ExportBlocksFile maps every local id to its rendered block. ExportBlocksFile = "blocks.json" // ExportStatusFile is `status --json`, so a site can paint freshness // dots beside cited sentences. ExportStatusFile = "status.json" )
`ds export hugo --out DIR` (§24 "docs site", build order 8): everything a static site generator needs at build time, as data files. Hugo cannot run a program during a build, so the Hugo module in integrations/hugo reads `data/docsync/blocks.json` from a shortcode and `status.json` from a partial; this command writes both. Docusaurus runs the remark plugin instead, which calls `ds render` directly.
const ( WriteDef = "def" WriteAdopt = "adopt" WriteRename = "rename" // WriteRepair comments directives an older build left bare, so `undo` // can put them back exactly as they were found. WriteRepair = "repair" )
The commands that write source (§22 "source is written only by def and adopt"; rename rewrites docs). The journal records which one made a write so `undo --list` can say what it is offering to reverse.
const ( ToolMap = "map" ToolFind = "find" ToolRead = "read" ToolLocate = "locate" ToolFacts = "facts" ToolWhy = "why" ToolContext = "context" ToolCheck = "check" ToolImpact = "impact" ToolDef = "def" ToolAck = "ack" )
MCP tool names (§26.1).
const ( DirName = scan.StateDir ConfigFile = "config.toml" LedgerFile = ledger.LedgerFile RefsFile = ledger.RefsFile AcksFile = ledger.AcksFile CISnippet = "ci-github.yml" AgentsFile = "AGENTS.md" GitignoreFile = ".gitignore" // GitattributesFile gives .ds/ files the merge behaviour they need. GitattributesFile = ".gitattributes" )
File names under .ds/.
const BlocksDir = "blocks"
The body store (§20.1). `check` has to answer "what did this block say when the sentence was acked", and the acked hash can be arbitrarily far behind: many scans back in this repo, or in another repository entirely. Neither the previous ledger nor `git show` can answer that — the ledger holds one row per id at one commit, and a merged row holds no body at all — so bodies are kept content-addressed, one file per hash, written by `scan` under .ds/blocks/ and by `publish` under the index entry's blocks/.
Content-addressing makes the store append-only and self-deduplicating: a block that did not change writes nothing, and a hash that is present is correct by construction. A missing body is not an error; it degrades the finding to block.ClassUnknown, which still flags.
const ( // DefaultKeep is how long a dead body is kept anyway. A repo that has // not synced lately may be about to ack against it, and a body is // cheap: when in doubt it stays. DefaultKeep = "30d" )
`ds prune` removes superseded block bodies (§20.1). The stores are content-addressed and append-only, so a block that churns leaves one body per version in `.ds/blocks/` and another in the index — both committed, so every superseded body is permanent history in two git repositories.
The safety property that matters is not "usually removes the right ones" but "cannot remove a live one". Liveness is computed as a set and only its complement is removed; anything that cannot be read is a refusal, never an assumption that the hashes it held were dead.
const DefaultName = "ds"
DefaultName is the binary name when WithName is not given.
const HashesFile = "hashes.json"
Secret resolution under `check --resolve` (§12): each provider is a process plugin `ds-resolve-<provider>` on PATH speaking the procplugin protocol, so any language can add one and no provider SDK enters this module. GitHub answers existence only; providers that can read a value return its hash and never the value, which the host enforces. Truth hashes are kept in .ds/hashes.json when resolve.store_hash is on, so the next run can report `rotated`.
const (
IndexDir = "index"
)
Workspace index handling (§21). The repo config's `workspace` names the index: a directory on disk, or a git URL cloned into .ds/index/.
const JournalFile = "journal.tsv"
JournalFile records every source write so `undo` can reverse the last one (§33 safety). One line per edit: batch, kind, time, file, line, old, new; tabs, newlines, and backslashes in the text fields are escaped the way the ledger does.
The file is machine-local and never committed (§6): `old` and `new` are raw source lines, which on a file under `[secret] paths` means the line holds a value. It is listed in `.ds/.gitignore` so that copy never enters history, where rotating the credential could not remove it.
const LedgerDir = "ledger"
LedgerDir holds sharded ledgers when ledger shard = true.
const LockFile = "lock"
LockFile is the machine-local file .ds/ writers lock (see withLock).
const MCPConfigFile = ".mcp.json"
MCPConfigFile is the repo-level MCP registration agents read (§26.10), the one MCPTargets always writes.
const MetricsFile = "metrics.json"
MetricsFile accumulates what the agent surface served (§26.11): bytes handed out by `context` and `read` against the bytes of the files those items came from. `report --metrics` prints the ratio.
const (
NotifiedFile = "notified.json"
)
`ds notify` (§22, §24 nightly): routes open error findings to owners with dedupe and escalation. A finding is sent once; it is sent again, marked escalated, when it is still open after notify.escalate_after. State lives in .ds/notified.json. The only built-in channel is a Slack incoming webhook (notify.slack, `$VAR` expanded from the environment by the CLI); without one the digest is printed, which is what a cron job mails.
const (
RunsFile = "runs.json"
)
`ds:run` execution (§9.4): only under `check --run`, only where run.enabled is true, `cmd=` only in docs matching run.allow, `id=` only with runnable=true. Results are kept in .ds/runs.json so `render` can show the command with its last outcome. This is code execution from a text file and is treated as such: every guard here is deliberate.
const (
URLCacheFile = "urls.json"
)
`ds:url` checking (§9.7): under `check --resolve` only, cached per url.ttl in .ds/urls.json, rate limited by url.rate_per_minute. Redirects are followed and the final URL recorded; the page title is read from the first titleReadCap bytes.
Variables ¶
var ( // ErrNotInitialised means no directory at or above where the command // started holds a config. Below one that does, the root is found // instead (App.locate), so this never sends someone to create a second // .ds inside an initialised repository. ErrNotInitialised = errors.New("no .ds/config.toml here or in any parent directory; run `init` first") // ErrNestedInit refuses `init` under an initialised repository unless // --force says a separate docsync root is wanted there. ErrNestedInit = errors.New("a parent directory already has .ds/config.toml") ErrExists = errors.New(".ds/config.toml already exists; pass --force to overwrite") ErrUsage = errors.New("usage") )
Errors.
var ( // ErrNothingToUndo is returned when the journal is empty. ErrNothingToUndo = errors.New("nothing to undo") // ErrUndoCommitted is returned when the next entry is already in git // history and --force was not given. ErrUndoCommitted = errors.New("nothing to undo since the last commit") // ErrUndoOrphans is returned when undoing would remove a def that // sentences still cite and --orphan was not given. ErrUndoOrphans = errors.New("undo would orphan a cited def") )
Errors.
var ( ErrNoWorkspace = errors.New("no workspace configured; set `workspace` in .ds/config.toml") ErrNotDefault = errors.New("publish runs only on the default branch (§21); pass --force to override") ErrIndexOffline = errors.New("workspace index unreachable and no cached copy") ErrIndexNoRemote = errors.New("index is a local directory; nothing to push") )
Errors.
var DoctorStatusValues = []string{doctorOK, doctorWarn, doctorFail}
DoctorStatusValues is the canonical order.
var ErrCommitAckKey = fmt.Errorf("%w: an ack in a commit message takes only %s", ErrUsage, strings.Join(commitAckKeys, "= and ")+"=")
ErrCommitAckKey is a key on a commit-message ack that is not in commitAckKeys. note= used to be read past without a word, so the note a developer wrote never reached the ack log (bug 73); any key that does nothing is refused rather than ignored.
var ErrDefCap = errors.New("mcp: def cap for this session reached (agents.max_defs_per_run); ask a human to raise it or run `def` themselves")
ErrDefCap is returned when an MCP session exceeds agents.max_defs_per_run.
var ErrForkPR = errors.New("--run and --resolve are disabled on pull requests from forks")
ErrForkPR refuses --run and --resolve on a fork pull request.
var ErrForkPRReview = errors.New("review --ai is disabled on pull requests from forks: it runs the [review] command from committed configuration")
ErrForkPRReview refuses `review --ai` on a fork pull request: it runs the [review] command out of .ds/config.toml, a committed file the pull request can change, so on a fork's PR it would run the fork's command with the repository's credentials.
var ErrGitHubEnv = errors.New("github comment needs GITHUB_TOKEN and GITHUB_REPOSITORY, and a pull request number (--pr or GITHUB_EVENT_PATH); --dry-run needs none of them")
ErrGitHubEnv names the missing environment.
var ErrHTTPRecords = errors.New("records: http source failed")
ErrHTTPRecords wraps a failed record request.
var ErrNoDir = fmt.Errorf("%w: --%s is not a directory", ErrUsage, flagDir)
ErrNoDir is a --dir that is not a directory.
var ErrNoReviewCommand = fmt.Errorf("%w: --ai needs [review] command in .ds/config.toml", ErrUsage)
ErrNoReviewCommand is returned by --ai without [review] command.
var ErrNoShell = errors.New("the shell is not on PATH")
ErrNoShell is returned when a ds:run or the [review] command is about to execute and the shell it runs under is not on PATH. It is an error rather than a skip: a check that was asked to run commands and ran none must not look like one whose commands passed.
var ErrNoSnapshot = fmt.Errorf("%s/%s not found; run `ds sync` to record the foreign blocks this repo cites", DirName, ledger.ForeignFile)
ErrNoSnapshot is returned by --frozen when no snapshot has been written. It never degrades to "no foreign blocks": a frozen check with nothing to resolve against would pass vacuously, reporting green for citations it never looked at.
var ErrNoTree = errors.New("render --at needs a VCS that lists a commit's files (cli.TreeVCS)")
ErrNoTree is returned by `render --at` when the VCS cannot list a commit's tree (it does not implement TreeVCS).
var ErrNoVCS = errors.New("git: not a repository or git not installed")
ErrNoVCS is returned by Git when the directory is not a repository or git is not installed; commands degrade to no permalinks and no diffs.
var ErrNotDir = errors.New("exists but is not a directory")
ErrNotDir is a path docsync keeps a directory at that holds something else.
var ErrNotMember = errors.New("this repository is not one of the workspace's repos")
ErrNotMember refuses a publish from a repository the workspace does not list: a fork or mirror of a listed one, or a repository that is not in the workspace at all.
var ErrOutsideRepo = fmt.Errorf("%w: path is outside the repository", ErrUsage)
ErrOutsideRepo refuses a path argument that leaves the repository.
var ErrPruneIndex = fmt.Errorf("prune --index needs the default branch and a fresh sync")
ErrPruneIndex is returned when `--index` is asked for from a position that cannot see who cites what.
var ErrPruneState = fmt.Errorf("prune: cannot read the state that says which bodies are live")
ErrPruneState is returned when a file the live set depends on cannot be read. Pruning on a partial view is how a live body gets deleted, so it is refused outright rather than degraded.
var ErrSettings = errors.New("settings.json is not a JSON object; fix it or remove it")
ErrSettings is returned when .claude/settings.json is not a JSON object.
var Exit = os.Exit
Exit is what Main calls with the exit code. It is a variable so a test or an embedder that must not terminate the process can replace it; nothing else in the package exits.
var MCPTargets = []MCPTarget{ {File: MCPConfigFile, Key: mcpServersKey}, {File: ".cursor/mcp.json", Key: mcpServersKey, Dir: ".cursor"}, {File: ".vscode/mcp.json", Key: vscodeKey, Dir: ".vscode", Stdio: true}, {File: ".gemini/settings.json", Key: mcpServersKey, Dir: ".gemini"}, }
MCPTargets are the clients `init --agents` registers `ds mcp` with. Only .mcp.json was written before, which most clients do not read (bug 108).
var WriteKinds = []string{WriteDef, WriteAdopt, WriteRename, WriteRepair}
WriteKinds is the canonical order.
Functions ¶
func ConfigTOML ¶
ConfigTOML renders a config as the TOML `init` writes. Only the keys a new repo needs appear; the rest fall back to defaults and are documented in the spec.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is one configured invocation.
type BuildInfo ¶
type BuildInfo struct {
// Version is the module version when installed with `go install …@v`,
// else "(devel)" for a build from a checkout.
Version string `json:"version"`
// Commit is the full revision the binary was built from.
Commit string `json:"commit"`
// Dirty says the checkout had uncommitted changes, so Commit alone does
// not describe the code that is running.
Dirty bool `json:"dirty"`
// Time is the commit's time, not the build's.
Time string `json:"time"`
// Go is the toolchain that built it.
Go string `json:"go"`
}
BuildInfo is what `ds version` reports about the running binary.
type CommitCounter ¶ added in v0.1.6
CommitCounter is the optional VCS upgrade behind `workspace.stale_after_commits` (bug 101): how many commits `to` is ahead of `from`. A VCS without it gets no commit-distance warning, never a guessed one.
type Git ¶
type Git struct {
Dir string
}
Git shells out to the `git` program, which must be on PATH, 2.24 or later for --end-of-options. Documented at the point of use per the library rules; the root module never does this.
Every value that is not a flag — a commit, a path, a URL — goes after --end-of-options or "--". They come from committed files a pull request can edit (the ledger header's commit, an at= in a doc, the workspace URL), and git reads anything starting with "-" as an option: a commit of `--output=<path>` made `git show` write that file.
func (Git) CommitIndex ¶ added in v0.1.6
CommitIndex implements IndexCommitter.
func (Git) CommitsBetween ¶ added in v0.1.6
CommitsBetween implements CommitCounter: the number of commits reachable from to and not from from. An unknown commit -- one this clone never fetched -- is an error, so the caller says nothing rather than guess.
func (Git) Introduced ¶ added in v0.1.6
Introduced returns the short sha of the oldest commit that changed how many times text occurs in path (git's pickaxe), which for a line written once is the commit that added it.
func (Git) Push ¶
Push commits everything under dir and pushes. A clean tree is not an error: publishing the same ledger twice is a no-op.
func (Git) Reachable ¶ added in v0.1.6
Reachable asks the remote for its HEAD with `git ls-remote`, never prompting. It reads nothing into the working tree and writes nothing.
type IndexCommitter ¶ added in v0.1.6
type IndexCommitter interface {
// CommitIndex commits everything under dir when dir is the top of its
// own git work tree, and reports whether it made a commit. A directory
// that is not a work tree's top -- a plain directory, or one nested
// inside an unrelated repository -- is not committed and is not an
// error: committing there would sweep up the enclosing repository's
// unrelated changes.
CommitIndex(dir, message string) (bool, error)
}
IndexCommitter is the optional VCS upgrade `publish` uses for an index that is a local directory rather than a managed clone (bug 100). Writing the files and stopping left them untracked in an index that is itself a git repository, so every later publish piled onto one uncommitted change and nothing recorded which repository wrote what, at which commit. A VCS that does not implement it is left alone: the files are written and nothing else happens.
type MCPTarget ¶ added in v0.1.6
type MCPTarget struct {
File string
Key string
Dir string
// Stdio adds "type": "stdio", which VS Code's mcp.json requires.
Stdio bool
}
MCPTarget is one MCP client's project-level registration file: where it is, the top-level key holding its servers, and the directory whose presence says the client is in use. The first target has no directory and is always written; the others are written only where their client has already left its directory, which is how "the configs it can find" (§26.10) is decided without guessing at what someone has installed.
type Notified ¶
type Notified struct {
FirstSent time.Time `json:"first_sent"`
LastSent time.Time `json:"last_sent"`
Escalated bool `json:"escalated"`
// Tier is the urgency a snapshot alert was last sent at, so a drift
// that worsens is news and a second edit at the same tier is not.
// Empty for an ordinary finding.
Tier string `json:"tier,omitempty"`
// Misses counts consecutive runs that could not compare a repo against
// the index. Not comparing is not the same as nothing to report: a
// broken index would otherwise hide staleness indefinitely, which is
// the vacuous pass this whole series has been about.
Misses int `json:"misses,omitempty"`
}
Notified is one delivered finding.
type NotifyState ¶
type NotifyState interface {
// Load reports the remembered state and whether any was found. The
// second value is not len(state) > 0: a repo that is green records an
// empty state, and treating that as "no memory" would make every later
// run announce itself as a first run.
Load(ctx context.Context) (state map[string]Notified, found bool, err error)
Save(ctx context.Context, state map[string]Notified) error
}
NotifyState is where `ds notify` keeps the memory that makes it quiet: which findings it has already sent, and when, so a second run says nothing new and `escalate_after` can measure from a first sighting.
It is an interface because that memory has to outlive a process, and where it outlives it is the operator's decision. A CI runner checks the repository out fresh every night and cannot write anything back, so the default file is empty on every run there and dedupe silently never holds — a notifier that repeats itself nightly is one people mute. The shipped nightly template restores the file from a cache; a workspace index or a database can implement this instead without the command changing.
A missing state is an empty map and no error: that is a first run. An unreadable one is an error, because treating a corrupt file as empty would silently reset dedupe, which looks exactly like working.
type Option ¶
type Option func(*App)
Option configures an App.
func Standard ¶
func Standard() Option
Standard is the built-in set; it exists so a custom binary reads `cli.WithDefaults(cli.Standard())` and gains new built-ins by upgrading.
func WithConfigDefaults ¶
WithConfigDefaults adjusts the defaults of an organisation-wide binary: every repository's config is read on top of them, so a key its file does not set falls back to the organisation's value, and `init` writes them into the keys it writes. Before, config was read on top of the built-in defaults only, so everything the hook set outside init's few keys was silently lost — an agent cap of 7 ran as 20.
func WithDefaults ¶
WithDefaults applies a preset such as Standard.
func WithExtractor ¶
WithExtractor registers extra tiers ahead of the built-in ones.
func WithHTTPClient ¶
WithHTTPClient replaces the client `check --resolve` uses for ds:url.
func WithNotifyState ¶
func WithNotifyState(s NotifyState) Option
WithNotifyState replaces where `ds notify` keeps its dedupe and escalation memory. The default is `.ds/notified.json`, which a CI runner discards between runs; an organisation whose CI cannot cache that file supplies a store that survives instead.
func WithPicker ¶
WithPicker registers Go `pick=` schemes for a custom binary; process plugins (`ds-pick-<scheme>`) need no registration.
func WithPluginLookup ¶
WithPluginLookup replaces PATH lookup for `ds-*` process plugins.
func WithRegistry ¶
WithRegistry replaces tier selection entirely (§37.3); WithExtractor then prepends to it. The escape hatch for a binary that wants no built-in tier.
type Prober ¶ added in v0.1.6
Prober is an optional upgrade of VCS: it answers whether a remote can be reached without fetching anything, which is what `ds doctor` asks of the workspace index (SPEC §22). A VCS without it is not forced to pretend; the doctor row says reachability was not checked instead.
type RunRecord ¶
type RunRecord struct {
Command string `json:"command"`
Output string `json:"output"`
OK bool `json:"ok"`
Expect string `json:"expect,omitempty"`
At time.Time `json:"at"`
}
RunRecord is one stored outcome.
type StaleBlock ¶
type StaleBlock struct {
ID string `json:"id"`
From string `json:"from"`
To string `json:"to"`
// Classes describe the difference when both bodies are available, so a
// reader can tell a signature change from a comment before deciding
// whether syncing is urgent.
Classes []block.Class `json:"class,omitempty"`
// Gone says the publishing repo no longer defines this id. It is the
// most urgent state, not the quietest: the next `ds sync` turns every
// citation of it `broken`, so hearing about it before that sync is the
// whole value. It must never share a branch with "unchanged" — doing so
// reported a repo whose cited block had been deleted as up to date.
Gone bool `json:"gone,omitempty"`
}
StaleBlock is one cited block whose upstream has moved past the snapshot.
type StaleRepo ¶
type StaleRepo struct {
Repo string `json:"repo"`
// Cited is how many of this repo's blocks the snapshot records; Behind
// how many of those have moved on.
Cited int `json:"cited"`
Behind int `json:"behind"`
// SnapshotCommit is what the snapshot recorded; IndexCommit what the
// index publishes now. Either may be empty when a repo published
// without a commit.
SnapshotCommit string `json:"snapshot_commit,omitempty"`
IndexCommit string `json:"index_commit,omitempty"`
Blocks []StaleBlock `json:"blocks,omitempty"`
// Compared is false when the index publishes nothing for this repo, so
// its blocks could not be looked up at all. Reporting that as "up to
// date" would be a claim about blocks nobody read — the same silent
// pass the whole snapshot section exists to prevent.
Compared bool `json:"compared"`
}
StaleRepo is one upstream repository's position relative to the snapshot.
type Staleness ¶
type Staleness struct {
// Present is false when this repo has no snapshot, which is the normal
// state for a repo with no workspace.
Present bool `json:"present"`
// SyncedAt is when the snapshot was written; Age its age.
SyncedAt time.Time `json:"synced_at,omitempty"`
Age string `json:"age,omitempty"`
// Compared is false when no index was reachable. The repos list is then
// empty and the reader is told the comparison was skipped, rather than
// being shown a silent "up to date" that was never checked.
Compared bool `json:"compared"`
Repos []StaleRepo `json:"repos,omitempty"`
}
Staleness is the snapshot section of `ds status`.
type Store ¶
type Store struct {
Root string
// Now stamps journal entries; nil means time.Now().UTC().
Now func() time.Time
// contains filtered or unexported fields
}
Store is the `.ds/` directory: config plus the three committed TSV files (§6). It is the default `Store` capability; a database or the workspace index can replace it later without touching commands.
func (*Store) AddMetrics ¶
AddMetrics adds one served item to the totals.
func (*Store) AppendAcks ¶
AppendAcks adds rows to the ack log on disk, under the .ds/ lock: it re-reads the log as it is now, appends the rows it does not already hold, and writes it back. It is the only way the log is written.
Why re-read: an ack command loads the log when it starts and used to write that copy back with its own rows appended, so two acks recorded at once — an agent's parallel tool calls, a triage group alongside a manual ack — each wrote a log without the other's row. Ten concurrent acks recorded one, and every command reported success. Rows already on disk are skipped by their encoded form, so a caller may pass the whole log it holds.
func (*Store) ApplyEdit ¶
ApplyEdit applies a library Edit to its file in the tree: the only source write the tool makes (§33 safety), and only from `def`. The file is read again here rather than trusting an earlier read, so an edit computed against stale content is refused by Edit.Apply instead of landing on the wrong line.
func (*Store) Journal ¶
Journal appends a batch of applied edits. Batch numbers increase by one per call so `undo` reverses one command's writes together. kind names the command that made them, one of WriteKinds.
func (*Store) LoadCache ¶
LoadCache reads the cache; a missing file, a foreign format, and entries extracted under different inputs are all empty, so a stale entry is never preferred to re-reading the file. now is the clock Stamp measures modification times against.
func (*Store) LoadConfig ¶
LoadConfig parses .ds/config.toml on top of the built-in defaults.
func (*Store) LoadConfigOnto ¶
LoadConfigOnto parses .ds/config.toml on top of base: what the file sets wins, and everything else keeps base's value (config.ParseOnto).
func (*Store) LoadForeign ¶
LoadForeign reads the snapshot; ok is false when the file is absent.
func (*Store) LoadHashes ¶
LoadHashes reads stored truth hashes; missing means none.
func (*Store) LoadMetrics ¶
LoadMetrics reads the totals; missing means zero.
func (*Store) LoadNotified ¶
LoadNotified reads the dedupe state.
func (*Store) LoadState ¶
LoadState reads the ledger, refs, and acks; a missing file is an empty value, a malformed one is an error. A sharded ledger directory is read when present and takes precedence over a single file.
func (*Store) SaveForeign ¶
SaveForeign writes the snapshot.
func (*Store) SaveHashes ¶
SaveHashes writes them.
func (*Store) SaveLedger ¶
SaveLedger writes ledger and refs atomically. With shard set the ledger goes to .ds/ledger/<top-level dir>.tsv, one file per directory, and the single-file ledger is removed so the two never disagree.
func (*Store) SaveLedgerSharded ¶
SaveLedgerSharded is SaveLedger with the layout chosen by shard.
func (*Store) SaveNotified ¶
SaveNotified writes it.
func (*Store) Undo ¶
Undo reverses the last batch: edits are undone in reverse order, each checked against the file so a later hand edit is never overwritten. It returns the reversed edits.
func (*Store) Write ¶
Write stores a file under .ds/ atomically — temp file then rename, so a crash mid-write never leaves a truncated ledger for the next check — and under the .ds/ lock, so two commands writing the same file cannot share its temp path: without the lock one rename failed, and interleaved writes to the shared temp file could rename a mixture of both into place.
func (*Store) WriteBodies ¶
WriteBodies adds bodies to the local store, skipping any already present: the file name is the content hash, so an existing file cannot disagree with what would be written. Writing goes through Store.Write so there is exactly one atomic-write path in the CLI, and a body is never observed half-written by a concurrent check.
type TreeVCS ¶ added in v0.1.6
type TreeVCS interface {
// Resolve returns the short sha that commit (a sha, tag or branch) names.
Resolve(commit string) (string, error)
// Tree maps every file path in commit's tree to its size in bytes.
Tree(commit string) (map[string]int64, error)
}
TreeVCS is the optional upgrade to VCS that `render --at` needs to render a page as of a commit rather than the page's old text over today's code: the commit a ref names, and every file in that commit's tree with its size. Git implements it; a VCS that does not makes `render --at` refuse rather than mix two commits in one page. It is an optional interface, not a VCS method, so a host's existing VCS keeps compiling.
type URLEntry ¶
type URLEntry struct {
Status int `json:"status"`
Final string `json:"final"`
Title string `json:"title,omitempty"`
Error string `json:"error,omitempty"`
CheckedAt time.Time `json:"checked_at"`
}
URLEntry is one cached outcome.
type UpdateReport ¶ added in v0.1.7
type UpdateReport struct {
// Current is the running version; Latest the release it was compared
// with (or the one --version named).
Current string `json:"current"`
Latest string `json:"latest"`
// Available says Latest is newer than Current.
Available bool `json:"update_available"`
// Updated says files were replaced; Installed lists them.
Updated bool `json:"updated"`
Installed []string `json:"installed,omitempty"`
// URL is the release page with its notes.
URL string `json:"url"`
// AutoUpdate is the automatic mode in effect (auto, notify, off) and
// AutoUpdateFrom what decided it: "default", "DS_UPDATE", the
// repository's config, or "not a release build".
AutoUpdate string `json:"auto_update"`
AutoUpdateFrom string `json:"auto_update_from"`
}
UpdateReport is `ds update --json`.
type VCS ¶
type VCS interface {
Head() (string, error)
Show(commit, path string) ([]byte, error)
Exists(commit string) bool
// User is the configured author name, for the ack log's actor.
User() string
// Churn is commits touching each file over recent history, for
// `report --unmarked`; the count is a ranking signal, not an audit.
Churn() (map[string]int, error)
// Branch is the current branch name, for publish's default-branch rule.
Branch() (string, error)
// RemoteURL is origin's URL, which names the repo in a workspace.
RemoteURL() string
// Clone, Pull, and Push manage the workspace index checkout.
Clone(url, dir string) error
Pull(dir string) error
Push(dir, message string) error
// Staged lists files in the index, for `impact --staged`.
Staged() ([]string, error)
// Message returns a commit's message, for `ack --from-commit`.
Message(commit string) (string, error)
}
VCS is what the CLI needs from version control: the current commit, a file at a commit, and whether a commit exists. Git is the only built-in; the interface exists so tests and other hosts can supply their own.
Source Files
¶
- agents.go
- bodies.go
- cache.go
- cli.go
- commands.go
- commands2.go
- committree.go
- contextsince.go
- doctorrows.go
- export.go
- frozen.go
- github.go
- journal.go
- lock_unix.go
- lsp.go
- lsproot.go
- mcp.go
- metrics.go
- notify.go
- plugins.go
- prune.go
- records_http.go
- resolve.go
- review.go
- run.go
- sharing_other.go
- snapshot.go
- snapshotnotify.go
- staleness.go
- store.go
- undo.go
- update.go
- urlcheck.go
- vcs.go
- version.go
- workspace.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
ds
command
Command ds is the standard docsync binary: the built-in verbs and tiers plus the shipped ext modules: the structured tier (YAML, TOML, HCL, with the `hcl:` pick scheme) and the tree-sitter syntax tier (Go, TypeScript, TSX, JavaScript, Python, SQL).
|
Command ds is the standard docsync binary: the built-in verbs and tiers plus the shipped ext modules: the structured tier (YAML, TOML, HCL, with the `hcl:` pick scheme) and the tree-sitter syntax tier (Go, TypeScript, TSX, JavaScript, Python, SQL). |
|
ds-resolve-aws
command
Command ds-resolve-aws is the AWS Secrets Manager resolver plugin (§12, §37.4).
|
Command ds-resolve-aws is the AWS Secrets Manager resolver plugin (§12, §37.4). |
|
ds-resolve-env
command
Command ds-resolve-env is the environment-variable resolver plugin (§12, §37.4), for the hops a def marks `source=env`: code that reads a variable such as STRIPE_KEY from its process environment.
|
Command ds-resolve-env is the environment-variable resolver plugin (§12, §37.4), for the hops a def marks `source=env`: code that reads a variable such as STRIPE_KEY from its process environment. |
|
ds-resolve-gcp
command
Command ds-resolve-gcp is the Google Secret Manager resolver plugin (§12, §37.4).
|
Command ds-resolve-gcp is the Google Secret Manager resolver plugin (§12, §37.4). |
|
ds-resolve-github
command
Command ds-resolve-github is the GitHub secret resolver plugin (§12, §37.4).
|
Command ds-resolve-github is the GitHub secret resolver plugin (§12, §37.4). |
|
ds-resolve-onepassword
command
Command ds-resolve-onepassword is the 1Password resolver plugin (§12, §37.4).
|
Command ds-resolve-onepassword is the 1Password resolver plugin (§12, §37.4). |
|
ds-resolve-vault
command
Command ds-resolve-vault is the HashiCorp Vault resolver plugin (§12, §37.4).
|
Command ds-resolve-vault is the HashiCorp Vault resolver plugin (§12, §37.4). |
|
internal
|
|
|
doctest
command
Command doctest runs the examples in docsync's guide pages against the built ds binary and fails when what a page shows is not what ds prints.
|
Command doctest runs the examples in docsync's guide pages against the built ds binary and fails when what a page shows is not what ds prints. |
|
Package selfupdate replaces a running program with a newer release of itself: it finds the newest release of one binary stream, downloads that platform's archive, verifies it against the release's checksums.txt, and swaps the program and its companion executables in place.
|
Package selfupdate replaces a running program with a newer release of itself: it finds the newest release of one binary stream, downloads that platform's archive, verifies it against the release's checksums.txt, and swaps the program and its companion executables in place. |