knowledge

package
v1.0.1 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("knowledge: not found")

ErrNotFound is returned when a requested knowledge item does not exist.

Functions

func HasHeadings

func HasHeadings(src string) bool

HasHeadings returns true when src contains at least one ATX heading. Cheap check before a full parse.

Types

type AddItemParams

type AddItemParams struct {
	Type          string
	Title         string
	Content       string
	URL           string
	Tags          []string
	Source        string // "manual", "discord", etc. — defaults to "manual"
	LearningValue int    // 1-5; 0 = unset

	// ParentID is set by recursive fan-out calls when inserting section children
	// of a Markdown document. Nil for top-level (root) items.
	// Raw UUID bytes matching pgtype.UUID.Bytes / uuid.UUID layout.
	ParentID *[16]byte

	// HeadingPath is the breadcrumb path for a section child,
	// e.g. "Introduction › Key Concepts". Empty for root items.
	HeadingPath string

	// HeadingLevel is the ATX heading depth (1–6) for a section, 0 for root.
	HeadingLevel int

	// Cross-domain reference fields (migration 000049).
	// No FK constraint (CLAUDE.md red-line §9); referential integrity in code.
	ProjectID  *[16]byte // nil → NULL
	TaskID     *[16]byte // nil → NULL
	DecisionID *[16]byte // nil → NULL
}

AddItemParams holds parameters for adding a new knowledge item.

type ErrDuplicate

type ErrDuplicate struct {
	ExistingTitle string
	Similarity    float64
}

ErrDuplicate is returned when AddItem detects content that is already saved (by URL exact match or by vector cosine similarity >= dedup threshold).

func (ErrDuplicate) Error

func (e ErrDuplicate) Error() string

type KnowledgeType

type KnowledgeType string

KnowledgeType defines the allowed types for a knowledge item.

const (
	TypeArticle      KnowledgeType = "article"
	TypeTIL          KnowledgeType = "til"
	TypeBookmark     KnowledgeType = "bookmark"
	TypeZettelkasten KnowledgeType = "zettelkasten"
)

type PreparedItem

type PreparedItem struct {
	// Params is the AddItemParams Prepare was called with, threaded through
	// so WriteItemTx does not require the caller to pass it a second time.
	Params AddItemParams
	// Vec is the computed embedding to store alongside the row, or nil when
	// no embedding was generated — either no embed client is configured or
	// the Embed call itself failed (both cases set DedupSkipped=true; see
	// embedAndCheckDupAtLevel's Warn log for which one happened).
	Vec []float32
	// DedupSkipped is true when the cosine-similarity dedup check did not
	// run at all (no embed client, Embed failed, or the similarity query
	// itself failed), as opposed to running and finding no duplicate at or
	// above dedupSimilarityThreshold. WriteItemTx does not branch on this
	// field; it exists so callers/tests can distinguish "no duplicate
	// found" from "dedup check never ran" without inspecting log output.
	DedupSkipped bool
}

PreparedItem is the out-of-band result of AddItem's pre-write phase — URL exact-match dedup plus embedding generation + cosine-similarity dedup — computed by Prepare strictly before any transaction opens (ADR 0003's G1 "先算後寫": docs/adr/0003-dual-backend-orchestration-seam-principle.md — the embedding call is external network I/O and must never run inside an open tx). WriteItemTx consumes a PreparedItem unchanged inside the tx that proposal.AcceptOrchestration's adapter opens via its BeginTx step (internal/proposal/accept_pg.go's pgAcceptAdapter).

type Section

type Section struct {
	// Level is 1–6 (H1–H6).
	Level int
	// Title is the plain-text heading text.
	Title string
	// HeadingPath is the full ancestor chain, e.g. "H1 Title › H2 Title".
	// For a top-level H1 section it equals Title.
	HeadingPath string
	// Content is the section body text (text under the heading, not including
	// the heading itself or any sub-headings and their content).
	Content string
}

Section is one heading-delimited chunk of a Markdown document.

func ParseMarkdownSections

func ParseMarkdownSections(src string) []Section

ParseMarkdownSections returns the list of sections found in src. Returns nil (not an error) when src contains no ATX headings — the caller should fall back to single-row insertion.

type Store

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

Store handles all database operations for the Knowledge bounded context.

func NewStore

func NewStore(pool *pgxpool.Pool, embed ai.ContextEmbeddingProvider, workspaceID *uuid.UUID) *Store

