kb

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package kb — conflict registry and degraded-marker support (Step 3). State is persisted in <root>/.cartographer/conflicts.json, which is local-only (gitignored) so it never enters the versioned history.

Package kb implements the data plane of the OKF knowledge base. Handles reading, atomic writing, and initialization of a KB on the filesystem.

Index

Constants

View Source
const AssetMaxFileSize = 1024 * 1024 // 1 MiB

AssetMaxFileSize is the largest file the data-plane asset API accepts.

Variables

This section is empty.

Functions

func ExtractAssetLinks(body string, basePath string) []string

ExtractAssetLinks returns the physical KB-relative targets of Markdown links that name a non-Markdown file. It deliberately does not turn those paths into ConceptIDs. basePath is the actual source file path, so links in an expanded owner's index.md resolve from the owner directory.

func ExtractLinks(body string, basePath string) []okf.ConceptID

ExtractLinks parses markdown links and wiki-links from the body of a concept and returns the referenced concept IDs. Absolute URLs and anchors are skipped. basePath is the concept's own path relative to the KB root (e.g. "arch/dossier/concept.md").

Markdown links [text](path.md) are resolved relative to basePath. Wiki- links [[id]] and [[id#section]] are root-relative: the ID is taken as-is (path from the KB root, without .md). The alias form [[id|text]] is not supported and is not extracted. Both syntaxes dedup against the same seen set.

func GitPathToConceptID

func GitPathToConceptID(path string) (string, bool)

GitPathToConceptID converts a git-relative file path to a ConceptID. Only paths of the form "data/<...>.md" are converted; all others return ("", false). Reserved files (index.md, log.md, _map.md, _archive.md) are excluded — note this means an expanded concept's "index.md" (D77 WP2) is not reported here; callers needing that mapping should go through WalkConcepts instead.

func RewriteLinks(body string, basePath string, moveMap map[string]string) (string, int)

RewriteLinks rewrites, in body, every markdown link and wiki-link whose resolved target concept ID is a key in moveMap (old ID → new ID), and returns the updated body plus the number of replacements performed. basePath is the linking concept's own current path relative to the KB root (same meaning and resolution rules as ExtractLinks' basePath): markdown hrefs are resolved relative to path.Dir(basePath) and rewritten to the new relative path (from the same directory) to the moved target, preserving any "#fragment" and adding back the ".md" suffix. Wiki-links are root-relative and are rewritten by simple ID substitution, preserving any "#section" suffix. Links whose resolved target is not in moveMap are left untouched.

func ShouldWarnGitIdentity added in v0.4.0

func ShouldWarnGitIdentity(gitSync, hasRemote bool, authorEmail string) bool

ShouldWarnGitIdentity reports whether a synchronised remote KB still uses Cartographer's placeholder author identity.

Types

type AssetEntry added in v0.4.0

type AssetEntry struct {
	Path       string `json:"path"`
	Size       int64  `json:"size"`
	SHA256     string `json:"sha256"`
	Executable bool   `json:"executable"`
}

AssetEntry describes one non-Markdown regular file owned by an expanded concept.

type BatchWriteOp added in v0.5.0

type BatchWriteOp struct {
	ID      okf.ConceptID
	FM      *okf.Frontmatter
	Body    string
	IfMatch string
}

BatchWriteOp is one target of an atomic multi-concept write (D125 WP1/WP2): the MCP layer materializes a "write" or "patch" request into this canonical id/frontmatter/body/if_match form — the same shape prepareWriteConcept already validates for a single concept — before calling WriteConceptBatch. IfMatch empty means create-only, exactly like WriteConcept when the target does not yet exist; the MCP layer is responsible for rejecting an empty IfMatch against an already-existing target before building the batch (WriteConceptBatch itself stays permissive, consistent with WriteConcept).

type BatchWriteResult added in v0.5.0

type BatchWriteResult struct {
	ID          string
	ContentHash string
}

BatchWriteResult reports one applied operation's resulting content-hash, in the same order as the BatchWriteOp slice passed to WriteConceptBatch.

type ConceptData

type ConceptData struct {
	Content        string
	FrontmatterRaw string
	Body           string
	ContentHash    string
}

ConceptData holds the result of reading a concept.

type Conflict

type Conflict struct {
	ConceptID     string   `json:"concept_id"`
	Path          string   `json:"path"`       // git-relative file path (e.g. "data/shared/notes/c.md")
	LocalSHA      string   `json:"local_sha"`  // HEAD SHA before the failed rebase
	RemoteSHA     string   `json:"remote_sha"` // SHA of <remote>/<branch> after fetch
	Branch        string   `json:"branch"`
	BaseBranch    string   `json:"base_branch,omitempty"`    // server profile only (D117)
	WorkingBranch string   `json:"working_branch,omitempty"` // server profile only (D117)
	PRNumber      int      `json:"pr_number,omitempty"`      // server profile only (D117)
	PRURL         string   `json:"pr_url,omitempty"`         // server profile only (D117)
	Files         []string `json:"files"`                    // all conflicting git paths in the same rebase
	DetectedAt    string   `json:"detected_at"`              // RFC3339 UTC

	// Step 4 — recorded resolution (empty until the agent calls git_conflict_resolve).
	ResolutionStrategy string `json:"resolution_strategy,omitempty"` // "ours" | "theirs" | "edit"
	ResolutionBody     string `json:"resolution_body,omitempty"`     // full reconciled file content, used when strategy="edit"
}

Conflict describes a rebase conflict detected on a specific concept.

type ContractMalformed added in v0.4.0

type ContractMalformed struct {
	Descriptor string
	Key        string
}

ContractMalformed identifies a tolerated malformed contract entry.

type Forge added in v0.4.0

Forge isolates the review boundary from Git transport. Implementations must not retain or expose credentials in errors.

type GateBlocker

type GateBlocker struct {
	ConceptPath string   // path of the Contradiction concept
	Involves    []string // concept IDs it involves
	Kind        string   // contradiction_kind value
	Reason      string   // reason field
}

GateBlocker describes a single blocking contradiction.

type GateResult

type GateResult struct {
	Pass     bool
	Blockers []GateBlocker
}

GateResult holds the result of a commit gate check.

type GitHubForge added in v0.4.0

type GitHubForge struct {
	APIURL string
	Token  string
	Client *http.Client
}

GitHubForge is GitHub's REST implementation of Forge. The stdlib client is intentionally injected so tests use httptest and production has no gh CLI dependency.

func (*GitHubForge) CreatePR added in v0.4.0

func (g *GitHubForge) CreatePR(ctx context.Context, owner, repo, head, base, title, body string) (PullRequest, error)

func (*GitHubForge) FindOpenPR added in v0.4.0

func (g *GitHubForge) FindOpenPR(ctx context.Context, owner, repo, head, base string) ([]PullRequest, error)

func (*GitHubForge) GetPR added in v0.4.0

func (g *GitHubForge) GetPR(ctx context.Context, owner, repo string, number int) (PullRequest, error)

func (*GitHubForge) MergeSquash added in v0.4.0

func (g *GitHubForge) MergeSquash(ctx context.Context, owner, repo string, number int, sha string) (MergeResult, error)

func (*GitHubForge) PRReady added in v0.4.0

func (g *GitHubForge) PRReady(ctx context.Context, owner, repo string, number int) (bool, error)

type GitStatus added in v0.4.0

type GitStatus struct {
	State           string     `json:"state"`
	LastError       string     `json:"last_error,omitempty"`
	LastAttemptAt   *time.Time `json:"last_attempt_at,omitempty"`
	HeadSHA         string     `json:"head_sha,omitempty"`
	UnpushedCommits *int       `json:"unpushed_commits"`
	IdentityWarning bool       `json:"identity_warning,omitempty"`
	Attempts        int        `json:"attempts"`
}

GitStatus is the durable-in-process snapshot exposed by sync_status. UnpushedCommits is nil when no trustworthy remote-tracking comparison exists.

type KB

type KB struct {
	Root string
	// AuthName is the mounted logical name used by authorization policy. It is
	// set by the transport at mount time and never contains a bearer secret.
	AuthName   string
	AutoCommit bool // if true, CommitOp creates a git commit after each write
	GitSync    bool // if true, SyncIn/SyncOut fetch/push with the "origin" remote

	// SyncInWindow is the freshness window for SyncIn (D76/WP3): if the last
	// successful SyncIn happened less than SyncInWindow ago, SyncIn is a
	// no-op — avoids a redundant fetch+pull on every write during a burst.
	// Zero disables the window (SyncIn runs on every call, pre-existing
	// behaviour).
	SyncInWindow time.Duration

	// GitAuthorName/GitAuthorEmail set the commit author identity used by
	// CommitOp and conflict-resolution commits. Empty values fall back to
	// defaultGitAuthorName/defaultGitAuthorEmail.
	GitAuthorName  string
	GitAuthorEmail string
	// GitAuthorExplicit is true only when Cartographer configuration supplied a
	// complete identity. When false CommitOp lets Git resolve its native author.
	GitAuthorExplicit bool
	// GitEnv is the per-KB environment (e.g. GIT_SSH_COMMAND,
	// GIT_COMMITTER_NAME/EMAIL) layered onto git subprocesses — see
	// gitx.runGitEnv. Nil means "run with the process environment", the
	// pre-existing behaviour.
	GitEnv []string

	// ServerGit is non-nil only for the opt-in server profile. Its dedicated
	// working branch is the only branch this process may push (D117).
	ServerGit *ServerGitConfig
	// ServerMergeLint is wired by the MCP layer to run the full lint during a
	// server-profile PR finalization. Keeping it injectable avoids a kb↔lint
	// import cycle while preserving an in-lock gate.
	ServerMergeLint func() error

	// SopsAgeKeyFile is the path to the SOPS age key file used to decrypt
	// this KB's secrets (e.g. via service_get resolve_secrets). Empty means
	// no per-KB key is configured — secret resolution fails clearly instead
	// of falling back to an ambient key.
	SopsAgeKeyFile string

	// AllowArtifactWrite gates the artifact_write/artifact_delete MCP tools
	// (D71): writing a provisioning artifact (skill/agent/hook/mcp) injects
	// instructions a client agent will execute, so the capability is opt-in
	// per-KB (config.KBSpec.AllowArtifactWrite), not implied by an rw token
	// alone. Default false. artifact_read/artifact_list are unaffected.
	AllowArtifactWrite bool

	// SyncOutDebounce is the debounce window for the async push worker
	// (D76/WP4): when > 0, gitWrap calls SchedulePush instead of SyncOut
	// inline, taking the push off the critical path of a write response.
	// The worker waits SyncOutDebounce after the last SchedulePush signal
	// before actually pushing, coalescing a burst of writes into one push.
	// Zero disables the worker entirely: gitWrap falls back to the
	// pre-existing synchronous SyncOut call (rollback flag) — see
	// pushworker.go.
	SyncOutDebounce time.Duration

	// OnPushConflict, if set, is invoked by the async push worker (see
	// pushworker.go, doAsyncPush) when SyncOut hits a rebase conflict, so
	// the caller (mcpserver.RegisterKBTools wires this at registration
	// time) can route it through the same conflict-registry/degraded
	// handling used for synchronous pushes. If nil, the worker only logs
	// the conflict to stderr.
	OnPushConflict func(*gitx.RebaseConflictError)

	// OnSyncIn, if set, runs after a successful SyncIn that changed HEAD.
	// mcpserver uses it to reconcile derived indexes with pulled KB files.
	OnSyncIn func()
	// contains filtered or unexported fields
}

KB represents an open knowledge base identified by its root on the filesystem. Always used as *KB; never copy a KB value (sync.Mutex field).

func Init

func Init(root string) (*KB, error)

Init initializes a new KB by creating the minimal skeleton: data/{index.md,log.md}, skills/, services/, agents/, hooks/. If the KB already exists (data/index.md present) it is a no-op.

agents/ and hooks/ are provisioning kinds (internal/provisioning, D48): agents/<name>.md is a single-file Claude subagent (source format — translated to OpenCode's native frontmatter at materialization time, D55), hooks/<name>/ is a directory (script + hook.json). Both are optional — a KB predating D48 with no agents/ or hooks/ directory simply yields zero artifacts of that kind (see provisioning.BuildManifest), so this is not a breaking change for existing KBs.

Init generates only content directories — no AGENTS.md, no .gitignore (D62): the KB is always mediated by the server, never edited directly by an agent, so the soft agent-contract file was pure noise. Local-only state (.cartographer/) is excluded via .git/info/exclude instead (see ensureInfoExclude), never via a versioned .gitignore.

func Open

func Open(root string) (*KB, error)

Open opens an existing KB by verifying that index.md exists at the root. As a side effect it self-migrates the local git-exclude entry for .cartographer/ (D62, ensureInfoExclude) — best-effort, so existing KBs created before D62 pick it up on first Open with no operator action.

func (*KB) AppendLog

func (kb *KB) AppendLog(entry string, ts time.Time) error

AppendLog prepends an entry to log.md (newest-on-top). The timestamp is provided by the caller to ensure testability.

func (*KB) ClearConflict

func (k *KB) ClearConflict(conceptID string) error

ClearConflict removes the conflict entry for conceptID. No-op if not present.

func (*KB) CommitGate

func (kb *KB) CommitGate(changedIDs []okf.ConceptID) (*GateResult, error)

CommitGate checks for open contradictions involving any of the given concept IDs. It walks all .md files, finds those with type=Contradiction and resolution_status=open, and checks if their "involves" list intersects with changedIDs. Returns Pass=true if no blocking contradictions found.

func (*KB) CommitOp

func (k *KB) CommitOp(message string) (sha string, err error)

CommitOp creates a git commit if AutoCommit is enabled, the KB root is a git repository, and the working tree is dirty. It is a no-op in every other case. If git commit itself fails, the error is returned to the caller; the wrapper in mcpserver treats commit errors as non-fatal (logs to stderr, does not surface to the MCP client).

On a successful commit, sha is the new commit's SHA, resolved via gitx.HeadSHA immediately after the commit — while the caller still holds the per-KB git lock (WithGitLock in mcpserver.gitWrap), so this is never a race with a concurrent commit/push on the same KB (D119: the audit log records this SHA on the write's completion event instead of a later, racier HEAD query). sha is empty when no commit was made (autocommit off, not a repo, clean tree, or "nothing to commit").

func (*KB) CommitPaths added in v0.2.0

func (k *KB) CommitPaths(paths []string, message string) error

CommitPaths creates one commit containing changes to paths only. The per-KB lock serialises its staging and commit with other git operations; gitx uses a temporary index so unrelated dirty work remains untouched.

func (*KB) ConceptCount

func (kb *KB) ConceptCount(archive string) (int, error)

ConceptCount recursively counts the non-reserved .md files (concepts) inside an archive, regardless of nesting depth (for atlas_overview).

func (*KB) ConfigureServerGit added in v0.4.0

func (k *KB) ConfigureServerGit(cfg ServerGitConfig) error

ConfigureServerGit validates and mounts a dedicated working branch. It is intentionally fail-fast: ambiguous local state is never repaired by reset.

func (*KB) CreateMap

func (kb *KB) CreateMap(name, title, kind string, conceptTypes []string, ontologyMode string) error

CreateMap creates a map or journal with minimal structure: _map.md, index.md, log.md (D77 WP1 — replaces the former CreateArchive/ "_archive.md" pair, which is now read-compat only, never written). name must be a kebab-case segment; the map must not already exist. kind must be "map" or "journal"; empty defaults to "map". If ontologyMode is empty, defaults to "flexible". It creates no optional lint contract.

func (*KB) CreateMapWithContract added in v0.4.0

func (kb *KB) CreateMapWithContract(name, title, kind string, conceptTypes []string, ontologyMode string, contract MapContract) error

CreateMapWithContract creates a map or journal with its optional lint contract serialized deterministically in _map.md.

func (*KB) DataRoot

func (kb *KB) DataRoot() string

DataRoot returns the conceptual root of the KB (index.md, log.md, archives). Concept paths resolved via ResolvePath are anchored here, not at Root. Siblings of data/ (skills/, services/) live directly under Root.

func (*KB) DeleteAsset added in v0.4.0

func (kb *KB) DeleteAsset(id okf.ConceptID, assetPath, ifMatch string) error

DeleteAsset deletes an asset after a raw-byte sha256 concurrency check and removes only now-empty directories below the expanded concept directory.

func (*KB) DeleteConcept

func (kb *KB) DeleteConcept(id okf.ConceptID) error

DeleteConcept permanently removes a concept's file from the KB (its "<id>.md" form or, for an expanded concept, its "<id>/index.md" form — see resolveConceptRelPath, D77 WP2). Rejects an empty ConceptID and reserved files (index.md, log.md, _map.md, _archive.md, AGENTS.md). Returns ErrNotFound if the file does not exist. Does not update inbound links or any index — callers are responsible for that (see concept_delete in mcpserver). Deleting an expanded concept removes only its index.md, leaving any satellite concepts under "<id>/" in place.

func (*KB) DeleteConceptWithAssets added in v0.4.0

func (kb *KB) DeleteConceptWithAssets(id okf.ConceptID, force bool) ([]AssetEntry, error)

DeleteConceptWithAssets preserves the existing non-recursive concept-delete contract while making asset loss explicit. Satellite Markdown concepts are never removed; force only acknowledges deletion of non-Markdown assets.

func (*KB) DeleteMap added in v0.2.0

func (kb *KB) DeleteMap(name string) error

DeleteMap removes a map or journal directory, but only if it is empty — i.e. it contains nothing but the scaffold files written by CreateMap (_map.md, index.md, log.md). If any concept remains under it, the map is left untouched and the error lists the concepts so the caller can move them out first (concept_move) before retrying.

func (*KB) ExpandConcept

func (kb *KB) ExpandConcept(id okf.ConceptID) error

ExpandConcept promotes a concept born as "<id>.md" into a directory "<id>/" whose "index.md" holds the same content (D77 WP2): the ID never changes — resolveConceptRelPath resolves reads and writes to the new location transparently — so no backlink rewrite is needed, unlike concept_move. Preconditions:

  • id has exactly two segments (map/concept): expanding a child would let its own children exceed maxConceptDepth once it grows satellites;
  • the concept exists in its direct "<id>.md" form;
  • "<id>/" does not already exist (the concept is not already expanded).

The inverse (concept_collapse) is intentionally not implemented (YAGNI — see docs/decisions/data-plane.md D77).

func (*KB) ExpandedCount

func (kb *KB) ExpandedCount(archive string) (int, error)

ExpandedCount counts the subdirectories of an archive (for atlas_overview).

func (*KB) FinalizeConflicts

func (k *KB) FinalizeConflicts() ([]string, error)

FinalizeConflicts converges the diverged history in a single git merge: it merges the remote conflict SHA into the current branch, overwrites every conflicting file with the content chosen by its recorded resolution, commits the merge, pushes (best-effort, respecting GitSync/remote), and clears the registry and degraded markers.

It must only be called when every open conflict has a recorded resolution (see PendingConflictCount). Returns the resolved concept IDs. On any git failure the merge is aborted and the pre-merge working tree (including the degraded markers) is restored.

func (*KB) FinalizeServerPR added in v0.4.0

func (k *KB) FinalizeServerPR(ctx context.Context, expectedHead string) error

FinalizeServerPR enforces review, a caller-supplied PR head, rebase and a force-with-lease update of only the dedicated working branch before asking the forge for a squash merge.

func (*KB) FlushPush

func (k *KB) FlushPush(timeout time.Duration) error

FlushPush forces immediate execution of any pending or in-flight push and waits for it to finish (or for timeout to elapse). It is a no-op — and does not start the worker — if the worker was never started (i.e. SchedulePush was never called on this KB, which is always the case when SyncOutDebounce == 0) or if there is currently nothing pending/running.

func (*KB) GitStatusSnapshot added in v0.4.0

func (k *KB) GitStatusSnapshot() GitStatus

GitStatusSnapshot recomputes divergence for an accurate post-restart view.

func (*KB) GraphNeighbors

func (kb *KB) GraphNeighbors(id okf.ConceptID, depth int, directions ...string) (map[string]int, error)

GraphNeighbors returns the concept IDs reachable from id within depth hops. The optional direction is out (default), in, or both. The returned map is conceptID → minimum distance from the starting concept. The starting concept and self-edges are not included. depth <= 0 defaults to 1.

func (*KB) HasRemote added in v0.4.0

func (k *KB) HasRemote() (string, bool)

HasRemote reports whether the KB has a configured origin remote.

func (kb *KB) IncomingLinks() (map[okf.ConceptID]map[okf.ConceptID]struct{}, error)

IncomingLinks returns the derived inbound links keyed by target concept. The graph is computed on demand so it always follows the KB files.

func (*KB) IndexHash added in v0.5.0

func (kb *KB) IndexHash(path string) (content, hash string, err error)

IndexHash reads the root or a Map/Journal's curated index.md — the same "path" convention as ReadIndex (empty/"." = root) — and returns its content alongside its content-hash. It is the bounded read half of the index-patch data plane (D122 WP1): unlike ReadIndex, which serves any folder including an expanded concept's own index.md, IndexHash accepts only the root and an existing Map/Journal descriptor (see curatedIndexRelPath) and is what index_patch reads before applying an edit.

func (*KB) ListArchives

func (kb *KB) ListArchives() ([]string, error)

ListArchives returns the names of first-level subdirectories of the data root, excluding reserved files (index.md, log.md, _map.md, _archive.md) and hidden dirs.

func (*KB) ListAssets added in v0.4.0

func (kb *KB) ListAssets(id okf.ConceptID) ([]AssetEntry, error)

ListAssets returns all regular, non-Markdown files below an expanded concept.

func (*KB) ListConflicts

func (k *KB) ListConflicts() ([]Conflict, error)

ListConflicts returns all open conflicts (never nil on success — may be empty slice).

func (*KB) ListExpanded

func (kb *KB) ListExpanded(archive string) ([]string, error)

ListExpanded returns the subdirectories of a map (its expanded concepts; path relative to root).

func (*KB) LogTail

func (kb *KB) LogTail(relPath string, n int) (string, error)

LogTail reads the last n entries relevant to relPath. An "entry" starts with "## ". If relPath is empty, uses the root log. n=0 uses the default (20).

Entries are never written per-directory (AppendLog always writes to root, prefixing "[<path>] " when a path is given — see toolLogAppend): to keep them discoverable, a non-empty relPath returns (a) the entries of "<relPath>/log.md" if that file exists and has any, followed by (b) the root-log entries whose text starts with "[<relPath>] ", up to n total.

func (*KB) MarkDegraded

func (k *KB) MarkDegraded(conceptID string) error

MarkDegraded sets status: degraded on the given concept's frontmatter. Best-effort: if the concept does not exist or its frontmatter cannot be parsed, an error is returned and the caller decides whether to log and continue.

func (*KB) MarkPushPending added in v0.4.0

func (k *KB) MarkPushPending()

MarkPushPending keeps an existing failure visible until a push succeeds.

func (*KB) PatchIndex added in v0.5.0

func (kb *KB) PatchIndex(path, ifMatch, newContent string) (string, error)

PatchIndex atomically replaces the content of a root or Map/Journal curated index.md — the bounded write half of IndexHash (D122 WP1). ifMatch must equal the index's current content-hash (as returned by IndexHash); a mismatch fails with ErrStaleWrite and leaves the file untouched. newContent is the full, already-materialized replacement text — the MCP layer applies old_string/new_string edits before calling this, the same division of labor WriteConcept/concept_patch use. Returns the new content-hash.

func (*KB) PendingConflictCount

func (k *KB) PendingConflictCount() (int, error)

PendingConflictCount returns how many open conflicts still lack a recorded resolution.

func (*KB) ReadArchiveMeta

func (kb *KB) ReadArchiveMeta(archive string) (*okf.Frontmatter, error)

ReadArchiveMeta reads and parses the frontmatter of an archive/map's descriptor, preferring "_map.md" over the legacy "_archive.md" (mapDescriptorRelPath, D77 WP1). A legacy "_archive.md" with no explicit "kind" field is treated as kind: map, so callers never see a Map without a kind.

func (*KB) ReadAsset added in v0.4.0

func (kb *KB) ReadAsset(id okf.ConceptID, assetPath string) ([]byte, AssetEntry, error)

ReadAsset returns the raw bytes and metadata of an owned asset.

func (*KB) ReadConcept

func (kb *KB) ReadConcept(id okf.ConceptID) (*ConceptData, error)

ReadConcept reads a concept by ID and returns it with raw frontmatter, body, and hash.

func (*KB) ReadIndex

func (kb *KB) ReadIndex(folderRelPath string) (string, error)

ReadIndex reads the contents of index.md in a folder (path relative to root). If folderRelPath is empty, reads the root index.md.

func (*KB) ReadMapContract added in v0.4.0

func (kb *KB) ReadMapContract(archive string) (MapContract, error)

ReadMapContract reads a map's optional lint contract. Unknown descriptor keys remain ignored for permissive consumption; malformed recognized keys are returned as findings for lint to surface without blocking reads.

func (*KB) ReadRaw

func (kb *KB) ReadRaw(relPath string) (string, error)

ReadRaw reads the text of a file by its path relative to the KB root.

func (*KB) ReconcileServerPR added in v0.4.0

func (k *KB) ReconcileServerPR() error

ReconcileServerPR retries a previously failed PR lookup/create from a status request. It does not change Git refs and is a no-op for Local Core.

func (*KB) RecordResolution

func (k *KB) RecordResolution(conceptID, strategy, body string) error

RecordResolution stores the agent's chosen resolution for a registered conflict. strategy must be "ours", "theirs", or "edit"; for "edit", body is the full reconciled file content (frontmatter + body). Returns an error if no conflict is registered for conceptID. The git transaction is deferred to FinalizeConflicts.

func (*KB) RecordServerRebaseConflict added in v0.4.0

func (k *KB) RecordServerRebaseConflict(err error)

RecordServerRebaseConflict persists server-specific metadata before the caller returns the structured git conflict. Non-concept files remain a fail-closed error because the agent-facing registry cannot safely edit them.

func (*KB) RegisterConflict

func (k *KB) RegisterConflict(c Conflict) error

RegisterConflict adds or updates the conflict entry for c.ConceptID. Idempotent: if an entry for the same ConceptID already exists it is replaced.

func (*KB) ResolvePath

func (kb *KB) ResolvePath(relPath string, writeMode bool) (string, error)

ResolvePath resolves a concept path relative to the KB data root, verifying:

  • no escape from the base (../)
  • no absolute path

Paths are anchored at DataRoot() (the conceptual root) by default. The services/ tree is a first-class concept root included in WalkConcepts but lives as a sibling of data/ under Root, so it is anchored at Root.

func (*KB) ResolveRootPath

func (kb *KB) ResolveRootPath(relPath string) (string, error)

ResolveRootPath resolves a path relative to the KB root (kb.Root itself, not DataRoot()), verifying the same invariants as ResolvePath (no absolute path, no escape from the base). Used by the provisioning-artifact tools (skills/, agents/, hooks/, mcp/, instructions.md — D71), which live at the KB root as siblings of data/, not inside it.

func (*KB) SchedulePush

func (k *KB) SchedulePush()

SchedulePush signals that a push is pending for this KB. It starts the worker on first use. Multiple signals received before the worker actually pushes are coalesced into a single SyncOut call: every call extends the debounce window (pushLastSignal), so a burst of writes close together results in exactly one push, issued SyncOutDebounce after the last one.

func (*KB) ServerGitStatus added in v0.4.0

func (k *KB) ServerGitStatus() ServerGitState

ServerGitStatus returns the persisted PR metadata alongside the current profile. It is safe for local-profile KBs too.

func (*KB) ServerWritesBlocked added in v0.4.0

func (k *KB) ServerWritesBlocked() error

ServerWritesBlocked prevents a second PR cycle while the result of a prior merge request is unknown. Only status reconciliation may clear it.

func (*KB) SetGitStatus added in v0.4.0

func (k *KB) SetGitStatus(state string, err error)

SetGitStatus records a status transition without taking the git-operation lock.

func (*KB) SyncIn

func (k *KB) SyncIn() (bool, error)

SyncIn performs a fetch + pull --rebase --autostash from the "origin" remote before a git-synchronised operation. It is a no-op when:

  • k.GitSync is false, or
  • the KB root is not a git repository, or
  • no "origin" remote is configured, or
  • the freshness window has not elapsed: k.SyncInWindow > 0 and the last successful SyncIn happened less than k.SyncInWindow ago (D76/WP3) — avoids a redundant fetch+pull on every write during a burst.

The returned bool reports whether a fetch was attempted, including a fetch that failed. Returns gitx.ErrRebaseConflict if the pull hits a conflict (the rebase is aborted automatically). Other network/git errors are propagated as is. lastSyncIn is only updated after a fetch+pull that actually succeeds. Callers must hold the git lock.

func (*KB) SyncInDue added in v0.2.0

func (k *KB) SyncInDue() bool

SyncInDue reports whether a SyncIn call could run a fetch. It is deliberately safe to call before acquiring the git lock so read-side callers can avoid the lock on the common disabled, no-remote, and fresh-window paths. A concurrent SyncIn may make the answer stale before the caller acquires that lock; SyncIn repeats the check and safely becomes a no-op in that case.

func (*KB) SyncOut

func (k *KB) SyncOut() error

SyncOut pushes the current branch to "origin" AFTER a successful commit. It is a no-op when:

  • k.GitSync is false, or
  • the KB root is not a git repository, or
  • no "origin" remote is configured.

On a non-fast-forward rejection the loop performs fetch + PullRebaseAutostash and retries the push with exponential backoff, capped at 5 attempts total. If PullRebaseAutostash returns ErrRebaseConflict, SyncOut returns immediately. After 5 failed attempts it returns an error.

func (*KB) Validate

func (kb *KB) Validate(scope string) ([]ValidationError, error)

Validate validates .md files in the scope (path relative to the KB; if empty, the entire KB). Returns the list of validation errors without stopping at the first one. Returns a Go error only for serious I/O errors.

func (*KB) WalkConcepts

func (kb *KB) WalkConcepts(fn func(id okf.ConceptID, content string) error) error

WalkConcepts calls fn for every non-reserved .md file in the KB, plus every expanded concept's index.md (D77 WP2): an index.md at exactly two path segments deep — e.g. "map/concept/index.md" — is the expanded form of the concept "map/concept" (see ExpandConcept) and is emitted with that ID. index.md at the root or at one segment deep (map-level, e.g. "map/index.md") stays reserved/excluded, same as before.

It walks the data/ conceptual tree plus the services/ tree (a sibling of data/ under Root whose type:Service concepts must participate in search, graph, lint and service_list). Other reserved files (log.md, _map.md, _archive.md, AGENTS.md) are always skipped. raw/ is outside both roots.

func (*KB) WithGitLock

func (k *KB) WithGitLock(fn func() error) error

WithGitLock acquires the per-KB mutex, executes fn, then releases it. This serialises git operations so that concurrent tool calls do not interleave their working-tree changes and commits.

func (*KB) WriteAsset added in v0.4.0

func (kb *KB) WriteAsset(id okf.ConceptID, assetPath string, data []byte, ifMatch string, executable *bool) (AssetEntry, error)

WriteAsset creates or overwrites an owned asset using the raw-byte sha256 as its optimistic-concurrency token.

func (*KB) WriteConcept

func (kb *KB) WriteConcept(id okf.ConceptID, fm *okf.Frontmatter, body string, ifMatch string) (string, error)

WriteConcept writes a concept to the KB with OKF semantic validation and optimistic concurrency. If ifMatch is non-empty and the file exists, the current ContentHash must match ifMatch. If ifMatch is non-empty and the file does not exist, returns ErrStaleWrite. If ifMatch is empty, overwrites without concurrency check. Returns the hash of the content actually written.

D72 WP4: two additional invariants on the write path (not on reads, so legacy KBs remain readable as-is):

  • depth guard: concepts under data/ (excluding services/) are capped at maxConceptDepth segments (map/concept/child);
  • implicit expansion stubbing: if this write creates a new map/concept directory that did not exist before, an index.md stub is generated for it (see stubExpandedIndex), so index_get never fails on a real expanded concept.

func (*KB) WriteConceptBatch added in v0.5.0

func (kb *KB) WriteConceptBatch(ops []BatchWriteOp, logMessage string, afterFiles func([]BatchWriteResult) error) ([]BatchWriteResult, error)

WriteConceptBatch atomically materializes every op's final content plus one summary log.md entry as a single logical operation (D125 WP1/WP2): every op is prepared — validated and its content built — before any disk write, so a preparation failure leaves the tree untouched. If any file write, the log append, or afterFiles fails, every file this call already wrote — including any implicit expanded-index stub a target's directory creation triggered — and log.md are restored to their exact pre-call bytes and mode before the error is returned: callers observe either the complete batch or the exact pre-call KB state, never an intermediate one.

afterFiles runs after every file and the log entry are committed, still before WriteConceptBatch returns success; it exists so the MCP layer can keep the keyword/FTS5 search indexes in step with the same atomicity boundary without this package depending on internal/search or internal/sqlindex. If afterFiles returns an error, it must first reconcile any index entries it already changed back to their pre-call state itself (it has the original content, read before the batch started) — this function then rolls the files and log back to match, so files and both indexes stay consistent in every outcome.

Callers must already hold the KB's write lock (gitWrap's WithGitLock): this is the single acquisition the whole batch runs under, not a second one.

