@basicmemory/openclaw-basic-memory 0.2.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +49 -19
  2. package/bm-client.ts +55 -21
  3. package/commands/cli.ts +1 -2
  4. package/commands/skills.ts +71 -10
  5. package/commands/slash.ts +4 -2
  6. package/config.ts +0 -16
  7. package/context-engine/basic-memory-context-engine.ts +22 -90
  8. package/dist/bm-client.d.ts +179 -0
  9. package/dist/bm-client.js +582 -0
  10. package/dist/commands/cli.d.ts +4 -0
  11. package/dist/commands/cli.js +116 -0
  12. package/dist/commands/skills.d.ts +2 -0
  13. package/dist/commands/skills.js +86 -0
  14. package/dist/commands/slash.d.ts +3 -0
  15. package/dist/commands/slash.js +92 -0
  16. package/dist/config.d.ts +17 -0
  17. package/dist/config.js +99 -0
  18. package/dist/context-engine/basic-memory-context-engine.d.ts +34 -0
  19. package/dist/context-engine/basic-memory-context-engine.js +176 -0
  20. package/dist/hooks/capture.d.ts +24 -0
  21. package/dist/hooks/capture.js +88 -0
  22. package/dist/hooks/recall.d.ts +24 -0
  23. package/dist/hooks/recall.js +56 -0
  24. package/dist/index.d.ts +9 -0
  25. package/dist/index.js +135 -0
  26. package/dist/logger.d.ts +13 -0
  27. package/dist/logger.js +34 -0
  28. package/dist/schema/conversation-schema.d.ts +5 -0
  29. package/dist/schema/conversation-schema.js +33 -0
  30. package/dist/schema/task-schema.d.ts +5 -0
  31. package/dist/schema/task-schema.js +34 -0
  32. package/dist/tools/build-context.d.ts +3 -0
  33. package/dist/tools/build-context.js +100 -0
  34. package/dist/tools/delete-note.d.ts +3 -0
  35. package/dist/tools/delete-note.js +52 -0
  36. package/dist/tools/edit-note.d.ts +3 -0
  37. package/dist/tools/edit-note.js +81 -0
  38. package/dist/tools/list-memory-projects.d.ts +3 -0
  39. package/dist/tools/list-memory-projects.js +86 -0
  40. package/dist/tools/list-workspaces.d.ts +3 -0
  41. package/dist/tools/list-workspaces.js +64 -0
  42. package/dist/tools/memory-provider.d.ts +15 -0
  43. package/dist/tools/memory-provider.js +274 -0
  44. package/dist/tools/move-note.d.ts +3 -0
  45. package/dist/tools/move-note.js +56 -0
  46. package/dist/tools/read-note.d.ts +3 -0
  47. package/dist/tools/read-note.js +57 -0
  48. package/dist/tools/schema-diff.d.ts +3 -0
  49. package/dist/tools/schema-diff.js +81 -0
  50. package/dist/tools/schema-infer.d.ts +3 -0
  51. package/dist/tools/schema-infer.js +66 -0
  52. package/dist/tools/schema-validate.d.ts +3 -0
  53. package/dist/tools/schema-validate.js +73 -0
  54. package/dist/tools/search-notes.d.ts +3 -0
  55. package/dist/tools/search-notes.js +97 -0
  56. package/dist/tools/write-note.d.ts +3 -0
  57. package/dist/tools/write-note.js +80 -0
  58. package/index.ts +27 -17
  59. package/openclaw.plugin.json +27 -20
  60. package/package.json +27 -8
  61. package/scripts/fetch-skills.ts +62 -67
  62. package/scripts/setup-bm.sh +27 -10
  63. package/skills/manifest.json +25 -0
  64. package/skills/memory-capture/SKILL.md +275 -0
  65. package/skills/memory-ci-capture/SKILL.md +56 -0
  66. package/skills/memory-continue/SKILL.md +148 -0
  67. package/skills/memory-curate/SKILL.md +243 -0
  68. package/skills/memory-literary-analysis/SKILL.md +15 -12
  69. package/skills/memory-notes/SKILL.md +22 -6
  70. package/skills/memory-onboarding/SKILL.md +142 -0
  71. package/skills/memory-onboarding/evals/evals.json +29 -0
  72. package/skills/memory-onboarding/references/assistant-setup.md +64 -0
  73. package/skills/memory-onboarding/references/conventions.md +118 -0
  74. package/skills/memory-onboarding/references/domain-playbooks.md +159 -0
  75. package/skills/memory-onboarding/references/schema-guide.md +136 -0
  76. package/skills/memory-schema/SKILL.md +7 -5
  77. package/skills/memory-tasks/SKILL.md +1 -1
  78. package/tools/build-context.ts +1 -1
  79. package/tools/delete-note.ts +1 -1
  80. package/tools/edit-note.ts +1 -1
  81. package/tools/list-memory-projects.ts +3 -2
  82. package/tools/list-workspaces.ts +1 -1
  83. package/tools/memory-provider.ts +1 -1
  84. package/tools/move-note.ts +1 -1
  85. package/tools/read-note.ts +1 -1
  86. package/tools/schema-diff.ts +1 -1
  87. package/tools/schema-infer.ts +1 -1
  88. package/tools/schema-validate.ts +1 -1
  89. package/tools/search-notes.ts +1 -1
  90. package/tools/write-note.ts +1 -1
  91. package/context-engine/basic-memory-context-engine.test.ts +0 -398