NewStore returns a Store backed by the given connection pool, scoped to the optional workspace. nil workspaceID = legacy unscoped mode. embed may be nil — embedding operations are skipped gracefully when absent.

func (*Store) AddItem

func (s *Store) AddItem(ctx context.Context, p AddItemParams) (*db.KnowledgeItem, error)

AddItem creates the knowledge item and synchronously generates and stores its embedding. If an embedding client is available:

  1. URL exact-match check (fast, no Gemini call needed).
  2. Vector cosine similarity check at the same heading_level (similarity >= 0.88 → ErrDuplicate).
  3. INSERT, then immediately store the embedding.

When p.Content contains ATX Markdown headings and p.ParentID is nil, a root row is inserted first, then each heading section is inserted as a child row (fan-out). Any Gemini error or nil vector (no API key) → dedup skipped, item inserted normally.

Delegates steps 1-2 to Prepare and step 3 to writeItemRow (both extracted so proposal.AcceptOrchestration can reuse them split across its PrepareOutOfBand/tx-scoped Materialize steps — see Prepare's doc comment).

func (*Store) GetByID

func (s *Store) GetByID(ctx context.Context, id uuid.UUID) (*db.KnowledgeItem, error)

GetByID returns a single knowledge item by ID within the current workspace scope. Returns ErrNotFound when the item does not exist or belongs to a different workspace.

func (*Store) List

func (s *Store) List(ctx context.Context, limit, offset int) ([]db.KnowledgeItem, error)

List returns knowledge items ordered by creation date.

func (*Store) ListByProjectID

func (s *Store) ListByProjectID(ctx context.Context, projectID uuid.UUID, limit int) ([]db.KnowledgeItem, error)

ListByProjectID returns knowledge items associated with a given project ID. Results are ordered by created_at DESC and capped at limit rows. SECURITY: scoped to workspace_id.

func (*Store) ListByTaskID

func (s *Store) ListByTaskID(ctx context.Context, taskID uuid.UUID, limit int) ([]db.KnowledgeItem, error)

ListByTaskID returns knowledge items associated with a given task ID. Results are ordered by created_at DESC and capped at limit rows. SECURITY: scoped to workspace_id.

func (*Store) ListChildren

func (s *Store) ListChildren(ctx context.Context, parentID uuid.UUID) ([]*db.KnowledgeItem, error)

ListChildren returns direct child rows of parentID, ordered by heading_level then created_at. Used by navigate_knowledge and outline_knowledge MCP tools.

func (*Store) ListRoots

func (s *Store) ListRoots(ctx context.Context) ([]*db.KnowledgeItem, error)

ListRoots returns top-level knowledge items (parent_id IS NULL) within the workspace scope, ordered by creation date descending. Used by navigate_knowledge when no parent_id is supplied.

func (*Store) Prepare

func (s *Store) Prepare(ctx context.Context, p AddItemParams) (PreparedItem, error)

Prepare runs AddItem's out-of-band pre-write phase without writing anything: URL exact-match dedup (top-level items only, mirroring AddItem's existing p.ParentID == nil guard) then embedding generation + cosine-similarity dedup at p.HeadingLevel. Returns ErrDuplicate when either check finds a match. Extracted from AddItem so proposal.AcceptOrchestration (the ADR 0003 dual-backend orchestration seam) can run it strictly before opening the transaction WriteItemTx writes inside.

func (*Store) Search

func (s *Store) Search(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)

Search performs full-text search. If an embedding client is available and the query has more than 3 words, it also performs vector similarity search and merges results using Reciprocal Rank Fusion. Results are ordered by strength × similarity DESC (Ebbinghaus decay weighting). On each hit, recall_count is incremented and last_recalled_at is set atomically.

func (*Store) SearchByCosine

func (s *Store) SearchByCosine(ctx context.Context, queryEmbedding []float32, limit int) ([]db.KnowledgeItem, error)

SearchByCosine returns the top-limit knowledge items whose embeddings are most similar to queryEmbedding, filtered by workspace_id. Delegates to the existing vectorSearch method.

SECURITY: filtered by workspace_id via vectorSearch → no cross-workspace data.

func (*Store) SearchCoarse

func (s *Store) SearchCoarse(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)

SearchCoarse searches only root rows (COALESCE(heading_level, 0) = 0 and parent_id IS NULL) for a coarse-grained overview search. Used by the search_knowledge MCP tool when mode="coarse".

func (*Store) SearchReadOnly

func (s *Store) SearchReadOnly(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)

