pi-hermes-memory 0.7.3 → 0.7.4

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/README.md CHANGED
@@ -119,7 +119,7 @@ System Prompt
119
119
  └─────────────────────────────────────────┘
120
120
  ```
121
121
 
122
- Set `"memoryPolicyStyle"` to `"compact"`, `"custom"`, or `"none"` to change only the policy text while keeping policy-only mode. Set `"memoryMode": "legacy-inject"` to restore the old behavior that injects MEMORY.md, USER.md, project memory, recent failures, and the skill index into the prompt.
122
+ Set `"memoryPolicyStyle"` to `"full"`, `"compact"`, `"custom"`, or `"none"` to choose policy verbosity while keeping policy-only mode. Set `"memoryMode": "legacy-inject"` to restore the old behavior that injects MEMORY.md, USER.md, project memory, recent failures, and the skill index into the prompt.
123
123
 
124
124
  ## Failure Memory
125
125
 
@@ -385,6 +385,10 @@ Create `~/.pi/agent/hermes-memory-config.json`:
385
385
  | `reviewEnabled` | `true` | Enable/disable background learning loop |
386
386
  | `autoConsolidate` | `true` | Auto-merge when memory hits capacity |
387
387
  | `correctionDetection` | `true` | Detect user corrections and save immediately |
388
+ | `correctionStrongPatterns` | unset | Optional case-insensitive regex sources replacing strong correction patterns; omitted preserves defaults, invalid entries are ignored |
389
+ | `correctionWeakPatterns` | unset | Optional case-insensitive regex sources replacing weak correction patterns; omitted preserves defaults, invalid entries are ignored |
390
+ | `correctionNegativePatterns` | unset | Optional case-insensitive regex sources replacing negative correction patterns; omitted preserves defaults, invalid entries are ignored |
391
+ | `correctionDirectiveWords` | unset | Optional directive words replacing the weak-pattern directive words; omitted preserves defaults |
388
392
  | `failureInjectionEnabled` | `true` | Legacy mode only: enable/disable injecting recent failure memories into the system prompt |
389
393
  | `failureInjectionMaxAgeDays` | `7` | Legacy mode only: maximum age in days for injected failure memories |
390
394
  | `failureInjectionMaxEntries` | `5` | Legacy mode only: maximum number of failure memories to inject |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hermes-memory",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. Token-aware policy-only memory by default, SQLite FTS5 search, auto-consolidation, procedural skills. 368 tests. Ported from Hermes agent.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/config.ts CHANGED
@@ -45,16 +45,19 @@ export const DEFAULT_CONFIG_PATH = path.join(
45
45
  "hermes-memory-config.json",
46
46
  );
47
47
 
48
- export function loadConfig(): MemoryConfig {
48
+ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
49
49
  try {
50
- if (fs.existsSync(DEFAULT_CONFIG_PATH)) {
51
- const raw = fs.readFileSync(DEFAULT_CONFIG_PATH, "utf-8");
50
+ if (fs.existsSync(configPath)) {
51
+ const raw = fs.readFileSync(configPath, "utf-8");
52
52
  const parsed = JSON.parse(raw);
53
53
  // Merge: override defaults with user config
54
54
  const config: MemoryConfig = { ...DEFAULT_CONFIG };
55
55
  const isNonNegativeNumber = (value: unknown): value is number => (
56
56
  typeof value === "number" && Number.isFinite(value) && value >= 0
57
57
  );
58
+ const isStringArray = (value: unknown): value is string[] => (
59
+ Array.isArray(value) && value.every((item) => typeof item === "string")
60
+ );
58
61
  if (parsed.memoryMode === "policy-only" || parsed.memoryMode === "legacy-inject") config.memoryMode = parsed.memoryMode;
59
62
  if (
60
63
  parsed.memoryPolicyStyle === "full" ||
@@ -74,6 +77,10 @@ export function loadConfig(): MemoryConfig {
74
77
  if (isNonNegativeNumber(parsed.flushRecentMessages)) config.flushRecentMessages = parsed.flushRecentMessages;
75
78
  if (typeof parsed.autoConsolidate === "boolean") config.autoConsolidate = parsed.autoConsolidate;
76
79
  if (typeof parsed.correctionDetection === "boolean") config.correctionDetection = parsed.correctionDetection;
80
+ if (isStringArray(parsed.correctionStrongPatterns)) config.correctionStrongPatterns = parsed.correctionStrongPatterns;
81
+ if (isStringArray(parsed.correctionWeakPatterns)) config.correctionWeakPatterns = parsed.correctionWeakPatterns;
82
+ if (isStringArray(parsed.correctionNegativePatterns)) config.correctionNegativePatterns = parsed.correctionNegativePatterns;
83
+ if (isStringArray(parsed.correctionDirectiveWords)) config.correctionDirectiveWords = parsed.correctionDirectiveWords;
77
84
  if (typeof parsed.failureInjectionEnabled === "boolean") config.failureInjectionEnabled = parsed.failureInjectionEnabled;
78
85
  if (typeof parsed.failureInjectionMaxAgeDays === "number") config.failureInjectionMaxAgeDays = parsed.failureInjectionMaxAgeDays;
79
86
  if (typeof parsed.failureInjectionMaxEntries === "number") config.failureInjectionMaxEntries = parsed.failureInjectionMaxEntries;
package/src/constants.ts CHANGED
@@ -182,6 +182,33 @@ export const CORRECTION_NEGATIVE_PATTERNS: RegExp[] = [
182
182
  /^stop.{0,5}(there|here|for now)/i,
183
183
  ];
184
184
 
185
+ /** Directive words required after weak correction patterns */
186
+ export const CORRECTION_DIRECTIVE_WORDS: string[] = [
187
+ "use",
188
+ "don't",
189
+ "dont",
190
+ "do",
191
+ "try",
192
+ "make",
193
+ "run",
194
+ "install",
195
+ "add",
196
+ "remove",
197
+ "delete",
198
+ "change",
199
+ "fix",
200
+ "put",
201
+ "set",
202
+ "write",
203
+ "go",
204
+ "stop",
205
+ "start",
206
+ "the",
207
+ "that",
208
+ "this",
209
+ "it",
210
+ ];
211
+
185
212
  // ─── Correction save prompt ───
186
213
  export const CORRECTION_SAVE_PROMPT = `The user just corrected you. Review what went wrong and save the correction to persistent memory.
