gramatr 0.3.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/CLAUDE.md +18 -0
- package/README.md +78 -0
- package/bin/clean-legacy-install.ts +28 -0
- package/bin/get-token.py +3 -0
- package/bin/gmtr-login.ts +547 -0
- package/bin/gramatr.js +33 -0
- package/bin/gramatr.ts +248 -0
- package/bin/install.ts +756 -0
- package/bin/render-claude-hooks.ts +16 -0
- package/bin/statusline.ts +437 -0
- package/bin/uninstall.ts +289 -0
- package/bin/version-sync.ts +46 -0
- package/codex/README.md +28 -0
- package/codex/hooks/session-start.ts +73 -0
- package/codex/hooks/stop.ts +34 -0
- package/codex/hooks/user-prompt-submit.ts +76 -0
- package/codex/install.ts +99 -0
- package/codex/lib/codex-hook-utils.ts +48 -0
- package/codex/lib/codex-install-utils.ts +123 -0
- package/core/feedback.ts +55 -0
- package/core/formatting.ts +167 -0
- package/core/install.ts +114 -0
- package/core/installer-cli.ts +122 -0
- package/core/migration.ts +244 -0
- package/core/routing.ts +98 -0
- package/core/session.ts +202 -0
- package/core/targets.ts +292 -0
- package/core/types.ts +178 -0
- package/core/version.ts +2 -0
- package/gemini/README.md +95 -0
- package/gemini/hooks/session-start.ts +72 -0
- package/gemini/hooks/stop.ts +30 -0
- package/gemini/hooks/user-prompt-submit.ts +74 -0
- package/gemini/install.ts +272 -0
- package/gemini/lib/gemini-hook-utils.ts +63 -0
- package/gemini/lib/gemini-install-utils.ts +169 -0
- package/hooks/GMTRPromptEnricher.hook.ts +650 -0
- package/hooks/GMTRRatingCapture.hook.ts +198 -0
- package/hooks/GMTRSecurityValidator.hook.ts +399 -0
- package/hooks/GMTRToolTracker.hook.ts +181 -0
- package/hooks/StopOrchestrator.hook.ts +78 -0
- package/hooks/gmtr-tool-tracker-utils.ts +105 -0
- package/hooks/lib/gmtr-hook-utils.ts +771 -0
- package/hooks/lib/identity.ts +227 -0
- package/hooks/lib/notify.ts +46 -0
- package/hooks/lib/paths.ts +104 -0
- package/hooks/lib/transcript-parser.ts +452 -0
- package/hooks/session-end.hook.ts +168 -0
- package/hooks/session-start.hook.ts +490 -0
- package/package.json +54 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* session-start.hook.ts — gramatr SessionStart Hook
|
|
4
|
+
*
|
|
5
|
+
* Replaces session-start.sh (529 lines). Initializes project context,
|
|
6
|
+
* registers session with gramatr server, displays banner and restore info.
|
|
7
|
+
*
|
|
8
|
+
* TRIGGER: SessionStart
|
|
9
|
+
* OUTPUT: stderr (user display), stdout (Claude context via current-project-context.json)
|
|
10
|
+
*
|
|
11
|
+
* ZERO external CLI dependencies — no jq, sed, curl, awk.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
import { existsSync, readFileSync, mkdirSync } from 'fs';
|
|
16
|
+
import { join, basename } from 'path';
|
|
17
|
+
import {
|
|
18
|
+
type GmtrConfig,
|
|
19
|
+
readHookInput,
|
|
20
|
+
getGitContext,
|
|
21
|
+
readGmtrConfig,
|
|
22
|
+
writeGmtrConfig,
|
|
23
|
+
checkServerHealth,
|
|
24
|
+
log,
|
|
25
|
+
now,
|
|
26
|
+
} from './lib/gmtr-hook-utils.ts';
|
|
27
|
+
import {
|
|
28
|
+
buildGitProjectContextPayload,
|
|
29
|
+
buildNonGitProjectContextPayload,
|
|
30
|
+
normalizeSessionStartResponse,
|
|
31
|
+
prepareProjectSessionState,
|
|
32
|
+
startRemoteSession,
|
|
33
|
+
writeCurrentProjectContextFile,
|
|
34
|
+
persistSessionRegistration,
|
|
35
|
+
} from '../core/session.ts';
|
|
36
|
+
|
|
37
|
+
// ── stdout (Claude context injection) ──
|
|
38
|
+
// Claude Code captures stdout from SessionStart hooks and injects it as context.
|
|
39
|
+
// stderr (via log()) is for terminal display only.
|
|
40
|
+
|
|
41
|
+
function emitStdout(msg: string): void {
|
|
42
|
+
process.stdout.write(msg + '\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write structured context to stdout so Claude receives it as injected context.
|
|
47
|
+
* This is the critical "last mile" — without this, Claude never sees session continuity data.
|
|
48
|
+
*/
|
|
49
|
+
function emitClaudeContext(opts: {
|
|
50
|
+
projectName: string;
|
|
51
|
+
projectId: string;
|
|
52
|
+
branch: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
interactionId: string | null;
|
|
55
|
+
resumed: boolean;
|
|
56
|
+
handoffContext: string | null;
|
|
57
|
+
projectEntityId: string | null;
|
|
58
|
+
config: GmtrConfig | null;
|
|
59
|
+
}): void {
|
|
60
|
+
const lines: string[] = [];
|
|
61
|
+
|
|
62
|
+
lines.push('grāmatr Session Context (Auto-loaded at Session Start)');
|
|
63
|
+
lines.push('');
|
|
64
|
+
|
|
65
|
+
// Project identity
|
|
66
|
+
lines.push(`Project: ${opts.projectName} (${opts.projectId})`);
|
|
67
|
+
lines.push(`Branch: ${opts.branch}`);
|
|
68
|
+
if (opts.interactionId) {
|
|
69
|
+
lines.push(`Interaction: ${opts.interactionId}${opts.resumed ? ' (resumed)' : ' (new)'}`);
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
|
|
73
|
+
// Session continuity — the critical piece
|
|
74
|
+
if (opts.resumed && opts.handoffContext) {
|
|
75
|
+
lines.push('## Previous Session Handoff');
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(opts.handoffContext);
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push('Present this context to the user automatically — they expect seamless session continuity.');
|
|
80
|
+
lines.push('Query gramatr memory (search_semantic) to enrich this handoff with additional details if needed.');
|
|
81
|
+
lines.push('');
|
|
82
|
+
} else if (opts.resumed) {
|
|
83
|
+
// Resumed but no handoff content — fall back to config data
|
|
84
|
+
lines.push('## Session Resumed (no handoff context available)');
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push('This session resumed an existing interaction but the previous session did not save handoff context.');
|
|
87
|
+
lines.push('Query gramatr memory (search_semantic) for recent session activity on this project.');
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Last compact context from .gmtr/settings.json
|
|
92
|
+
const compact = opts.config?.last_compact;
|
|
93
|
+
if (compact?.summary) {
|
|
94
|
+
lines.push('## Last Session Summary (from local cache)');
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push(compact.summary);
|
|
97
|
+
lines.push('');
|
|
98
|
+
|
|
99
|
+
if (compact.metadata?.files_changed?.length) {
|
|
100
|
+
lines.push('Files modified: ' + compact.metadata.files_changed.join(', '));
|
|
101
|
+
}
|
|
102
|
+
if (compact.metadata?.commits?.length) {
|
|
103
|
+
lines.push('Commits: ' + compact.metadata.commits.join('; '));
|
|
104
|
+
}
|
|
105
|
+
if (compact.metadata?.files_changed?.length || compact.metadata?.commits?.length) {
|
|
106
|
+
lines.push('');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Recent commits file
|
|
111
|
+
const home = process.env.HOME || '';
|
|
112
|
+
const lastCommitsFile = join(home, '.claude', 'last-session-commits.txt');
|
|
113
|
+
if (existsSync(lastCommitsFile)) {
|
|
114
|
+
try {
|
|
115
|
+
const commits = readFileSync(lastCommitsFile, 'utf8').trim();
|
|
116
|
+
if (commits) {
|
|
117
|
+
lines.push('## Recent Commits (from last session)');
|
|
118
|
+
lines.push('');
|
|
119
|
+
const commitLines = commits.split('\n').slice(0, 5);
|
|
120
|
+
for (const cl of commitLines) {
|
|
121
|
+
lines.push(` ${cl}`);
|
|
122
|
+
}
|
|
123
|
+
lines.push('');
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Silent — non-critical
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Session stats
|
|
131
|
+
const stats = opts.config?.continuity_stats;
|
|
132
|
+
if (stats?.total_sessions && stats.total_sessions > 1) {
|
|
133
|
+
lines.push(`Session #${stats.total_sessions} on this project.`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Only emit if we have meaningful content beyond the header
|
|
138
|
+
if (lines.length > 3) {
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
emitStdout(line);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Banner ──
|
|
146
|
+
|
|
147
|
+
function displayBanner(): void {
|
|
148
|
+
const CYAN = '\x1b[1;36m';
|
|
149
|
+
const WHITE = '\x1b[1;37m';
|
|
150
|
+
const DIM = '\x1b[0;90m';
|
|
151
|
+
const RESET = '\x1b[0m';
|
|
152
|
+
|
|
153
|
+
log('');
|
|
154
|
+
log(`${CYAN}\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e${RESET}`);
|
|
155
|
+
log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
|
|
156
|
+
log(`${CYAN}\u2502${RESET} ${CYAN} __ _ ${WHITE} _ __ __ _ _ __ ___ __ _${CYAN}| |_ _ __${RESET} ${CYAN}\u2502${RESET}`);
|
|
157
|
+
log(`${CYAN}\u2502${RESET} ${CYAN}/ _\` |${WHITE}| '__/ _\` | '_ \` _ \\ / _\` |${CYAN}| __| '__|${RESET} ${CYAN}\u2502${RESET}`);
|
|
158
|
+
log(`${CYAN}\u2502${RESET} ${CYAN}| (_| |${WHITE}| | | (_| | | | | | | (_| |${CYAN}| |_| |${RESET} ${CYAN}\u2502${RESET}`);
|
|
159
|
+
log(`${CYAN}\u2502${RESET} ${CYAN} \\__, |${WHITE}|_| \\__,_|_| |_| |_|\\__,_|${CYAN} \\__|_|${RESET} ${CYAN}\u2502${RESET}`);
|
|
160
|
+
log(`${CYAN}\u2502${RESET} ${CYAN} |___/${RESET} ${CYAN}\u2502${RESET}`);
|
|
161
|
+
log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
|
|
162
|
+
log(`${CYAN}\u2502${RESET} ${WHITE}Your cross-agent AI brain${RESET} ${CYAN}\u2502${RESET}`);
|
|
163
|
+
log(`${CYAN}\u2502${RESET} ${DIM}\u00a9 2026 gr\u0101matr, LLC. All rights reserved.${RESET} ${CYAN}\u2502${RESET}`);
|
|
164
|
+
log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
|
|
165
|
+
log(`${CYAN}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f${RESET}`);
|
|
166
|
+
log('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Sync Ratings (background, non-blocking) ──
|
|
170
|
+
|
|
171
|
+
function syncRatingsInBackground(): void {
|
|
172
|
+
const syncScript = join(process.env.GMTR_DIR || join(process.env.HOME || '', 'gmtr-client'), 'hooks', 'sync-ratings.hook.ts');
|
|
173
|
+
if (existsSync(syncScript)) {
|
|
174
|
+
try {
|
|
175
|
+
const child = spawn('bun', ['run', syncScript], {
|
|
176
|
+
detached: true,
|
|
177
|
+
stdio: 'ignore',
|
|
178
|
+
});
|
|
179
|
+
child.unref();
|
|
180
|
+
} catch {
|
|
181
|
+
// Silent failure — non-critical
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Main ──
|
|
187
|
+
|
|
188
|
+
async function main(): Promise<void> {
|
|
189
|
+
try {
|
|
190
|
+
// Redirect all output to stderr so user sees it
|
|
191
|
+
// (stdout is captured by Claude Code for context)
|
|
192
|
+
|
|
193
|
+
// Display banner
|
|
194
|
+
displayBanner();
|
|
195
|
+
|
|
196
|
+
// Read hook input
|
|
197
|
+
const input = await readHookInput();
|
|
198
|
+
const sessionId = input.session_id || 'unknown';
|
|
199
|
+
const transcriptPath = input.transcript_path || '';
|
|
200
|
+
const cwd = input.cwd || process.cwd();
|
|
201
|
+
|
|
202
|
+
// Health check
|
|
203
|
+
log('Checking gramatr server health...');
|
|
204
|
+
const health = await checkServerHealth();
|
|
205
|
+
if (health.healthy) {
|
|
206
|
+
log('gramatr server: HEALTHY');
|
|
207
|
+
log(` Response: ${health.detail}`);
|
|
208
|
+
} else {
|
|
209
|
+
log(`gramatr server: UNHEALTHY (${health.detail})`);
|
|
210
|
+
log(' WARNING: Memory operations may fail');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Sync ratings in background
|
|
214
|
+
syncRatingsInBackground();
|
|
215
|
+
|
|
216
|
+
// Check git context
|
|
217
|
+
log('');
|
|
218
|
+
log('Checking current directory...');
|
|
219
|
+
|
|
220
|
+
const git = getGitContext();
|
|
221
|
+
const home = process.env.HOME || '';
|
|
222
|
+
const timestamp = now();
|
|
223
|
+
|
|
224
|
+
if (git) {
|
|
225
|
+
// ── Git Repository Path ──
|
|
226
|
+
const prepared = prepareProjectSessionState({
|
|
227
|
+
git,
|
|
228
|
+
sessionId,
|
|
229
|
+
transcriptPath,
|
|
230
|
+
});
|
|
231
|
+
const projectId = prepared.projectId;
|
|
232
|
+
|
|
233
|
+
log('Git repository detected');
|
|
234
|
+
log(` Project: ${git.projectName}`);
|
|
235
|
+
log(` Project ID: ${projectId}`);
|
|
236
|
+
log(` Branch: ${git.branch}`);
|
|
237
|
+
log(` Commit: ${git.commit}`);
|
|
238
|
+
log(` Remote: ${git.remote}`);
|
|
239
|
+
|
|
240
|
+
// Ensure .gmtr directory exists
|
|
241
|
+
const gmtrDir = join(git.root, '.gmtr');
|
|
242
|
+
if (!existsSync(gmtrDir)) {
|
|
243
|
+
mkdirSync(gmtrDir, { recursive: true });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let config = prepared.config;
|
|
247
|
+
const projectEntityId = prepared.projectEntityId;
|
|
248
|
+
const restoreNeeded = prepared.restoreNeeded;
|
|
249
|
+
const hasRestoreContext = prepared.hasRestoreContext;
|
|
250
|
+
|
|
251
|
+
log(prepared.created ? ' Creating new settings.json configuration (v2.1)' : ' Found settings.json configuration');
|
|
252
|
+
if (projectEntityId) {
|
|
253
|
+
log(` Project Entity ID: ${projectEntityId}`);
|
|
254
|
+
} else {
|
|
255
|
+
log(' Project entity not yet created in gramatr');
|
|
256
|
+
}
|
|
257
|
+
if (prepared.created) {
|
|
258
|
+
log(' Created settings.json (v2.1)');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Register session with gramatr server
|
|
262
|
+
const sessionStart = await startRemoteSession({
|
|
263
|
+
clientType: 'claude_code',
|
|
264
|
+
sessionId,
|
|
265
|
+
projectId,
|
|
266
|
+
projectName: git.projectName,
|
|
267
|
+
gitRemote: git.remote,
|
|
268
|
+
directory: git.root,
|
|
269
|
+
});
|
|
270
|
+
const sessionResult = normalizeSessionStartResponse(sessionStart);
|
|
271
|
+
config = persistSessionRegistration(git.root, sessionStart) || config;
|
|
272
|
+
|
|
273
|
+
if (sessionResult.interactionId) {
|
|
274
|
+
log(` Interaction: ${sessionResult.interactionId}`);
|
|
275
|
+
if (sessionResult.resumed) {
|
|
276
|
+
log(' Resumed existing interaction');
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
log(' gramatr session registration failed (non-blocking)');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── Emit structured context to stdout for Claude ──
|
|
283
|
+
emitClaudeContext({
|
|
284
|
+
projectName: git.projectName,
|
|
285
|
+
projectId,
|
|
286
|
+
branch: git.branch,
|
|
287
|
+
sessionId,
|
|
288
|
+
interactionId: sessionResult.interactionId,
|
|
289
|
+
resumed: sessionResult.resumed,
|
|
290
|
+
handoffContext: sessionResult.handoffContext,
|
|
291
|
+
projectEntityId,
|
|
292
|
+
config,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
writeCurrentProjectContextFile(
|
|
296
|
+
buildGitProjectContextPayload({
|
|
297
|
+
git,
|
|
298
|
+
sessionId,
|
|
299
|
+
workingDirectory: cwd,
|
|
300
|
+
sessionStart: timestamp,
|
|
301
|
+
projectId,
|
|
302
|
+
projectEntityId,
|
|
303
|
+
restoreNeeded,
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
log(' Project context saved to ~/.claude/current-project-context.json');
|
|
308
|
+
log('');
|
|
309
|
+
|
|
310
|
+
// Display instructions based on initialization status
|
|
311
|
+
const gmtrConfigPath = join(git.root, '.gmtr', 'settings.json');
|
|
312
|
+
|
|
313
|
+
if (!projectEntityId) {
|
|
314
|
+
log('gramatr initialization required');
|
|
315
|
+
log('');
|
|
316
|
+
log('Run: `/gmtr-init`');
|
|
317
|
+
log('');
|
|
318
|
+
log('This will:');
|
|
319
|
+
log(` 1. Search gramatr for existing project: ${git.projectName}`);
|
|
320
|
+
log(' 2. Create project entity if not found');
|
|
321
|
+
log(' 3. Link entity to .gmtr.json');
|
|
322
|
+
log(' 4. Enable full memory persistence');
|
|
323
|
+
log('');
|
|
324
|
+
} else {
|
|
325
|
+
log('Loading project context from gramatr');
|
|
326
|
+
log('');
|
|
327
|
+
log('Please load project context from gramatr:');
|
|
328
|
+
log('');
|
|
329
|
+
log(`1. Load project summary: \`mcp__gramatr__gmtr_execute_intent({intent: "Show recent activity for project ${git.projectName}", constraints: {entity_types: ["project"], max_results: 1}, detail_level: "summary"})\``);
|
|
330
|
+
log('2. Display brief summary:');
|
|
331
|
+
log(' - Project name and metadata');
|
|
332
|
+
log(' - Last 5-7 observations (recent activity)');
|
|
333
|
+
log(' - Show observation timestamps');
|
|
334
|
+
log(`3. Load related entities from \`${gmtrConfigPath}\`:`);
|
|
335
|
+
log(' - Check .related_entities for linked databases, people, services, concepts');
|
|
336
|
+
log(' - Optionally fetch details for key related entities (use gmtr_execute_intent with detail_level: summary)');
|
|
337
|
+
log('4. Keep summary concise - just enough context to resume work');
|
|
338
|
+
log(' NOTE: Using intelligent tools (gmtr_execute_intent) provides 80-95% token reduction vs direct get_entities calls');
|
|
339
|
+
log('');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Display restore context if available
|
|
343
|
+
if (hasRestoreContext && config?.last_compact) {
|
|
344
|
+
log('Context Restored from /gmtr-compact');
|
|
345
|
+
log('');
|
|
346
|
+
|
|
347
|
+
const compact = config.last_compact;
|
|
348
|
+
if (compact.summary) {
|
|
349
|
+
log('Last Session Summary:');
|
|
350
|
+
log(compact.summary);
|
|
351
|
+
log('');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (compact.metadata?.files_changed?.length) {
|
|
355
|
+
log('Files Modified:');
|
|
356
|
+
for (const f of compact.metadata.files_changed) {
|
|
357
|
+
log(` \u2022 ${f}`);
|
|
358
|
+
}
|
|
359
|
+
log('');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (compact.metadata?.commits?.length) {
|
|
363
|
+
log('Commits:');
|
|
364
|
+
for (const c of compact.metadata.commits) {
|
|
365
|
+
log(` \u2022 ${c}`);
|
|
366
|
+
}
|
|
367
|
+
log('');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const turnCount = compact.turns?.length || 0;
|
|
371
|
+
log(`Loaded from cache: ${turnCount} recent turns`);
|
|
372
|
+
log('Full conversation history in gramatr');
|
|
373
|
+
log('');
|
|
374
|
+
} else if (restoreNeeded) {
|
|
375
|
+
log('Full Context Restore Needed');
|
|
376
|
+
log('');
|
|
377
|
+
log('Last session was cleared/compacted.');
|
|
378
|
+
log('');
|
|
379
|
+
log('Run: `/gmtr-restore`');
|
|
380
|
+
log('');
|
|
381
|
+
log('This will restore:');
|
|
382
|
+
log(' \u2022 Last session summary');
|
|
383
|
+
log(' \u2022 Recent activity and decisions');
|
|
384
|
+
log(' \u2022 File changes and commits');
|
|
385
|
+
log('');
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
// ── Non-Git Path ──
|
|
389
|
+
log(' Not a git repository');
|
|
390
|
+
log(` Working directory: ${cwd}`);
|
|
391
|
+
|
|
392
|
+
const projectName = basename(cwd);
|
|
393
|
+
const gmtrConfigPath = join(cwd, '.gmtr', 'settings.json');
|
|
394
|
+
|
|
395
|
+
if (existsSync(gmtrConfigPath)) {
|
|
396
|
+
log(' Found settings.json configuration');
|
|
397
|
+
|
|
398
|
+
let config = readGmtrConfig(cwd);
|
|
399
|
+
if (config) {
|
|
400
|
+
const projectEntityId = config.project_entity_id || null;
|
|
401
|
+
|
|
402
|
+
// Update session stats
|
|
403
|
+
config.last_session_id = sessionId;
|
|
404
|
+
config.current_session = {
|
|
405
|
+
...config.current_session,
|
|
406
|
+
session_id: sessionId,
|
|
407
|
+
transcript_path: transcriptPath,
|
|
408
|
+
last_updated: timestamp,
|
|
409
|
+
token_limit: 200000,
|
|
410
|
+
};
|
|
411
|
+
config.continuity_stats = config.continuity_stats || {};
|
|
412
|
+
config.continuity_stats.total_sessions = (config.continuity_stats.total_sessions || 0) + 1;
|
|
413
|
+
config.metadata = config.metadata || {};
|
|
414
|
+
config.metadata.updated = timestamp;
|
|
415
|
+
|
|
416
|
+
writeGmtrConfig(cwd, config);
|
|
417
|
+
|
|
418
|
+
if (projectEntityId) {
|
|
419
|
+
log(` Project Entity ID: ${projectEntityId} (non-git project)`);
|
|
420
|
+
} else {
|
|
421
|
+
log(' Project entity not yet created in gramatr');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
writeCurrentProjectContextFile(
|
|
425
|
+
buildNonGitProjectContextPayload({
|
|
426
|
+
cwd,
|
|
427
|
+
sessionId,
|
|
428
|
+
sessionStart: timestamp,
|
|
429
|
+
projectEntityId,
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
log(' No settings.json found - project not initialized');
|
|
435
|
+
|
|
436
|
+
writeCurrentProjectContextFile(
|
|
437
|
+
buildNonGitProjectContextPayload({
|
|
438
|
+
cwd,
|
|
439
|
+
sessionId,
|
|
440
|
+
sessionStart: timestamp,
|
|
441
|
+
projectEntityId: null,
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
log(' Project context saved to ~/.claude/current-project-context.json');
|
|
447
|
+
log('');
|
|
448
|
+
log('gramatr initialization available');
|
|
449
|
+
log('');
|
|
450
|
+
log('This directory is not a git repository. You can still use gramatr.');
|
|
451
|
+
log('');
|
|
452
|
+
log('Run: `/gmtr-init`');
|
|
453
|
+
log('');
|
|
454
|
+
log('This will:');
|
|
455
|
+
log(' 1. Ask if you want to initialize git for this project');
|
|
456
|
+
log(' 2. Help set up git repository (if desired)');
|
|
457
|
+
log(' 3. Create gramatr project entity (git or non-git)');
|
|
458
|
+
log(' 4. Enable full gramatr memory integration');
|
|
459
|
+
log('');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Show recent commits from last session if available
|
|
463
|
+
const lastCommitsFile = join(home, '.claude', 'last-session-commits.txt');
|
|
464
|
+
if (existsSync(lastCommitsFile)) {
|
|
465
|
+
try {
|
|
466
|
+
const commits = readFileSync(lastCommitsFile, 'utf8').trim();
|
|
467
|
+
if (commits) {
|
|
468
|
+
log('');
|
|
469
|
+
log(' Recent commits from last session:');
|
|
470
|
+
const lines = commits.split('\n').slice(0, 3);
|
|
471
|
+
for (const line of lines) {
|
|
472
|
+
log(` ${line}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
// Silent failure
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
log('');
|
|
481
|
+
log('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');
|
|
482
|
+
log('Session initialization complete');
|
|
483
|
+
log('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');
|
|
484
|
+
} catch (err) {
|
|
485
|
+
// Never crash — fail gracefully
|
|
486
|
+
log(`[gramatr] session-start error: ${String(err)}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gramatr",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "gramatr — your cross-agent AI brain. Intelligence layer for Claude Code, Codex, Gemini CLI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/gramatr/gramatr.git",
|
|
9
|
+
"directory": "packages/client"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://gramatr.com",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"claude",
|
|
15
|
+
"mcp",
|
|
16
|
+
"intelligence",
|
|
17
|
+
"routing",
|
|
18
|
+
"classifier",
|
|
19
|
+
"memory",
|
|
20
|
+
"codex",
|
|
21
|
+
"gemini"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"gramatr": "./bin/gramatr.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"install-client": "npx tsx bin/install.ts",
|
|
28
|
+
"install-codex": "npx tsx codex/install.ts",
|
|
29
|
+
"install-gemini": "npx tsx gemini/install.ts",
|
|
30
|
+
"install-desktop": "npx tsx desktop/install.ts",
|
|
31
|
+
"install-chatgpt": "npx tsx chatgpt/install.ts",
|
|
32
|
+
"build-mcpb": "npx tsx desktop/build-mcpb.ts",
|
|
33
|
+
"install-web": "echo 'No local install needed. See packages/client/web/README.md for setup instructions.'",
|
|
34
|
+
"migrate-clean-install": "npx tsx bin/clean-legacy-install.ts --apply",
|
|
35
|
+
"gramatr": "npx tsx bin/gramatr.ts",
|
|
36
|
+
"lint": "pnpm exec biome lint --only=correctness/noUnusedImports --only=correctness/noUnusedVariables --only=correctness/noUndeclaredVariables --only=suspicious/noGlobalIsNan --files-ignore-unknown=true package.json bin chatgpt codex core desktop gemini hooks lib tools web vitest.config.ts",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:coverage": "vitest run --coverage",
|
|
39
|
+
"version:patch": "npm version patch --no-git-tag-version && npm run version:sync",
|
|
40
|
+
"version:minor": "npm version minor --no-git-tag-version && npm run version:sync",
|
|
41
|
+
"version:major": "npm version major --no-git-tag-version && npm run version:sync",
|
|
42
|
+
"version:sync": "npx tsx bin/version-sync.ts",
|
|
43
|
+
"prepublishOnly": "npm run version:sync"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20.0.0"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"tsx": "^4.21.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"vitest": "^4.0.18"
|
|
53
|
+
}
|
|
54
|
+
}
|