blun-king-cli 9.1.398 → 9.1.399
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/LIESMICH.txt +15 -0
- package/README.md +14 -0
- package/bin/validated-learning-signal.cjs +115 -8
- package/blun.mjs +2 -1
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -392,6 +392,21 @@ eingestuft ist. Fehlt dieser Beleg, bleibt das Ziel aktiv und King erhält eine
|
|
|
392
392
|
konkrete Nachbesserung statt einer falschen Fertigmeldung. Ziele ohne ausdrücklich
|
|
393
393
|
gesetzte Abschlussbedingung behalten ihr bisheriges Verhalten.
|
|
394
394
|
|
|
395
|
+
Projektbezogenes Lernen ohne Übersprechen
|
|
396
|
+
------------------------------------------
|
|
397
|
+
|
|
398
|
+
Ab BLUN King 9.1.399 bleiben bestätigte Rot-zu-Grün-Lernkandidaten innerhalb
|
|
399
|
+
des Git-Projekts, in dem sie entstanden sind. Zwei Arbeitskopien desselben
|
|
400
|
+
Remote-Repositories teilen denselben anonymisierten Projekt-Schlüssel; andere
|
|
401
|
+
Repositories erhalten getrennte Lernspeicher. Lokale Git-Projekte ohne Remote
|
|
402
|
+
werden anhand ihres Repository-Wurzelpfads getrennt.
|
|
403
|
+
|
|
404
|
+
Weder Remote-Adresse noch lokaler Projektpfad werden im Lernkandidaten
|
|
405
|
+
gespeichert. Außerhalb eines Git-Projekts bleibt der bisherige globale
|
|
406
|
+
Profilspeicher erhalten. Damit kann eine gemessene Projektregel später wieder
|
|
407
|
+
aufgerufen werden, ohne als vermeintlich allgemeine Regel in fachfremde Projekte
|
|
408
|
+
zu gelangen.
|
|
409
|
+
|
|
395
410
|
Keine doppelte Telegram-Antwort ohne neue Nachricht
|
|
396
411
|
---------------------------------------------------
|
|
397
412
|
|
package/README.md
CHANGED
|
@@ -401,6 +401,20 @@ eingestuft ist. Fehlt dieser Beleg, bleibt das Ziel aktiv und King erhält eine
|
|
|
401
401
|
konkrete Nachbesserung statt einer falschen Fertigmeldung. Ziele ohne ausdrücklich
|
|
402
402
|
gesetzte Abschlussbedingung behalten ihr bisheriges Verhalten.
|
|
403
403
|
|
|
404
|
+
## Projektbezogenes Lernen ohne Übersprechen
|
|
405
|
+
|
|
406
|
+
Ab BLUN King 9.1.399 bleiben bestätigte Rot-zu-Grün-Lernkandidaten innerhalb
|
|
407
|
+
des Git-Projekts, in dem sie entstanden sind. Zwei Arbeitskopien desselben
|
|
408
|
+
Remote-Repositories teilen denselben anonymisierten Projekt-Schlüssel; andere
|
|
409
|
+
Repositories erhalten getrennte Lernspeicher. Lokale Git-Projekte ohne Remote
|
|
410
|
+
werden anhand ihres Repository-Wurzelpfads getrennt.
|
|
411
|
+
|
|
412
|
+
Weder Remote-Adresse noch lokaler Projektpfad werden im Lernkandidaten
|
|
413
|
+
gespeichert. Außerhalb eines Git-Projekts bleibt der bisherige globale
|
|
414
|
+
Profilspeicher erhalten. Damit kann eine gemessene Projektregel später wieder
|
|
415
|
+
aufgerufen werden, ohne als vermeintlich allgemeine Regel in fachfremde Projekte
|
|
416
|
+
zu gelangen.
|
|
417
|
+
|
|
404
418
|
## Keine doppelte Telegram-Antwort ohne neue Nachricht
|
|
405
419
|
|
|
406
420
|
Ab BLUN King 9.1.398 prüft jeder Text-Ausgangspfad vor dem Senden die jüngste
|
|
@@ -115,13 +115,96 @@ function profileKey(value) {
|
|
|
115
115
|
return (normalized || 'default').replace(/[^a-z0-9._-]/giu, '_').slice(0, 64);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function
|
|
119
|
-
|
|
118
|
+
function normalizedRemote(value) {
|
|
119
|
+
let remote = typeof value === 'string' ? value.trim() : '';
|
|
120
|
+
if (!remote) return '';
|
|
121
|
+
remote = remote.replace(/^git@([^:]+):/iu, 'ssh://git@$1/');
|
|
122
|
+
try {
|
|
123
|
+
const parsed = new URL(remote);
|
|
124
|
+
const host = parsed.hostname.toLowerCase();
|
|
125
|
+
const pathname = parsed.pathname.replace(/^\/+|\/+$/gu, '').replace(/\.git$/iu, '').toLowerCase();
|
|
126
|
+
return host && pathname ? `${host}/${pathname}` : '';
|
|
127
|
+
} catch {
|
|
128
|
+
return remote.replace(/\\/gu, '/').replace(/\.git$/iu, '').toLowerCase();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function gitConfigPath(dotGitPath) {
|
|
133
|
+
try {
|
|
134
|
+
const stat = fs.lstatSync(dotGitPath);
|
|
135
|
+
if (stat.isDirectory()) return path.join(dotGitPath, 'config');
|
|
136
|
+
if (!stat.isFile()) return '';
|
|
137
|
+
const pointer = fs.readFileSync(dotGitPath, 'utf8').match(/^gitdir:\s*(.+)$/imu)?.[1]?.trim();
|
|
138
|
+
if (!pointer) return '';
|
|
139
|
+
const gitDir = path.resolve(path.dirname(dotGitPath), pointer);
|
|
140
|
+
const commonPointerPath = path.join(gitDir, 'commondir');
|
|
141
|
+
if (fs.existsSync(commonPointerPath)) {
|
|
142
|
+
const commonPointer = fs.readFileSync(commonPointerPath, 'utf8').trim();
|
|
143
|
+
if (commonPointer) return path.join(path.resolve(gitDir, commonPointer), 'config');
|
|
144
|
+
}
|
|
145
|
+
return path.join(gitDir, 'config');
|
|
146
|
+
} catch {
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function originRemote(configPath) {
|
|
152
|
+
if (!configPath) return '';
|
|
153
|
+
try {
|
|
154
|
+
const stat = fs.statSync(configPath);
|
|
155
|
+
if (!stat.isFile() || stat.size > 1024 * 1024) return '';
|
|
156
|
+
const lines = fs.readFileSync(configPath, 'utf8').split(/\r?\n/gu);
|
|
157
|
+
let inOrigin = false;
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
const section = line.match(/^\s*\[([^\]]+)\]\s*$/u)?.[1]?.trim();
|
|
160
|
+
if (section !== undefined) {
|
|
161
|
+
inOrigin = /^remote\s+"origin"$/iu.test(section);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!inOrigin) continue;
|
|
165
|
+
const url = line.match(/^\s*url\s*=\s*(.+?)\s*$/iu)?.[1];
|
|
166
|
+
if (url) return normalizedRemote(url);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
return '';
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveValidatedLearningProject(projectDir) {
|
|
175
|
+
const requested = typeof projectDir === 'string' ? projectDir.trim() : '';
|
|
176
|
+
if (!requested) return { scope: 'global', projectId: '' };
|
|
177
|
+
let current;
|
|
178
|
+
try {
|
|
179
|
+
current = fs.realpathSync(path.resolve(requested));
|
|
180
|
+
} catch {
|
|
181
|
+
return { scope: 'global', projectId: '' };
|
|
182
|
+
}
|
|
183
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
184
|
+
const dotGitPath = path.join(current, '.git');
|
|
185
|
+
if (fs.existsSync(dotGitPath)) {
|
|
186
|
+
const remote = originRemote(gitConfigPath(dotGitPath));
|
|
187
|
+
const identity = remote ? `remote:${remote}` : `path:${current.toLowerCase()}`;
|
|
188
|
+
return {
|
|
189
|
+
scope: 'project',
|
|
190
|
+
projectId: crypto.createHash('sha256').update(identity).digest('hex').slice(0, 16),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const parent = path.dirname(current);
|
|
194
|
+
if (parent === current) break;
|
|
195
|
+
current = parent;
|
|
196
|
+
}
|
|
197
|
+
return { scope: 'global', projectId: '' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function candidateDirectory(homeDir, profile, projectId = '') {
|
|
201
|
+
const base = path.join(
|
|
120
202
|
path.resolve(homeDir),
|
|
121
203
|
'self-improvement',
|
|
122
204
|
'validated-learning',
|
|
123
205
|
profileKey(profile),
|
|
124
206
|
);
|
|
207
|
+
return projectId ? path.join(base, 'projects', projectId) : base;
|
|
125
208
|
}
|
|
126
209
|
|
|
127
210
|
function atomicCreate(filePath, value) {
|
|
@@ -161,7 +244,8 @@ function readCandidate(filePath) {
|
|
|
161
244
|
function readValidatedLearningCandidates(options = {}) {
|
|
162
245
|
const homeDir = typeof options.homeDir === 'string' ? options.homeDir.trim() : '';
|
|
163
246
|
if (!homeDir) return [];
|
|
164
|
-
const
|
|
247
|
+
const project = resolveValidatedLearningProject(options.projectDir);
|
|
248
|
+
const directory = candidateDirectory(homeDir, options.profile, project.projectId);
|
|
165
249
|
try {
|
|
166
250
|
return fs.readdirSync(directory, { withFileTypes: true })
|
|
167
251
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
@@ -255,21 +339,25 @@ function pendingCandidateForSignal(candidates, signal) {
|
|
|
255
339
|
|
|
256
340
|
function createCandidate(signal, options, nowIso) {
|
|
257
341
|
const profile = profileKey(options.profile);
|
|
342
|
+
const projectId = typeof options.projectId === 'string' ? options.projectId : '';
|
|
258
343
|
const commandHash = verificationCommandHash(signal);
|
|
259
344
|
const digest = crypto.createHash('sha256').update([
|
|
260
345
|
profile,
|
|
346
|
+
projectId,
|
|
261
347
|
commandHash,
|
|
262
348
|
signal.failedToolCallId,
|
|
263
349
|
signal.successfulToolCallId,
|
|
264
350
|
].join('\0')).digest('hex').slice(0, 32);
|
|
265
351
|
const id = `vl-${digest}`;
|
|
266
|
-
const filePath = path.join(candidateDirectory(options.homeDir, profile), `${id}.json`);
|
|
352
|
+
const filePath = path.join(candidateDirectory(options.homeDir, profile, projectId), `${id}.json`);
|
|
267
353
|
atomicCreate(filePath, {
|
|
268
354
|
schemaVersion: SCHEMA_VERSION,
|
|
269
355
|
id,
|
|
270
356
|
type: 'validated_red_green',
|
|
271
357
|
status: 'pending',
|
|
272
358
|
profile,
|
|
359
|
+
scope: projectId ? 'project' : 'global',
|
|
360
|
+
...(projectId ? { projectId } : {}),
|
|
273
361
|
verification: {
|
|
274
362
|
toolName: signal.toolName,
|
|
275
363
|
commandHash,
|
|
@@ -286,6 +374,7 @@ function createCandidate(signal, options, nowIso) {
|
|
|
286
374
|
function syncValidatedLearningCandidate(history, options = {}) {
|
|
287
375
|
const signal = detectValidatedLearningSignal(history);
|
|
288
376
|
const homeDir = typeof options.homeDir === 'string' ? options.homeDir.trim() : '';
|
|
377
|
+
const project = resolveValidatedLearningProject(options.projectDir);
|
|
289
378
|
if (!homeDir) {
|
|
290
379
|
return signal === null ? null : {
|
|
291
380
|
...signal,
|
|
@@ -300,6 +389,7 @@ function syncValidatedLearningCandidate(history, options = {}) {
|
|
|
300
389
|
let candidates = readValidatedLearningCandidates({
|
|
301
390
|
homeDir,
|
|
302
391
|
profile: options.profile,
|
|
392
|
+
projectDir: options.projectDir,
|
|
303
393
|
});
|
|
304
394
|
const recordedCandidateIds = successfulMistakeRecordCandidateIds(history);
|
|
305
395
|
for (const candidateId of recordedCandidateIds) {
|
|
@@ -309,7 +399,11 @@ function syncValidatedLearningCandidate(history, options = {}) {
|
|
|
309
399
|
markCandidate(candidate, 'recorded', nowIso);
|
|
310
400
|
}
|
|
311
401
|
if (recordedCandidateIds.size > 0) {
|
|
312
|
-
candidates = readValidatedLearningCandidates({
|
|
402
|
+
candidates = readValidatedLearningCandidates({
|
|
403
|
+
homeDir,
|
|
404
|
+
profile: options.profile,
|
|
405
|
+
projectDir: options.projectDir,
|
|
406
|
+
});
|
|
313
407
|
}
|
|
314
408
|
let expiredCandidate = false;
|
|
315
409
|
for (const candidate of candidates) {
|
|
@@ -320,11 +414,19 @@ function syncValidatedLearningCandidate(history, options = {}) {
|
|
|
320
414
|
}
|
|
321
415
|
}
|
|
322
416
|
if (expiredCandidate) {
|
|
323
|
-
candidates = readValidatedLearningCandidates({
|
|
417
|
+
candidates = readValidatedLearningCandidates({
|
|
418
|
+
homeDir,
|
|
419
|
+
profile: options.profile,
|
|
420
|
+
projectDir: options.projectDir,
|
|
421
|
+
});
|
|
324
422
|
}
|
|
325
423
|
const retention = pruneTerminalValidatedLearningCandidates({ candidates });
|
|
326
424
|
if (retention.deleted > 0) {
|
|
327
|
-
candidates = readValidatedLearningCandidates({
|
|
425
|
+
candidates = readValidatedLearningCandidates({
|
|
426
|
+
homeDir,
|
|
427
|
+
profile: options.profile,
|
|
428
|
+
projectDir: options.projectDir,
|
|
429
|
+
});
|
|
328
430
|
}
|
|
329
431
|
if (signal !== null) {
|
|
330
432
|
const duplicate = pendingCandidateForSignal(candidates, signal);
|
|
@@ -332,7 +434,11 @@ function syncValidatedLearningCandidate(history, options = {}) {
|
|
|
332
434
|
return { ...duplicate, source: 'current_turn' };
|
|
333
435
|
}
|
|
334
436
|
return {
|
|
335
|
-
...createCandidate(signal, {
|
|
437
|
+
...createCandidate(signal, {
|
|
438
|
+
homeDir,
|
|
439
|
+
profile: options.profile,
|
|
440
|
+
projectId: project.projectId,
|
|
441
|
+
}, nowIso),
|
|
336
442
|
source: 'current_turn',
|
|
337
443
|
};
|
|
338
444
|
}
|
|
@@ -352,5 +458,6 @@ module.exports = {
|
|
|
352
458
|
normalizeCommand,
|
|
353
459
|
pruneTerminalValidatedLearningCandidates,
|
|
354
460
|
readValidatedLearningCandidates,
|
|
461
|
+
resolveValidatedLearningProject,
|
|
355
462
|
syncValidatedLearningCandidate,
|
|
356
463
|
};
|
package/blun.mjs
CHANGED
|
@@ -232224,7 +232224,8 @@ var init_validated_learning_signal = __esmMin((() => {
|
|
|
232224
232224
|
if (!shouldSyncValidatedLearningCandidate(this.agent.context.history)) return;
|
|
232225
232225
|
const signal = syncValidatedLearningCandidate(this.agent.context.history, {
|
|
232226
232226
|
homeDir: process.env["BLUN_SHARED_HOME"] ?? process.env["BLUN_HOME"] ?? "",
|
|
232227
|
-
profile: process.env["BLUN_PROFILE"] ?? "default"
|
|
232227
|
+
profile: process.env["BLUN_PROFILE"] ?? "default",
|
|
232228
|
+
projectDir: process.cwd()
|
|
232228
232229
|
});
|
|
232229
232230
|
if (signal === null) {
|
|
232230
232231
|
if (existing.length > 0) this.agent.context.removeSystemRemindersMatching(isValidatedLearningSignalReminder);
|