memoryintel 1.0.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 (39) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +9 -0
  3. package/LICENSE +21 -0
  4. package/README.md +192 -0
  5. package/dist/adapters/claudeCode.js +96 -0
  6. package/dist/adapters/genericPointer.js +39 -0
  7. package/dist/cli.js +157 -0
  8. package/dist/commands/daemonStart.js +8 -0
  9. package/dist/commands/dashboardToggle.js +17 -0
  10. package/dist/commands/init.js +111 -0
  11. package/dist/commands/load.js +82 -0
  12. package/dist/commands/status.js +24 -0
  13. package/dist/commands/update.js +108 -0
  14. package/dist/core/atomicWrite.js +6 -0
  15. package/dist/core/compressionConfig.js +37 -0
  16. package/dist/core/discovery.js +14 -0
  17. package/dist/core/eventLog.js +4 -0
  18. package/dist/core/gitPorcelain.js +45 -0
  19. package/dist/core/headingMatch.js +44 -0
  20. package/dist/core/lock.js +67 -0
  21. package/dist/core/memoryIndex.js +19 -0
  22. package/dist/core/pathSafety.js +43 -0
  23. package/dist/core/sectionWriter.js +91 -0
  24. package/dist/core/toon.js +118 -0
  25. package/dist/daemon/daemonHandle.js +52 -0
  26. package/dist/daemon/globalPaths.js +15 -0
  27. package/dist/daemon/health.js +14 -0
  28. package/dist/daemon/lifecycle.js +54 -0
  29. package/dist/daemon/registry.js +60 -0
  30. package/dist/daemon/server.js +92 -0
  31. package/dist/daemon/settings.js +13 -0
  32. package/dist/daemon/views/layout.js +233 -0
  33. package/dist/daemon/views/projectPage.js +111 -0
  34. package/dist/daemon/views/registryPage.js +54 -0
  35. package/dist/skill.js +46 -0
  36. package/dist/templates/starterFiles.js +22 -0
  37. package/hooks/hooks.json +11 -0
  38. package/package.json +52 -0
  39. package/skills/memoryintel/SKILL.md +55 -0
