portable-agent-layer 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
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,10 +20,8 @@ import {
20
20
  addEvidence,
21
21
  createOpinion,
22
22
  findSimilarOpinion,
23
- getLastReflectDate,
24
23
  readOpinions,
25
24
  saveOpinion,
26
- setLastReflectDate,
27
25
  } from "../hooks/lib/opinions";
28
26
  import { palHome } from "../hooks/lib/paths";
29
27
  import { similarity } from "../hooks/lib/text-similarity";
@@ -157,12 +155,9 @@ function loadRatings(daysBack: number): Rating[] {
157
155
  function promoteToOpinions(notes: ParsedNote[], dryRun: boolean): OpinionChange[] {
158
156
  const changes: OpinionChange[] = [];
159
157
  const opinions = readOpinions();
160
- const lastReflect = getLastReflectDate();
161
158
 
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
- );
159
+ // All O notes in the window deduplication happens at the evidence level
160
+ const opinionNotes = notes.filter((n) => n.type === "O");
166
161
 
167
162
  // Group similar notes together
168
163
  const groups = new Map<string, ParsedNote[]>();
@@ -202,7 +197,11 @@ function promoteToOpinions(notes: ParsedNote[], dryRun: boolean): OpinionChange[
202
197
  }
203
198
  } else if (group.length >= 2) {
204
199
  // New opinion — requires at least 2 occurrences
205
- const opinion = createOpinion(representative, `${group.length}x in reflect period`);
200
+ // Store individual note texts as evidence so re-runs deduplicate correctly
201
+ let opinion = createOpinion(representative, group[0].text.slice(0, 120));
202
+ for (const note of group.slice(1)) {
203
+ opinion = addEvidence(opinion, "supporting", note.text.slice(0, 120));
204
+ }
206
205
  changes.push({
207
206
  statement: representative,
208
207
  action: "created",
@@ -458,7 +457,6 @@ if (dryRun) {
458
457
  } else {
459
458
  const report = formatReport(period, notes, ratings, opinionChanges);
460
459
  const filepath = writeReport(report, period);
461
- setLastReflectDate(new Date().toISOString().slice(0, 10));
462
460
  console.log(`\nCreated reflection report: ${filepath}`);
463
461
 
464
462
  const opinions = readOpinions();