Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

460 lines
18 KiB
Go

package main
import (
"encoding/json"
"github.com/spf13/cobra"
)
// memoryDaemonTool is the daemon-tool relay seam. It is indirected through a
// package var so tests can stub the daemon call (asserting the lowered
// tool + args) without a running daemon.
var memoryDaemonTool = requireDaemonTool
// Shared persistent flags for the memory group.
var (
memoryIndex string
memoryFormat string
)
var memoryCmd = &cobra.Command{
Use: "memory",
Short: "Session & durable memory — the Layer-2 note / memory verbs over the daemon",
Long: `The memory verb group exposes the daemon's session-memory and
cross-session development-memory surface as CLI verbs:
note — save a per-session note (save_note)
notes — query session notes (query_notes)
distill — fold a session's notes into a digest (distill_session)
store — store a durable cross-session memory (store_memory)
recall — query the durable memory store (query_memories)
surface — proactively surface memories for a task / working set (surface_memories)
Session notes are scoped to a session and survive context compactions; durable
memories are workspace-wide and survive daemon restarts and team rotation.
Every subcommand is a thin shell over one MCP tool on the daemon that owns the
repo. Requires a running daemon that tracks the repo.`,
// A daemon-required / flag-validation error from a subcommand is
// self-explanatory; don't bury it under the full usage dump.
SilenceUsage: true,
}
func init() {
memoryCmd.PersistentFlags().StringVar(&memoryIndex, "index", ".", "repository path the daemon must track")
memoryCmd.PersistentFlags().StringVar(&memoryIndex, "repo", ".", "alias for --index")
memoryCmd.PersistentFlags().StringVar(&memoryFormat, "format", "text", "output format: text|json")
memoryCmd.AddCommand(memoryNoteCmd)
memoryCmd.AddCommand(memoryNotesCmd)
memoryCmd.AddCommand(memoryDistillCmd)
memoryCmd.AddCommand(memoryStoreCmd)
memoryCmd.AddCommand(memoryRecallCmd)
memoryCmd.AddCommand(memorySurfaceCmd)
rootCmd.AddCommand(memoryCmd)
}
// runMemoryTool calls the daemon tool and renders the result. With --format
// json (or as the fallback for any shape) it pretty-prints the JSON; otherwise
// it runs the supplied text renderer. A nil renderer falls through to JSON.
func runMemoryTool(cmd *cobra.Command, tool string, args map[string]any, text func(*cobra.Command, json.RawMessage) error) error {
raw, err := memoryDaemonTool(memoryIndex, tool, args)
if err != nil {
return err
}
if memoryFormat == "json" || text == nil {
return emitDaemonJSON(cmd, raw)
}
return text(cmd, raw)
}
// --- memory note (save_note) -----------------------------------------------
var (
memoryNoteBody string
memoryNoteSymbol string
memoryNoteFile string
memoryNoteTags string
memoryNoteLinks string
memoryNotePin bool
memoryNoteID string
memoryNoteNoAutolink bool
)
var memoryNoteCmd = &cobra.Command{
Use: "note",
Short: "Save a per-session note (save_note)",
Long: `Persists an agent-authored note for the current session, auto-linked to
symbols mentioned in --body. Pass --id to update an existing note.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("body") {
toolArgs["body"] = memoryNoteBody
}
if cmd.Flags().Changed("symbol") {
toolArgs["symbol_id"] = memoryNoteSymbol
}
if cmd.Flags().Changed("file") {
toolArgs["file_path"] = memoryNoteFile
}
if cmd.Flags().Changed("tags") {
toolArgs["tags"] = memoryNoteTags
}
if cmd.Flags().Changed("links") {
toolArgs["links"] = memoryNoteLinks
}
if cmd.Flags().Changed("pin") {
toolArgs["pinned"] = memoryNotePin
}
if cmd.Flags().Changed("id") {
toolArgs["id"] = memoryNoteID
}
if cmd.Flags().Changed("no-autolink") {
toolArgs["no_autolink"] = memoryNoteNoAutolink
}
return runMemoryTool(cmd, "save_note", toolArgs, nil)
},
}
// --- memory notes (query_notes) --------------------------------------------
var (
memoryNotesSymbol string
memoryNotesFile string
memoryNotesTag string
memoryNotesText string
memoryNotesSession string
memoryNotesSince string
memoryNotesLimit int
memoryNotesPinned bool
)
var memoryNotesCmd = &cobra.Command{
Use: "notes",
Short: "Query session notes (query_notes)",
Long: `Search saved session notes by symbol, file, tag, free-text, session, or
recency. Pass --session all to query every session for the workspace.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("symbol") {
toolArgs["symbol_id"] = memoryNotesSymbol
}
if cmd.Flags().Changed("file") {
toolArgs["file_path"] = memoryNotesFile
}
if cmd.Flags().Changed("tag") {
toolArgs["tag"] = memoryNotesTag
}
if cmd.Flags().Changed("text") {
toolArgs["text"] = memoryNotesText
}
if cmd.Flags().Changed("session") {
toolArgs["session_id"] = memoryNotesSession
}
if cmd.Flags().Changed("since") {
toolArgs["since"] = memoryNotesSince
}
if cmd.Flags().Changed("limit") {
toolArgs["limit"] = memoryNotesLimit
}
if cmd.Flags().Changed("pinned") {
toolArgs["pinned_only"] = memoryNotesPinned
}
return runMemoryTool(cmd, "query_notes", toolArgs, nil)
},
}
// --- memory distill (distill_session) --------------------------------------
var (
memoryDistillSession string
memoryDistillMaxSymbols int
memoryDistillMaxFiles int
memoryDistillMaxTags int
memoryDistillMaxRecent int
)
var memoryDistillCmd = &cobra.Command{
Use: "distill",
Short: "Fold a session's notes into a digest (distill_session)",
Long: `Aggregates a session's saved notes into a digest: top symbols / files /
tags, pinned notes, recent excerpts, and a short summary. Pass --session all to
distill every session for the workspace.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("session") {
toolArgs["session_id"] = memoryDistillSession
}
if cmd.Flags().Changed("max-symbols") {
toolArgs["max_symbols"] = memoryDistillMaxSymbols
}
if cmd.Flags().Changed("max-files") {
toolArgs["max_files"] = memoryDistillMaxFiles
}
if cmd.Flags().Changed("max-tags") {
toolArgs["max_tags"] = memoryDistillMaxTags
}
if cmd.Flags().Changed("max-recent") {
toolArgs["max_recent"] = memoryDistillMaxRecent
}
return runMemoryTool(cmd, "distill_session", toolArgs, nil)
},
}
// --- memory store (store_memory) -------------------------------------------
var (
memoryStoreBody string
memoryStoreTitle string
memoryStoreSymbols string
memoryStoreFiles string
memoryStoreTags string
memoryStoreKind string
memoryStoreSource string
memoryStoreImportance int
memoryStoreConfidence float64
memoryStorePin bool
memoryStoreSupersedes string
memoryStoreScope string
memoryStoreID string
memoryStoreNoAutolink bool
)
var memoryStoreCmd = &cobra.Command{
Use: "store",
Short: "Store a durable cross-session memory (store_memory)",
Long: `Persists a cross-session development memory: an invariant, gotcha,
decision, convention, constraint, incident, or reference fact future agents in
this workspace should know. Anchor it with --symbols / --files for high-quality
surfacing. Pass --id to update; --supersedes to replace older memories.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("body") {
toolArgs["body"] = memoryStoreBody
}
if cmd.Flags().Changed("title") {
toolArgs["title"] = memoryStoreTitle
}
if cmd.Flags().Changed("symbols") {
toolArgs["symbol_ids"] = memoryStoreSymbols
}
if cmd.Flags().Changed("files") {
toolArgs["file_paths"] = memoryStoreFiles
}
if cmd.Flags().Changed("tags") {
toolArgs["tags"] = memoryStoreTags
}
if cmd.Flags().Changed("kind") {
toolArgs["kind"] = memoryStoreKind
}
if cmd.Flags().Changed("source") {
toolArgs["source"] = memoryStoreSource
}
if cmd.Flags().Changed("importance") {
toolArgs["importance"] = memoryStoreImportance
}
if cmd.Flags().Changed("confidence") {
toolArgs["confidence"] = memoryStoreConfidence
}
if cmd.Flags().Changed("pin") {
toolArgs["pinned"] = memoryStorePin
}
if cmd.Flags().Changed("supersedes") {
toolArgs["supersedes"] = memoryStoreSupersedes
}
if cmd.Flags().Changed("scope") {
toolArgs["scope"] = memoryStoreScope
}
if cmd.Flags().Changed("id") {
toolArgs["id"] = memoryStoreID
}
if cmd.Flags().Changed("no-autolink") {
toolArgs["no_autolink"] = memoryStoreNoAutolink
}
return runMemoryTool(cmd, "store_memory", toolArgs, nil)
},
}
// --- memory recall (query_memories) ----------------------------------------
var (
memoryRecallSymbol string
memoryRecallFile string
memoryRecallTag string
memoryRecallKind string
memoryRecallSource string
memoryRecallAuthor string
memoryRecallText string
memoryRecallSince string
memoryRecallMinImportance int
memoryRecallPinned bool
memoryRecallSuperseded bool
memoryRecallLimit int
memoryRecallScope string
)
var memoryRecallCmd = &cobra.Command{
Use: "recall",
Short: "Query the durable memory store (query_memories)",
Long: `Search the cross-session memory store by symbol, file, tag, kind, source,
author, free-text, recency, or importance. Workspace-wide; no session boundary.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("symbol") {
toolArgs["symbol_id"] = memoryRecallSymbol
}
if cmd.Flags().Changed("file") {
toolArgs["file_path"] = memoryRecallFile
}
if cmd.Flags().Changed("tag") {
toolArgs["tag"] = memoryRecallTag
}
if cmd.Flags().Changed("kind") {
toolArgs["kind"] = memoryRecallKind
}
if cmd.Flags().Changed("source") {
toolArgs["source"] = memoryRecallSource
}
if cmd.Flags().Changed("author") {
toolArgs["author"] = memoryRecallAuthor
}
if cmd.Flags().Changed("text") {
toolArgs["text"] = memoryRecallText
}
if cmd.Flags().Changed("since") {
toolArgs["since"] = memoryRecallSince
}
if cmd.Flags().Changed("min-importance") {
toolArgs["min_importance"] = memoryRecallMinImportance
}
if cmd.Flags().Changed("pinned") {
toolArgs["pinned_only"] = memoryRecallPinned
}
if cmd.Flags().Changed("include-superseded") {
toolArgs["include_superseded"] = memoryRecallSuperseded
}
if cmd.Flags().Changed("limit") {
toolArgs["limit"] = memoryRecallLimit
}
if cmd.Flags().Changed("scope") {
toolArgs["scope"] = memoryRecallScope
}
return runMemoryTool(cmd, "query_memories", toolArgs, nil)
},
}
// --- memory surface (surface_memories) -------------------------------------
var (
memorySurfaceTask string
memorySurfaceSymbols string
memorySurfaceFiles string
memorySurfaceLimit int
memorySurfaceMinScore float64
memorySurfaceSuperseded bool
memorySurfaceScope string
)
var memorySurfaceCmd = &cobra.Command{
Use: "surface",
Short: "Proactively surface memories for a task / working set (surface_memories)",
Long: `Given a task description and/or anchor symbols / files, returns memories
ranked by symbol overlap, file overlap, keyword hits, importance, pinning,
recency, and confidence. The memory analogue of smart_context.`,
RunE: func(cmd *cobra.Command, _ []string) error {
toolArgs := map[string]any{}
if cmd.Flags().Changed("task") {
toolArgs["task"] = memorySurfaceTask
}
if cmd.Flags().Changed("symbols") {
toolArgs["symbol_ids"] = memorySurfaceSymbols
}
if cmd.Flags().Changed("files") {
toolArgs["file_paths"] = memorySurfaceFiles
}
if cmd.Flags().Changed("limit") {
toolArgs["limit"] = memorySurfaceLimit
}
if cmd.Flags().Changed("min-score") {
toolArgs["min_score"] = memorySurfaceMinScore
}
if cmd.Flags().Changed("include-superseded") {
toolArgs["include_superseded"] = memorySurfaceSuperseded
}
if cmd.Flags().Changed("scope") {
toolArgs["scope"] = memorySurfaceScope
}
return runMemoryTool(cmd, "surface_memories", toolArgs, nil)
},
}
func init() {
// note (save_note)
memoryNoteCmd.Flags().StringVar(&memoryNoteBody, "body", "", "note text (auto-linked to mentioned symbols)")
memoryNoteCmd.Flags().StringVar(&memoryNoteSymbol, "symbol", "", "primary symbol the note attaches to (symbol_id)")
memoryNoteCmd.Flags().StringVar(&memoryNoteFile, "file", "", "primary file the note attaches to (file_path)")
memoryNoteCmd.Flags().StringVar(&memoryNoteTags, "tags", "", "comma-separated labels (e.g. decision,bug,follow-up)")
memoryNoteCmd.Flags().StringVar(&memoryNoteLinks, "links", "", "comma-separated symbol IDs to attach explicitly")
memoryNoteCmd.Flags().BoolVar(&memoryNotePin, "pin", false, "pin the note (pinned)")
memoryNoteCmd.Flags().StringVar(&memoryNoteID, "id", "", "existing note ID to update")
memoryNoteCmd.Flags().BoolVar(&memoryNoteNoAutolink, "no-autolink", false, "skip the body→symbol auto-linker (no_autolink)")
// notes (query_notes)
memoryNotesCmd.Flags().StringVar(&memoryNotesSymbol, "symbol", "", "return notes attached/auto-linked to this symbol (symbol_id)")
memoryNotesCmd.Flags().StringVar(&memoryNotesFile, "file", "", "return notes attached to this file path (file_path)")
memoryNotesCmd.Flags().StringVar(&memoryNotesTag, "tag", "", "return notes carrying this tag")
memoryNotesCmd.Flags().StringVar(&memoryNotesText, "text", "", "case-insensitive substring filter on the body")
memoryNotesCmd.Flags().StringVar(&memoryNotesSession, "session", "", "limit to a session (e.g. 'all' for every session) (session_id)")
memoryNotesCmd.Flags().StringVar(&memoryNotesSince, "since", "", "only notes updated at/after this RFC-3339 timestamp")
memoryNotesCmd.Flags().IntVar(&memoryNotesLimit, "limit", 50, "cap the result set")
memoryNotesCmd.Flags().BoolVar(&memoryNotesPinned, "pinned", false, "return only pinned notes (pinned_only)")
// distill (distill_session)
memoryDistillCmd.Flags().StringVar(&memoryDistillSession, "session", "", "session to distill (e.g. 'all') (session_id)")
memoryDistillCmd.Flags().IntVar(&memoryDistillMaxSymbols, "max-symbols", 10, "cap on the top-symbols list (max_symbols)")
memoryDistillCmd.Flags().IntVar(&memoryDistillMaxFiles, "max-files", 10, "cap on the top-files list (max_files)")
memoryDistillCmd.Flags().IntVar(&memoryDistillMaxTags, "max-tags", 10, "cap on the top-tags list (max_tags)")
memoryDistillCmd.Flags().IntVar(&memoryDistillMaxRecent, "max-recent", 8, "number of recent note excerpts (max_recent)")
// store (store_memory)
memoryStoreCmd.Flags().StringVar(&memoryStoreBody, "body", "", "memory text (auto-linked to mentioned symbols)")
memoryStoreCmd.Flags().StringVar(&memoryStoreTitle, "title", "", "short caption used as the surfacing headline")
memoryStoreCmd.Flags().StringVar(&memoryStoreSymbols, "symbols", "", "comma-separated primary symbol anchors (symbol_ids)")
memoryStoreCmd.Flags().StringVar(&memoryStoreFiles, "files", "", "comma-separated primary file anchors (file_paths)")
memoryStoreCmd.Flags().StringVar(&memoryStoreTags, "tags", "", "comma-separated labels (e.g. invariant,gotcha)")
memoryStoreCmd.Flags().StringVar(&memoryStoreKind, "kind", "", "kind: invariant|constraint|convention|gotcha|decision|incident|reference")
memoryStoreCmd.Flags().StringVar(&memoryStoreSource, "source", "", "source: manual|distilled|incident|review")
memoryStoreCmd.Flags().IntVar(&memoryStoreImportance, "importance", 3, "1..5 operator-assigned weight")
memoryStoreCmd.Flags().Float64Var(&memoryStoreConfidence, "confidence", 1.0, "0.0..1.0 — how sure this still holds")
memoryStoreCmd.Flags().BoolVar(&memoryStorePin, "pin", false, "pin the memory (pinned)")
memoryStoreCmd.Flags().StringVar(&memoryStoreSupersedes, "supersedes", "", "comma-separated memory IDs this replaces")
memoryStoreCmd.Flags().StringVar(&memoryStoreScope, "scope", "", "scope: workspace (default) or global")
memoryStoreCmd.Flags().StringVar(&memoryStoreID, "id", "", "existing memory ID to update")
memoryStoreCmd.Flags().BoolVar(&memoryStoreNoAutolink, "no-autolink", false, "skip the body→symbol auto-linker (no_autolink)")
// recall (query_memories)
memoryRecallCmd.Flags().StringVar(&memoryRecallSymbol, "symbol", "", "return memories anchored/auto-linked to this symbol (symbol_id)")
memoryRecallCmd.Flags().StringVar(&memoryRecallFile, "file", "", "return memories anchored to this file path (file_path)")
memoryRecallCmd.Flags().StringVar(&memoryRecallTag, "tag", "", "return memories carrying this tag")
memoryRecallCmd.Flags().StringVar(&memoryRecallKind, "kind", "", "filter by kind: invariant|constraint|convention|gotcha|decision|incident|reference")
memoryRecallCmd.Flags().StringVar(&memoryRecallSource, "source", "", "filter by source: manual|distilled|incident|review")
memoryRecallCmd.Flags().StringVar(&memoryRecallAuthor, "author", "", "filter by author agent")
memoryRecallCmd.Flags().StringVar(&memoryRecallText, "text", "", "case-insensitive substring filter on body or title")
memoryRecallCmd.Flags().StringVar(&memoryRecallSince, "since", "", "only memories updated at/after this RFC-3339 timestamp")
memoryRecallCmd.Flags().IntVar(&memoryRecallMinImportance, "min-importance", 0, "only memories with importance >= this (1..5) (min_importance)")
memoryRecallCmd.Flags().BoolVar(&memoryRecallPinned, "pinned", false, "return only pinned memories (pinned_only)")
memoryRecallCmd.Flags().BoolVar(&memoryRecallSuperseded, "include-superseded", false, "include superseded memories (include_superseded)")
memoryRecallCmd.Flags().IntVar(&memoryRecallLimit, "limit", 50, "cap the result set")
memoryRecallCmd.Flags().StringVar(&memoryRecallScope, "scope", "", "scope: workspace (default), global, or both")
// surface (surface_memories)
memorySurfaceCmd.Flags().StringVar(&memorySurfaceTask, "task", "", "natural-language task description")
memorySurfaceCmd.Flags().StringVar(&memorySurfaceSymbols, "symbols", "", "comma-separated anchor symbols (symbol_ids)")
memorySurfaceCmd.Flags().StringVar(&memorySurfaceFiles, "files", "", "comma-separated anchor files (file_paths)")
memorySurfaceCmd.Flags().IntVar(&memorySurfaceLimit, "limit", 10, "cap the surfaced set")
memorySurfaceCmd.Flags().Float64Var(&memorySurfaceMinScore, "min-score", 0, "drop hits below this score (min_score)")
memorySurfaceCmd.Flags().BoolVar(&memorySurfaceSuperseded, "include-superseded", false, "include superseded memories (include_superseded)")
memorySurfaceCmd.Flags().StringVar(&memorySurfaceScope, "scope", "", "scope: workspace (default), global, or both")
}