SearchReadOnly performs the identical FTS+vector search as Search but never mutates knowledge_items — no recall_count/last_recalled_at bump. Used by contextpack.Assembler.retrieveKnowledge so assemble_context stays genuinely read-only (internal/discipline/discipline.go DeliberatelyExcludedTools).

func (*Store) SoftPruneDecayed

func (s *Store) SoftPruneDecayed(ctx context.Context, cutoff time.Time, strengthThreshold float64) (int64, error)

SoftPruneDecayed implements decay.PrunerStore. It sets archived_at=NOW() on knowledge_items that are:

  • not already archived (archived_at IS NULL)
  • older than cutoff (created_at < cutoff, i.e. age > 90 days)
  • Ebbinghaus strength < strengthThreshold

Decisions table is never touched by this method.

func (*Store) UpdateLearningValue

func (s *Store) UpdateLearningValue(ctx context.Context, id uuid.UUID, value int) error

UpdateLearningValue sets the star-rating (1–5) for the given knowledge item. Returns ErrNotFound when no row matches within the workspace scope.

func (*Store) WriteItemTx

func (s *Store) WriteItemTx(ctx context.Context, tx pgx.Tx, prep PreparedItem) (*db.KnowledgeItem, error)

WriteItemTx inserts the row described by prep.Params inside the given open tx, writing the embedding computed by Prepare (prep.Vec) in the same tx when present. Companion to Prepare — see PreparedItem's doc comment for the split and ADR 0003 for why the two are separate calls. Used by internal/proposal/accept_pg.go's pgAcceptAdapter.Materialize (wired by the A1-seam task, not this one).

type StoreIface

type StoreIface interface {
	AddItem(ctx context.Context, p AddItemParams) (*db.KnowledgeItem, error)
	Search(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)
	// SearchReadOnly is identical to Search (same FTS+vector ranking, same
	// workspace scope) but never bumps recall_count/last_recalled_at — no
	// Store write of any kind. Used by contextpack.Assembler.retrieveKnowledge
	// so assemble_context stays genuinely read-only (see
	// internal/discipline/discipline.go DeliberatelyExcludedTools).
	SearchReadOnly(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)
	// SearchCoarse searches only root rows (heading_level=0 or parent_id IS NULL).
	// Used by search_knowledge mode="coarse".
	SearchCoarse(ctx context.Context, query string, limit int) ([]db.KnowledgeItem, error)
	List(ctx context.Context, limit, offset int) ([]db.KnowledgeItem, error)
	GetByID(ctx context.Context, id uuid.UUID) (*db.KnowledgeItem, error)
	// UpdateLearningValue sets the star-rating (1–5) for the given knowledge
	// item. Returns ErrNotFound when the item does not exist in the workspace.
	// SECURITY: value is validated 1–5 exhaustively by the handler layer.
	UpdateLearningValue(ctx context.Context, id uuid.UUID, value int) error
	// SearchByCosine returns the top-limit knowledge items most similar to queryEmbedding.
	// SECURITY: scoped to workspace_id.
	SearchByCosine(ctx context.Context, queryEmbedding []float32, limit int) ([]db.KnowledgeItem, error)
	// ListChildren returns direct children of parentID ordered by heading_level, created_at.
	// Used by navigate_knowledge and outline_knowledge tools.
	ListChildren(ctx context.Context, parentID uuid.UUID) ([]*db.KnowledgeItem, error)
	// ListRoots returns top-level items (parent_id IS NULL) scoped to the workspace.
	// Used by navigate_knowledge when no parent_id argument is supplied.
	ListRoots(ctx context.Context) ([]*db.KnowledgeItem, error)
	// ListByProjectID returns knowledge items associated with a project UUID.
	// Results are ordered by created_at DESC and capped at limit rows.
	// SECURITY: scoped to workspace_id.
	ListByProjectID(ctx context.Context, projectID uuid.UUID, limit int) ([]db.KnowledgeItem, error)
	// ListByTaskID returns knowledge items associated with a task UUID.
	// Results are ordered by created_at DESC and capped at limit rows.
	// SECURITY: scoped to workspace_id.
	ListByTaskID(ctx context.Context, taskID uuid.UUID, limit int) ([]db.KnowledgeItem, error)
}

StoreIface is the backend-agnostic contract for the Knowledge bounded context. Search semantics differ between backends (Postgres FTS + pgvector vs SQLite FTS5 + sqlite-vec); the interface itself stays minimal.

Jump to

Keyboard shortcuts

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