187
214
 
@@ -20,6 +20,7 @@ import {
20
20
  CORRECTION_STRONG_PATTERNS,
21
21
  CORRECTION_WEAK_PATTERNS,
22
22
  CORRECTION_NEGATIVE_PATTERNS,
23
+ CORRECTION_DIRECTIVE_WORDS,
23
24
  ENTRY_DELIMITER,
24
25
  } from "../constants.js";
25
26
  import type { MemoryConfig } from "../types.js";
@@ -38,23 +39,71 @@ function extractCorrectionDirective(text: string): string {
38
39
  return cleaned || text;
39
40
  }
40
41
 
42
+ function compileCorrectionPatterns(
43
+ configured: string[] | undefined,
44
+ defaults: RegExp[],
45
+ ): RegExp[] {
46
+ if (configured === undefined) return defaults;
47
+
48
+ const patterns: RegExp[] = [];
49
+ for (const source of configured) {
50
+ try {
51
+ patterns.push(new RegExp(source, "i"));
52
+ } catch {
53
+ // Ignore invalid configured regex entries; valid entries still apply.
54
+ }
55
+ }
56
+ return patterns;
57
+ }
58
+
59
+ function escapeRegexLiteral(value: string): string {
60
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
61
+ }
62
+
63
+ function hasDirectiveWord(remainder: string, words: string[]): boolean {
64
+ if (words.length === 0) return false;
65
+ const source = words.map(escapeRegexLiteral).join("|");
66
+ return new RegExp(`\\b(${source})\\b`, "i").test(remainder);
67
+ }
68
+
41
69
  /**
42
70
  * Check if a user message is a correction using the two-pass filter.
43
71
  * Returns true if the message should trigger an immediate save.
44
72
  */
