portable-agent-layer 0.12.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.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: opinion
3
+ description: "Opinion tracker for relationship notes. PROACTIVE: When the user confirms a preference ('yes exactly', 'keep doing that'), contradicts one ('no, don't do that', 'stop'), or you observe a recurring behavioral pattern — invoke this to update opinion confidence."
4
+ ---
5
+
6
+ # Opinion Tracker
7
+
8
+ Tool: `tools/opinion.ts`
9
+
10
+ Manage confidence-scored opinions about the user. Opinions are promoted from recurring relationship notes (the W/O/B observations captured at session end). This tool is the fast path to grow opinion confidence — relationship notes feed the slow path (+2% per reflect cycle), while explicit confirmations here jump +10%.
11
+
12
+ Opinions at ≥85% confidence are automatically injected into every session context.
13
+
14
+ ## When to Use
15
+
16
+ **Proactively call this tool during conversations when:**
17
+
18
+ - The user **explicitly confirms** a preference → `--confirmation`
19
+ - "yes, keep it short" → confirms concise preference
20
+ - "exactly, that's how I want it" → confirms the approach you used
21
+ - User accepts an unusual choice without pushback → implicit confirmation
22
+
23
+ - The user **explicitly contradicts** a preference → `--contradiction`
24
+ - "no, I actually want more detail here" → contradicts concise preference
25
+ - "stop doing X" → contradicts a tracked pattern
26
+
27
+ - You observe a **recurring behavioral pattern** → `--supporting`
28
+ - User does the same thing for the 2nd+ time in a session
29
+ - Pattern matches an existing tracked opinion
30
+
31
+ **Do NOT call when:**
32
+ - The observation is ephemeral or task-specific (not a general preference)
33
+ - You're unsure whether it's a real preference or a one-off request
34
+ - The opinion would be trivially obvious ("user wants correct code")
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ # List all tracked opinions
40
+ bun opinion.ts list
41
+
42
+ # Show details for a specific opinion (fuzzy-matched)
43
+ bun opinion.ts show "keywords"
44
+
45
+ # Add a new opinion (starts at 50%)
46
+ bun opinion.ts add "statement" [--category workflow]
47
+
48
+ # Add evidence to an existing opinion
49
+ bun opinion.ts evidence "keywords" --supporting "why" # +2%
50
+ bun opinion.ts evidence "keywords" --counter "why" # -5%
51
+ bun opinion.ts evidence "keywords" --confirmation "why" # +10%
52
+ bun opinion.ts evidence "keywords" --contradiction "why" # -20%
53
+ ```
54
+
55
+ ## Categories
56
+
57
+ `communication` | `technical` | `workflow` | `general`
58
+
59
+ ## Confidence Lifecycle
60
+
61
+ ```
62
+ New opinion (add) → 50%
63
+ ├── supporting evidence → +2% each
64
+ ├── counter evidence → -5% each
65
+ ├── explicit confirmation → +10% each
66
+ ├── explicit contradiction → -20% each
67
+ └── at ≥85% → auto-injected into session context
68
+ ```
69
+
70
+ ## Examples
71
+
72
+ ```bash
73
+ # User says "yes keep it concise, I don't need long explanations"
74
+ bun opinion.ts evidence "concise responses" --confirmation "User explicitly said keep it concise"
75
+
76
+ # User asks for detailed explanation (contradicts concise preference)
77
+ bun opinion.ts evidence "concise responses" --contradiction "User requested detailed explanation for this topic"
78
+
79
+ # You notice user prefers iterative development for the 3rd time
80
+ bun opinion.ts evidence "iterative development" --supporting "User again chose incremental approach over big-bang change"
81
+
82
+ # New pattern: user always checks git status before committing
83
+ bun opinion.ts add "User checks git status before every commit" --category workflow
84
+ ```
@@ -6,13 +6,13 @@
6
6
  * contradictions, or new behavioral patterns.
7
7
  *
8
8
  * Usage:
9
- * bun run tool:opinion -- list List all opinions
10
- * bun run tool:opinion -- show "statement" Show opinion details
11
- * bun run tool:opinion -- add "statement" [--category workflow] Add new opinion
12
- * bun run tool:opinion -- evidence "statement" --supporting "why" Add supporting evidence
13
- * bun run tool:opinion -- evidence "statement" --counter "why" Add counter evidence
14
- * bun run tool:opinion -- evidence "statement" --confirmation "why" Explicit user confirmation
15
- * bun run tool:opinion -- evidence "statement" --contradiction "why" Explicit user contradiction
9
+ * bun opinion.ts list List all opinions
10
+ * bun opinion.ts show "statement" Show opinion details
11
+ * bun opinion.ts add "statement" [--category workflow] Add new opinion
12
+ * bun opinion.ts evidence "statement" --supporting "why" Add supporting evidence
13
+ * bun opinion.ts evidence "statement" --counter "why" Add counter evidence
14
+ * bun opinion.ts evidence "statement" --confirmation "why" Explicit user confirmation
15
+ * bun opinion.ts evidence "statement" --contradiction "why" Explicit user contradiction
16
16
  */
17
17
 
18
18
  import {
@@ -23,7 +23,7 @@ import {
23
23
  type OpinionCategory,
24
24
  readOpinions,
25
25
  saveOpinion,
26
- } from "../hooks/lib/opinions";
26
+ } from "../../../../src/hooks/lib/opinions";
27
27
 
28
28
  const args = process.argv.slice(2);
29
29
  const command = args[0];
@@ -82,7 +82,7 @@ switch (command) {
82
82
  case "show": {
83
83
  const statement = args[1];
84
84
  if (!statement) {
85
- console.error('Usage: bun run tool:opinion -- show "statement"');
85
+ console.error('Usage: bun opinion.ts show "statement"');
86
86
  process.exit(1);
87
87
  }
88
88
 
@@ -128,9 +128,7 @@ switch (command) {
128
128
  case "add": {
129
129
  const statement = args[1];
130
130
  if (!statement) {
131
- console.error(
132
- 'Usage: bun run tool:opinion -- add "statement" [--category workflow]'
133
- );
131
+ console.error('Usage: bun opinion.ts add "statement" [--category workflow]');
134
132
  process.exit(1);
135
133
  }
136
134
 
@@ -156,7 +154,7 @@ switch (command) {
156
154
  const statement = args[1];
157
155
  if (!statement) {
158
156
  console.error(
159
- 'Usage: bun run tool:opinion -- evidence "statement" --supporting "description"'
157
+ 'Usage: bun opinion.ts evidence "statement" --supporting "description"'
160
158
  );
161
159
  process.exit(1);
162
160
  }
@@ -226,20 +224,6 @@ switch (command) {
226
224
  evidence "keywords" --contradiction "why" User explicitly contradicted (-20%)
227
225
 
228
226
  Categories: communication, technical, workflow, general
229
-
230
- Examples:
231
- bun run tool:opinion -- list
232
- bun run tool:opinion -- evidence "concise direct responses" --confirmation "User said: keep it short"
233
- bun run tool:opinion -- evidence "concise direct responses" --contradiction "User asked for detailed explanation"
234
- bun run tool:opinion -- add "User prefers iterative development" --category workflow
235
- bun run tool:opinion -- show "iterative development"
236
-
237
- Confidence lifecycle:
238
- New opinions start at 50%. Supporting notes from reflect add +2% each.
239
- Explicit confirmations jump +10%, contradictions drop -20%.
240
- At >=85%, opinions are auto-injected into every session context.
241
-
242
- Usage: bun run tool:opinion -- <command> [args]
243
227
  `);