@@ -1,29 +1,26 @@
1
1
  /**
2
- * Fetch all memory-* skills from basicmachines-co/basic-memory-skills.
2
+ * Copy all memory-* skills from the top-level basic-memory skills source.
3
3
  *
4
- * Auto-discovers skill directories via GitHub Contents API, downloads each
5
- * SKILL.md, writes to skills/<dir>/SKILL.md, and generates skills/manifest.json.
6
- *
7
- * Env vars:
8
- * GITHUB_TOKEN — optional, avoids 60 req/hr unauthenticated rate limit
9
- * SKILLS_BRANCH — branch to fetch from (default: "main")
4
+ * Auto-discovers skill directories in ../../../skills, copies each skill's
5
+ * SKILL.md plus any bundled resources (references/, evals/, assets/, ...)
6
+ * into this package's skills/<dir>/, and generates skills/manifest.json.
10
7
  */
11
8
 
12
- import { mkdirSync, writeFileSync } from "node:fs"
9
+ import {
10
+ cpSync,
11
+ existsSync,
12
+ mkdirSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ rmSync,
16
+ writeFileSync,
17
+ } from "node:fs"
13
18
  import { dirname, resolve } from "node:path"
14
19
  import { fileURLToPath } from "node:url"
15
20
 
16
21
  const __dirname = dirname(fileURLToPath(import.meta.url))
17
22
  const SKILLS_DIR = resolve(__dirname, "..", "skills")
18
-
19
- const REPO = "basicmachines-co/basic-memory-skills"
20
- const BRANCH = process.env.SKILLS_BRANCH ?? "main"
21
- const TOKEN = process.env.GITHUB_TOKEN
22
-
23
- interface GitHubEntry {
24
- name: string
25
- type: "file" | "dir"
26
- }
23
+ const SOURCE_SKILLS_DIR = resolve(__dirname, "..", "..", "..", "skills")
27
24
 
