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
- func ExtractAssetLinks(body string, basePath string) []string
- func ExtractLinks(body string, basePath string) []okf.ConceptID
- func GitPathToConceptID(path string) (string, bool)
- func RewriteLinks(body string, basePath string, moveMap map[string]string) (string, int)
- func ShouldWarnGitIdentity(gitSync, hasRemote bool, authorEmail string) bool
- type AssetEntry
- type BatchWriteOp
- type BatchWriteResult
- type ConceptData
- type Conflict
- type ContractMalformed
- type Forge
- type GateBlocker
- type GateResult
- type GitHubForge
- func (g *GitHubForge) CreatePR(ctx context.Context, owner, repo, head, base, title, body string) (PullRequest, error)
- func (g *GitHubForge) FindOpenPR(ctx context.Context, owner, repo, head, base string) ([]PullRequest, error)
- func (g *GitHubForge) GetPR(ctx context.Context, owner, repo string, number int) (PullRequest, error)
- func (g *GitHubForge) MergeSquash(ctx context.Context, owner, repo string, number int, sha string) (MergeResult, error)
- func (g *GitHubForge) PRReady(ctx context.Context, owner, repo string, number int) (bool, error)
- type GitStatus
- type KB
- func (kb *KB) AppendLog(entry string, ts time.Time) error
- func (k *KB) ClearConflict(conceptID string) error
- func (kb *KB) CommitGate(changedIDs []okf.ConceptID) (*GateResult, error)
- func (k *KB) CommitOp(message string) (sha string, err error)
- func (k *KB) CommitPaths(paths []string, message string) error
- func (kb *KB) ConceptCount(archive string) (int, error)
- func (k *KB) ConfigureServerGit(cfg ServerGitConfig) error
- func (kb *KB) CreateMap(name, title, kind string, conceptTypes []string, ontologyMode string) error
- func (kb *KB) CreateMapWithContract(name, title, kind string, conceptTypes []string, ontologyMode string, ...) error
- func (kb *KB) DataRoot() string
- func (kb *KB) DeleteAsset(id okf.ConceptID, assetPath, ifMatch string) error
- func (kb *KB) DeleteConcept(id okf.ConceptID) error
- func (kb *KB) DeleteConceptWithAssets(id okf.ConceptID, force bool) ([]AssetEntry, error)
- func (kb *KB) DeleteMap(name string) error
- func (kb *KB) ExpandConcept(id okf.ConceptID) error
- func (kb *KB) ExpandedCount(archive string) (int, error)
- func (k *KB) FinalizeConflicts() ([]string, error)
- func (k *KB) FinalizeServerPR(ctx context.Context, expectedHead string) error
- func (k *KB) FlushPush(timeout time.Duration) error
- func (k *KB) GitStatusSnapshot() GitStatus
- func (kb *KB) GraphNeighbors(id okf.ConceptID, depth int, directions ...string) (map[string]int, error)
- func (k *KB) HasRemote() (string, bool)
- func (kb *KB) IncomingLinks() (map[okf.ConceptID]map[okf.ConceptID]struct{}, error)
- func (kb *KB) IndexHash(path string) (content, hash string, err error)
- func (kb *KB) ListArchives() ([]string, error)
- func (kb *KB) ListAssets(id okf.ConceptID) ([]AssetEntry, error)
- func (k *KB) ListConflicts() ([]Conflict, error)
- func (kb *KB) ListExpanded(archive string) ([]string, error)
- func (kb *KB) LogTail(relPath string, n int) (string, error)
- func (k *KB) MarkDegraded(conceptID string) error
- func (k *KB) MarkPushPending()
- func (kb *KB) PatchIndex(path, ifMatch, newContent string) (string, error)
- func (k *KB) PendingConflictCount() (int, error)
- func (kb *KB) ReadArchiveMeta(archive string) (*okf.Frontmatter, error)
- func (kb *KB) ReadAsset(id okf.ConceptID, assetPath string) ([]byte, AssetEntry, error)
- func (kb *KB) ReadConcept(id okf.ConceptID) (*ConceptData, error)
- func (kb *KB) ReadIndex(folderRelPath string) (string, error)
- func (kb *KB) ReadMapContract(archive string) (MapContract, error)
- func (kb *KB) ReadRaw(relPath string) (string, error)
- func (k *KB) ReconcileServerPR() error
- func (k *KB) RecordResolution(conceptID, strategy, body string) error
- func (k *KB) RecordServerRebaseConflict(err error)
- func (k *KB) RegisterConflict(c Conflict) error
- func (kb *KB) ResolvePath(relPath string, writeMode bool) (string, error)
- func (kb *KB) ResolveRootPath(relPath string) (string, error)
- func (k *KB) SchedulePush()
- func (k *KB) ServerGitStatus() ServerGitState
- func (k *KB) ServerWritesBlocked() error
- func (k *KB) SetGitStatus(state string, err error)
- func (k *KB) SyncIn() (bool, error)
- func (k *KB) SyncInDue() bool
- func (k *KB) SyncOut() error
- func (kb *KB) Validate(scope string) ([]ValidationError, error)
- func (kb *KB) WalkConcepts(fn func(id okf.ConceptID, content string) error) error
- func (k *KB) WithGitLock(fn func() error) error
- func (kb *KB) WriteAsset(id okf.ConceptID, assetPath string, data []byte, ifMatch string, ...) (AssetEntry, error)
- func (kb *KB) WriteConcept(id okf.ConceptID, fm *okf.Frontmatter, body string, ifMatch string) (string, error)
- func (kb *KB) WriteConceptBatch(ops []BatchWriteOp, logMessage string, ...) ([]BatchWriteResult, error)
- func (kb *KB) WriteExpandedConcept(id okf.ConceptID, fm *okf.Frontmatter, body string, ifMatch string) (string, error)
- func (kb *KB) WriteFileAtomic(relPath string, data []byte) error
- type MapContract
- type MergeResult
- type PullRequest
- type ServerGitConfig
- type ServerGitState
- type ValidationError
Constants ¶
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 ¶ added in v0.4.0
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 ¶
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 ¶
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 ¶
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
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
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
BatchWriteResult reports one applied operation's resulting content-hash, in the same order as the BatchWriteOp slice passed to WriteConceptBatch.
type ConceptData ¶
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
ContractMalformed identifies a tolerated malformed contract entry.
type Forge ¶ added in v0.4.0
type Forge interface {
FindOpenPR(context.Context, string, string, string, string) ([]PullRequest, error)
GetPR(context.Context, string, string, int) (PullRequest, error)
CreatePR(context.Context, string, string, string, string, string, string) (PullRequest, error)
PRReady(context.Context, string, string, int) (bool, error)
MergeSquash(context.Context, string, string, int, string) (MergeResult, error)
}
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
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)
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 ¶
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 ¶
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 ¶
AppendLog prepends an entry to log.md (newest-on-top). The timestamp is provided by the caller to ensure testability.
func (*KB) ClearConflict ¶
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 ¶
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
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 ¶
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 ¶
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 ¶
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
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 ¶
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
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
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 ¶
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 ¶
ExpandedCount counts the subdirectories of an archive (for atlas_overview).
func (*KB) FinalizeConflicts ¶
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
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 ¶
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
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
HasRemote reports whether the KB has a configured origin remote.
func (*KB) IncomingLinks ¶ added in v0.4.0
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
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 ¶
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 ¶
ListConflicts returns all open conflicts (never nil on success — may be empty slice).
func (*KB) ListExpanded ¶
ListExpanded returns the subdirectories of a map (its expanded concepts; path relative to root).
func (*KB) LogTail ¶
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 ¶
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
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 ¶
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
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 ¶
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) ReconcileServerPR ¶ added in v0.4.0
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 ¶
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
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 ¶
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 ¶
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 ¶
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
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
SetGitStatus records a status transition without taking the git-operation lock.
func (*KB) SyncIn ¶
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
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 ¶
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 ¶
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 ¶
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.
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
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 ¶
ValidationError describes a single OKF validation error.