244
228
  break;
245
229
  }
@@ -6,6 +6,8 @@ Load context on-demand by reading the file at the path listed. Only load what th
6
6
 
7
7
  | Topic | Path |
8
8
  |-------|------|
9
+ | PAL system overview | `~/.agents/PAL/README.md` |
10
+ | System architecture | `~/.agents/PAL/SYSTEM_ARCHITECTURE.md` |
9
11
  | Memory format & guidelines | `~/.agents/PAL/MEMORY_SYSTEM.md` |
10
12
  | Work tracking (projects, sessions) | `~/.agents/PAL/WORK_TRACKING.md` |
11
13
  | Opinion tracking | `~/.agents/PAL/OPINION_TRACKING.md` |
@@ -1,3 +1,3 @@
1
1
  # Opinion Tracking
2
2
 
3
- PAL tracks confidence-scored opinions about the user. When you notice the user confirming or contradicting a behavioral pattern, update it via `bun run tool:opinion`. Run `bun run tool:opinion -- --help` for full usage and examples. Opinions at ≥85% confidence are automatically injected into every session context.
3
+ PAL tracks confidence-scored opinions about the user. When you notice the user confirming or contradicting a behavioral pattern, use the `opinion` skill to update confidence. Opinions at ≥85% confidence are automatically injected into every session context.
@@ -0,0 +1,127 @@
1
+ # PAL — Portable Agent Layer
2
+
3
+ PAL is a persistent, cross-platform, cross-agent layer for portable AI workflows, memory, and accumulated knowledge. It runs inside any compatible AI coding agent (Claude Code, opencode) as an interconnected set of skills, hooks, tools, memory, and configuration — all orchestrated by The Algorithm.
4
+
5
+ ## How It Works
6
+
7
+ **CLAUDE.md** (or the agent equivalent) is the entry point — generated from a template by the CLI installer. It defines execution modes, The Algorithm routing, and the context routing table. The agent loads it natively every session. A SessionStart hook keeps it fresh automatically.
8
+
9
+ **The PAL home directory (`~/.agents/PAL/`)** contains all system documentation, user context (TELOS), and routing files. The rest of the system lives in the PAL package (`src/`) and the agent's config directory (`~/.claude/` or `~/.config/opencode/`).
10
+
11
+ ## Directory Structure
12
+
13
+ ```
14
+ ~/.agents/PAL/ # PAL home — user context + routing
15
+ ALGORITHM.md # The execution engine (4-phase)
16
+ CONTEXT_ROUTING.md # On-demand context routing table
17
+ MEMORY_SYSTEM.md # Memory guidelines
18
+ OPINION_TRACKING.md # Opinion system reference
19
+ STEERING_RULES.md # Behavioral rules
20
+ WORK_TRACKING.md # Work tracking reference
21
+ telos/ # User life context (TELOS)
22
+ MISSION.md, GOALS.md, PROJECTS.md, BELIEFS.md,
23
+ CHALLENGES.md, STRATEGIES.md, IDEAS.md, LEARNED.md,
24
+ MODELS.md, NARRATIVES.md
25
+
26
+ <PAL package>/ # The PAL codebase
27
+ src/
28
+ cli/ # CLI entry point (pal command)
29
+ hooks/ # Session lifecycle hooks
30
+ handlers/ # Individual stop/prompt handlers
31
+ lib/ # Shared utilities
32
+ targets/ # Agent-specific installers (Claude, opencode)
33
+ tools/ # Standalone CLI tools
34
+ assets/
35
+ skills/ # Bundled skills (16+)
36
+ templates/PAL/ # Templates for PAL home files
37
+
38
+ <PAL package>/memory/ # Persistent memory (gitignored)
39
+ state/ # Runtime state (sessions, counts, caches)
40
+ signals/ # Rating signals (ratings.jsonl)
41
+ relationship/ # Daily interaction notes + opinions
42
+ YYYY-MM/YYYY-MM-DD.md # Daily relationship notes
43
+ opinions.json # Confidence-tracked opinions
44
+ reflections/ # Periodic reflection reports
45
+ session-learning/ # Per-session learnings
46
+ failures/ # Low-rating context dumps
47
+ wisdom/ # Crystallized principles (frames)
48
+ synthesis/ # Pattern synthesis reports
49
+ ```
50
+
51
+ ## Core Subsystems
52
+
53
+ ### The Algorithm (`ALGORITHM.md`)
54
+ The 4-phase execution engine: Observe, Plan, Execute, Verify. Transitions from CURRENT STATE to IDEAL STATE via verifiable criteria. Used for all complex work.
55
+
56
+ ### Three Execution Modes
57
+ Every response uses exactly one mode:
58
+ - **MINIMAL** — Greetings, acknowledgments
59
+ - **NATIVE** — Simple, quick tasks
60
+ - **ALGORITHM** — Multi-step, complex work (invokes the full 4-phase engine)
61
+
62
+ ### Skills (`assets/skills/`)
63
+ Bundled skills installed into the agent's skill directory. Each has a `SKILL.md` defining triggers, workflows, and capabilities. Skills are the primary capability unit — they tell the AI what it can do and when to do it.
64
+
65
+ ### Hooks (`src/hooks/`)
66
+ Event hooks across the session lifecycle:
67
+ - **SessionStart** → `LoadContext.ts` — inject dynamic context, regenerate CLAUDE.md if stale
68
+ - **UserPromptSubmit** → `UserPromptOrchestrator.ts` — rating capture, session naming
69
+ - **PreToolUse** → `SecurityValidator.ts` — block dangerous commands; `SkillGuard.ts` — block false-positive skill matches
70
+ - **Stop** → `StopOrchestrator.ts` — work tracking, relationship capture, learnings, backups, reflect trigger
71
+
72
+ ### Memory (`memory/`)
73
+ Persistent storage across sessions:
74
+ - **signals/** — User satisfaction ratings (explicit + implicit)
75
+ - **relationship/** — Daily interaction notes, confidence-tracked opinions, reflection reports
76
+ - **session-learning/** — Per-session context and learnings
77
+ - **failures/** — Low-rating session context dumps for pattern avoidance
78
+ - **wisdom/** — Crystallized principles that compound over time
79
+ - **synthesis/** — Weekly pattern synthesis reports
80
+ - **state/** — Session registry, counts cache, debug logs
81
+
82
+ ### Tools (`src/tools/`)
83
+ CLI utilities: `tool:opinion` (manage opinions), `tool:reflect` (relationship reflection), `tool:analyze` (learning analysis), `tool:tokens` (usage tracking), `tool:export` / `tool:import` (state portability).
84
+
85
+ ### TELOS (`telos/`)
86
+ Personal context system — mission, goals, projects, beliefs, challenges, strategies, ideas, learnings, mental models, narratives. Managed via the telos skill.
87
+
88
+ ### Security (`SecurityValidator.ts`)
89
+ Hook-based security: validates Bash commands and file operations against dangerous patterns. Fail-open design — blocks known-dangerous operations without breaking legitimate work.
90
+
91
+ ## Startup & Context Loading
92
+
93
+ At session start, three things happen:
94
+ 1. **CLAUDE.md** loads natively (identity, modes, routing table)
95
+ 2. **`loadAtStartup` files** from `pal-settings.json` are loaded by `LoadContext.ts`
96
+ 3. **Dynamic context** injected by `LoadContext.ts`: crystallized principles, tracked opinions (≥85%), recent interaction notes, learning digest, signal trends, failure patterns, active work summary — each toggleable in `pal-settings.json → dynamicContext`
97
+
98
+ All other documentation loads on-demand via the routing table in CLAUDE.md.
99
+
100
+ ## CLI
101
+
102
+ ```
103
+ pal # Start agent session with auto-summary on exit
104
+ pal cli init # Scaffold PAL home + install hooks
105
+ pal cli install [--claude] # Register hooks/skills for Claude Code
106
+ pal cli install [--opencode] # Register hooks/skills for opencode
107
+ pal cli uninstall # Remove hooks/skills
108
+ pal cli status # Show configuration
109
+ pal cli doctor # Check prerequisites and health
110
+ pal cli export [path] # Export user state to zip
111
+ pal cli import [path] # Import state from zip
112
+ pal cli update # Update PAL
113
+ ```
114
+
115
+ ## Cross-Platform & Cross-Agent
116
+
117
+ PAL is designed to work identically across:
118
+ - **Platforms:** macOS, Linux, Windows
119
+ - **Agents:** Claude Code, opencode (and future tools)
120
+ - **Environment overrides:** `PAL_HOME`, `PAL_PKG`, `PAL_CLAUDE_DIR`, `PAL_OPENCODE_DIR`, `PAL_AGENTS_DIR`
121
+
122
+ ## Extending PAL
123
+
124
+ - **Add a skill:** Use the `create-skill` skill or manually create `assets/skills/<name>/SKILL.md`
125
+ - **Add startup files:** Append to `pal-settings.json → loadAtStartup.files`
126
+ - **Add user context:** Create files in `~/.agents/PAL/telos/`
127
+ - **Toggle dynamic context:** Set keys in `pal-settings.json → dynamicContext` to `false`
@@ -22,9 +22,13 @@ Correct: Read the handler, imports, and patterns first → integrate with what's
22
22
  Bad: Page broken → change CSS, API, config, and routes at once. Still broken, now you don't know which change helped or hurt.
23
23
  Correct: Dev tools → 404 on API → fix the route → verify → move to next issue.
24
24
 
25
- **Minimal scope.** Only change what was asked. No bonus refactoring, no extra cleanup, no unsolicited improvements.
26
- Bad: Fix bug on line 42, also refactor the whole file → 200-line diff for a one-line fix.
27
- Correct: Fix the bug → 1-line diff.
25
+ **Minimal scope.** Only change what was asked. No bonus features, no unsolicited additions.
26
+ Bad: Fix bug on line 42, also add a new logging framework → 200-line diff for a one-line fix.
27
+ Correct: Fix the bug → focused diff.
28
+
29
+ **Cleanup on touch.** When modifying a file, assess it for dead code, unnecessary complexity, or poor organization. Offer to clean it up — don't silently refactor, flag what you found and ask. This applies to the file being edited, not neighboring files.
30
+ Bad: Editing a handler → silently rewrite the whole file and three others.
31
+ Correct: Editing a handler → notice dead imports and a 200-line function → "This file has dead imports and a function that could be split — want me to clean it up?"
28
32
 
29
33
  **Ask before destructive actions.** Deletes, force pushes, production deploys — always ask first.
30
34
  Bad: "Clean up cruft" → delete 15 files including backups without asking.
@@ -0,0 +1,471 @@
1
+ # PAL System Architecture
2
+
3
+ <!--
4
+ SYSTEM ARCHITECTURE TEMPLATE
5
+ =============================
6
+ This file defines the GENERIC architecture patterns for any PAL installation.
7
+ These are the foundational patterns that apply to ALL PAL implementations.
8
+
9
+ WHAT GOES HERE:
10
+ - The Founding Principles (universal)
11
+ - Generic system patterns (skill structure, hook lifecycle, memory layout)
12
+ - Architecture diagrams showing how components interact
13
+ - Design philosophy and constraints
14
+
15
+ WHAT DOES NOT GO HERE:
16
+ - User-specific skill counts, configurations, or API keys
17
+ - Personal projects or deployment details
18
+ - Ephemeral state or session-specific information
19
+ -->
20
+
21
+ **The Founding Principles and Universal Architecture Patterns for the Portable Agent Layer**
22
+
23
+ ---
24
+
25
+ ## Core Philosophy
26
+
27
+ **PAL is scaffolding for AI, not a replacement for human intelligence.**
28
+
29
+ AI systems need structure to be reliable. Like physical scaffolding supports construction work, PAL provides the architectural framework that makes AI assistance dependable, maintainable, and effective — regardless of which AI agent runs underneath.
30
+
31
+ ---
32
+
33
+ ## The Founding Principles
34
+
35
+ ### 1. Portability First
36
+
37
+ **PAL exists so your AI context, memory, and workflows survive any single tool.**
38
+
39
+ AI agents come and go. Your accumulated knowledge, preferences, and workflows should not be locked into one vendor's tool. PAL abstracts the agent-specific layer so the same skills, hooks, memory, and configuration work across Claude Code, opencode, and future agents.
40
+
41
+ **What portability means in practice:**
42
+ - No agent-specific assumptions in core logic
43
+ - Agent-specific code isolated in `src/targets/`
44
+ - Skills, memory, and TELOS are agent-agnostic
45
+ - A single `pal cli install --<agent>` registers everything
46
+
47
+ ### 2. Cross-Platform by Default
48
+
49
+ **Every feature must work identically on macOS, Linux, and Windows.**
50
+
51
+ No platform-specific code without a portable fallback. Path resolution, shell commands, and file operations use cross-platform abstractions. Environment overrides (`PAL_HOME`, `PAL_PKG`, etc.) let users customize paths without touching code.
52
+
53
+ ### 3. The Continuously Upgrading Algorithm
54
+
55
+ **The Algorithm is the gravitational center — everything else exists to serve it.**
56
+
57
+ PAL is built around a universal pattern for accomplishing any task: **Current State → Ideal State** via verifiable criteria. This pattern applies at every scale.
58
+
59
+ Everything feeds back into improving The Algorithm:
60
+ - The **Memory System** captures signals from every interaction
61
+ - The **Hook System** detects sentiment, ratings, and behavioral patterns
62
+ - The **Learning System** organizes evidence by session
63
+ - The **Wisdom System** crystallizes recurring patterns into principles
64
+ - The **Relationship System** tracks how the user works and what they prefer
65
+
66
+ ### 4. Scaffolding > Model
67
+
68
+ **The system architecture matters more than the underlying AI model.**
69
+
70
+ A well-structured system with good scaffolding will outperform a more powerful model with poor structure. PAL's value comes from:
71
+ - Organized workflows that guide AI execution
72
+ - Routing systems that activate the right context
73
+ - Quality gates that verify outputs
74
+ - Memory systems that enable learning
75
+ - Feedback systems that provide awareness
76
+
77
+ ### 5. As Deterministic as Possible
78
+
79
+ **Favor predictable, repeatable outcomes over flexibility.**
80
+
81
+ - Same input → Same output
82
+ - Behavior defined by code and hooks, not prompt variations
83
+ - Version control tracks explicit changes
84
+ - Hooks run deterministic TypeScript, not prompt-driven logic
85
+
86
+ ### 6. Code Before Prompts
87
+
88
+ **Write code to solve problems, use prompts to orchestrate code.**
89
+
90
+ Prompts should never replicate functionality that code can provide:
91
+ - Hooks are TypeScript, not prompt instructions
92
+ - Tools are CLI programs, not natural language procedures
93
+ - Memory is structured JSON/JSONL, not freeform text
94
+ - Configuration is `pal-settings.json`, not prose in CLAUDE.md
95
+
96
+ ### 7. Clear Thinking + Prompting is King
97
+
98
+ **The quality of outcomes depends on the quality of thinking and prompts.**
99
+
100
+ Before any code, before any architecture — there must be clear thinking:
101
+ - Understand the problem deeply before solving it
102
+ - Define success criteria before building
103
+ - Challenge assumptions before accepting them
104
+ - Simplify before optimizing
105
+
106
+ ### 8. UNIX Philosophy
107
+
108
+ **Do one thing well. Compose tools through standard interfaces.**
109
+
110
+ - Each hook handler does one thing
111
+ - Each tool is a standalone CLI program
112
+ - Each skill is a self-contained capability
113
+ - Hooks compose via `Promise.allSettled` — independent, parallel, isolated failures
114
+ - Tools compose via `bun run tool:<name>` — standard I/O, exit codes
115
+
116
+ ### 9. CLI as Interface
117
+
118
+ **Every operation should be accessible via command line.**
119
+
120
+ - `pal cli <command>` for system management
121
+ - `bun run tool:<name>` for utilities
122
+ - Skills triggered via agent slash commands
123
+ - No hidden operations — everything is scriptable and testable
124
+
125
+ ### 10. Spec / Test First
126
+
127
+ **Define expected behavior before writing implementation.**
128
+
129
+ - Write tests before implementation
130
+ - Tests should fail initially
131
+ - Implement until tests pass
132
+ - Run `check-write`, `type-check`, and `test` after every edit
133
+
134
+ ### 11. Custom Skill Management
135
+
136
+ **Skills are the organizational unit for all domain expertise.**
137
+
138
+ Skills are active orchestrators, not passive documentation:
139
+ - **Self-activating:** Trigger automatically based on user request
140
+ - **Self-contained:** Package all context, workflows, and tools
141
+ - **Composable:** Can invoke other skills and agents
142
+ - **Evolvable:** Easy to add, modify, or deprecate
143
+
144
+ ### 12. Custom Memory System
145
+
146
+ **Automatic capture and preservation of valuable work.**
147
+
148
+ Every session, every insight, every decision — captured automatically:
149
+ - Relationship notes extracted via inference
150
+ - Learnings captured per session
151
+ - Ratings tracked (explicit and implicit)
152
+ - Wisdom crystallized from recurring patterns
153
+ - Failures logged for pattern avoidance
154
+
155
+ ### 13. Permission to Fail
156
+
157
+ **Explicit permission to say "I don't know" prevents hallucinations.**
158
+
159
+ The AI has explicit permission to say "I don't know" when:
160
+ - Information isn't available in context
161
+ - Multiple conflicting answers seem equally valid
162
+ - Verification isn't possible
163
+
164
+ Fabricating an answer is far worse than admitting uncertainty.
165
+
166
+ ---
167
+
168
+ ## Skill System Architecture
169
+
170
+ ### Canonical Skill Structure
171
+
172
+ ```
173
+ assets/skills/<name>/
174
+ ├── SKILL.md # Main skill file (REQUIRED)
175
+ └── tools/ # CLI tools for automation (optional)
176
+ └── tool-name.ts # TypeScript CLI tool
177
+ ```
178
+
179
+ ### SKILL.md Format
180
+
181
+ ```markdown
182
+ ---
183
+ name: skill-name
184
+ description: What it does. Use when [triggers]. Capabilities.
185
+ ---
186
+
187
+ # Skill Name
188
+
189
+ Brief description.
190
+
191
+ ## Workflow
192
+ [Steps, decision trees, or procedures]
193
+ ```
194
+
195
+ ### Key Rules
196
+
197
+ - **Description**: Used by the agent for skill matching — be specific about triggers
198
+ - **One SKILL.md per skill**: The single entry point
199
+ - **Tools are optional**: Only add when the skill needs CLI automation
200
+ - **Skills are agent-agnostic**: No agent-specific assumptions in SKILL.md
201
+
202
+ ---
203
+
204
+ ## Hook System Architecture
205
+
206
+ ### Lifecycle
207
+
208
+ ```
209
+ ┌─────────────────────┐
210
+ │ Session Start │──► LoadContext.ts
211
+ │ │ - Regenerate CLAUDE.md if stale
212
+ │ │ - Inject dynamic context (system-reminder)
213
+ └─────────────────────┘
214
+
215
+ ┌─────────────────────┐
216
+ │ User Prompt Submit │──► UserPromptOrchestrator.ts
217
+ │ │ - Rating capture (explicit/implicit)
218
+ │ │ - Session naming (first prompt)
219
+ └─────────────────────┘
220
+
221
+ ┌─────────────────────┐
222
+ │ Pre Tool Use │──► SecurityValidator.ts
223
+ │ │ - Bash command validation
224
+ │ │ - File path validation
225
+ │ │──► SkillGuard.ts
226
+ │ │ - Block false-positive skill matches
227
+ └─────────────────────┘
228
+
229
+ ┌─────────────────────┐
230
+ │ Stop │──► StopOrchestrator.ts
231
+ │ │ - Work session capture
232
+ │ │ - Relationship extraction (Haiku inference)
233
+ │ │ - Work learning capture
234
+ │ │ - Failure logging
235
+ │ │ - Reflect trigger check
236
+ │ │ - Auto-backup
237
+ │ │ - Count updates
238
+ │ │ - Tab reset
239
+ └─────────────────────┘
240
+ ```
241
+
242
+ ### Design Principles
243
+
244
+ - **Fail-open**: Hook errors never block the user's session
245
+ - **Parallel execution**: Stop handlers run via `Promise.allSettled` — one failure doesn't block others
246
+ - **Idempotent**: Handlers check for existing state before writing (e.g., session dedup)
247
+ - **Timeout-aware**: Inference calls have hard timeouts (8s default)
248
+ - **Subagent-aware**: `LoadContext.ts` skips heavy context loading for subagent sessions
249
+
250
+ ### Configuration
251
+
252
+ Hooks are registered in the agent's settings file during `pal cli install`:
253
+
254
+ ```json
255
+ {
256
+ "hooks": {
257
+ "SessionStart": [{ "type": "command", "command": "bun run <path>/LoadContext.ts" }],
258
+ "UserPromptSubmit": [{ "type": "command", "command": "bun run <path>/UserPromptOrchestrator.ts" }],
259
+ "PreToolUse": [
260
+ { "type": "command", "command": "bun run <path>/SecurityValidator.ts", "matcher": "Bash" },
261
+ { "type": "command", "command": "bun run <path>/SkillGuard.ts", "matcher": "Skill" }
262
+ ],
263
+ "Stop": [{ "type": "command", "command": "bun run <path>/StopOrchestrator.ts" }]
264
+ }
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Memory System Architecture
271
+
272
+ ### Directory Structure
273
+
274
+ ```
275
+ memory/
276
+ ├── state/ # Runtime state
277
+ │ ├── sessions.json # Session registry (name, cwd, status, summary)
278
+ │ ├── projects.json # Multi-session project tracking
279
+ │ ├── counts.json # Cached counts for greeting
280
+ │ ├── last-responses.json # Cached responses for rating correlation
281
+ │ ├── pending-failure.json # Deferred failure capture
282
+ │ └── debug.log # Hook execution logs
283
+
284
+ ├── signals/ # User feedback
285
+ │ └── ratings.jsonl # Explicit + implicit ratings
286
+
287
+ ├── relationship/ # Interaction tracking
288
+ │ ├── YYYY-MM/
289
+ │ │ └── YYYY-MM-DD.md # Daily notes (W/O/B format)
290
+ │ ├── opinions.json # Confidence-tracked opinions
291
+ │ └── reflections/ # Periodic reflection reports
292
+
293
+ ├── session-learning/ # Per-session learnings
294
+ │ └── YYYY-MM/
295
+ │ └── YYYY-MM-DD_title.md # Session learning with frontmatter
296
+
297
+ ├── failures/ # Low-rating context dumps
298
+ │ └── YYYY-MM/
299
+ │ └── YYYY-MM-DD_context.md # Failed session context for avoidance
300
+
301
+ ├── wisdom/ # Crystallized principles
302
+ │ └── frames/
303
+ │ └── domain.md # Domain-specific principles with confidence
304
+
305
+ └── synthesis/ # Pattern aggregation
306
+ └── YYYY-MM/
307
+ └── YYYY-MM-DD_period.md # Weekly/monthly synthesis reports
308
+ ```
309
+
310
+ ### Data Flow
311
+
312
+ ```
313
+ Session interaction
314
+
315
+ ├─► [Stop] Rating capture ──► signals/ratings.jsonl
316
+
317
+ ├─► [Stop] Relationship extraction (Haiku) ──► relationship/YYYY-MM/YYYY-MM-DD.md
318
+
319
+ ├─► [Stop] Work learning capture ──► session-learning/YYYY-MM/...
320
+
321
+ ├─► [Stop] Failure capture (low ratings) ──► failures/YYYY-MM/...
322
+
323
+ ├─► [Periodic] Reflect trigger ──► relationship/opinions.json
324
+ │ relationship/reflections/...
325
+
326
+ ├─► [Periodic] Synthesis ──► synthesis/YYYY-MM/...
327
+
328
+ └─► [Periodic] Wisdom graduation ──► wisdom/frames/...
329
+ ```
330
+
331
+ ### Relationship Note Format
332
+
333
+ Three note types, captured via Haiku inference at session end:
334
+
335
+ ```
336
+ W — World facts (user's situation, projects, tools)
337
+ O(c=0.85) — Opinions/preferences with confidence
338
+ B(c=0.75) — Beliefs/behavioral patterns with confidence
339
+ ```
340
+
341
+ ### Opinion Lifecycle
342
+
343
+ ```
344
+ Relationship notes (O/B types)
345
+
346
+ ├─► Reflect tool groups similar notes
347
+ │ ├─► 2+ occurrences → new opinion at 50% confidence
348
+ │ └─► Matches existing → +2% supporting evidence
349
+
350
+ ├─► AI confirmation → +10% (via tool:opinion)
351
+ ├─► AI contradiction → -20% (via tool:opinion)
352
+
353
+ └─► At ≥85% confidence → auto-injected into session context
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Context Loading Architecture
359
+
360
+ ### Two-Layer Design
361
+
362
+ **Static context** (loaded natively by the agent):
363
+ - CLAUDE.md — identity, modes, context routing table
364
+ - Loaded once at session start, always available
365
+
366
+ **Dynamic context** (injected by LoadContext hook):
367
+ - Changes per-session, can't live in a static file
368
+ - Injected as `<system-reminder>` block to stdout
369
+ - Each section independently toggleable in `pal-settings.json → dynamicContext`
370
+
371
+ ### Injection Order
372
+
373
+ ```
374
+ LoadContext.ts
375
+
376
+ ├─► Regenerate CLAUDE.md if template/telos changed
377
+
378
+ └─► Build system-reminder:
379
+ 1. loadAtStartup files (user-configured)
380
+ 2. Crystallized principles (wisdom frames)
381
+ 3. Tracked opinions (≥85% confidence)
382
+ 4. Recent interaction notes (last 2 days)
383
+ 5. Learning digest (this project + other recent)
384
+ 6. Pattern synthesis recommendations
385
+ 7. Signal trends (today/week/trend)
386
+ 8. Failure patterns (last 5 low-rating contexts)
387
+ 9. Active work summary (sessions + projects)
388
+ ```
389
+
390
+ ### On-Demand Context
391
+
392
+ Everything else loads via the routing table in CLAUDE.md. The AI reads files only when the current task requires that context — no upfront loading of the full system.
393
+
394
+ ---
395
+
396
+ ## Security Architecture
397
+
398
+ ### Fail-Open Design
399
+
400
+ `SecurityValidator.ts` runs on PreToolUse for Bash commands. It blocks known-dangerous patterns but never prevents legitimate work:
401
+
402
+ - **Blocked**: `rm -rf /`, `chmod 777`, known secret file paths, command injection patterns
403
+ - **Allowed**: Everything else passes through
404
+ - **On error**: The hook exits silently — a broken security hook never blocks the user
405
+
406
+ ### What's Protected
407
+
408
+ - Dangerous shell commands (recursive deletes, permission changes)
409
+ - Known secret file paths (.env, credentials, key files)
410
+ - Command injection patterns in arguments
411
+
412
+ ### What's NOT Protected
413
+
414
+ PAL does not implement:
415
+ - Network-level security (no firewall, no proxy)
416
+ - File encryption (memory is plaintext on disk)
417
+ - Access control (anyone with filesystem access can read memory)
418
+
419
+ Users are responsible for securing their own machine and API keys.
420
+
421
+ ---
422
+
423
+ ## Cross-Platform Architecture
424
+
425
+ ### Agent Abstraction
426
+
427
+ ```
428
+ src/targets/
429
+ ├── claude/ # Claude Code specific
430
+ │ ├── install.ts # Register hooks + skills in ~/.claude/settings.json
431
+ │ └── uninstall.ts
432
+ ├── opencode/ # opencode specific
433
+ │ ├── install.ts # Register hooks + skills in opencode config
434
+ │ ├── uninstall.ts
435
+ │ └── plugin.ts # opencode plugin interface
436
+ └── lib.ts # Shared: JSON read/write, settings merge, TELOS scaffold
437
+ ```
438
+
439
+ ### Path Resolution
440
+
441
+ All paths resolve through `src/hooks/lib/paths.ts`:
442
+
443
+ | Path | Default | Override |
444
+ |------|---------|----------|
445
+ | PAL home | `~/.agents/PAL` | `PAL_HOME` |
446
+ | PAL package | Auto-detected from source | `PAL_PKG` |
447
+ | Claude config | `~/.claude` | `PAL_CLAUDE_DIR` |
448
+ | opencode config | `~/.config/opencode` | `PAL_OPENCODE_DIR` |
449
+ | Agents dir | `~/.agents` | `PAL_AGENTS_DIR` |
450
+
451
+ ### Portability Contract
452
+
453
+ - Core logic (`src/hooks/lib/`, `src/tools/`) has zero agent-specific imports
454
+ - Agent-specific code lives only in `src/targets/`
455
+ - Skills reference no agent-specific APIs
456
+ - Memory format is plain JSON/JSONL/Markdown — readable by any tool
457
+
458
+ ---
459
+
460
+ ## File Naming Conventions
461
+
462
+ | Type | Convention | Example |
463
+ |------|------------|---------|
464
+ | Skill directory | kebab-case | `extract-wisdom/`, `first-principles/` |
465
+ | SKILL.md | Uppercase | `SKILL.md` |
466
+ | Hook files | PascalCase | `LoadContext.ts`, `StopOrchestrator.ts` |
467
+ | Handler files | kebab-case | `session-name.ts`, `work-learning.ts` |
468
+ | Library files | kebab-case | `text-similarity.ts`, `signal-trends.ts` |
469
+ | Tool files | kebab-case | `relationship-reflect.ts`, `token-cost.ts` |
470
+ | Memory files | date-prefixed | `2026-03-24.md`, `2026-03-24_weekly.md` |
471
+ | Template files | UPPER_SNAKE | `ALGORITHM.md`, `CONTEXT_ROUTING.md` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.12.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": {
@@ -45,7 +45,6 @@
45
45
  "install:all": "bun run src/cli/index.ts cli install",
46
46
  "uninstall": "bun run src/cli/index.ts cli uninstall",
47
47
  "tool:analyze": "bun run src/tools/analyze.ts",
48
- "tool:opinion": "bun run src/tools/opinion.ts",
49
48
  "tool:reflect": "bun run src/tools/relationship-reflect.ts",
50
49
  "tool:export": "bun run src/tools/export.ts",
51
50
  "tool:import": "bun run src/tools/import.ts",
@@ -22,7 +22,7 @@ const OBSERVATION_SCHEMA = {
22
22
  type: "string",
23
23
  enum: ["O", "W", "B"],
24
24
  description:
25
- "O=opinion/preference, W=factual observation, B=belief/behavioral pattern",
25
+ "O=opinion/preference, W=factual observation, B=biographical (what the AI did this session)",
26
26
  },
27
27
  text: { type: "string" },
28
28
  confidence: { type: "number" },
@@ -74,11 +74,11 @@ export async function captureRelationship(
74
74
  logDebug("relationship", "Calling inference...");
75
75
  const result = await inference({
76
76
  system:
77
- "You analyze user messages from an AI coding session to extract relationship observations. " +
78
- "Types: O=opinions/preferences (how they like to work, what they want), " +
79
- "B=beliefs/behavioral patterns (how they approach problems, decision-making style, recurring habits), " +
80
- "W=world facts (their situation, projects, tools they use). " +
81
- "Focus on: preferences, corrections, frustrations, positive reactions, communication style, problem-solving approach. " +
77
+ "You analyze messages from an AI coding session to extract relationship observations. " +
78
+ "Types: O=opinions/preferences (how the user likes to work, what they want), " +
79
+ "B=biographical (what the AI accomplished this session, written in first-person), " +
80
+ "W=world facts (user's situation, projects, tools they use). " +
81
+ "Focus on: preferences, corrections, frustrations, positive reactions, communication style, and session accomplishments. " +
82
82
  "Return 0-3 observations. If nothing notable, return empty observations array. Be concise.",
83
83
  user: `User messages from this session:\n${userMessages.map((m, i) => `${i + 1}. ${m}`).join("\n")}`,
84
84
  maxTokens: 300,
@@ -9,7 +9,7 @@
9
9
  * This avoids the 1-5s inference latency that previously blocked every first prompt.
10
10
  */