func (*KB) WriteExpandedConcept added in v0.2.0

func (kb *KB) WriteExpandedConcept(id okf.ConceptID, fm *okf.Frontmatter, body string, ifMatch string) (string, error)

WriteExpandedConcept writes the index of a new or existing expanded concept directly to "<id>/index.md". Unlike WriteConcept, it never falls back to the direct "<id>.md" form; callers that intentionally create an expanded concept can therefore write its index before any satellite exists.

func (*KB) WriteFileAtomic

func (kb *KB) WriteFileAtomic(relPath string, data []byte) error

WriteFileAtomic writes data to relPath atomically (write to temp + rename).

type MapContract added in v0.4.0

type MapContract struct {
	RequiredFields           []string
	RequiredFieldsByType     map[string][]string
	RequireIndexEntry        bool
	MachinePathAllowPrefixes []string
	Malformed                []ContractMalformed
}

MapContract declares the optional deterministic lint contract of a map. RequiredFields apply to every concept; RequiredFieldsByType are additive. MachinePathAllowPrefixes lists absolute path prefixes that the machine_path lint (D124) must treat as this map's operational target paths rather than client-local paths — e.g. a container image's home directory or a remote node's runtime path, which are identical across every reader's machine and therefore not a false positive.

func (MapContract) RequiredFor added in v0.4.0

