portable-agent-layer 0.13.0 → 0.14.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,8 +37,8 @@ export interface Opinion {
37
37
  // ── Confidence Deltas (matching original PAI) ──
38
38
 
39
39
  const CONFIDENCE_DELTAS: Record<EvidenceType, number> = {
40
- supporting: 0.02,
41
- counter: -0.05,
40
+ supporting: 0.05,
41
+ counter: -0.1,
42
42
  confirmation: 0.1,
43
43
  contradiction: -0.2,
44
44
  };
@@ -130,13 +130,13 @@ export function findSimilarOpinion(
130
130
  return null;
131
131
  }
132
132
 
133
- /** Create a new opinion from a recurring note. Starts at confidence 0.50. */
133
+ /** Create a new opinion from a recurring note. Starts at confidence 0.60. */
134
134
  export function createOpinion(statement: string, source: string): Opinion {
135
135
  const now = new Date().toISOString().slice(0, 10);
136
136
  return {
137
137
  id: slugify(statement),
138
138
  statement,
139
- confidence: 0.5,
139
+ confidence: 0.6,
140
140
  category: classifyCategory(statement),
141
141
  evidence: [{ date: now, type: "supporting", source }],
142
142
  created: now,
@@ -144,12 +144,19 @@ export function createOpinion(statement: string, source: string): Opinion {
144
144
  };
145
145
  }
146
146
 
147
- /** Add evidence to an opinion and adjust its confidence. */
147
+ /** Check if an opinion already has evidence with this exact source text. */
148
+ export function hasEvidence(opinion: Opinion, source: string): boolean {
149
+ return opinion.evidence.some((e) => e.source === source);
150
+ }
151
+
152
+ /** Add evidence to an opinion and adjust its confidence. No-op if duplicate. */
148
153
  export function addEvidence(
149
154
  opinion: Opinion,
150
155
  type: EvidenceType,
151
156
  source: string
152
157
  ): Opinion {
158
+ if (hasEvidence(opinion, source)) return opinion;
159
+
153
160
  const now = new Date().toISOString().slice(0, 10);
154
161
  const delta = CONFIDENCE_DELTAS[type];
155
162
  const newConfidence = Math.min(
@@ -100,6 +100,32 @@ const STOP_WORDS = new Set([
100
100
  "own",
101
101
  ]);
102
102
 
103
+ /** Strip common English suffixes to normalize word forms. Applied twice to handle stacked suffixes. */
104
+ function stemOnce(word: string): string {
105
+ if (word.length <= 4) return word;
106
+ return word
107
+ .replace(/(?<=.{4})tion$/, "")
108
+ .replace(/(?<=.{4})sion$/, "")
109
+ .replace(/(?<=.{4})ment$/, "")
110
+ .replace(/(?<=.{3})ness$/, "")
111
+ .replace(/(?<=.{3})ings$/, "")
112
+ .replace(/(?<=.{3})ing$/, "")
113
+ .replace(/(?<=.{3})ies$/, "y")
114
+ .replace(/(?<=.{3})ive$/, "")
115
+ .replace(/(?<=.{3})ous$/, "")
116
+ .replace(/(?<=.{3})ful$/, "")
117
+ .replace(/(?<=.{3})ally$/, "")
118
+ .replace(/(?<=.{3})ly$/, "")
119
+ .replace(/(?<=.{3})ed$/, "")
120
+ .replace(/(?<=.{3})er$/, "")
121
+ .replace(/(?<=.{3})es$/, "")
122
+ .replace(/(?<=.{3})s$/, "");
123
+ }
124
+
125
+ function stem(word: string): string {
126
+ return stemOnce(stemOnce(word));
127
+ }
128
+
103
129
  export function extractKeywords(text: string): Set<string> {
104
130
  return new Set(
105
131
  text
@@ -107,6 +133,8 @@ export function extractKeywords(text: string): Set<string> {
107
133
  .replace(/[^a-z0-9\s-]/g, " ")
108
134
  .split(/\s+/)
109
135
  .filter((w) => w.length > 2 && !STOP_WORDS.has(w))
136
+ .map((w) => stem(w))
137
+ .filter((w) => w.length > 2)
110
138
  );
111
139
  }
112
140
 
@@ -20,7 +20,6 @@ import {
20
20
  addEvidence,
21
21
  createOpinion,
22
22
  findSimilarOpinion,
23
- getLastReflectDate,
24
23
  readOpinions,
25
24
  saveOpinion,
26
25
  setLastReflectDate,
@@ -157,12 +156,9 @@ function loadRatings(daysBack: number): Rating[] {
157
156
  function promoteToOpinions(notes: ParsedNote[], dryRun: boolean): OpinionChange[] {
158
157
  const changes: OpinionChange[] = [];
159
158
  const opinions = readOpinions();
160
- const lastReflect = getLastReflectDate();
161
159
 
162
- // Only O notes become opinions (B=biographical, about the AI, not user preferences)
163
- const opinionNotes = notes.filter(
164
- (n) => n.type === "O" && (!lastReflect || n.date > lastReflect)
165
- );
160
+ // All O notes in the window deduplication happens at the evidence level
161
+ const opinionNotes = notes.filter((n) => n.type === "O");
166
162
 
167
163
  // Group similar notes together
168
164
  const groups = new Map<string, ParsedNote[]>();
@@ -202,7 +198,11 @@ function promoteToOpinions(notes: ParsedNote[], dryRun: boolean): OpinionChange[
202
198
  }
203
199
  } else if (group.length >= 2) {
204
200
  // New opinion — requires at least 2 occurrences
205
- const opinion = createOpinion(representative, `${group.length}x in reflect period`);
201
+ // Store individual note texts as evidence so re-runs deduplicate correctly
202
+ let opinion = createOpinion(representative, group[0].text.slice(0, 120));
203
+ for (const note of group.slice(1)) {
204
+ opinion = addEvidence(opinion, "supporting", note.text.slice(0, 120));
205
+ }
206
206
  changes.push({
207
207
  statement: representative,
208
208
  action: "created",