11
11
 
12
- // import { spawn } from "node:child_process";
12
+ import { spawn } from "node:child_process";
13
13
  import { inference } from "../lib/inference";
14
14
  import { logDebug, logError } from "../lib/log";
15
15
  import {
@@ -41,25 +41,24 @@ export async function captureSessionName(
41
41
  writeSessionName(sessionId, name);
42
42
  logDebug("session-name", `Named from prompt: "${name}"`);
43
43
 
44
- // TODO: re-enable when a consumer exists (tab titles, dashboard)
45
- // // 2. Spawn detached background process to upgrade with inference
46
- // if (!process.env.ANTHROPIC_API_KEY) return;
47
- // try {
48
- // const promptB64 = Buffer.from(message.slice(0, 800)).toString("base64");
49
- // const child = spawn(
50
- // "bun",
51
- // [import.meta.filename, "--upgrade", sessionId, promptB64, fallback],
52
- // {
53
- // detached: true,
54
- // stdio: "ignore",
55
- // env: { ...process.env, CLAUDECODE: undefined },
56
- // }
57
- // );
58
- // child.unref();
59
- // logDebug("session-name", "Spawned background inference upgrade");
60
- // } catch {
61
- // // Non-critical — deterministic name is already stored
62
- // }
44
+ // Spawn detached background process to upgrade with Haiku inference
45
+ if (!process.env.ANTHROPIC_API_KEY) return;
46
+ try {
47
+ const promptB64 = Buffer.from(message.slice(0, 800)).toString("base64");
48
+ const child = spawn(
49
+ "bun",
50
+ [import.meta.filename, "--upgrade", sessionId, promptB64, name],
51
+ {
52
+ detached: true,
53
+ stdio: "ignore",
54
+ env: { ...process.env, CLAUDECODE: undefined },
55
+ }
56
+ );
57
+ child.unref();
58
+ logDebug("session-name", "Spawned background Haiku upgrade");
59
+ } catch {
60
+ // Non-critical deterministic name is already stored
61
+ }
63
62
  }
64
63
 
65
64
  /**
@@ -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(
@@ -5,7 +5,7 @@
5
5
  * Notes live at memory/relationship/YYYY-MM/YYYY-MM-DD.md
6
6
  * W = world (facts about user's situation)
7
7
  * O = opinion (preference with confidence)
8
- * B = belief (behavioral pattern with confidence)
8
+ * B = biographical (what the AI did this session, first-person)
9
9
  *
10
10
  * Extraction is handled by the relationship handler via Haiku inference.
11
11
  * This lib provides storage and reading utilities only.
@@ -80,7 +80,7 @@ export function appendNotes(notes: RelationshipNote[], sessionId?: string): void
80
80
  if (sessionId) lines.push(`<!-- session:${sessionId} -->`);
81
81
 
82
82
  for (const note of fresh) {
83
- if ((note.type === "O" || note.type === "B") && note.confidence !== undefined) {
83
+ if (note.type === "O" && note.confidence !== undefined) {
84
84
  lines.push(`- ${note.type}(c=${note.confidence}): ${note.text}`);
85
85
  } else {
86
86
  lines.push(`- ${note.type}: ${note.text}`);
@@ -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
 
@@ -3,7 +3,7 @@
3
3
  * RelationshipReflect — Periodic reflection on relationship patterns.
4
4
  *
5
5
  * Reads recent relationship notes and ratings to:
6
- * - Promote recurring O/B notes into tracked opinions with confidence
6
+ * - Promote recurring O notes into tracked opinions with confidence
7
7
  * - Update confidence on existing opinions via supporting evidence
8
8
  * - Generate a summary report
9
9
  *
@@ -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 and B notes become opinions, skip already-processed notes
163
- const opinionNotes = notes.filter(
164
- (n) => (n.type === "O" || n.type === "B") && (!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",
@@ -225,7 +224,7 @@ interface OpinionSummary {
225
224
  }
226
225
 
227
226
  function groupNoteOccurrences(notes: ParsedNote[]): OpinionSummary[] {
228
- const opNotes = notes.filter((n) => n.type === "O" || n.type === "B");
227
+ const opNotes = notes.filter((n) => n.type === "O");
229
228
  const groups = new Map<
230
229
  string,
231
230
  { confidences: number[]; dates: string[]; text: string }
@@ -397,7 +396,7 @@ if (values.help) {
397
396
  RelationshipReflect — Periodic reflection + opinion promotion
398
397
 
399
398
  Reads recent relationship notes and ratings. Promotes recurring
400
- observations (O/B types) into tracked opinions with confidence scoring.
399
+ observations (O type) into tracked opinions with confidence scoring.
401
400
 
402
401
  Usage:
403
402
  bun run tool:reflect Reflect on last 7 days (default)
@@ -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();