func (c MapContract) RequiredFor(conceptType string) []string

RequiredFor returns the map-wide fields plus fields for conceptType.

type MergeResult added in v0.4.0

type MergeResult struct {
	Merged bool
	SHA    string
}

MergeResult is the forge-confirmed squash merge outcome. SHA is the commit GitHub reports as merged and must later be observed on the protected base.

type PullRequest added in v0.4.0

type PullRequest struct {
	Number   int    `json:"number"`
	URL      string `json:"url"`
	HeadSHA  string `json:"head_sha"`
	BaseSHA  string `json:"base_sha,omitempty"`
	State    string `json:"state"`
	Merged   bool   `json:"merged"`
	MergeSHA string `json:"merge_sha,omitempty"`
}

PullRequest is the non-secret forge state retained for a server-profile KB.

type ServerGitConfig added in v0.4.0

type ServerGitConfig struct {
	BaseBranch    string
	WorkingBranch string
	Owner         string
	Repository    string
	Forge         Forge
}

ServerGitConfig is the resolved, non-secret configuration for the server profile. Token material belongs only in Forge and is never persisted.

type ServerGitState added in v0.4.0

type ServerGitState struct {
	Profile        string `json:"profile"`
	BaseBranch     string `json:"base_branch"`
	WorkingBranch  string `json:"working_branch"`
	PRNumber       int    `json:"pr_number,omitempty"`
	PRURL          string `json:"pr_url,omitempty"`
	PRHeadSHA      string `json:"pr_head_sha,omitempty"`
	PRBaseSHA      string `json:"pr_base_sha,omitempty"`
	LastForgeError string `json:"last_forge_error,omitempty"`
	Phase          string `json:"phase,omitempty"` // open | merge_uncertain
	MergeSHA       string `json:"merge_sha,omitempty"`
}

ServerGitState is local operational state, deliberately outside history.

type ValidationError

type ValidationError struct {
	Path    string // path relative to the KB
	Message string
}

ValidationError describes a single OKF validation error.

Jump to

Keyboard shortcuts

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