@remnic/plugin-claude-code 1.0.1 → 9.3.515
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-plugin/plugin.json +1 -1
- package/hooks/bin/session-start.sh +117 -13
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remnic",
|
|
3
3
|
"description": "Universal memory for AI agents — automatic recall, observation, and cross-agent knowledge sharing",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"author": "Joshua Warren",
|
|
6
6
|
"homepage": "https://github.com/joshuaswarren/remnic",
|
|
7
7
|
"repository": "https://github.com/joshuaswarren/remnic"
|
|
@@ -56,7 +56,87 @@ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write
|
|
|
56
56
|
CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd||'')" "$INPUT" 2>/dev/null || echo "")"
|
|
57
57
|
PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
# Resolve git context for the session's cwd (issue #569 PR 5). Produces
|
|
60
|
+
# either a JSON object for the `codingContext` field, or an empty string
|
|
61
|
+
# when the cwd is not inside a git repo. All git calls are wrapped in &&
|
|
62
|
+
# so any failure silently drops back to no-context.
|
|
63
|
+
CODING_CONTEXT_JSON=""
|
|
64
|
+
if [ -n "$CWD" ] && [ -d "$CWD" ] && command -v git >/dev/null 2>&1; then
|
|
65
|
+
# `git` calls are short-timeout and local. Any failure → empty.
|
|
66
|
+
REMNIC_GIT_TOP="$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
67
|
+
if [ -n "$REMNIC_GIT_TOP" ]; then
|
|
68
|
+
REMNIC_GIT_BRANCH="$(git -C "$REMNIC_GIT_TOP" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")"
|
|
69
|
+
[ "$REMNIC_GIT_BRANCH" = "HEAD" ] && REMNIC_GIT_BRANCH=""
|
|
70
|
+
REMNIC_GIT_ORIGIN="$(git -C "$REMNIC_GIT_TOP" remote get-url origin 2>/dev/null || echo "")"
|
|
71
|
+
REMNIC_GIT_DEFAULT_BRANCH="$(git -C "$REMNIC_GIT_TOP" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/origin/||' || echo "")"
|
|
72
|
+
CODING_CONTEXT_JSON="$(REMNIC_GIT_TOP="$REMNIC_GIT_TOP" REMNIC_GIT_BRANCH="$REMNIC_GIT_BRANCH" REMNIC_GIT_ORIGIN="$REMNIC_GIT_ORIGIN" REMNIC_GIT_DEFAULT_BRANCH="$REMNIC_GIT_DEFAULT_BRANCH" node -e "
|
|
73
|
+
// Mirror the pure logic from @remnic/core's resolveGitContext so the
|
|
74
|
+
// hook produces the same projectId without calling into the daemon
|
|
75
|
+
// first. FNV-1a 32-bit stable hash.
|
|
76
|
+
const rootPath = process.env.REMNIC_GIT_TOP || '';
|
|
77
|
+
const branch = process.env.REMNIC_GIT_BRANCH || null;
|
|
78
|
+
const origin = process.env.REMNIC_GIT_ORIGIN || '';
|
|
79
|
+
const defaultBranch = process.env.REMNIC_GIT_DEFAULT_BRANCH || null;
|
|
80
|
+
function stableHash(input) {
|
|
81
|
+
let hash = 0x811c9dc5;
|
|
82
|
+
for (let i = 0; i < input.length; i++) {
|
|
83
|
+
hash ^= input.charCodeAt(i);
|
|
84
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
85
|
+
}
|
|
86
|
+
return hash.toString(16).padStart(8, '0');
|
|
87
|
+
}
|
|
88
|
+
// Mirrors packages/remnic-core/src/coding/git-context.ts
|
|
89
|
+
// normalizeOriginUrl. Keep the two in sync so the hook-computed
|
|
90
|
+
// projectId matches what the daemon computes on the same origin.
|
|
91
|
+
function normalizeOriginUrl(raw) {
|
|
92
|
+
let u = (raw || '').trim();
|
|
93
|
+
if (!u) return '';
|
|
94
|
+
// Case-insensitive .git strip — matches the TS canonical form.
|
|
95
|
+
if (/\\.git\$/i.test(u)) u = u.slice(0, -4);
|
|
96
|
+
// Windows drive-letter: short-circuit scp parsing.
|
|
97
|
+
if (/^[A-Za-z]:[\\\\/]/.test(u)) return u.toLowerCase();
|
|
98
|
+
// Protocol form: handles ssh://, https://, file:///, bracketed
|
|
99
|
+
// IPv6 hosts, optional user, optional port, and empty host
|
|
100
|
+
// (file:///path).
|
|
101
|
+
const proto = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^@/]+@)?(\\[[^\\]]+\\]|[^/:]*)(?::(\\d+))?(\\/.*)?\$/i.exec(u);
|
|
102
|
+
if (proto) {
|
|
103
|
+
let host = proto[1] || '';
|
|
104
|
+
const wasBracketed = host.startsWith('[') && host.endsWith(']');
|
|
105
|
+
if (wasBracketed) host = host.slice(1, -1);
|
|
106
|
+
const port = proto[2];
|
|
107
|
+
const p = (proto[3] || '').replace(/^\\/+/, '');
|
|
108
|
+
const hostPort = port
|
|
109
|
+
? (wasBracketed ? '[' + host + ']:' + port : host + ':' + port)
|
|
110
|
+
: host;
|
|
111
|
+
const prefix = hostPort.length > 0 ? hostPort : 'localhost';
|
|
112
|
+
return (prefix + '/' + p).toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
// scp form: [user@]host:path — user@ optional, bracketed IPv6 host
|
|
115
|
+
// supported. A matched path starting with // is a protocol-URL
|
|
116
|
+
// leftover and is rejected.
|
|
117
|
+
const scp = /^(?:([^@\\s\\/]+)@)?(\\[[^\\]]+\\]|[^:@\\s\\/]+):(.+)\$/.exec(u);
|
|
118
|
+
if (scp) {
|
|
119
|
+
let host = scp[2] || '';
|
|
120
|
+
if (host.startsWith('[') && host.endsWith(']')) host = host.slice(1, -1);
|
|
121
|
+
const p = scp[3] || '';
|
|
122
|
+
if (p.startsWith('//')) return u.toLowerCase();
|
|
123
|
+
return (host + '/' + p.replace(/^\\/+/, '')).toLowerCase();
|
|
124
|
+
}
|
|
125
|
+
return u.toLowerCase();
|
|
126
|
+
}
|
|
127
|
+
const normalized = normalizeOriginUrl(origin);
|
|
128
|
+
const projectId = normalized ? 'origin:' + stableHash(normalized) : 'root:' + stableHash(rootPath);
|
|
129
|
+
process.stdout.write(JSON.stringify({
|
|
130
|
+
projectId,
|
|
131
|
+
branch: branch || null,
|
|
132
|
+
rootPath,
|
|
133
|
+
defaultBranch: defaultBranch || null,
|
|
134
|
+
}));
|
|
135
|
+
" 2>/dev/null || echo "")"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
log "session=$SESSION_ID project=$PROJECT_NAME coding-context=${CODING_CONTEXT_JSON:+yes}"
|
|
60
140
|
|
|
61
141
|
# Health check — start daemon if not running
|
|
62
142
|
if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
|
|
@@ -82,12 +162,29 @@ fi
|
|
|
82
162
|
|
|
83
163
|
QUERY="Starting a new coding session in project: ${PROJECT_NAME}. Recall relevant memories, preferences, decisions, patterns, and context about this project and the user."
|
|
84
164
|
|
|
85
|
-
REQUEST_BODY="$(node -e "
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
165
|
+
REQUEST_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
|
|
166
|
+
const body = {
|
|
167
|
+
query: process.argv[1],
|
|
168
|
+
sessionKey: process.argv[2],
|
|
169
|
+
topK: 12,
|
|
170
|
+
mode: 'auto',
|
|
171
|
+
};
|
|
172
|
+
const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
|
|
173
|
+
if (raw) {
|
|
174
|
+
try { body.codingContext = JSON.parse(raw); } catch (_) {
|
|
175
|
+
// Context envelope was provided but failed to parse. Explicitly
|
|
176
|
+
// clear any previously-attached context for this session so a
|
|
177
|
+
// malformed envelope does not silently keep stale state.
|
|
178
|
+
body.codingContext = null;
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
// No git context resolvable for this cwd. Explicitly clear any
|
|
182
|
+
// previously-attached context so a session that moves out of a repo
|
|
183
|
+
// does not keep routing to the old project namespace.
|
|
184
|
+
body.codingContext = null;
|
|
185
|
+
}
|
|
186
|
+
process.stdout.write(JSON.stringify(body));
|
|
187
|
+
" "$QUERY" "$SESSION_ID" 2>/dev/null)"
|
|
91
188
|
|
|
92
189
|
[ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
|
|
93
190
|
|
|
@@ -104,12 +201,19 @@ RESPONSE="$(echo "$RAW" | sed '$d')"
|
|
|
104
201
|
|
|
105
202
|
if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
|
|
106
203
|
log "full recall failed (curl=$CURL_EXIT http=$HTTP_STATUS) — falling back to minimal"
|
|
107
|
-
MINIMAL_BODY="$(node -e "
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
204
|
+
MINIMAL_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
|
|
205
|
+
const body = {
|
|
206
|
+
query: process.argv[1],
|
|
207
|
+
sessionKey: process.argv[2],
|
|
208
|
+
topK: 8,
|
|
209
|
+
mode: 'minimal',
|
|
210
|
+
};
|
|
211
|
+
const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
|
|
212
|
+
if (raw) {
|
|
213
|
+
try { body.codingContext = JSON.parse(raw); } catch (_) { /* ignore */ }
|
|
214
|
+
}
|
|
215
|
+
process.stdout.write(JSON.stringify(body));
|
|
216
|
+
" "$QUERY" "$SESSION_ID" 2>/dev/null)"
|
|
113
217
|
RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
|
|
114
218
|
-X POST "$REMNIC_URL" \
|
|
115
219
|
-H "Authorization: Bearer ${REMNIC_TOKEN}" \
|