45
- export function isCorrection(text: string): boolean {
73
+ type CorrectionPatternConfig = Pick<MemoryConfig,
74
+ "correctionStrongPatterns" |
75
+ "correctionWeakPatterns" |
76
+ "correctionNegativePatterns" |
77
+ "correctionDirectiveWords"
78
+ >;
79
+
80
+ export function isCorrection(text: string, config?: CorrectionPatternConfig): boolean {
81
+ const negativePatterns = compileCorrectionPatterns(
82
+ config?.correctionNegativePatterns,
83
+ CORRECTION_NEGATIVE_PATTERNS,
84
+ );
85
+ const strongPatterns = compileCorrectionPatterns(
86
+ config?.correctionStrongPatterns,
87
+ CORRECTION_STRONG_PATTERNS,
88
+ );
89
+ const weakPatterns = compileCorrectionPatterns(
90
+ config?.correctionWeakPatterns,
91
+ CORRECTION_WEAK_PATTERNS,
92
+ );
93
+ const directiveWords = config?.correctionDirectiveWords ?? CORRECTION_DIRECTIVE_WORDS;
94
+
46
95
  // Check negative patterns first — suppress even if positive matches
47
- for (const pattern of CORRECTION_NEGATIVE_PATTERNS) {
96
+ for (const pattern of negativePatterns) {
48
97
  if (pattern.test(text)) return false;
49
98
  }
50
99
 
51
100
  // Check strong patterns — always trigger
52
- for (const pattern of CORRECTION_STRONG_PATTERNS) {
101
+ for (const pattern of strongPatterns) {
53
102
  if (pattern.test(text)) return true;
54
103
  }
55
104
 
56
105
  // Check weak patterns — only trigger if followed by a directive clause
57
- for (const pattern of CORRECTION_WEAK_PATTERNS) {
106
+ for (const pattern of weakPatterns) {
58
107
  if (pattern.test(text)) {
59
108
  // Look for a directive after the weak pattern match
60
109
  // Directive = a verb or "the/that/this" in the remainder of the text
@@ -62,7 +111,7 @@ export function isCorrection(text: string): boolean {
62
111
  if (match && match.index === 0) {
63
112
  const remainder = text.slice(match[0].length).trim();
64
113
  // Simple heuristic: remainder contains something directive-ish
65
- if (/\b(use|don'?t|do|try|make|run|install|add|remove|delete|change|fix|put|set|write|go|stop|start|the|that|this|it)\b/i.test(remainder)) {
114
+ if (hasDirectiveWord(remainder, directiveWords)) {
66
115
  return true;
67
116
  }
68
117
  }
@@ -91,7 +140,7 @@ export function setupCorrectionDetector(
91
140
  if (event.message.role !== "user") return;
92
141
  const text = getMessageText(event.message);
93
142
  if (!text) return;
94
- if (isCorrection(text)) {
143
+ if (isCorrection(text, config)) {
95
144
  pendingCorrection = true;
96
145
  }
97
146
  });
package/src/types.ts CHANGED
@@ -39,6 +39,14 @@ export interface MemoryConfig {
39
39
  autoConsolidate: boolean;
40
40
  /** Detect user corrections and trigger immediate memory save. Default: true */
41
41
  correctionDetection: boolean;
42
+ /** Override strong correction regex sources. Missing = defaults; [] = none. */
43
+ correctionStrongPatterns?: string[];
44
+ /** Override weak correction regex sources. Missing = defaults; [] = none. */
45
+ correctionWeakPatterns?: string[];
46
+ /** Override negative correction regex sources. Missing = defaults; [] = none. */
47
+ correctionNegativePatterns?: string[];
48
+ /** Override directive words used after weak correction patterns. Missing = defaults; [] = none. */
49
+ correctionDirectiveWords?: string[];
42
50
  /** Inject recent failure memories into the system prompt. Default: true */
43
51
  failureInjectionEnabled: boolean;
44
52
  /** Maximum age in days for injected failure memories. Default: 7 */