@@ -0,0 +1,233 @@
1
+ export function escapeHtml(s) {
2
+ return s
3
+ .replace(/&/g, '&')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#39;');
8
+ }
9
+ // Tiers a recency value (days since some event) into the tool's one signature device: a
10
+ // three-step freshness read (fresh / aging / stale) used consistently for both "when did we
11
+ // last see this project" and "when was this memory file last touched" — the two questions
12
+ // this whole dashboard exists to answer at a glance.
13
+ export function freshnessTier(daysAgo) {
14
+ if (daysAgo === null || daysAgo > 7)
15
+ return 'stale';
16
+ if (daysAgo <= 1)
17
+ return 'fresh';
18
+ return 'aging';
19
+ }
20
+ export function daysSince(iso) {
21
+ return Math.floor((Date.now() - new Date(iso).getTime()) / (24 * 60 * 60 * 1000));
22
+ }
23
+ // A day-only label ("0d ago") is indistinguishable for anything from just now up to almost 24h
24
+ // old - on an actively-worked project nearly every file lands in that bucket, making the
25
+ // dashboard's staleness read useless right when it matters most. Minutes/hours below the 24h
26
+ // mark, days once it's crossed.
27
+ export function formatAge(ms) {
28
+ const totalMinutes = Math.floor(Math.max(0, ms) / 60000);
29
+ if (totalMinutes < 1)
30
+ return 'just now';
31
+ if (totalMinutes < 60)
32
+ return `${totalMinutes}m ago`;
33
+ const totalHours = Math.floor(totalMinutes / 60);
34
+ if (totalHours < 24)
35
+ return `${totalHours}h ago`;
36
+ const totalDays = Math.floor(totalHours / 24);
37
+ return `${totalDays}d ago`;
38
+ }
39
+ const DISPLAY_FONT = `Georgia, 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', serif`;
40
+ const BODY_FONT = `-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif`;
41
+ const MONO_FONT = `'SF Mono', 'Cascadia Code', 'JetBrains Mono', Consolas, 'Liberation Mono', monospace`;
42
+ const BASE_STYLES = `
43
+ :root {
44
+ color-scheme: light dark;
45
+ --bg: #F6F3EC;
46
+ --surface: #FFFFFF;
47
+ --ink: #211D17;
48
+ --muted: #736B5C;
49
+ --accent: #A8752C;
50
+ --border: rgba(33, 29, 23, 0.14);
51
+ --fresh: #3F7D52;
52
+ --aging: #B8853A;
53
+ --stale: #A8514A;
54
+ }
55
+ @media (prefers-color-scheme: dark) {
56
+ :root {
57
+ --bg: #16130F;
58
+ --surface: #201C16;
59
+ --ink: #EEE8DA;
60
+ --muted: #A79D8C;
61
+ --accent: #E3AE58;
62
+ --border: rgba(238, 232, 218, 0.16);
63
+ --fresh: #6FAE82;
64
+ --aging: #D9A653;
65
+ --stale: #D97C74;
66
+ }
67
+ }
68
+
69
+ * { box-sizing: border-box; }
70
+ body {
71
+ font-family: ${BODY_FONT};
72
+ background: var(--bg);
73
+ color: var(--ink);
74
+ max-width: 720px;
75
+ margin: 3rem auto;
76
+ padding: 0 1.25rem 4rem;
77
+ line-height: 1.55;
78
+ }
79
+ h1, h2, h3 { font-family: ${DISPLAY_FONT}; line-height: 1.2; font-weight: 600; margin: 0 0 0.5rem; }
80
+ h1 { font-size: 1.6rem; }
81
+ h2 { font-size: 1.15rem; margin-top: 2rem; color: var(--muted); font-weight: 600; letter-spacing: 0.01em; }
82
+ a { color: var(--accent); text-decoration: none; }
83
+ a:hover, a:focus-visible { text-decoration: underline; }
84
+ a:focus-visible, summary:focus-visible, button:focus-visible {
85
+ outline: 2px solid var(--accent);
86
+ outline-offset: 2px;
87
+ }
88
+
89
+ .eyebrow {
90
+ font-family: ${MONO_FONT};
91
+ font-size: 0.72rem;
92
+ letter-spacing: 0.12em;
93
+ text-transform: uppercase;
94
+ color: var(--muted);
95
+ margin-bottom: 0.35rem;
96
+ }
97
+ .path {
98
+ font-family: ${MONO_FONT};
99
+ font-size: 0.82rem;
100
+ color: var(--muted);
101
+ word-break: break-all;
102
+ }
103
+
104
+ .card {
105
+ background: var(--surface);
106
+ border: 1px solid var(--border);
107
+ border-left: 3px solid var(--border);
108
+ border-radius: 6px;
109
+ padding: 1.1rem 1.2rem;
110
+ margin-bottom: 1rem;
111
+ animation: rise 0.25s ease-out backwards;
112
+ }
113
+ .card.fresh { border-left-color: var(--fresh); }
114
+ .card.aging { border-left-color: var(--aging); }
115
+ .card.stale { border-left-color: var(--stale); }
116
+ .card:nth-of-type(2) { animation-delay: 0.03s; }
117
+ .card:nth-of-type(3) { animation-delay: 0.06s; }
118
+ .card:nth-of-type(4) { animation-delay: 0.09s; }
119
+ @keyframes rise {
120
+ from { opacity: 0; transform: translateY(4px); }
121
+ to { opacity: 1; transform: translateY(0); }
122
+ }
123
+ @media (prefers-reduced-motion: reduce) {
124
+ .card { animation: none; }
125
+ }
126
+
127
+ .muted { opacity: 0.75; color: var(--muted); font-size: 0.9em; }
128
+ .missing { opacity: 0.6; font-style: italic; }
129
+
130
+ .mental-model {
131
+ font-family: ${DISPLAY_FONT};
132
+ font-size: 1.15rem;
133
+ font-style: italic;
134
+ border-left: 3px solid var(--accent);
135
+ padding-left: 1rem;
136
+ margin: 0.5rem 0 0;
137
+ white-space: pre-wrap;
138
+ }
139
+
140
+ pre {
141
+ font-family: ${MONO_FONT};
142
+ font-size: 0.85rem;
143
+ white-space: pre-wrap;
144
+ word-break: break-word;
145
+ background: color-mix(in srgb, currentColor 6%, transparent);
146
+ padding: 0.75rem;
147
+ border-radius: 6px;
148
+ margin: 0.5rem 0 0;
149
+ }
150
+
151
+ .tag {
152
+ display: inline-block;
153
+ font-family: ${MONO_FONT};
154
+ font-size: 0.72rem;
155
+ border-radius: 999px;
156
+ padding: 0.15em 0.75em;
157
+ background: color-mix(in srgb, currentColor 10%, transparent);
158
+ margin: 0 0.4em 0.4em 0;
159
+ }
160
+
161
+ details {
162
+ border-bottom: 1px solid var(--border);
163
+ padding: 0.5rem 0;
164
+ }
165
+ details:last-child { border-bottom: none; }
166
+ summary {
167
+ cursor: pointer;
168
+ font-family: ${MONO_FONT};
169
+ font-size: 0.88rem;
170
+ }
171
+ summary .stale-label.fresh { color: var(--fresh); }
172
+ summary .stale-label.aging { color: var(--aging); }
173
+ summary .stale-label.stale { color: var(--stale); }
174
+
175
+ .timeline-entry {
176
+ position: relative;
177
+ padding-left: 1rem;
178
+ border-left: 2px solid var(--border);
179
+ padding-bottom: 1rem;
180
+ margin-bottom: 0;
181
+ }
182
+ .timeline-entry:last-child { padding-bottom: 0; }
183
+ .timeline-files { display: block; font-family: var(--mono, monospace); font-size: 0.82em; }
184
+ .timeline-entry::before {
185
+ content: '';
186
+ position: absolute;
187
+ left: -5px;
188
+ top: 0.3rem;
189
+ width: 8px;
190
+ height: 8px;
191
+ border-radius: 50%;
192
+ background: var(--accent);
193
+ }
194
+
195
+ .empty-state {
196
+ font-family: ${DISPLAY_FONT};
197
+ font-style: italic;
198
+ color: var(--muted);
199
+ padding: 2rem 0;
200
+ }
201
+
202
+ .dashboard-controls {
203
+ margin-top: 3rem;
204
+ padding-top: 1.5rem;
205
+ border-top: 1px solid var(--border);
206
+ }
207
+ .btn-stop {
208
+ font-family: ${BODY_FONT};
209
+ font-size: 0.85rem;
210
+ font-weight: 600;
211
+ color: var(--stale);
212
+ background: color-mix(in srgb, var(--stale) 10%, var(--surface));
213
+ border: 1px solid var(--stale);
214
+ border-radius: 6px;
215
+ padding: 0.5rem 1rem;
216
+ cursor: pointer;
217
+ }
218
+ .btn-stop:hover { background: color-mix(in srgb, var(--stale) 18%, var(--surface)); }
219
+ `;
220
+ export function pageShell(title, bodyHtml) {
221
+ return `<!doctype html>
222
+ <html lang="en">
223
+ <head>
224
+ <meta charset="utf-8">
225
+ <meta name="viewport" content="width=device-width, initial-scale=1">
226
+ <title>${escapeHtml(title)}</title>
227
+ <style>${BASE_STYLES}</style>
228
+ </head>
229
+ <body>
230
+ ${bodyHtml}
231
+ </body>
232
+ </html>`;
233
+ }
@@ -0,0 +1,111 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, basename } from 'node:path';
3
+ import { WRITABLE_FILES } from '../../core/pathSafety.js';
4
+ import { computeFileHealth } from '../health.js';
5
+ import { detectToolsWired } from '../registry.js';
6
+ import { getCeilingLines, countLines } from '../../core/compressionConfig.js';
7
+ import { escapeHtml, pageShell, freshnessTier, formatAge } from './layout.js';
8
+ function renderFileBrowser(memoryRoot) {
9
+ const groups = {};
10
+ for (const file of WRITABLE_FILES) {
11
+ if (file === 'context/currentMentalModel.md')
12
+ continue;
13
+ const [domain] = file.split('/');
14
+ (groups[domain] ??= []).push(file);
15
+ }
16
+ const health = computeFileHealth(memoryRoot);
17
+ const healthByFile = Object.fromEntries(health.map((h) => [h.file, h]));
18
+ const sections = Object.entries(groups).map(([domain, files]) => {
19
+ const items = files.map((file) => {
20
+ const path = join(memoryRoot, file);
21
+ const content = existsSync(path) ? readFileSync(path, 'utf-8').trim() : '';
22
+ const staleness = healthByFile[file]?.staleDays;
23
+ const lastUpdated = healthByFile[file]?.lastUpdated;
24
+ const tier = freshnessTier(staleness ?? null);
25
+ const stalenessLabel = lastUpdated ? formatAge(Date.now() - new Date(lastUpdated).getTime()) : 'never updated';
26
+ const lines = countLines(content);
27
+ const ceiling = getCeilingLines(memoryRoot, file);
28
+ const sizeClass = lines > ceiling ? 'stale' : 'muted';
29
+ const sizeLabel = `${lines}/${ceiling} lines`;
30
+ return `<details><summary>${escapeHtml(file)} <span class="muted stale-label ${tier}">(${stalenessLabel})</span> <span class="${sizeClass}">${escapeHtml(sizeLabel)}</span></summary><pre>${escapeHtml(content || '(empty)')}</pre></details>`;
31
+ }).join('\n');
32
+ return `<h3>${escapeHtml(domain)}</h3>\n${items}`;
33
+ });
34
+ return sections.join('\n');
35
+ }
36
+ function readEvents(memoryRoot) {
37
+ const eventsPath = join(memoryRoot, 'memory-events.jsonl');
38
+ if (!existsSync(eventsPath))
39
+ return [];
40
+ return readFileSync(eventsPath, 'utf-8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
41
+ }
42
+ // `session-load` events exist for KPI analysis (how often is this project's memory actually
43
+ // read, how many tokens does a session bootstrap cost), not to narrate "what changed" - the
44
+ // question this timeline exists to answer. A `load` fires every session start, so mixing it in
45
+ // unfiltered would bury real content changes under routine reads. Still fully inspectable via
46
+ // ?type=session-load - this only affects the unfiltered default view.
47
+ const TELEMETRY_ONLY_TYPES = new Set(['session-load']);
48
+ function renderEventTimeline(memoryRoot, typeFilter) {
49
+ const events = readEvents(memoryRoot);
50
+ if (events.length === 0)
51
+ return '<p class="muted">No events yet.</p>';
52
+ const filtered = typeFilter
53
+ ? events.filter((e) => e.type === typeFilter)
54
+ : events.filter((e) => !TELEMETRY_ONLY_TYPES.has(e.type));
55
+ if (filtered.length === 0)
56
+ return '<p class="muted">No events match this filter.</p>';
57
+ // update() logs one event per file it writes, not one per `update` call - a single logical
58
+ // checkpoint spanning N files (e.g. currentMentalModel.md + progress.md for the same change)
59
+ // produces N events with the identical, agent-written `summary` text. Without the affected
60
+ // file shown, two such events render as indistinguishable back-to-back lines - reads as a
61
+ // duplicate even though they're two real, distinct writes. See business/roadmap.md "Next".
62
+ return filtered.slice().reverse().map((e) => {
63
+ const files = Array.isArray(e.affectedFiles) ? e.affectedFiles : [];
64
+ const filesLabel = files.length > 0 ? `<span class="muted timeline-files">${escapeHtml(files.join(', '))}</span>` : '';
65
+ return `<div class="timeline-entry"><span class="tag">${escapeHtml(e.type)}</span>${escapeHtml(e.summary)} ${filesLabel}<div class="muted">${escapeHtml(e.timestamp)}</div></div>`;
66
+ }).join('\n');
67
+ }
68
+ // KPI summary for `session-load` events: how often this project's memory is actually read, and
69
+ // roughly how much it costs per read. `totalChars` on each event is what was actually loaded at
70
+ // that moment - avgTokens is a ~4-chars/token estimate (a standard approximation, not a real
71
+ // tokenizer count; load time has no access to one).
72
+ function renderSessionActivity(memoryRoot, projectRoot) {
73
+ const loads = readEvents(memoryRoot).filter((e) => e.type === 'session-load');
74
+ if (loads.length === 0)
75
+ return '<p class="muted">No session loads recorded yet.</p>';
76
+ const totalChars = loads.reduce((sum, e) => sum + (typeof e.totalChars === 'number' ? e.totalChars : 0), 0);
77
+ const avgTokens = Math.round(totalChars / loads.length / 4);
78
+ const last = loads[loads.length - 1];
79
+ const filterHref = `/project?path=${encodeURIComponent(projectRoot)}&type=session-load`;
80
+ return `<p><strong>${loads.length}</strong> session load(s) recorded &middot; avg ~${avgTokens} tokens/load (est.) &middot; last: ${formatAge(Date.now() - new Date(last.timestamp).getTime())}</p>
81
+ <p class="muted"><a href="${escapeHtml(filterHref)}">view raw load events</a></p>`;
82
+ }
83
+ export function renderProjectPage(projectRoot, options = {}) {
84
+ const memoryRoot = join(projectRoot, '.memoryintel');
85
+ const mentalModelPath = join(memoryRoot, 'context', 'currentMentalModel.md');
86
+ const mentalModel = existsSync(mentalModelPath) ? readFileSync(mentalModelPath, 'utf-8').trim() : '(no mental model yet)';
87
+ const tools = detectToolsWired(projectRoot);
88
+ const toolsHtml = tools.map((t) => `<span class="tag">${escapeHtml(t)}</span>`).join('') || '<span class="muted">no tools wired</span>';
89
+ const body = `
90
+ <a href="/">&larr; All projects</a>
91
+ <div class="eyebrow" style="margin-top: 1rem;">${escapeHtml(basename(projectRoot))}</div>
92
+ <h1>${escapeHtml(basename(projectRoot))}</h1>
93
+ <div class="path">${escapeHtml(projectRoot)}</div>
94
+
95
+ <h2>Current understanding</h2>
96
+ <div class="mental-model">${escapeHtml(mentalModel)}</div>
97
+
98
+ <h2>Automation status</h2>
99
+ <p>${toolsHtml}</p>
100
+
101
+ <h2>Session activity</h2>
102
+ ${renderSessionActivity(memoryRoot, projectRoot)}
103
+
104
+ <h2>Memory files</h2>
105
+ ${renderFileBrowser(memoryRoot)}
106
+
107
+ <h2>Event timeline</h2>
108
+ ${renderEventTimeline(memoryRoot, options.typeFilter)}
109
+ `;
110
+ return pageShell(projectRoot, body);
111
+ }
@@ -0,0 +1,54 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, basename } from 'node:path';
3
+ import { escapeHtml, pageShell, freshnessTier, daysSince } from './layout.js';
4
+ function mentalModelPreview(projectPath) {
5
+ const path = join(projectPath, '.memoryintel', 'context', 'currentMentalModel.md');
6
+ if (!existsSync(path))
7
+ return '(no mental model yet)';
8
+ return readFileSync(path, 'utf-8').trim().split('\n')[0] ?? '(empty)';
9
+ }
10
+ const DASHBOARD_CONTROLS = `
11
+ <div class="dashboard-controls">
12
+ <form method="POST" action="/stop">
13
+ <button type="submit" class="btn-stop">Stop dashboard</button>
14
+ </form>
15
+ <p class="muted">Stops the daemon and disables the dashboard for every Memory Intel project on this machine. <code>memoryintel dashboard enable</code> turns it back on, or it starts itself again the next time any project needs it.</p>
16
+ </div>
17
+ `;
18
+ export function renderRegistryPage(entries) {
19
+ const projectPaths = Object.keys(entries);
20
+ if (projectPaths.length === 0) {
21
+ return pageShell('Memory Intel', `
22
+ <div class="eyebrow">Memory Intel</div>
23
+ <h1>Project registry</h1>
24
+ <p class="empty-state">No projects registered yet. Run <code>memoryintel init</code> in a project to see it here.</p>
25
+ ${DASHBOARD_CONTROLS}
26
+ `);
27
+ }
28
+ const cards = projectPaths.map((path) => {
29
+ const entry = entries[path];
30
+ if (!existsSync(path)) {
31
+ return `<div class="card missing stale">
32
+ <strong>${escapeHtml(basename(path))}</strong>
33
+ <div class="path">${escapeHtml(path)}</div>
34
+ <p>(missing — this project's directory no longer exists)</p>
35
+ </div>`;
36
+ }
37
+ const preview = mentalModelPreview(path);
38
+ const tier = freshnessTier(daysSince(entry.lastSessionAt));
39
+ const tools = entry.toolsWired.map((t) => `<span class="tag">${escapeHtml(t)}</span>`).join('') || '<span class="muted">no tools wired</span>';
40
+ return `<div class="card ${tier}">
41
+ <h3><a href="/project?path=${encodeURIComponent(path)}">${escapeHtml(basename(path))}</a></h3>
42
+ <div class="path">${escapeHtml(path)}</div>
43
+ <p class="mental-model" style="font-size: 1rem; margin-top: 0.6rem;">${escapeHtml(preview)}</p>
44
+ <p class="muted">Last session: ${escapeHtml(entry.lastSessionAt)}</p>
45
+ <p>${tools}</p>
46
+ </div>`;
47
+ });
48
+ return pageShell('Memory Intel', `
49
+ <div class="eyebrow">Memory Intel</div>
50
+ <h1>Project registry</h1>
51
+ ${cards.join('\n')}
52
+ ${DASHBOARD_CONTROLS}
53
+ `);
54
+ }
package/dist/skill.js ADDED
@@ -0,0 +1,46 @@
1
+ import { USAGE } from './cli.js';
2
+ export function createSkillMarkdown() {
3
+ return `---
4
+ name: memoryintel
5
+ description: Give an AI coding agent persistent, cross-session project memory. Use when the user asks to set up persistent project memory, or when the current project already contains a .memoryintel/ directory.
6
+ ---
7
+
8
+ # Memory Intel
9
+
10
+ Memory Intel gives an AI coding agent durable, cross-session understanding of a project —
11
+ architecture, decisions, progress, and a running "mental model" — that survives new chats, new
12
+ agent sessions, and switching tools entirely (Claude Code, Cursor, Codex, Gemini CLI).
13
+
14
+ ## First time in this project? (no \`.memoryintel/\` yet)
15
+
16
+ If the user asks to set up persistent project memory, run:
17
+
18
+ npx -y memoryintel init
19
+
20
+ This is a one-time step. It scaffolds \`.memoryintel/\` and installs pointer files for tools without
21
+ native hook support (Cursor, Codex, Gemini CLI, opencode) — safe to re-run later, it never
22
+ overwrites existing content. Claude Code automation doesn't come from this command at all: it comes
23
+ from the memoryintel plugin's own bundled hooks, active globally for every project once the plugin
24
+ itself is installed — \`init\` never touches \`.claude/settings.json\`.
25
+
26
+ ## Already initialized? (\`.memoryintel/\` exists)
27
+
28
+ Read \`.memoryintel/instructions.md\` first — it is the authoritative, per-project guide. In short:
29
+
30
+ - At the start of a session: run \`memoryintel load [--domain technical|business|research]\` and
31
+ treat the output as project context.
32
+ - At the end of a session, only if your work changed real project understanding (new architecture,
33
+ decision, feature, integration, or roadmap item — never for formatting/typos): draft an
34
+ update-plan and run \`memoryintel update\`.
35
+ - If the user asks to turn the dashboard on or off: \`memoryintel dashboard enable\` /
36
+ \`memoryintel dashboard disable\`.
37
+
38
+ ## Command reference
39
+
40
+ ${USAGE}
41
+ ## Sandboxed environments
42
+
43
+ If \`npx\`/a global \`memoryintel\` install aren't directly runnable, fall back to invoking the
44
+ package's built CLI directly: \`node "$(npm root -g)/memoryintel/dist/cli.js" <command>\`.
45
+ `;
46
+ }
@@ -0,0 +1,22 @@
1
+ export const STARTER_FILES = [
2
+ { relPath: 'context/projectBrief.md', headings: ['Overview'] },
3
+ { relPath: 'context/objectives.md', headings: ['Objectives'] },
4
+ { relPath: 'context/activeContext.md', headings: ['Current Focus'] },
5
+ { relPath: 'context/decisions.md', headings: ['Decisions Log'] },
6
+ { relPath: 'context/progress.md', headings: ['Status'] },
7
+ { relPath: 'context/learnings.md', headings: ['Learnings'] },
8
+ { relPath: 'technical/architecture.md', headings: ['Overview', 'Components', 'Data Flow', 'Integrations'] },
9
+ { relPath: 'technical/techContext.md', headings: ['Stack', 'Conventions', 'Environment'] },
10
+ { relPath: 'technical/patterns.md', headings: ['Design Patterns', 'Anti-Patterns'] },
11
+ { relPath: 'technical/integrations.md', headings: ['External Services', 'Internal Dependencies'] },
12
+ { relPath: 'technical/infrastructure.md', headings: ['Deployment', 'Hosting', 'CI/CD'] },
13
+ { relPath: 'business/productContext.md', headings: ['Product Overview', 'Users', 'Value Proposition'] },
14
+ { relPath: 'business/roadmap.md', headings: ['Now', 'Next', 'Later'] },
15
+ { relPath: 'business/stakeholders.md', headings: ['Team', 'External Stakeholders'] },
16
+ { relPath: 'business/marketContext.md', headings: ['Market Overview', 'Competitors'] },
17
+ { relPath: 'research/findings.md', headings: ['Key Findings'] },
18
+ { relPath: 'research/references.md', headings: ['Sources'] },
19
+ { relPath: 'research/hypotheses.md', headings: ['Open Hypotheses'] }
20
+ ];
21
+ // No headings — always fully overwritten by `update`, never section-addressed.
22
+ export const MENTAL_MODEL_STARTER = '_No sessions yet._\n';
@@ -0,0 +1,11 @@
1
+ {
2
+ "description": "Load and maintain persistent project memory automatically",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ { "hooks": [{ "type": "command", "command": "npx -y memoryintel load" }] }
6
+ ],
7
+ "Stop": [
8
+ { "hooks": [{ "type": "command", "command": "npx -y memoryintel check-stop" }] }
9
+ ]
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "memoryintel",
3
+ "version": "1.0.0",
4
+ "description": "Persistent, cross-session project memory for AI coding agents.",
5
+ "type": "module",
6
+ "bin": {
7
+ "memoryintel": "./dist/cli.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/adeeshsharma/memoryintel.git"
12
+ },
13
+ "homepage": "https://github.com/adeeshsharma/memoryintel#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/adeeshsharma/memoryintel/issues"
16
+ },
17
+ "author": {
18
+ "name": "Adeesh Sharma",
19
+ "url": "https://github.com/adeeshsharma"
20
+ },
21
+ "keywords": [
22
+ "ai-agent",
23
+ "claude-code",
24
+ "cli",
25
+ "memory",
26
+ "context",
27
+ "skill",
28
+ "mcp"
29
+ ],
30
+ "license": "MIT",
31
+ "files": [
32
+ "dist",
33
+ "skills",
34
+ ".claude-plugin",
35
+ "hooks"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json && node scripts/build-skill.js && node scripts/ensure-executable.mjs",
39
+ "build:skill": "node scripts/build-skill.js",
40
+ "build:skill:check": "node scripts/build-skill.js --check",
41
+ "test": "vitest run",
42
+ "prepublishOnly": "npm run build && npm test"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^5.5.0",
49
+ "vitest": "^2.0.0",
50
+ "@types/node": "^20.0.0"
51
+ }
52
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: memoryintel
3
+ description: Give an AI coding agent persistent, cross-session project memory. Use when the user asks to set up persistent project memory, or when the current project already contains a .memoryintel/ directory.
4
+ ---
5
+
6
+ # Memory Intel
7
+
8
+ Memory Intel gives an AI coding agent durable, cross-session understanding of a project —
9
+ architecture, decisions, progress, and a running "mental model" — that survives new chats, new
10
+ agent sessions, and switching tools entirely (Claude Code, Cursor, Codex, Gemini CLI).
11
+
12
+ ## First time in this project? (no `.memoryintel/` yet)
13
+
14
+ If the user asks to set up persistent project memory, run:
15
+
16
+ npx -y memoryintel init
17
+
18
+ This is a one-time step. It scaffolds `.memoryintel/` and installs pointer files for tools without
19
+ native hook support (Cursor, Codex, Gemini CLI, opencode) — safe to re-run later, it never
20
+ overwrites existing content. Claude Code automation doesn't come from this command at all: it comes
21
+ from the memoryintel plugin's own bundled hooks, active globally for every project once the plugin
22
+ itself is installed — `init` never touches `.claude/settings.json`.
23
+
24
+ ## Already initialized? (`.memoryintel/` exists)
25
+
26
+ Read `.memoryintel/instructions.md` first — it is the authoritative, per-project guide. In short:
27
+
28
+ - At the start of a session: run `memoryintel load [--domain technical|business|research]` and
29
+ treat the output as project context.
30
+ - At the end of a session, only if your work changed real project understanding (new architecture,
31
+ decision, feature, integration, or roadmap item — never for formatting/typos): draft an
32
+ update-plan and run `memoryintel update`.
33
+ - If the user asks to turn the dashboard on or off: `memoryintel dashboard enable` /
34
+ `memoryintel dashboard disable`.
35
+
36
+ ## Command reference
37
+
38
+ Usage: memoryintel <command> [options]
39
+
40
+ Commands:
41
+ init [path] Initialize .memoryintel/ in the current or given directory
42
+ load [--domain <d>] Print resolved memory context to stdout
43
+ update <plan.toon|-> Apply an update-plan (file path, or - for stdin)
44
+ status Print a human-readable summary of current memory state
45
+ check-stop Stop-hook check: emit a JSON allow/block decision
46
+ dashboard <enable|disable> Turn the shared local dashboard on or off
47
+ daemon start Run the dashboard daemon in the foreground (usually auto-started)
48
+
49
+ An update-plan row may set kind=compress to compact an oversized section; update() only applies
50
+ such a row when its target file is currently git-clean.
51
+
52
+ ## Sandboxed environments
53
+
54
+ If `npx`/a global `memoryintel` install aren't directly runnable, fall back to invoking the
55
+ package's built CLI directly: `node "$(npm root -g)/memoryintel/dist/cli.js" <command>`.