28
25
  interface SkillManifestEntry {
29
26
  dir: string
@@ -31,31 +28,6 @@ interface SkillManifestEntry {
31
28
  description: string
32
29
  }
33
30
 
34
- function headers(): Record<string, string> {
35
- const h: Record<string, string> = {
36
- Accept: "application/vnd.github.v3+json",
37
- "User-Agent": "openclaw-basic-memory/fetch-skills",
38
- }
39
- if (TOKEN) h.Authorization = `Bearer ${TOKEN}`
40
- return h
41
- }
42
-
43
- async function fetchJSON<T>(url: string): Promise<T> {
44
- const res = await fetch(url, { headers: headers() })
45
- if (!res.ok) {
46
- throw new Error(`GET ${url} → ${res.status} ${res.statusText}`)
47
- }
48
- return res.json() as Promise<T>
49
- }
50
-
51
- async function fetchText(url: string): Promise<string> {
52
- const res = await fetch(url, { headers: headers() })
53
- if (!res.ok) {
54
- throw new Error(`GET ${url} → ${res.status} ${res.statusText}`)
55
- }
56
- return res.text()
57
- }
58
-
59
31
  function parseFrontmatter(md: string): { name: string; description: string } {
60
32
  const match = md.match(/^---\r?\n([\s\S]*?)\r?\n---/)
61
33
  if (!match) throw new Error("SKILL.md missing YAML frontmatter")
@@ -76,58 +48,81 @@ function parseFrontmatter(md: string): { name: string; description: string } {
76
48
  return { name, description }
77
49
  }
78
50
 
79
- async function main() {
80
- console.log(`Fetching skills from ${REPO}@${BRANCH}`)
51
+ function discoverSkillDirs(): string[] {
52
+ if (!existsSync(SOURCE_SKILLS_DIR)) {
53
+ throw new Error(`Missing source skills directory: ${SOURCE_SKILLS_DIR}`)
54
+ }
81
55
 
82
- // 1. Discover all memory-* directories
83
- const contentsUrl = `https://api.github.com/repos/${REPO}/contents?ref=${BRANCH}`
84
- const entries = await fetchJSON<GitHubEntry[]>(contentsUrl)
85
- const skillDirs = entries
86
- .filter((e) => e.type === "dir" && e.name.startsWith("memory-"))
56
+ const skillDirs = readdirSync(SOURCE_SKILLS_DIR, { withFileTypes: true })
57
+ .filter((e) => e.isDirectory() && e.name.startsWith("memory-"))
87
58
  .map((e) => e.name)
88
59
  .sort()
89
60
 
90
61
  if (skillDirs.length === 0) {
91
- throw new Error("No memory-* directories found in repo")
62
+ throw new Error(`No memory-* directories found in ${SOURCE_SKILLS_DIR}`)
92
63
  }
93
64
 
65
+ return skillDirs
66
+ }
67
+
68
+ function main() {
69
+ console.log(`Copying skills from ${SOURCE_SKILLS_DIR}`)
70
+
71
+ const skillDirs = discoverSkillDirs()
94
72
  console.log(`Found ${skillDirs.length} skills: ${skillDirs.join(", ")}`)
95
73
 
96
- // 2. Download each SKILL.md and parse frontmatter
97
74
  const manifest: SkillManifestEntry[] = []
98
75
 
99
- const results = await Promise.all(
100
- skillDirs.map(async (dir) => {
101
- const rawUrl = `https://raw.githubusercontent.com/${REPO}/${BRANCH}/${dir}/SKILL.md`
102
- const content = await fetchText(rawUrl)
103
- const meta = parseFrontmatter(content)
104
- return { dir, content, meta }
105
- }),
106
- )
76
+ for (const dir of skillDirs) {
77
+ const skillPath = resolve(SOURCE_SKILLS_DIR, dir, "SKILL.md")
78
+ if (!existsSync(skillPath)) {
79
+ throw new Error(`Missing SKILL.md for ${dir}: ${skillPath}`)
80
+ }
81
+
82
+ const content = readFileSync(skillPath, "utf8")
83
+ const meta = parseFrontmatter(content)
107
84
 
108
- // 3. Write files and build manifest
109
- for (const { dir, content, meta } of results) {
110
85
  const outDir = resolve(SKILLS_DIR, dir)
86
+ // Clear any previously generated copy so files deleted or renamed in
87
+ // the source skill don't survive as stale artifacts in the package.
88
+ rmSync(outDir, { recursive: true, force: true })
111
89
  mkdirSync(outDir, { recursive: true })
112
90
  writeFileSync(resolve(outDir, "SKILL.md"), content)
91
+
92
+ // Copy bundled resources (references/, evals/, assets/, ...) so
93
+ // multi-file skills stay self-contained in the generated package.
94
+ for (const entry of readdirSync(resolve(SOURCE_SKILLS_DIR, dir), {
95
+ withFileTypes: true,
96
+ })) {
97
+ if (entry.name === "SKILL.md") continue
98
+ cpSync(
99
+ resolve(SOURCE_SKILLS_DIR, dir, entry.name),
100
+ resolve(outDir, entry.name),
101
+ { recursive: true },
102
+ )
103
+ }
104
+
113
105
  manifest.push({ dir, name: meta.name, description: meta.description })
114
106
  console.log(` ✓ ${dir}`)
115
107
  }
116
108
 
117
- // Sort manifest by dir name for deterministic output
118
109
  manifest.sort((a, b) => a.dir.localeCompare(b.dir))
119
110
 
120
- // 4. Write manifest
121
111
  mkdirSync(SKILLS_DIR, { recursive: true })
122
112
  writeFileSync(
123
113
  resolve(SKILLS_DIR, "manifest.json"),
124
114
  `${JSON.stringify(manifest, null, 2)}\n`,
125
115
  )
126
116
 
127
- console.log(`\nWrote ${manifest.length} skills + manifest.json to skills/`)
117
+ console.log(
118
+ `\nWrote ${manifest.length} skills + manifest.json to ${SKILLS_DIR}`,
119
+ )
128
120
  }
129
121
 
130
- main().catch((err) => {
131
- console.error("Fatal:", err.message)
122
+ try {
123
+ main()
124
+ } catch (err) {
125
+ const message = err instanceof Error ? err.message : String(err)
126
+ console.error("Fatal:", message)
132
127
  process.exit(1)
133
- })
128
+ }
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env bash
2
- # Install basic-memory CLI via uv from a pinned release tag.
2
+ # Install the latest released basic-memory CLI via uv.
3
3
  # Idempotent — safe to re-run. Fails gracefully when uv is absent.
4
4
  set -euo pipefail
5
5
 
6
6
  BM_REPO="https://github.com/basicmachines-co/basic-memory.git"
7
- BM_REF="${BM_REF:-v0.20.2}"
7
+ BM_REF="${BM_REF:-}"
8
8
 
9
9
  # ── check for uv ──────────────────────────────────────────────────
10
10
  if ! command -v uv >/dev/null 2>&1; then
@@ -16,16 +16,33 @@ if ! command -v uv >/dev/null 2>&1; then
16
16
  fi
17
17
 
18
18
  # ── install basic-memory ────────────────────────────────
19
- echo "Installing basic-memory from ${BM_REPO}@${BM_REF} ..."
20
- uv tool install \
21
- "basic-memory @ git+${BM_REPO}@${BM_REF}" \
22
- --force
19
+ if [[ -n "${BM_REF}" ]]; then
20
+ echo "Installing basic-memory from ${BM_REPO}@${BM_REF} ..."
21
+ uv tool install \
22
+ "basic-memory @ git+${BM_REPO}@${BM_REF}" \
23
+ --force
24
+ else
25
+ echo "Installing latest basic-memory from PyPI ..."
26
+ uv tool install basic-memory --force
27
+ fi
23
28
 
24
29
  # ── verify ────────────────────────────────────────────────────────
25
- if ! command -v bm >/dev/null 2>&1; then
26
- echo "❌ bm binary not found on PATH after install."
27
- echo " You may need to add uv's bin directory to your PATH."
30
+ UV_BIN_DIR="$(uv tool dir --bin)"
31
+ BM_BIN="${UV_BIN_DIR}/bm"
32
+ if [[ ! -x "${BM_BIN}" ]]; then
33
+ BM_BIN="${UV_BIN_DIR}/basic-memory"
34
+ fi
35
+
36
+ if [[ ! -x "${BM_BIN}" ]]; then
37
+ echo "❌ basic-memory binary not found in uv's tool bin after install."
38
+ echo " You may need to add uv's bin directory to your PATH: ${UV_BIN_DIR}"
28
39
  exit 1
29
40
  fi
30
41
 
31
- echo "✅ $(bm --version)"
42
+ PATH_BM="$(command -v bm || true)"
43
+ if [[ -n "${PATH_BM}" && "${PATH_BM}" != "${UV_BIN_DIR}/bm" ]]; then
44
+ echo "⚠ bm on PATH resolves to ${PATH_BM}"
45
+ echo " uv installed basic-memory at ${BM_BIN}; move ${UV_BIN_DIR} earlier in PATH or set bmPath."
46
+ fi
47
+
48
+ echo "✅ $("${BM_BIN}" --version)"
@@ -1,4 +1,24 @@
1
1
  [
2
+ {
3
+ "dir": "memory-capture",
4
+ "name": "memory-capture",
5
+ "description": "Capture the current state of a working thread or conversation into a single coherent Basic Memory note — synthesize where it landed, don't append a log. On re-capture, rewrite the same note in place instead of duplicating. Use mid-thread or end-of-thread when decisions, insights, or context are worth preserving."
6
+ },
7
+ {
8
+ "dir": "memory-ci-capture",
9
+ "name": "memory-ci-capture",
10
+ "description": "Synthesize GitHub delivery context into a concise Basic Memory project update. Use in CI after `bm ci collect` prepares a ProjectUpdateContext; return only structured AgentSynthesis JSON for `bm ci publish`."
11
+ },
12
+ {
13
+ "dir": "memory-continue",
14
+ "name": "memory-continue",
15
+ "description": "Resume prior work by rebuilding context from the Basic Memory knowledge graph — pick up where you left off using memory:// URLs, recent activity, and search. Use when starting a session or when the user says 'continue with...', 'back to...', or 'where were we?'"
16
+ },
17
+ {
18
+ "dir": "memory-curate",
19
+ "name": "memory-curate",
20
+ "description": "Curate the Basic Memory knowledge graph: find orphan notes and suggest links, propose typed relations, merge duplicates, audit tags and folders, and build hub notes. Use to organize, connect, and improve a knowledge base as notes accumulate."
21
+ },
2
22
  {
3
23
  "dir": "memory-defrag",
4
24
  "name": "memory-defrag",
@@ -29,6 +49,11 @@
29
49
  "name": "memory-notes",
30
50
  "description": "How to write well-structured Basic Memory notes: frontmatter, observations with semantic categories, relations with wiki-links, and best practices for building a rich knowledge graph. Use when creating or improving notes."
31
51
  },
52
+ {
53
+ "dir": "memory-onboarding",
54
+ "name": "memory-onboarding",
55
+ "description": "Guide someone new to Basic Memory through designing and building a complete personal knowledge system — interview them about what they want to track, propose a structure, build it with schemas and instruction notes, teach them to use it, and set up their AI assistant to load it automatically. Use this skill whenever a user says they're new to Basic Memory, wants to 'get started', 'set up', or 'onboard' with Basic Memory, doesn't know what to use it for, asks how to organize their memory project or knowledge base, wants help designing folders/schemas/conventions, or asks how to make their assistant remember context between sessions. Also use it when a user has an empty or messy Basic Memory project and wants structure."
56
+ },
32
57
  {
33
58
  "dir": "memory-reflect",
34
59
  "name": "memory-reflect",
@@ -0,0 +1,275 @@
1
+ ---
2
+ name: memory-capture
3
+ description: "Capture the current state of a working thread or conversation into a single coherent Basic Memory note — synthesize where it landed, don't append a log. On re-capture, rewrite the same note in place instead of duplicating. Use mid-thread or end-of-thread when decisions, insights, or context are worth preserving."
4
+ ---
5
+
6
+ # Memory Capture
7
+
8
+ Capture the gist of a working thread — the decisions made, insights surfaced, and context built — into a single coherent Basic Memory note that reflects where the thread has landed.
9
+
10
+ ## Purpose
11
+
12
+ A thread has a beginning, middle, and end. Things change as the conversation progresses: an early decision gets revised, a problem looks different in light of new information, a trade-off is settled differently than it first seemed. When this skill is invoked, capture the **current state of understanding**, not the history of how it got there.
13
+
14
+ If the skill is invoked more than once in the same thread, the **same note is rewritten** so it stays coherent — not appended to. The result should read top-to-bottom as a single document about the thread's outcome, with brief prose where a meaningful change is worth acknowledging.
15
+
16
+ ## When to Use
17
+
18
+ Typical timing is **mid-thread or end-of-thread**, after enough has been settled to be worth preserving.
19
+
20
+ Use this skill when:
21
+ - Key decisions have been made and shouldn't evaporate when the thread closes
22
+ - A design, debugging, or planning discussion has produced something concrete
23
+ - The user explicitly asks to capture, save, or remember what's been discussed
24
+ - Toward the end of a session, to summarize the outcome
25
+
26
+ It is fine — and expected — to invoke this skill multiple times in the same thread as the conversation evolves.
27
+
28
+ ## Same-Thread Detection
29
+
30
+ To rewrite the same note on re-capture instead of duplicating, key the note to a stable `thread_id` in its frontmatter.
31
+
32
+ **If your agent exposes a stable session or thread id**, store it as `thread_id` so subsequent captures within the same thread find and rewrite the same note. Any value that stays constant for the duration of the thread works — a session UUID, a conversation id, a ticket number the work is scoped to.
33
+
34
+ > **Example (hosts with a JSONL transcript):** some agents write a per-session transcript whose filename is a stable session UUID. If yours does, you can derive the id from the most-recently-modified transcript file and use it as `thread_id`. This is optional — only do it if your host actually exposes such a transcript.
35
+
36
+ **If no stable id is available**, match the existing note by title/topic instead: search for a note covering the same thread (`search_notes(query="<topic>")`), and if you find the one this thread already produced, rewrite it. Omit `thread_id` and rely on a consistent title.
37
+
38
+ ## Decision Flow
39
+
40
+ 1. **Determine the thread key.** Use a stable session/thread id if your agent exposes one; otherwise plan to match by title/topic.
41
+ 2. **Search Basic Memory** for the existing thread note.
42
+ - With a thread id, use `metadata_filters` (not `query`) — full-text query doesn't reliably match YAML frontmatter custom fields:
43
+ ```python
44
+ search_notes(
45
+ metadata_filters={"thread_id": "<thread-id>"},
46
+ project="<project>"
47
+ )
48
+ ```
49
+ - Without one, search by topic and identify the note this thread already produced:
50
+ ```python
51
+ search_notes(query="<thread topic>", project="<project>")
52
+ ```
53
+ 3. **If a match is found:**
54
+ - Read the existing note (use the full permalink returned by search)
55
+ - Synthesize a new version that integrates the latest understanding from the conversation
56
+ - Overwrite via `write_note` with `overwrite=True` (same title, same `thread_id` if used, same directory)
57
+ 4. **If no match is found:**
58
+ - Synthesize the note from the conversation
59
+ - If you have a thread id, pass `metadata={"thread_id": "<thread-id>"}` to `write_note` (it surfaces as a custom frontmatter field)
60
+ - Save it
61
+
62
+ ## Synthesis Rules
63
+
64
+ When updating an existing thread note, **synthesize, don't append**:
65
+
66
+ - Decisions that are still current → keep, possibly refined
67
+ - Decisions that have been superseded → replaced inline (the new one goes where the old one was)
68
+ - Significant revisions that deserve explanation → a sentence woven into the relevant section, *not* an appended changelog
69
+ - Outdated context → removed
70
+
71
+ Goal: the note reads top-to-bottom as a single coherent document. A reader who never saw the conversation should still understand the outcome from the note alone. There is no `## Changes` section at the bottom; revisions live in the prose where they're relevant.
72
+
73
+ ## Escape Hatch
74
+
75
+ If the user explicitly asks for a separate note (e.g., "capture this as a new note, don't merge with the existing thread note"), skip the same-thread lookup and create a fresh note without setting `thread_id`. This is rare; the default is to update.
76
+
77
+ ## Note Structure
78
+
79
+ ```markdown
80
+ ---
81
+ title: <descriptive title for the thread>
82
+ type: note
83
+ thread_id: <thread-id, if your agent exposes one>
84
+ tags:
85
+ - relevant
86
+ - tags
87
+ ---
88
+
89
+ # <Title>
90
+
91
+ ## Context
92
+
93
+ What this thread is about — the situation, problem, or topic being explored.
94
+
95
+ ## <One or more topical sections>
96
+
97
+ The actual content. Could be decisions, a design rationale, an investigation summary, etc.
98
+
99
+ ## Observations
100
+
101
+ - [decision] What was decided #tag
102
+ - [insight] Key understanding gained #tag
103
+ - [tradeoff] Option A chosen over B because... #tag
104
+
105
+ ## Relations
106
+
107
+ - relates_to [[Related Concept]]
108
+ - implements [[Parent Spec]]
109
+ ```
110
+
111
+ ## Common Observation Categories
112
+
113
+ - `[decision]` — choices made
114
+ - `[insight]` — understanding gained
115
+ - `[pattern]` — reusable approaches
116
+ - `[learning]` — lessons learned
117
+ - `[tradeoff]` — options weighed
118
+ - `[problem]` — issues identified
119
+ - `[solution]` — fixes applied
120
+
121
+ ## Title
122
+
123
+ The title should reflect the thread's topic. On update, the title can be refined if the topic has clarified — but it should still describe the same thread. Don't drift to a wholly new topic; if that's needed, use the escape hatch and create a new note.
124
+
125
+ ## MCP Tools Used
126
+
127
+ ```python
128
+ # Find existing thread note by thread id (use metadata_filters, not query)
129
+ search_notes(
130
+ metadata_filters={"thread_id": "<thread-id>"},
131
+ project="<project>"
132
+ )
133
+
134
+ # Or, without a thread id, find it by topic
135
+ search_notes(query="<thread topic>", project="<project>")
136
+
137
+ # Read existing thread note (use the full permalink from search results)
138
+ read_note(
139
+ identifier="<full-permalink>",
140
+ project="<project>"
141
+ )
142
+
143
+ # Create
144
+ write_note(
145
+ title="<title>",
146
+ content="<markdown body — frontmatter is generated from title/tags/metadata>",
147
+ directory="<folder>",
148
+ tags=["..."],
149
+ metadata={"thread_id": "<thread-id>"}, # omit if no stable id
150
+ project="<project>"
151
+ )
152
+
153
+ # Overwrite an existing note (same path)
154
+ write_note(
155
+ title="<same title>",
156
+ content="<new content>",
157
+ directory="<same folder>",
158
+ tags=["..."],
159
+ metadata={"thread_id": "<same thread-id>"}, # omit if no stable id
160
+ overwrite=True,
161
+ project="<project>"
162
+ )
163
+ ```
164
+
165
+ ## Examples
166
+
167
+ ### Example 1 — First capture during a brand design conversation
168
+
169
+ **Preceding conversation:** The user has been working through visual identity decisions for a new product. They settled on a deep navy primary (`#2B3651`), explored accent options and chose orange (`#F26B3A`) for warmth, and picked Inter as the body font with Helvetica Neue as the display font.
170
+
171
+ **User asks to capture.**
172
+
173
+ **Result — note created:**
174
+
175
+ ```markdown
176
+ ---
177
+ title: Visual identity — initial decisions
178
+ type: note
179
+ thread_id: 7c1d4a2e-3b5f-4d8a-9e1c-2f6b8a4d7c39
180
+ tags:
181
+ - branding
182
+ - design
183
+ ---
184
+
185
+ # Visual identity — initial decisions
186
+
187
+ ## Context
188
+
189
+ Working through the visual identity for the new product. This thread covers the initial palette and typography pass — a starting point that will likely be refined.
190
+
191
+ ## Color palette
192
+
193
+ - Primary: deep navy `#2B3651` — calm and professional
194
+ - Accent: warm orange `#F26B3A` — energy and warmth as a complement to the navy
195
+
196
+ ## Typography
197
+
198
+ - Body: Inter — neutral, readable at small sizes
199
+ - Display: Helvetica Neue — strong presence for headings without being heavy
200
+
201
+ ## Observations
202
+
203
+ - [decision] Primary color is navy `#2B3651` #branding
204
+ - [decision] Accent color is orange `#F26B3A` #branding
205
+ - [decision] Inter for body, Helvetica Neue for display #typography
206
+ - [tradeoff] Considered teal as accent; orange tested better for warmth #branding
207
+
208
+ ## Relations
209
+
210
+ - relates_to [[Brand Strategy]]
211
+ ```
212
+
213
+ ### Example 2 — Update capture later in the same thread
214
+
215
+ **Preceding conversation (continued):** After the initial decisions above, the conversation continued. The orange accent felt too aggressive in mock-ups, so we tested a coral (`#E89B7A`) which read warmer and more refined. The body font also shifted: Geist felt slightly tighter and more modern than Inter. Helvetica Neue for display stayed.
216
+
217
+ **User asks to capture again — same thread.**
218
+
219
+ **Result — same note rewritten (note the same `thread_id`):**
220
+
221
+ ```markdown
222
+ ---
223
+ title: Visual identity — initial decisions
224
+ type: note
225
+ thread_id: 7c1d4a2e-3b5f-4d8a-9e1c-2f6b8a4d7c39
226
+ tags:
227
+ - branding
228
+ - design
229
+ ---
230
+
231
+ # Visual identity — initial decisions
232
+
233
+ ## Context
234
+
235
+ Working through the visual identity for the new product. This thread settled on a navy + coral palette and a Geist/Helvetica typography pairing after a round of refinement.
236
+
237
+ ## Color palette
238
+
239
+ - Primary: deep navy `#2B3651` — calm and professional
240
+ - Accent: coral `#E89B7A` — warm and refined
241
+
242
+ The accent went through a round of revision: an initial orange (`#F26B3A`) felt too aggressive in mock-ups, so we shifted to a coral that reads warmer and more refined while keeping the energy.
243
+
244
+ ## Typography
245
+
246
+ - Body: Geist — slightly tighter and more modern than Inter, which we tried first
247
+ - Display: Helvetica Neue — strong presence for headings without being heavy
248
+
249
+ ## Observations
250
+
251
+ - [decision] Primary color is navy `#2B3651` #branding
252
+ - [decision] Accent color is coral `#E89B7A` — warmer and more refined than the originally-chosen orange #branding
253
+ - [decision] Geist for body, Helvetica Neue for display #typography
254
+ - [tradeoff] Inter felt neutral but Geist edged it for spacing and modernity #typography
255
+ - [tradeoff] Orange accent rejected as too aggressive; coral preferred #branding
256
+
257
+ ## Relations
258
+
259
+ - relates_to [[Brand Strategy]]
260
+ ```
261
+
262
+ Notice that:
263
+ - The orange and Inter decisions are **no longer the primary content** — they're acknowledged in prose ("which we tried first," "originally-chosen orange") and in tradeoff observations
264
+ - There is **no "Changes" section** at the bottom — revisions are integrated where they belong
265
+ - The note still reads top-to-bottom as a single coherent document
266
+ - The `thread_id` is unchanged, so the note was updated in place rather than duplicated
267
+
268
+ ## Best Practices
269
+
270
+ 1. **Capture the current state, not the history.** The note represents where the thread has landed.
271
+ 2. **Synthesize, don't log.** Each invocation produces a coherent document, not an accumulating record.
272
+ 3. **Brief prose for revisions.** A sentence in the section that changed is enough — don't add a changelog.
273
+ 4. **Always run the same-thread lookup** before deciding to create or update.
274
+ 5. **Use observations for the structured layer.** Decisions, insights, tradeoffs go in `## Observations` so they're searchable.
275
+ 6. **Link relations liberally.** Notes the user might want to reach from this one.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: memory-ci-capture
3
+ description: Synthesize GitHub delivery context into a concise Basic Memory project update. Use in CI after `bm ci collect` prepares a ProjectUpdateContext; return only structured AgentSynthesis JSON for `bm ci publish`.
4
+ ---
5
+
6
+ # Memory CI Capture
7
+
8
+ Turn a meaningful GitHub delivery moment into project memory. GitHub records the
9
+ mechanics. Basic Memory remembers what changed and why.
10
+
11
+ ## Inputs
12
+
13
+ Read the `ProjectUpdateContext` JSON produced by `bm ci collect` at
14
+ `.github/basic-memory/project-update-context.json`. Treat it as the immutable
15
+ source of truth for repository, event type, PR number, workflow run, SHA, source
16
+ URL, timestamps, and deployment environment.
17
+
18
+ Do not invent tests, deploy checks, linked issues, product impact, or decisions.
19
+ If evidence is absent, say so briefly in `verification` or leave the field empty.
20
+
21
+ ## Output
22
+
23
+ Return only JSON matching the `AgentSynthesis` shape:
24
+
25
+ ```json
26
+ {
27
+ "summary": "What changed.",
28
+ "why_it_matters": "Why this update matters for future humans and agents.",
29
+ "user_facing_changes": [],
30
+ "internal_changes": [],
31
+ "verification": [],
32
+ "follow_ups": [],
33
+ "decision_candidates": [],
34
+ "task_candidates": []
35
+ }
36
+ ```
37
+
38
+ ## Synthesis Rules
39
+
40
+ - Prefer a short explanation over a commit-by-commit changelog.
41
+ - Preserve intent, changed behavior, source links, verification evidence present
42
+ in the context, and concrete follow-ups.
43
+ - Put explicit product or architecture decisions in `decision_candidates` only
44
+ when the source context clearly contains them.
45
+ - Put future work in `task_candidates` only when it is concrete enough to act on.
46
+ - Keep the tone factual and useful. This is project memory, not marketing copy.
47
+
48
+ ## Event Guidance
49
+
50
+ For merged pull requests, focus on why the PR existed, what area changed, what
51
+ issues it advanced or closed, and what verification evidence appears in the
52
+ context.
53
+
54
+ For production deploys, focus on what reached production, the deployed SHA,
55
+ environment, workflow run, and verification evidence. Do not overclaim success
56
+ beyond the workflow and source facts.