create-ai-memory 0.1.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.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/bin/create-ai-memory.js +41 -0
- package/create-ai-memory.plugin.zsh +5 -0
- package/hooks/claude/session-start.sh +16 -0
- package/hooks/claude/session-summary.sh +54 -0
- package/hooks/claude/settings.snippet.json +19 -0
- package/hooks/git/commit-msg +178 -0
- package/hooks/git/pre-push +19 -0
- package/install.sh +124 -0
- package/package.json +44 -0
- package/shell/adapters.zsh +75 -0
- package/shell/ai-mem.zsh +483 -0
- package/vault-template/_Global_Profile.md +33 -0
- package/vault-template/_Standards.md +29 -0
- package/vault-template/_projects/_project_template.md +26 -0
- package/vault-template/_session_logs/_session_template.md +12 -0
package/shell/ai-mem.zsh
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# === AI CLI + Obsidian memory ===
|
|
2
|
+
# Portable, agent-agnostic session memory. Source this from ~/.zshrc after
|
|
3
|
+
# exporting AI_MEM_ROOT to point at your vault. Zsh-only (uses print -r, ${(s)},
|
|
4
|
+
# select). Add a new agent by defining _ai_adapter_<name> in adapters.zsh and
|
|
5
|
+
# listing it in AI_MEM_AGENTS; a matching <name>-start function is generated.
|
|
6
|
+
|
|
7
|
+
# Directory holding this module, used to source sibling files.
|
|
8
|
+
AI_MEM_HOME="${0:A:h}"
|
|
9
|
+
|
|
10
|
+
# Where the shipped example notes and templates live, used to auto-scaffold a
|
|
11
|
+
# vault on first use so install.sh is optional (plugin-manager installs work).
|
|
12
|
+
AI_MEM_TEMPLATE_SRC="${AI_MEM_HOME:h}/vault-template"
|
|
13
|
+
|
|
14
|
+
# Vault root. Override in ~/.zshrc; defaults to a hidden dir under $HOME.
|
|
15
|
+
: "${AI_MEM_ROOT:=$HOME/.ai-memory/_Ai_Memory}"
|
|
16
|
+
if [[ -d "$AI_MEM_ROOT" ]]; then
|
|
17
|
+
AI_MEM_ROOT="$(cd "$AI_MEM_ROOT" && pwd -P)"
|
|
18
|
+
fi
|
|
19
|
+
export AI_MEM_ROOT
|
|
20
|
+
export AI_MEM_GLOBAL="$AI_MEM_ROOT/_Global_Profile.md"
|
|
21
|
+
export AI_MEM_STANDARDS="$AI_MEM_ROOT/_Standards.md"
|
|
22
|
+
export AI_MEM_PROJECT_DIR="$AI_MEM_ROOT/_projects"
|
|
23
|
+
export AI_MEM_SESSION_DIR="$AI_MEM_ROOT/_session_logs"
|
|
24
|
+
|
|
25
|
+
_ai_mem_guard() {
|
|
26
|
+
local path="${1:-}"
|
|
27
|
+
case "$path" in
|
|
28
|
+
"$AI_MEM_ROOT"|"$AI_MEM_ROOT"/*) return 0 ;;
|
|
29
|
+
*)
|
|
30
|
+
echo "Refusing to touch non-memory path: $path"
|
|
31
|
+
return 1
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Read a vault note only when it exists inside the memory root.
|
|
37
|
+
_ai_mem_note_contents() {
|
|
38
|
+
local path="${1:-}"
|
|
39
|
+
if [[ -z "$path" || ! -f "$path" ]]; then
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
_ai_mem_guard "$path" || return 1
|
|
44
|
+
print -r -- "$(<"$path")"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_ai_mem_resolve_project() {
|
|
48
|
+
# Prefer the git repo we are actually standing in. Otherwise `cd`-ing between
|
|
49
|
+
# projects in one shell keeps a stale AI_MEM_ACTIVE_PROJECT pinned, so a later
|
|
50
|
+
# claude-start writes its log under the wrong project's folder.
|
|
51
|
+
local git_root=""
|
|
52
|
+
git_root="$(git rev-parse --show-toplevel 2>/dev/null)" || true
|
|
53
|
+
if [[ -n "$git_root" ]]; then
|
|
54
|
+
basename "$git_root"
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Outside any repo, fall back to the active session's project, then the cwd.
|
|
59
|
+
if [[ -n "${AI_MEM_ACTIVE_PROJECT:-}" ]]; then
|
|
60
|
+
print -r -- "$AI_MEM_ACTIVE_PROJECT"
|
|
61
|
+
return 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
basename "$PWD"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_ai_mem_project_session_dir() {
|
|
68
|
+
local project_name="${1:-}"
|
|
69
|
+
if [[ -z "$project_name" ]]; then
|
|
70
|
+
project_name="$(_ai_mem_resolve_project)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
print -r -- "$AI_MEM_SESSION_DIR/$project_name"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_ai_mem_graphify_repo_root() {
|
|
77
|
+
local git_root=""
|
|
78
|
+
git_root="$(git rev-parse --show-toplevel 2>/dev/null)" || true
|
|
79
|
+
if [[ -n "$git_root" ]]; then
|
|
80
|
+
print -r -- "$git_root"
|
|
81
|
+
else
|
|
82
|
+
print -r -- "$PWD"
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_ai_mem_graphify_context() {
|
|
87
|
+
local repo_root="$(_ai_mem_graphify_repo_root)"
|
|
88
|
+
local graph_root="$repo_root/graphify-out"
|
|
89
|
+
local graph_json="$graph_root/graph.json"
|
|
90
|
+
local graph_report="$graph_root/GRAPH_REPORT.md"
|
|
91
|
+
local graph_wiki="$graph_root/wiki/index.md"
|
|
92
|
+
|
|
93
|
+
if [[ ! -f "$graph_json" ]]; then
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
export AI_GRAPHIFY_ROOT="$graph_root"
|
|
98
|
+
export AI_GRAPHIFY_GRAPH_JSON="$graph_json"
|
|
99
|
+
|
|
100
|
+
printf '%s\n' \
|
|
101
|
+
"Graphify context:" \
|
|
102
|
+
"- Knowledge graph available at: $graph_root" \
|
|
103
|
+
"- Use graphify query/path/explain before raw grep for codebase or architecture questions." \
|
|
104
|
+
"- Read the graph report for broad overviews: $graph_report"
|
|
105
|
+
|
|
106
|
+
if [[ -f "$graph_wiki" ]]; then
|
|
107
|
+
printf '%s\n' "- Use the wiki index for broad navigation: $graph_wiki"
|
|
108
|
+
fi
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Returns the newest saved session log for the current project.
|
|
112
|
+
# The active run gets a fresh log, so this only feeds carryover context.
|
|
113
|
+
_ai_mem_latest_session_log() {
|
|
114
|
+
local project_name="${1:-}"
|
|
115
|
+
if [[ -z "$project_name" ]]; then
|
|
116
|
+
project_name="$(_ai_mem_resolve_project)"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
local latest=""
|
|
120
|
+
local project_session_dir
|
|
121
|
+
project_session_dir="$(_ai_mem_project_session_dir "$project_name")"
|
|
122
|
+
|
|
123
|
+
latest="$(find "$project_session_dir" -maxdepth 1 -type f -name "${project_name}-*.md" 2>/dev/null | sort | tail -n 1)" || true
|
|
124
|
+
if [[ -z "$latest" ]]; then
|
|
125
|
+
latest="$(find "$AI_MEM_SESSION_DIR" -maxdepth 1 -type f -name "${project_name}-*.md" 2>/dev/null | sort | tail -n 1)" || true
|
|
126
|
+
fi
|
|
127
|
+
if [[ -n "$latest" ]]; then
|
|
128
|
+
_ai_mem_guard "$latest" || return 1
|
|
129
|
+
print -r -- "$latest"
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Copy the shipped profile, standards, and templates into the vault on first use.
|
|
134
|
+
# Idempotent and additive: it never overwrites a file the user already has, so a
|
|
135
|
+
# plugin-manager install (source only, no install.sh) still gets a working vault.
|
|
136
|
+
_ai_mem_ensure_vault() {
|
|
137
|
+
[[ -d "$AI_MEM_TEMPLATE_SRC" ]] || return 0
|
|
138
|
+
mkdir -p "$AI_MEM_PROJECT_DIR" "$AI_MEM_SESSION_DIR"
|
|
139
|
+
local rel
|
|
140
|
+
for rel in _Global_Profile.md _Standards.md \
|
|
141
|
+
_projects/_project_template.md _session_logs/_session_template.md; do
|
|
142
|
+
local src="$AI_MEM_TEMPLATE_SRC/$rel" dst="$AI_MEM_ROOT/$rel"
|
|
143
|
+
if [[ -f "$src" && ! -f "$dst" ]]; then
|
|
144
|
+
_ai_mem_guard "$dst" || return 1
|
|
145
|
+
cp "$src" "$dst"
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_ai_mem_prepare_session() {
|
|
151
|
+
local project_name="${1:-}"
|
|
152
|
+
if [[ -z "$project_name" ]]; then
|
|
153
|
+
project_name="$(_ai_mem_resolve_project)"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
_ai_mem_ensure_vault || return 1
|
|
157
|
+
|
|
158
|
+
local project_note="$AI_MEM_PROJECT_DIR/${project_name}.md"
|
|
159
|
+
local project_session_dir
|
|
160
|
+
project_session_dir="$(_ai_mem_project_session_dir "$project_name")"
|
|
161
|
+
local previous_session_note=""
|
|
162
|
+
previous_session_note="$(_ai_mem_latest_session_log "$project_name")" || return 1
|
|
163
|
+
local session_note="$project_session_dir/${project_name}-$(date +%Y-%m-%d_%H-%M-%S).md"
|
|
164
|
+
|
|
165
|
+
_ai_mem_guard "$project_note" || return 1
|
|
166
|
+
_ai_mem_guard "$session_note" || return 1
|
|
167
|
+
|
|
168
|
+
mkdir -p "$AI_MEM_PROJECT_DIR" "$AI_MEM_SESSION_DIR" "$project_session_dir"
|
|
169
|
+
|
|
170
|
+
if [[ ! -f "$project_note" ]]; then
|
|
171
|
+
PROJECT_NAME="$project_name" perl -0pe 's/\[Insert Project Name\]/$ENV{PROJECT_NAME}/g' \
|
|
172
|
+
"$AI_MEM_PROJECT_DIR/_project_template.md" > "$project_note"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
SESSION_DATE="$(date +%Y-%m-%d)" PROJECT_NAME="$project_name" perl -0pe 's/\{\{date\}\}/$ENV{SESSION_DATE}/g; s/\{\{project_name\}\}/$ENV{PROJECT_NAME}/g' \
|
|
176
|
+
"$AI_MEM_SESSION_DIR/_session_template.md" > "$session_note"
|
|
177
|
+
|
|
178
|
+
export AI_MEM_ACTIVE_PROJECT="$project_name"
|
|
179
|
+
export AI_MEM_PREVIOUS_SESSION_LOG="$previous_session_note"
|
|
180
|
+
export AI_MEM_ACTIVE_SESSION_LOG="$session_note"
|
|
181
|
+
|
|
182
|
+
print -r -- "$project_name|$project_note|$previous_session_note|$session_note"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_ai_mem_context_prompt() {
|
|
186
|
+
local project_note="${1:-}"
|
|
187
|
+
local previous_session_note="${2:-}"
|
|
188
|
+
local session_note="${3:-}"
|
|
189
|
+
local previous_session_label="(none yet)"
|
|
190
|
+
|
|
191
|
+
if [[ -n "$previous_session_note" ]]; then
|
|
192
|
+
previous_session_label="$previous_session_note"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
cat <<EOF
|
|
196
|
+
Read these notes before doing anything else:
|
|
197
|
+
- Global profile:
|
|
198
|
+
$(_ai_mem_note_contents "$AI_MEM_GLOBAL")
|
|
199
|
+
|
|
200
|
+
- Standards:
|
|
201
|
+
$(_ai_mem_note_contents "$AI_MEM_STANDARDS")
|
|
202
|
+
|
|
203
|
+
- Project context: $project_note
|
|
204
|
+
- Latest prior session log: $previous_session_label
|
|
205
|
+
- Active session log: $session_note
|
|
206
|
+
|
|
207
|
+
Use the Obsidian vault as the persistent memory layer.
|
|
208
|
+
Treat the global profile and standards note as the shared baseline for every run.
|
|
209
|
+
Read the latest prior session log for continuity before acting. Do not load the full session history unless the user asks for it.
|
|
210
|
+
Keep durable preferences and project facts in the vault, and keep the active session log updated with decisions, blockers, and next steps.
|
|
211
|
+
EOF
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Mark the current shell as having loaded AI vault context for the active repo.
|
|
215
|
+
_ai_mem_mark_commit_ready() {
|
|
216
|
+
local project_name="${1:-}"
|
|
217
|
+
local source="${2:-ai-context}"
|
|
218
|
+
local ready_dir="$AI_MEM_SESSION_DIR/.context-ready"
|
|
219
|
+
local ready_file
|
|
220
|
+
local token
|
|
221
|
+
|
|
222
|
+
if [[ -z "$project_name" ]]; then
|
|
223
|
+
project_name="$(_ai_mem_resolve_project)"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
ready_file="$ready_dir/${project_name}.token"
|
|
227
|
+
_ai_mem_guard "$ready_file" || return 1
|
|
228
|
+
|
|
229
|
+
mkdir -p "$ready_dir"
|
|
230
|
+
|
|
231
|
+
token="${EPOCHSECONDS:-$(date +%s)}-$$-$RANDOM"
|
|
232
|
+
{
|
|
233
|
+
printf '%s\n' "$token"
|
|
234
|
+
printf 'source=%s\n' "$source"
|
|
235
|
+
printf 'project=%s\n' "$project_name"
|
|
236
|
+
printf 'loaded_at=%s\n' "$(date +%Y-%m-%dT%H:%M:%S%z)"
|
|
237
|
+
} > "$ready_file"
|
|
238
|
+
|
|
239
|
+
export AI_MEM_CONTEXT_READY=1
|
|
240
|
+
export AI_MEM_CONTEXT_TOKEN="$token"
|
|
241
|
+
export AI_MEM_CONTEXT_SOURCE="$source"
|
|
242
|
+
export AI_MEM_CONTEXT_READY_FILE="$ready_file"
|
|
243
|
+
export AI_MEM_CONTEXT_PROJECT="$project_name"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Re-export the active-session vars in the CALLER's shell. _ai_mem_prepare_session
|
|
247
|
+
# exports them too, but it is always invoked inside $(...) command substitution, so
|
|
248
|
+
# those exports die in the subshell and never reach the launched client or its hooks
|
|
249
|
+
# (Claude's SessionStart/Stop hooks gate on AI_MEM_ACTIVE_SESSION_LOG).
|
|
250
|
+
_ai_mem_export_active() {
|
|
251
|
+
export AI_MEM_ACTIVE_PROJECT="${1:-}"
|
|
252
|
+
export AI_MEM_PREVIOUS_SESSION_LOG="${2:-}"
|
|
253
|
+
export AI_MEM_ACTIVE_SESSION_LOG="${3:-}"
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
ai-start() {
|
|
257
|
+
local project_name="${1:-}"
|
|
258
|
+
shift || true
|
|
259
|
+
|
|
260
|
+
local resolved
|
|
261
|
+
resolved="$(_ai_mem_prepare_session "$project_name")" || return 1
|
|
262
|
+
|
|
263
|
+
local active_project project_note previous_session_note session_note
|
|
264
|
+
IFS='|' read -r active_project project_note previous_session_note session_note <<< "$resolved"
|
|
265
|
+
_ai_mem_export_active "$active_project" "$previous_session_note" "$session_note"
|
|
266
|
+
|
|
267
|
+
local previous_session_label="(none yet)"
|
|
268
|
+
if [[ -n "$previous_session_note" ]]; then
|
|
269
|
+
previous_session_label="$previous_session_note"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
cat <<EOF
|
|
273
|
+
AI memory prepared
|
|
274
|
+
- Project: $active_project
|
|
275
|
+
- Global: $AI_MEM_GLOBAL
|
|
276
|
+
- Project note: $project_note
|
|
277
|
+
- Latest prior session log: $previous_session_label
|
|
278
|
+
- Active session log: $session_note
|
|
279
|
+
EOF
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
ai-context() {
|
|
283
|
+
local project_name="${1:-}"
|
|
284
|
+
local resolved project_note previous_session_note session_note
|
|
285
|
+
resolved="$(_ai_mem_prepare_session "$project_name")" || return 1
|
|
286
|
+
|
|
287
|
+
local active_project
|
|
288
|
+
IFS='|' read -r active_project project_note previous_session_note session_note <<< "$resolved"
|
|
289
|
+
_ai_mem_export_active "$active_project" "$previous_session_note" "$session_note"
|
|
290
|
+
|
|
291
|
+
_ai_mem_mark_commit_ready "$active_project" "ai-context" || return 1
|
|
292
|
+
_ai_mem_context_prompt "$project_note" "$previous_session_note" "$session_note"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Yes/no helper used by the session-mode prompts. Empty answer counts as no.
|
|
296
|
+
_ai_yesno() {
|
|
297
|
+
local prompt="$1" reply
|
|
298
|
+
while true; do
|
|
299
|
+
read -r "reply?${prompt} [y/N] "
|
|
300
|
+
case "$reply" in
|
|
301
|
+
[Yy]*) return 0 ;;
|
|
302
|
+
[Nn]*|"") return 1 ;;
|
|
303
|
+
*) echo "Answer y or n." ;;
|
|
304
|
+
esac
|
|
305
|
+
done
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
# Optional per-session skills, defined by you. Each entry maps a short key to
|
|
309
|
+
# "yes/no prompt::instruction block injected when the skill is enabled"
|
|
310
|
+
# and AI_MEM_SKILL_ORDER sets the ask order. Both are empty by default, so a
|
|
311
|
+
# stock install asks nothing and injects nothing. Define your own in ~/.zshrc
|
|
312
|
+
# BEFORE sourcing this file, e.g.:
|
|
313
|
+
# typeset -gA AI_MEM_SKILLS
|
|
314
|
+
# AI_MEM_SKILLS[terse]='Use terse output this session?::Respond tersely; drop filler and hedging.'
|
|
315
|
+
# AI_MEM_SKILLS[design]='Use strict UI design discipline this session?::Apply careful frontend/UI design review to any design work.'
|
|
316
|
+
# AI_MEM_SKILL_ORDER=(terse design)
|
|
317
|
+
# The chosen block is injected into every launched agent, so a session's skills
|
|
318
|
+
# persist as instructions for that whole run.
|
|
319
|
+
typeset -gA AI_MEM_SKILLS
|
|
320
|
+
typeset -ga AI_MEM_SKILL_ORDER
|
|
321
|
+
|
|
322
|
+
# Ask which optional skills to enable. Each is independent; answer y/n per skill.
|
|
323
|
+
# Echoes a pipe-joined list of chosen keys; empty means a plain session.
|
|
324
|
+
_ai_session_modes_pick() {
|
|
325
|
+
local modes=() key prompt
|
|
326
|
+
for key in "${AI_MEM_SKILL_ORDER[@]}"; do
|
|
327
|
+
prompt="${AI_MEM_SKILLS[$key]%%::*}"
|
|
328
|
+
[[ -n "$prompt" ]] || continue
|
|
329
|
+
_ai_yesno "$prompt" && modes+=("$key")
|
|
330
|
+
done
|
|
331
|
+
print -r -- "${(j:|:)modes}"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Build the instruction block to inject from the chosen skill keys.
|
|
335
|
+
_ai_session_modes_instructions() {
|
|
336
|
+
local modes="$1" block="" m text
|
|
337
|
+
for m in ${(s:|:)modes}; do
|
|
338
|
+
text="${AI_MEM_SKILLS[$m]#*::}"
|
|
339
|
+
[[ -n "$text" ]] && block+="$text"$'\n\n'
|
|
340
|
+
done
|
|
341
|
+
print -r -- "${block%$'\n\n'}"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Start an AI client with the shared memory block and the chosen session mode.
|
|
345
|
+
_ai_session_start() {
|
|
346
|
+
local launcher="${1:-}"
|
|
347
|
+
if (( $# > 0 )); then
|
|
348
|
+
shift
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
local session_prompt="${*:-}"
|
|
352
|
+
|
|
353
|
+
local resolved
|
|
354
|
+
resolved="$(_ai_mem_prepare_session)" || return 1
|
|
355
|
+
|
|
356
|
+
local active_project project_note previous_session_note session_note
|
|
357
|
+
IFS='|' read -r active_project project_note previous_session_note session_note <<< "$resolved"
|
|
358
|
+
_ai_mem_export_active "$active_project" "$previous_session_note" "$session_note"
|
|
359
|
+
|
|
360
|
+
local session_modes mode_block
|
|
361
|
+
session_modes="$(_ai_session_modes_pick)" || return 1
|
|
362
|
+
mode_block="$(_ai_session_modes_instructions "$session_modes")"
|
|
363
|
+
|
|
364
|
+
# AI_SESSION_MODES carries the chosen skill keys; AI_SESSION_STYLE_LABEL
|
|
365
|
+
# carries the assembled instruction block for launchers to inject.
|
|
366
|
+
export AI_SESSION_MODES="$session_modes"
|
|
367
|
+
export AI_SESSION_STYLE_LABEL="$mode_block"
|
|
368
|
+
|
|
369
|
+
local memory_prompt
|
|
370
|
+
memory_prompt="$(_ai_mem_context_prompt "$project_note" "$previous_session_note" "$session_note")"
|
|
371
|
+
|
|
372
|
+
local graphify_context=""
|
|
373
|
+
graphify_context="$(_ai_mem_graphify_context)" || return 1
|
|
374
|
+
if [[ -n "$graphify_context" ]]; then
|
|
375
|
+
memory_prompt+=$'\n\n'
|
|
376
|
+
memory_prompt+="$graphify_context"
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
if [[ -n "$mode_block" ]]; then
|
|
380
|
+
memory_prompt+=$'\n\n'
|
|
381
|
+
memory_prompt+="$mode_block"
|
|
382
|
+
fi
|
|
383
|
+
if [[ -n "$session_prompt" ]]; then
|
|
384
|
+
memory_prompt+=$'\n\n'
|
|
385
|
+
memory_prompt+="$session_prompt"
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
_ai_mem_mark_commit_ready "$active_project" "$launcher-start" || return 1
|
|
389
|
+
|
|
390
|
+
# Dispatch to the agent adapter. Each adapter is passed the assembled
|
|
391
|
+
# memory prompt, the mode block, and any remaining CLI args (e.g. files to
|
|
392
|
+
# open). Adapters live in adapters.zsh; add one to support a new agent.
|
|
393
|
+
if ! typeset -f "_ai_adapter_$launcher" >/dev/null; then
|
|
394
|
+
echo "Unknown AI launcher: $launcher (define _ai_adapter_$launcher in adapters.zsh)" >&2
|
|
395
|
+
return 1
|
|
396
|
+
fi
|
|
397
|
+
"_ai_adapter_$launcher" "$memory_prompt" "$mode_block" "$@"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# Load the agent adapters, then generate a <name>-start launcher for every
|
|
401
|
+
# registered agent that has a matching adapter. Users extend by appending to
|
|
402
|
+
# AI_MEM_AGENTS (space-separated) and defining _ai_adapter_<name>.
|
|
403
|
+
source "$AI_MEM_HOME/adapters.zsh"
|
|
404
|
+
: "${AI_MEM_AGENTS:=claude codex gemini cursor opencode}"
|
|
405
|
+
for _ai_agent in ${(z)AI_MEM_AGENTS}; do
|
|
406
|
+
if typeset -f "_ai_adapter_$_ai_agent" >/dev/null; then
|
|
407
|
+
eval "${_ai_agent}-start() { _ai_session_start ${_ai_agent} \"\$@\"; }"
|
|
408
|
+
fi
|
|
409
|
+
done
|
|
410
|
+
unset _ai_agent
|
|
411
|
+
|
|
412
|
+
_ai_mem_current_project() {
|
|
413
|
+
_ai_mem_resolve_project
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_ai_mem_today_session_log() {
|
|
417
|
+
local project_name="${1:-}"
|
|
418
|
+
if [[ -z "$project_name" ]]; then
|
|
419
|
+
project_name="$(_ai_mem_resolve_project)"
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
_ai_mem_ensure_vault || return 1
|
|
423
|
+
|
|
424
|
+
local today
|
|
425
|
+
today="$(date +%Y-%m-%d)"
|
|
426
|
+
local project_session_dir
|
|
427
|
+
project_session_dir="$(_ai_mem_project_session_dir "$project_name")"
|
|
428
|
+
|
|
429
|
+
if [[ -n "${AI_MEM_ACTIVE_SESSION_LOG:-}" && -f "$AI_MEM_ACTIVE_SESSION_LOG" ]]; then
|
|
430
|
+
case "$AI_MEM_ACTIVE_SESSION_LOG" in
|
|
431
|
+
"$AI_MEM_ROOT"|"$AI_MEM_ROOT"/*)
|
|
432
|
+
if [[ "$AI_MEM_ACTIVE_SESSION_LOG" == "$project_session_dir/${project_name}-${today}_"* ]]; then
|
|
433
|
+
print -r -- "$AI_MEM_ACTIVE_SESSION_LOG"
|
|
434
|
+
return 0
|
|
435
|
+
fi
|
|
436
|
+
;;
|
|
437
|
+
esac
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
local latest=""
|
|
441
|
+
latest="$(find "$project_session_dir" -maxdepth 1 -type f -name "${project_name}-${today}_*.md" 2>/dev/null | sort | tail -n 1)" || true
|
|
442
|
+
if [[ -z "$latest" ]]; then
|
|
443
|
+
latest="$(find "$AI_MEM_SESSION_DIR" -maxdepth 1 -type f -name "${project_name}-${today}_*.md" 2>/dev/null | sort | tail -n 1)" || true
|
|
444
|
+
fi
|
|
445
|
+
if [[ -n "$latest" ]]; then
|
|
446
|
+
_ai_mem_guard "$latest" || return 1
|
|
447
|
+
print -r -- "$latest"
|
|
448
|
+
return 0
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
local session_note="$project_session_dir/${project_name}-${today}_$(date +%H-%M-%S).md"
|
|
452
|
+
_ai_mem_guard "$session_note" || return 1
|
|
453
|
+
mkdir -p "$project_session_dir"
|
|
454
|
+
SESSION_DATE="$today" PROJECT_NAME="$project_name" perl -0pe 's/\{\{date\}\}/$ENV{SESSION_DATE}/g; s/\{\{project_name\}\}/$ENV{PROJECT_NAME}/g' \
|
|
455
|
+
"$AI_MEM_SESSION_DIR/_session_template.md" > "$session_note"
|
|
456
|
+
print -r -- "$session_note"
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
codex-note() {
|
|
460
|
+
ai-note "$@"
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
ai-note() {
|
|
464
|
+
local note_text="${*:-}"
|
|
465
|
+
if [[ -z "$note_text" ]]; then
|
|
466
|
+
echo "Usage: ai-note <note text>"
|
|
467
|
+
return 1
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
local project_name session_note timestamp
|
|
471
|
+
project_name="$(_ai_mem_current_project)" || return 1
|
|
472
|
+
session_note="$(_ai_mem_today_session_log "$project_name")" || return 1
|
|
473
|
+
timestamp="$(date +%H:%M)"
|
|
474
|
+
|
|
475
|
+
_ai_mem_guard "$session_note" || return 1
|
|
476
|
+
|
|
477
|
+
if ! grep -q '^### Live Notes' "$session_note" 2>/dev/null; then
|
|
478
|
+
printf '\n### Live Notes\n' >> "$session_note"
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
printf '\n- %s %s\n' "$timestamp" "$note_text" >> "$session_note"
|
|
482
|
+
printf 'Appended to %s\n' "$session_note"
|
|
483
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: ai-global-profile
|
|
3
|
+
user: [Your Name]
|
|
4
|
+
role: [Your role, e.g. Software Developer]
|
|
5
|
+
workflow: [e.g. spec-driven]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Universal Developer Directives
|
|
9
|
+
|
|
10
|
+
> Example profile. Replace the bracketed parts with your own preferences.
|
|
11
|
+
> ai-mem injects this file verbatim at the top of every agent session, so keep
|
|
12
|
+
> it to durable, cross-project rules — not task detail.
|
|
13
|
+
|
|
14
|
+
## Output Formatting
|
|
15
|
+
* Keep explanations concise and technical.
|
|
16
|
+
* Output code blocks with appropriate syntax highlighting.
|
|
17
|
+
|
|
18
|
+
## AI Code Standards
|
|
19
|
+
* **Value-Driven Development:** Prefer changes that create durable value over
|
|
20
|
+
cosmetic or speculative work. If value is unclear, state the tradeoff first.
|
|
21
|
+
* **Backward Compatibility:** Avoid breaking public APIs, file formats, or
|
|
22
|
+
config keys unless explicitly approved; prefer additive changes and fallbacks.
|
|
23
|
+
* **Repository Rules First:** Read and respect repo-local instruction files
|
|
24
|
+
(`AGENTS.md`, `.claude/CLAUDE.md`, `.cursor/`, `GEMINI.md`, …) before editing.
|
|
25
|
+
Those rules override this profile for that repo.
|
|
26
|
+
* **Simplicity is correctness:** Do not add abstractions, dependencies, or
|
|
27
|
+
flexibility without a demonstrated need or measured bottleneck.
|
|
28
|
+
* **Documentation Required:** Add appropriate doc comments for the file type on
|
|
29
|
+
anything you create or change, and keep them in sync with behavior.
|
|
30
|
+
|
|
31
|
+
## Commit Policy
|
|
32
|
+
* Use Conventional Commits: `type(scope): description` with a structured body.
|
|
33
|
+
* Types: feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: ai-standards
|
|
3
|
+
scope: global
|
|
4
|
+
status: active
|
|
5
|
+
mirror_of: _Global_Profile.md
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Shared Standards
|
|
9
|
+
|
|
10
|
+
> Example standards note. ai-mem injects this after the profile on every
|
|
11
|
+
> session. Use it for extra shared rules you want visible in every agent run.
|
|
12
|
+
> If a repo-local instruction conflicts with this note, the repo-local wins.
|
|
13
|
+
|
|
14
|
+
## Engineering
|
|
15
|
+
* **SOLID by default:** small, cohesive modules with explicit dependencies.
|
|
16
|
+
* **Composition over inheritance:** prefer delegation and interfaces.
|
|
17
|
+
* **Safe evolution:** prefer feature flags and compatibility shims over
|
|
18
|
+
rewrites; isolate behavior-changing refactors and call out the risk.
|
|
19
|
+
* **Quality bar:** keep tests, types, linting, and docs aligned with the change.
|
|
20
|
+
If a standard cannot be met this turn, say so explicitly.
|
|
21
|
+
|
|
22
|
+
## Memory Continuity
|
|
23
|
+
* Load the current project note plus the latest prior session log for the active
|
|
24
|
+
project. Do not load full session history unless asked.
|
|
25
|
+
* Keep durable preferences and project facts in the vault; keep run-specific
|
|
26
|
+
decisions, blockers, and next steps in the session log.
|
|
27
|
+
|
|
28
|
+
## Standards Addendum
|
|
29
|
+
* Keep this note and `_Global_Profile.md` in lockstep when shared rules change.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: ai-project-context
|
|
3
|
+
project_name: [Insert Project Name]
|
|
4
|
+
repo_root: [Insert Repo Root]
|
|
5
|
+
status: active
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Project Snapshot
|
|
9
|
+
* **Purpose:** [What problem this repository solves]
|
|
10
|
+
* **Current Objective:** [The main outcome you are working toward]
|
|
11
|
+
* **Success Criteria:** [How you know the work is done]
|
|
12
|
+
|
|
13
|
+
# Architecture at a Glance
|
|
14
|
+
* **Stack:** [Core languages, frameworks, and services]
|
|
15
|
+
* **System Shape:** [Monorepo, app/service split, modular boundaries]
|
|
16
|
+
* **Design Principles:** [High-level patterns that matter here]
|
|
17
|
+
* **Durability Rules:** [Backward compatibility, migrations, API stability]
|
|
18
|
+
|
|
19
|
+
# Constraints and Decisions
|
|
20
|
+
* **Key Constraints:** [Non-negotiables, technical limits, operational constraints]
|
|
21
|
+
* **Active Decisions:** [Important choices already made and why]
|
|
22
|
+
* **Open Questions:** [Things still unresolved]
|
|
23
|
+
|
|
24
|
+
# Repo-Specific Rules
|
|
25
|
+
* [Project-local instruction files and conventions]
|
|
26
|
+
* [Anything that must always be respected in this repo]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: {{date}}
|
|
3
|
+
project: {{project_name}}
|
|
4
|
+
repo_root: [Insert Repo Root]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Session Outcome
|
|
8
|
+
* **High-Level Summary:** [What changed or was decided]
|
|
9
|
+
* **Important Decisions:** [Durable decisions only]
|
|
10
|
+
* **Constraints / Blockers:** [What is still limiting progress]
|
|
11
|
+
* **Next Step:** [Most important follow-up]
|
|
12
|
+
* **Notes for Future AI:** [Any context needed to continue cleanly]
|