hippo-memory 1.36.0 → 1.38.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/dist/autolearn.d.ts +16 -0
- package/dist/autolearn.d.ts.map +1 -1
- package/dist/autolearn.js +26 -0
- package/dist/autolearn.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +116 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/src/autolearn.js +26 -0
- package/dist/src/autolearn.js.map +1 -1
- package/dist/src/cli.js +116 -16
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.js +14 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/autolearn.d.ts
CHANGED
|
@@ -13,6 +13,22 @@ export declare function captureError(exitCode: number, stderr: string, command:
|
|
|
13
13
|
* Looks for fix:, revert:, bug:, error:, hotfix: commit messages.
|
|
14
14
|
*/
|
|
15
15
|
export declare function extractLessons(gitLog: string, customPatterns?: string[]): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Split parsed lessons into ones worth storing and low-information ones.
|
|
18
|
+
*
|
|
19
|
+
* `extractLessons` is a published API surface (exported from src/index.ts)
|
|
20
|
+
* and does one job: parse a git log into candidate lesson strings. Adding a
|
|
21
|
+
* quality check inside it would break that contract and silently change
|
|
22
|
+
* what external callers get back. So parsing and admission are separate
|
|
23
|
+
* steps: this function is the write-path gate, called by each caller that
|
|
24
|
+
* actually stores a lesson, not by the parser itself.
|
|
25
|
+
*
|
|
26
|
+
* Order is preserved in both output arrays.
|
|
27
|
+
*/
|
|
28
|
+
export declare function partitionLessons(lessons: string[]): {
|
|
29
|
+
kept: string[];
|
|
30
|
+
dropped: string[];
|
|
31
|
+
};
|
|
16
32
|
/**
|
|
17
33
|
* Check if a substantially similar memory already exists.
|
|
18
34
|
* Returns true if overlap > threshold (default 0.7).
|
package/dist/autolearn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autolearn.d.ts","sourceRoot":"","sources":["../src/autolearn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"autolearn.d.ts","sourceRoot":"","sources":["../src/autolearn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAK/D;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,WAAW,CAoBb;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAsClF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAWzF;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,kBAAkB,EAAE,MAAM,GAAG,WAAW,EAAE,EAC1C,MAAM,EAAE,MAAM,EACd,SAAS,SAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAWT;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBzF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAY9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7D"}
|
package/dist/autolearn.js
CHANGED
|
@@ -6,6 +6,7 @@ import { execSync, execFileSync, spawn } from 'child_process';
|
|
|
6
6
|
import { createMemory, Layer } from './memory.js';
|
|
7
7
|
import { loadAllEntries } from './store.js';
|
|
8
8
|
import { textOverlap } from './search.js';
|
|
9
|
+
import { isContentWorthStoring } from './audit.js';
|
|
9
10
|
/**
|
|
10
11
|
* Create a MemoryEntry capturing a command failure.
|
|
11
12
|
* Content format: "Command '<cmd>' failed: <truncated stderr>"
|
|
@@ -68,6 +69,31 @@ export function extractLessons(gitLog, customPatterns) {
|
|
|
68
69
|
// Deduplicate exact matches at extraction time
|
|
69
70
|
return [...new Set(lessons)];
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Split parsed lessons into ones worth storing and low-information ones.
|
|
74
|
+
*
|
|
75
|
+
* `extractLessons` is a published API surface (exported from src/index.ts)
|
|
76
|
+
* and does one job: parse a git log into candidate lesson strings. Adding a
|
|
77
|
+
* quality check inside it would break that contract and silently change
|
|
78
|
+
* what external callers get back. So parsing and admission are separate
|
|
79
|
+
* steps: this function is the write-path gate, called by each caller that
|
|
80
|
+
* actually stores a lesson, not by the parser itself.
|
|
81
|
+
*
|
|
82
|
+
* Order is preserved in both output arrays.
|
|
83
|
+
*/
|
|
84
|
+
export function partitionLessons(lessons) {
|
|
85
|
+
const kept = [];
|
|
86
|
+
const dropped = [];
|
|
87
|
+
for (const lesson of lessons) {
|
|
88
|
+
if (isContentWorthStoring(lesson)) {
|
|
89
|
+
kept.push(lesson);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
dropped.push(lesson);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { kept, dropped };
|
|
96
|
+
}
|
|
71
97
|
/**
|
|
72
98
|
* Check if a substantially similar memory already exists.
|
|
73
99
|
* Returns true if overlap > threshold (default 0.7).
|
package/dist/autolearn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autolearn.js","sourceRoot":"","sources":["../src/autolearn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAe,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"autolearn.js","sourceRoot":"","sources":["../src/autolearn.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA2BH,OAAO,EASL,WAAW,EAEZ,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA2BH,OAAO,EASL,WAAW,EAEZ,MAAM,aAAa,CAAC;AA6GrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AA4ChC,kFAAkF;AAClF,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AA4ID,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,CAwCjI;AA6xED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,MAAqB,GAAG,MAAM,CAyF3F;AAyGD;;;GAGG;AACH,oHAAoH;AACpH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAuD/D;AA2jGD,oHAAoH;AACpH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,EACtF,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAkB,GAC1B,IAAI,CA6BN"}
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ import { loadConfig } from './config.js';
|
|
|
54
54
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
55
55
|
import { pushGoal, getActiveGoals, completeGoal, suspendGoal, resumeGoal, applyGoalStackBoost } from './goals.js';
|
|
56
56
|
import { rowToGoal } from './goals.js';
|
|
57
|
-
import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
|
|
57
|
+
import { captureError, extractLessons, partitionLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
|
|
58
58
|
import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
|
|
59
59
|
import { extractPathTags } from './path-context.js';
|
|
60
60
|
import { detectScope } from './scope.js';
|
|
@@ -316,6 +316,11 @@ function cmdInitScan(scanDir, flags) {
|
|
|
316
316
|
console.log(`Initialized global store at ${globalRoot}\n`);
|
|
317
317
|
}
|
|
318
318
|
let totalLessons = 0;
|
|
319
|
+
// Rolled up so the cross-repo summary reports the gate too. Each repo
|
|
320
|
+
// already prints its own count inside learnFromRepo, but the aggregate
|
|
321
|
+
// line showed only what was ADDED - and the point of this change is
|
|
322
|
+
// that a dropped subject is never invisible.
|
|
323
|
+
let totalLowInfo = 0;
|
|
319
324
|
const seedDays = parseInt(String(flags['days'] ?? '365'), 10);
|
|
320
325
|
for (const repo of repos) {
|
|
321
326
|
const name = path.basename(repo);
|
|
@@ -331,12 +336,15 @@ function cmdInitScan(scanDir, flags) {
|
|
|
331
336
|
const result = learnFromRepo(repoHippo, repo, seedDays, name);
|
|
332
337
|
added = result.added;
|
|
333
338
|
totalLessons += added;
|
|
339
|
+
totalLowInfo += result.lowInfo;
|
|
334
340
|
}
|
|
335
341
|
const status = alreadyExists ? 'existing' : 'new';
|
|
336
342
|
const entries = loadAllEntries(repoHippo);
|
|
337
343
|
console.log(` ${name.padEnd(25)} ${status.padEnd(10)} ${entries.length} memories${added > 0 ? ` (+${added} from git)` : ''}`);
|
|
338
344
|
}
|
|
339
|
-
console.log(`\n${repos.length} repositories, ${totalLessons} new lessons learned
|
|
345
|
+
console.log(`\n${repos.length} repositories, ${totalLessons} new lessons learned` +
|
|
346
|
+
(totalLowInfo > 0 ? `, ${totalLowInfo} low-information subject(s) dropped` : '') +
|
|
347
|
+
'.');
|
|
340
348
|
console.log(`Global store: ${globalRoot}`);
|
|
341
349
|
if (!flags['no-schedule']) {
|
|
342
350
|
setupDailySchedule(globalRoot);
|
|
@@ -781,9 +789,21 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
781
789
|
let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive') : [];
|
|
782
790
|
// v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
|
|
783
791
|
// Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
|
|
784
|
-
// filter drops (pre-rank AND post-rank)
|
|
785
|
-
//
|
|
786
|
-
//
|
|
792
|
+
// filter drops (pre-rank AND post-rank); its meaning is unchanged by C5.
|
|
793
|
+
//
|
|
794
|
+
// C5 (2026-08-24): search-engine internal drops (scored-to-zero rows that
|
|
795
|
+
// hybridSearch/physicsSearch returns fewer of than they were given) now
|
|
796
|
+
// count toward droppedByBudget, not "not counted at all" as the old v1
|
|
797
|
+
// convention had it. That old convention is exactly why the `Cutoff:` line
|
|
798
|
+
// never printed: cmdRecall measured droppedByBudget from `results.length -
|
|
799
|
+
// limit` (cli.ts ~1547) AFTER the search call, but `results` had already
|
|
800
|
+
// been ranked and truncated by the search engine to a handful of rows, so
|
|
801
|
+
// `limit < results.length` was almost always false and the counter stayed
|
|
802
|
+
// 0 while hundreds of candidates silently vanished (see the plan's measured
|
|
803
|
+
// table: 397 of 400 candidates gone, every counter reading 0). droppedByBudget
|
|
804
|
+
// is now derived as "everything not attributed to a named pre-rank filter",
|
|
805
|
+
// computed after the final `--limit` slice — see the definition near
|
|
806
|
+
// line ~1545 for the exact formula and the double-count argument.
|
|
787
807
|
// totalCandidates = post-SQL-predicate count (api.recall parity: measured
|
|
788
808
|
// after loadRecallSearchEntries, before the JS scope filter). NOTE the
|
|
789
809
|
// v1.12.13 accounting convention: SQL-excluded rows (quarantine + the
|
|
@@ -792,6 +812,9 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
792
812
|
// defense-in-depth (LIKE/regex divergence, exact-mode mismatch).
|
|
793
813
|
const totalCandidatesCountCmd = localEntries.length + globalEntries.length;
|
|
794
814
|
let droppedPreRankCountCmd = 0;
|
|
815
|
+
// Graph expansion adds candidates AFTER totalCandidatesCountCmd is taken,
|
|
816
|
+
// so they are folded back in before the budget residual is derived.
|
|
817
|
+
let graphAddedCountCmd = 0;
|
|
795
818
|
// v1.25.0: JS half of the recall scope rule (private-scope regex deny with
|
|
796
819
|
// explicit-request unlock), via the canonical helper — do not inline a
|
|
797
820
|
// fourth copy of this predicate.
|
|
@@ -979,6 +1002,19 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
979
1002
|
}
|
|
980
1003
|
}
|
|
981
1004
|
if (hops > 0) {
|
|
1005
|
+
// graphExpandRecall can SURFACE rows that were never in the lexical
|
|
1006
|
+
// candidate pool (a graph neighbour reached by entity edge, not by
|
|
1007
|
+
// query match), and totalCandidatesCountCmd was snapshotted before the
|
|
1008
|
+
// search. Without this the derived budget count goes negative, clamps
|
|
1009
|
+
// to 0, and the accounting silently breaks: 1 candidate, 2 returned,
|
|
1010
|
+
// 0 drops. Found independently by two reviewers. Count the additions
|
|
1011
|
+
// so the invariant holds on graph-expanded recalls too.
|
|
1012
|
+
// GROSS, not net. graphExpandRecall both adds neighbours AND evicts weak
|
|
1013
|
+
// base rows in one call (graph-recall.ts:285), so a net delta of 0 hides
|
|
1014
|
+
// 3 added + 3 evicted: the additions escape the candidate total and the
|
|
1015
|
+
// evictions escape the drop count, and the Cutoff line goes silent again.
|
|
1016
|
+
// Compare ID sets so both directions are counted.
|
|
1017
|
+
const beforeGraphIds = new Set(results.map((r) => r.entry.id));
|
|
982
1018
|
results = graphExpandRecall(results, {
|
|
983
1019
|
hops,
|
|
984
1020
|
maxNeighbors,
|
|
@@ -993,6 +1029,11 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
993
1029
|
? { requested: recallExplicitScope, additive: true }
|
|
994
1030
|
: {},
|
|
995
1031
|
});
|
|
1032
|
+
// Rows the graph surfaced that the lexical pool never held.
|
|
1033
|
+
for (const r of results) {
|
|
1034
|
+
if (!beforeGraphIds.has(r.entry.id))
|
|
1035
|
+
graphAddedCountCmd++;
|
|
1036
|
+
}
|
|
996
1037
|
}
|
|
997
1038
|
}
|
|
998
1039
|
// ACC EVC-adaptive recall (RESEARCH.md §PFC.ACC). When the initial top-K is
|
|
@@ -1339,12 +1380,29 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1339
1380
|
results = results.filter((r) => r.entry.layer === layerFilter);
|
|
1340
1381
|
droppedPreRankCountCmd += beforeLayerFilter - results.length;
|
|
1341
1382
|
}
|
|
1342
|
-
// v1.12.13 / C5 — WYSIATI dropped_by_budget counter
|
|
1343
|
-
|
|
1383
|
+
// v1.12.13 / C5 — WYSIATI dropped_by_budget counter. Apply the final
|
|
1384
|
+
// `--limit` slice first, then derive the count ARITHMETICALLY as
|
|
1385
|
+
// "everything lost that droppedPreRank did not already claim":
|
|
1386
|
+
//
|
|
1387
|
+
// droppedByBudget = totalCandidates - droppedPreRank - returned
|
|
1388
|
+
//
|
|
1389
|
+
// This is the invariant the plan requires (totalCandidates == droppedPreRank
|
|
1390
|
+
// + droppedByBudget + returned) restated as an assignment, so it holds by
|
|
1391
|
+
// construction rather than by two counters happening to agree. It also
|
|
1392
|
+
// cannot double-count the post-search droppedPreRank sites (--filter-
|
|
1393
|
+
// conflicts, --outcome, --layer, ~1270/1528/1541): those are subtracted
|
|
1394
|
+
// once here, not re-counted, because this line does not re-walk any filter
|
|
1395
|
+
// — it only compares the two totals already tracked above. Everything left
|
|
1396
|
+
// over — search-engine internal rank-step drops AND the `--limit` slice
|
|
1397
|
+
// itself — lands in droppedByBudget, per the C5 accounting change in the
|
|
1398
|
+
// comment near line ~976. Clamped at 0 as a defensive floor: if a future
|
|
1399
|
+
// pipeline change ever returns MORE rows than totalCandidates minus
|
|
1400
|
+
// droppedPreRank (should not happen), report "nothing dropped" rather than
|
|
1401
|
+
// a negative count.
|
|
1344
1402
|
if (limit < results.length) {
|
|
1345
|
-
droppedByBudgetCountCmd = results.length - limit;
|
|
1346
1403
|
results = results.slice(0, limit);
|
|
1347
1404
|
}
|
|
1405
|
+
const droppedByBudgetCountCmd = Math.max(0, totalCandidatesCountCmd + graphAddedCountCmd - droppedPreRankCountCmd - results.length);
|
|
1348
1406
|
// v0.33 / J1 — CLI per-pipeline anchoring detector. Each pipeline (api.recall,
|
|
1349
1407
|
// cmdRecall, MCP) computes its own AnchoringHint via the shared detectAnchoring
|
|
1350
1408
|
// helper against its own top-1 + its own per-(tenant, session) ring buffer.
|
|
@@ -1385,7 +1443,13 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1385
1443
|
// interference; query_repeat is a re-ask, not memory competition).
|
|
1386
1444
|
const cmdSuppressedByInterference = cmdAnchoringHint?.reason === 'memory_dominance' ? 1 : 0;
|
|
1387
1445
|
const cmdSuppressionSummary = api.buildSuppressionSummary({
|
|
1388
|
-
|
|
1446
|
+
// PUBLISHED total includes graph-surfaced rows. Folding graphAdded into
|
|
1447
|
+
// the derivation but not into the reported total made the invariant hold
|
|
1448
|
+
// internally and break externally by exactly that count: JSON consumers
|
|
1449
|
+
// saw total != preRank + byBudget + returned, and the text line could
|
|
1450
|
+
// read "showing 8 of 10" having actually considered 12. The number a
|
|
1451
|
+
// caller sees must be the number the arithmetic used.
|
|
1452
|
+
totalCandidates: totalCandidatesCountCmd + graphAddedCountCmd,
|
|
1389
1453
|
droppedPreRank: droppedPreRankCountCmd,
|
|
1390
1454
|
droppedByBudget: droppedByBudgetCountCmd,
|
|
1391
1455
|
summarySubstitutionsAdded: 0,
|
|
@@ -1761,8 +1825,12 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1761
1825
|
if (showWhy) {
|
|
1762
1826
|
const s = cmdSuppressionSummary;
|
|
1763
1827
|
const clauses = [];
|
|
1828
|
+
// "dropped to fit limit" pointed at the wrong control: the residual covers
|
|
1829
|
+
// search-ranking and token-budget drops too, and fires even when --limit
|
|
1830
|
+
// was never passed. Measured: `recall --budget 20 --why` printed "39
|
|
1831
|
+
// dropped to fit limit" with no --limit flag in the command at all.
|
|
1764
1832
|
if (s.droppedByBudget > 0)
|
|
1765
|
-
clauses.push(`${s.droppedByBudget}
|
|
1833
|
+
clauses.push(`${s.droppedByBudget} not shown (rank, budget or limit)`);
|
|
1766
1834
|
if (s.droppedPreRank > 0)
|
|
1767
1835
|
clauses.push(`${s.droppedPreRank} filtered pre-rank`);
|
|
1768
1836
|
if (s.summarySubstitutionsAdded > 0)
|
|
@@ -5770,18 +5838,49 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
|
|
|
5770
5838
|
const prefix = label ? `[${label}] ` : '';
|
|
5771
5839
|
if (!isGitRepo(repoPath)) {
|
|
5772
5840
|
console.log(`${prefix}No git history found (or not a git repository).`);
|
|
5773
|
-
return { added: 0, skipped: 0 };
|
|
5841
|
+
return { added: 0, skipped: 0, lowInfo: 0 };
|
|
5774
5842
|
}
|
|
5775
5843
|
const gitLog = fetchGitLog(repoPath, days);
|
|
5776
5844
|
if (!gitLog.trim()) {
|
|
5777
5845
|
console.log(`${prefix}No fix/revert/bug commits found in the specified period.`);
|
|
5778
|
-
return { added: 0, skipped: 0 };
|
|
5846
|
+
return { added: 0, skipped: 0, lowInfo: 0 };
|
|
5779
5847
|
}
|
|
5780
|
-
const
|
|
5781
|
-
if (
|
|
5848
|
+
const parsedLessons = extractLessons(gitLog);
|
|
5849
|
+
if (parsedLessons.length === 0) {
|
|
5782
5850
|
console.log(`${prefix}No fix/revert/bug commits found in the specified period.`);
|
|
5783
|
-
return { added: 0, skipped: 0 };
|
|
5851
|
+
return { added: 0, skipped: 0, lowInfo: 0 };
|
|
5784
5852
|
}
|
|
5853
|
+
// DF4: admission gate lives at the write path, not in extractLessons
|
|
5854
|
+
// (a published API surface that only parses). Bare subjects like "fixed
|
|
5855
|
+
// signals" are dropped here, before they ever become a memory.
|
|
5856
|
+
// The gate filters the loop INPUT, so a dropped lesson neither stores nor
|
|
5857
|
+
// invalidates. That is deliberate, and it was argued both ways.
|
|
5858
|
+
//
|
|
5859
|
+
// Round 1 of review called the lost invalidation a P1: a migration subject
|
|
5860
|
+
// too thin to store ("replace webpack with vite") would stop weakening
|
|
5861
|
+
// stale webpack memories. True. So the loop was widened to walk every
|
|
5862
|
+
// parsed lesson with the gate on the write alone.
|
|
5863
|
+
//
|
|
5864
|
+
// Round 2 then found the cure was worse. STORAGE is what makes invalidation
|
|
5865
|
+
// idempotent here: a stored lesson is recognised by deduplicateLesson on
|
|
5866
|
+
// the next scan and short-circuits before invalidating again. A lesson that
|
|
5867
|
+
// invalidates but is never stored has no such record, so every rescan
|
|
5868
|
+
// re-invalidates, and invalidateMatching halves half_life_days each time.
|
|
5869
|
+
// Measured: 7 -> 3 -> 1 over two runs. That is compounding data damage.
|
|
5870
|
+
//
|
|
5871
|
+
// Measured frequency decided it. Across 413 real auto-learn rows in 4
|
|
5872
|
+
// stores, 24 are gated and ZERO of those carry an invalidation target; the
|
|
5873
|
+
// 45 lessons that do carry targets all pass the gate and are unaffected
|
|
5874
|
+
// either way. Both failure modes are empty on real data, so the tie breaks
|
|
5875
|
+
// on which one is benign if it ever fires: not invalidating is a missed
|
|
5876
|
+
// improvement, re-invalidating forever is damage.
|
|
5877
|
+
//
|
|
5878
|
+
// Documented limitation, pinned by test: a migration subject too thin to
|
|
5879
|
+
// store also does not invalidate. Making invalidateMatching idempotent
|
|
5880
|
+
// would allow both, and is backlogged - it is a latent issue for the manual
|
|
5881
|
+
// `hippo invalidate` path too, not just this one.
|
|
5882
|
+
const { kept: lessons, dropped } = partitionLessons(parsedLessons);
|
|
5883
|
+
const lowInfo = dropped.length;
|
|
5785
5884
|
let added = 0;
|
|
5786
5885
|
let skipped = 0;
|
|
5787
5886
|
// AT1 (plan §3 containment): per-lesson refusal must not abort the rest
|
|
@@ -5841,8 +5940,9 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
|
|
|
5841
5940
|
}
|
|
5842
5941
|
console.log(`${prefix}${added} new lessons added, ${skipped} duplicates skipped` +
|
|
5843
5942
|
(rejected > 0 ? `, ${rejected} rejected value(s) skipped` : '') +
|
|
5943
|
+
(lowInfo > 0 ? `, ${lowInfo} low-information subject(s) dropped` : '') +
|
|
5844
5944
|
'.');
|
|
5845
|
-
return { added, skipped };
|
|
5945
|
+
return { added, skipped, lowInfo };
|
|
5846
5946
|
}
|
|
5847
5947
|
function cmdLearn(hippoRoot, flags) {
|
|
5848
5948
|
requireInit(hippoRoot);
|