pi-continuous-learning 0.4.0 → 0.5.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/README.md CHANGED
@@ -95,6 +95,32 @@ The script:
95
95
  - **Global timeout:** The process exits after 5 minutes regardless of progress.
96
96
  - **Stale lock detection:** If a previous run crashed, the lockfile is automatically cleaned up after 10 minutes or if the owning process is no longer alive.
97
97
 
98
+ ### Logging
99
+
100
+ The analyzer writes structured JSON logs to `~/.pi/continuous-learning/analyzer.log` (configurable via `log_path` in config). Each run logs:
101
+
102
+ - **Run timing** - total duration and per-project duration
103
+ - **Token usage** - input, output, cache read/write, total
104
+ - **Cost** - USD cost per project and total
105
+ - **Instinct changes** - counts of created, updated, and deleted instincts
106
+ - **Skip reasons** - why projects were skipped (no new observations, below threshold, etc.)
107
+ - **Errors** - full error details with stack traces
108
+
109
+ Each log line is a JSON object, making it easy to parse with `jq`:
110
+
111
+ ```bash
112
+ # View recent run summaries
113
+ cat ~/.pi/continuous-learning/analyzer.log | jq 'select(.event == "run_complete")'
114
+
115
+ # Check total cost over time
116
+ cat ~/.pi/continuous-learning/analyzer.log | jq 'select(.event == "run_complete") | .total_cost_usd'
117
+
118
+ # See which projects were processed
119
+ cat ~/.pi/continuous-learning/analyzer.log | jq 'select(.event == "project_complete") | {project: .project_name, duration_s: (.duration_ms/1000), cost: .cost_usd}'
120
+ ```
121
+
122
+ The log file auto-rotates at 10 MB (old content moved to `analyzer.log.old`). When the log file is not writable, output falls back to stderr.
123
+
98
124
  ### Setting up a schedule (macOS)
99
125
 
100
126
  The recommended way to run the analyzer on a recurring schedule on macOS is with `launchd`, which persists across reboots and handles log rotation.
@@ -124,9 +150,9 @@ cat > ~/Library/LaunchAgents/com.pi-continuous-learning.analyze.plist << EOF
124
150
  <key>StartInterval</key>
125
151
  <integer>300</integer>
126
152
  <key>StandardOutPath</key>
127
- <string>/tmp/pi-cl-analyze.log</string>
153
+ <string>/tmp/pi-cl-analyze-stdout.log</string>
128
154
  <key>StandardErrorPath</key>
129
- <string>/tmp/pi-cl-analyze.log</string>
155
+ <string>/tmp/pi-cl-analyze-stderr.log</string>
130
156
  <key>EnvironmentVariables</key>
131
157
  <dict>
132
158
  <key>PATH</key>
@@ -153,8 +179,11 @@ The analyzer will now run every 5 minutes (300 seconds) in the background, start
153
179
  # Check if the job is loaded
154
180
  launchctl list | grep pi-continuous-learning
155
181
 
156
- # View recent output
157
- tail -20 /tmp/pi-cl-analyze.log
182
+ # View recent log entries (structured JSON)
183
+ tail -5 ~/.pi/continuous-learning/analyzer.log | jq .
184
+
185
+ # View stderr output (fallback only)
186
+ tail -20 /tmp/pi-cl-analyze-stderr.log
158
187
  ```
159
188
 
160
189
  #### Disabling the schedule
@@ -186,7 +215,7 @@ Use cron:
186
215
  crontab -e
187
216
 
188
217
  # Add this line (runs every 5 minutes):
189
- */5 * * * * pi-cl-analyze >> /tmp/pi-cl-analyze.log 2>&1
218
+ */5 * * * * pi-cl-analyze 2>> /tmp/pi-cl-analyze-stderr.log
190
219
  ```
191
220
 
192
221
  To disable, remove the line from `crontab -e`.
@@ -273,6 +302,7 @@ Only include the fields you want to change — missing fields use the defaults a
273
302
  | `max_injection_chars` | 4000 | Character budget for the injection block (~1000 tokens) |
274
303
  | `model` | `claude-haiku-4-5` | Model for the background analyzer (lightweight models recommended to minimize cost) |
275
304
  | `timeout_seconds` | 120 | Per-project timeout for the analyzer LLM session |
305
+ | `log_path` | `~/.pi/continuous-learning/analyzer.log` | Path to the analyzer log file |
276
306
 
277
307
  ## Storage
278
308
 
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Structured logging for the background analyzer CLI.
3
+ * Writes to a configurable log file (default: ~/.pi/continuous-learning/analyzer.log).
4
+ * Each line is a JSON object for easy parsing and grep-ability.
5
+ *
6
+ * Log levels: info, warn, error
7
+ * Never throws - all I/O failures fall back to stderr.
8
+ */
9
+ export interface ProjectRunStats {
10
+ readonly project_id: string;
11
+ readonly project_name: string;
12
+ readonly duration_ms: number;
13
+ readonly observations_processed: number;
14
+ readonly observations_total: number;
15
+ readonly instincts_created: number;
16
+ readonly instincts_updated: number;
17
+ readonly instincts_deleted: number;
18
+ readonly tokens_input: number;
19
+ readonly tokens_output: number;
20
+ readonly tokens_cache_read: number;
21
+ readonly tokens_cache_write: number;
22
+ readonly tokens_total: number;
23
+ readonly cost_usd: number;
24
+ readonly model: string;
25
+ readonly skipped_reason?: string;
26
+ }
27
+ export interface RunSummary {
28
+ readonly total_duration_ms: number;
29
+ readonly projects_processed: number;
30
+ readonly projects_skipped: number;
31
+ readonly projects_errored: number;
32
+ readonly projects_total: number;
33
+ readonly total_tokens: number;
34
+ readonly total_cost_usd: number;
35
+ readonly total_instincts_created: number;
36
+ readonly total_instincts_updated: number;
37
+ readonly total_instincts_deleted: number;
38
+ readonly project_stats: readonly ProjectRunStats[];
39
+ }
40
+ export declare class AnalyzeLogger {
41
+ private readonly logPath;
42
+ constructor(logPath?: string);
43
+ getLogPath(): string;
44
+ info(message: string, data?: Record<string, unknown>): void;
45
+ warn(message: string, data?: Record<string, unknown>): void;
46
+ error(message: string, error?: unknown, data?: Record<string, unknown>): void;
47
+ /** Log the start of a full analyzer run */
48
+ runStart(projectCount: number): void;
49
+ /** Log that a project was skipped (with reason) */
50
+ projectSkipped(projectId: string, projectName: string, reason: string): void;
51
+ /** Log the start of a project analysis */
52
+ projectStart(projectId: string, projectName: string, newObservations: number, totalObservations: number): void;
53
+ /** Log per-project results after analysis completes */
54
+ projectComplete(stats: ProjectRunStats): void;
55
+ /** Log a project that errored during analysis */
56
+ projectError(projectId: string, projectName: string, error: unknown): void;
57
+ /** Log the full run summary */
58
+ runComplete(summary: RunSummary): void;
59
+ private ensureLogDir;
60
+ private write;
61
+ private rotateIfNeeded;
62
+ }
63
+ //# sourceMappingURL=analyze-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-logger.d.ts","sourceRoot":"","sources":["../../src/cli/analyze-logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Structured logging for the background analyzer CLI.
3
+ * Writes to a configurable log file (default: ~/.pi/continuous-learning/analyzer.log).
4
+ * Each line is a JSON object for easy parsing and grep-ability.
5
+ *
6
+ * Log levels: info, warn, error
7
+ * Never throws - all I/O failures fall back to stderr.
8
+ */
9
+ import { appendFileSync, mkdirSync, renameSync, statSync } from "node:fs";
10
+ import { dirname } from "node:path";
11
+ import { join } from "node:path";
12
+ import { getBaseDir } from "../storage.js";
13
+ // ---------------------------------------------------------------------------
14
+ // Constants
15
+ // ---------------------------------------------------------------------------
16
+ const DEFAULT_LOG_FILENAME = "analyzer.log";
17
+ const MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB - rotate beyond this
18
+ // ---------------------------------------------------------------------------
19
+ // Logger
20
+ // ---------------------------------------------------------------------------
21
+ export class AnalyzeLogger {
22
+ logPath;
23
+ constructor(logPath) {
24
+ this.logPath = logPath ?? join(getBaseDir(), DEFAULT_LOG_FILENAME);
25
+ this.ensureLogDir();
26
+ }
27
+ getLogPath() {
28
+ return this.logPath;
29
+ }
30
+ info(message, data) {
31
+ this.write("info", message, data);
32
+ }
33
+ warn(message, data) {
34
+ this.write("warn", message, data);
35
+ }
36
+ error(message, error, data) {
37
+ const errorData = { ...data };
38
+ if (error instanceof Error) {
39
+ errorData.error_message = error.message;
40
+ errorData.error_stack = error.stack;
41
+ }
42
+ else if (error !== undefined) {
43
+ errorData.error_message = String(error);
44
+ }
45
+ this.write("error", message, errorData);
46
+ }
47
+ /** Log the start of a full analyzer run */
48
+ runStart(projectCount) {
49
+ this.info("Analyzer run started", {
50
+ event: "run_start",
51
+ project_count: projectCount,
52
+ pid: process.pid,
53
+ });
54
+ }
55
+ /** Log that a project was skipped (with reason) */
56
+ projectSkipped(projectId, projectName, reason) {
57
+ this.info(`Skipped ${projectName}`, {
58
+ event: "project_skipped",
59
+ project_id: projectId,
60
+ project_name: projectName,
61
+ reason,
62
+ });
63
+ }
64
+ /** Log the start of a project analysis */
65
+ projectStart(projectId, projectName, newObservations, totalObservations) {
66
+ this.info(`Processing ${projectName}`, {
67
+ event: "project_start",
68
+ project_id: projectId,
69
+ project_name: projectName,
70
+ new_observations: newObservations,
71
+ total_observations: totalObservations,
72
+ });
73
+ }
74
+ /** Log per-project results after analysis completes */
75
+ projectComplete(stats) {
76
+ const durationSec = (stats.duration_ms / 1000).toFixed(1);
77
+ const costFormatted = stats.cost_usd.toFixed(4);
78
+ this.info(`Completed ${stats.project_name} in ${durationSec}s - ` +
79
+ `tokens: ${stats.tokens_total}, cost: $${costFormatted}, ` +
80
+ `instincts: +${stats.instincts_created} ~${stats.instincts_updated} -${stats.instincts_deleted}`, { event: "project_complete", ...stats });
81
+ }
82
+ /** Log a project that errored during analysis */
83
+ projectError(projectId, projectName, error) {
84
+ this.error(`Error processing ${projectName}`, error, {
85
+ event: "project_error",
86
+ project_id: projectId,
87
+ project_name: projectName,
88
+ });
89
+ }
90
+ /** Log the full run summary */
91
+ runComplete(summary) {
92
+ const durationSec = (summary.total_duration_ms / 1000).toFixed(1);
93
+ const costFormatted = summary.total_cost_usd.toFixed(4);
94
+ this.info(`Run complete in ${durationSec}s - ` +
95
+ `${summary.projects_processed}/${summary.projects_total} projects processed, ` +
96
+ `${summary.projects_skipped} skipped, ${summary.projects_errored} errored - ` +
97
+ `tokens: ${summary.total_tokens}, cost: $${costFormatted}, ` +
98
+ `instincts: +${summary.total_instincts_created} ~${summary.total_instincts_updated} -${summary.total_instincts_deleted}`, { event: "run_complete", ...summary });
99
+ }
100
+ // -------------------------------------------------------------------------
101
+ // Internal
102
+ // -------------------------------------------------------------------------
103
+ ensureLogDir() {
104
+ try {
105
+ mkdirSync(dirname(this.logPath), { recursive: true });
106
+ }
107
+ catch {
108
+ // Best effort
109
+ }
110
+ }
111
+ write(level, message, data) {
112
+ const entry = {
113
+ timestamp: new Date().toISOString(),
114
+ level,
115
+ message,
116
+ ...data,
117
+ };
118
+ const line = JSON.stringify(entry) + "\n";
119
+ try {
120
+ this.rotateIfNeeded();
121
+ appendFileSync(this.logPath, line, "utf-8");
122
+ }
123
+ catch {
124
+ // Fall back to stderr - never lose log entries entirely
125
+ process.stderr.write(`[analyze] ${line}`);
126
+ }
127
+ }
128
+ rotateIfNeeded() {
129
+ try {
130
+ const stat = statSync(this.logPath);
131
+ if (stat.size > MAX_LOG_SIZE_BYTES) {
132
+ renameSync(this.logPath, this.logPath + ".old");
133
+ }
134
+ }
135
+ catch {
136
+ // File doesn't exist yet or stat failed - fine
137
+ }
138
+ }
139
+ }
140
+ //# sourceMappingURL=analyze-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-logger.js","sourceRoot":"","sources":["../../src/cli/analyze-logger.ts"],"names":[],"mappings":"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"}
@@ -12,11 +12,12 @@ import { buildAnalyzerSystemPrompt } from "./analyze-prompt.js";
12
12
  import { createInstinctListTool, createInstinctReadTool, createInstinctWriteTool, createInstinctDeleteTool, } from "../instinct-tools.js";
13
13
  import { readAgentsMd } from "../agents-md.js";
14
14
  import { homedir } from "node:os";
15
+ import { AnalyzeLogger } from "./analyze-logger.js";
15
16
  // ---------------------------------------------------------------------------
16
- // Lockfile guard ensures only one instance runs at a time
17
+ // Lockfile guard - ensures only one instance runs at a time
17
18
  // ---------------------------------------------------------------------------
18
19
  const LOCKFILE_NAME = "analyze.lock";
19
- const LOCK_STALE_MS = 10 * 60 * 1000; // 10 minutes stale lock threshold
20
+ const LOCK_STALE_MS = 10 * 60 * 1000; // 10 minutes - stale lock threshold
20
21
  function getLockfilePath(baseDir) {
21
22
  return join(baseDir, LOCKFILE_NAME);
22
23
  }
@@ -33,14 +34,14 @@ function acquireLock(baseDir) {
33
34
  if (age < LOCK_STALE_MS) {
34
35
  return false; // Process alive and lock is fresh
35
36
  }
36
- // Process alive but lock is stale treat as abandoned
37
+ // Process alive but lock is stale - treat as abandoned
37
38
  }
38
39
  catch {
39
- // Process is dead lock is orphaned, safe to take over
40
+ // Process is dead - lock is orphaned, safe to take over
40
41
  }
41
42
  }
42
43
  catch {
43
- // Malformed lockfile remove and proceed
44
+ // Malformed lockfile - remove and proceed
44
45
  }
45
46
  }
46
47
  writeFileSync(lockPath, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }), "utf-8");
@@ -53,19 +54,55 @@ function releaseLock(baseDir) {
53
54
  unlinkSync(lockPath);
54
55
  }
55
56
  catch {
56
- // Best effort don't crash on cleanup
57
+ // Best effort - don't crash on cleanup
57
58
  }
58
59
  }
59
60
  // ---------------------------------------------------------------------------
60
61
  // Global timeout
61
62
  // ---------------------------------------------------------------------------
62
63
  const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total
63
- function startGlobalTimeout(timeoutMs) {
64
+ function startGlobalTimeout(timeoutMs, logger) {
64
65
  setTimeout(() => {
65
- console.error("[analyze] Global timeout reached. Exiting.");
66
+ logger.error("Global timeout reached, forcing exit");
66
67
  process.exit(2);
67
68
  }, timeoutMs).unref();
68
69
  }
70
+ /**
71
+ * Wraps instinct tools to count create/update/delete operations.
72
+ * Returns new tool instances that increment the provided counts.
73
+ */
74
+ function wrapInstinctToolsWithTracking(projectId, projectName, baseDir, counts) {
75
+ const writeTool = createInstinctWriteTool(projectId, projectName, baseDir);
76
+ const deleteTool = createInstinctDeleteTool(projectId, baseDir);
77
+ const trackedWrite = {
78
+ ...writeTool,
79
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
80
+ const result = await writeTool.execute(toolCallId, params, signal, onUpdate, ctx);
81
+ const details = result.details;
82
+ if (details?.action === "created") {
83
+ counts.created++;
84
+ }
85
+ else {
86
+ counts.updated++;
87
+ }
88
+ return result;
89
+ },
90
+ };
91
+ const trackedDelete = {
92
+ ...deleteTool,
93
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
94
+ const result = await deleteTool.execute(toolCallId, params, signal, onUpdate, ctx);
95
+ counts.deleted++;
96
+ return result;
97
+ },
98
+ };
99
+ return {
100
+ listTool: createInstinctListTool(projectId, baseDir),
101
+ readTool: createInstinctReadTool(projectId, baseDir),
102
+ writeTool: trackedWrite,
103
+ deleteTool: trackedDelete,
104
+ };
105
+ }
69
106
  function loadProjectsRegistry(baseDir) {
70
107
  const path = getProjectsRegistryPath(baseDir);
71
108
  if (!existsSync(path))
@@ -104,19 +141,23 @@ function hasNewObservations(projectId, meta, baseDir) {
104
141
  }
105
142
  return true;
106
143
  }
107
- async function analyzeProject(project, config, baseDir) {
144
+ async function analyzeProject(project, config, baseDir, logger) {
108
145
  const meta = loadProjectMeta(project.id, baseDir);
109
- if (!hasNewObservations(project.id, meta, baseDir))
110
- return false;
146
+ if (!hasNewObservations(project.id, meta, baseDir)) {
147
+ return { ran: false, skippedReason: "no new observations" };
148
+ }
111
149
  const obsPath = getObservationsPath(project.id, baseDir);
112
150
  const sinceLineCount = meta.last_observation_line_count ?? 0;
113
151
  const { lines: newObsLines, totalLineCount } = tailObservationsSince(obsPath, sinceLineCount);
114
- if (newObsLines.length === 0)
115
- return false;
152
+ if (newObsLines.length === 0) {
153
+ return { ran: false, skippedReason: "no new observation lines" };
154
+ }
116
155
  const obsCount = countObservations(project.id, baseDir);
117
- if (obsCount < config.min_observations_to_analyze)
118
- return false;
119
- console.log(`[analyze] Processing ${project.name} (${project.id}): ${newObsLines.length} new observations (${obsCount} total)`);
156
+ if (obsCount < config.min_observations_to_analyze) {
157
+ return { ran: false, skippedReason: `below threshold (${obsCount}/${config.min_observations_to_analyze})` };
158
+ }
159
+ const startTime = Date.now();
160
+ logger.projectStart(project.id, project.name, newObsLines.length, obsCount);
120
161
  runDecayPass(project.id, baseDir);
121
162
  const instinctsDir = join(getProjectDir(project.id, baseDir), "instincts", "personal");
122
163
  const agentsMdProject = readAgentsMd(join(project.root, "AGENTS.md"));
@@ -131,7 +172,7 @@ async function analyzeProject(project, config, baseDir) {
131
172
  }));
132
173
  }
133
174
  catch {
134
- // Skills loading is best-effort continue without them
175
+ // Skills loading is best-effort - continue without them
135
176
  }
136
177
  const userPrompt = buildAnalyzerUserPrompt(obsPath, instinctsDir, project, {
137
178
  agentsMdProject,
@@ -143,11 +184,14 @@ async function analyzeProject(project, config, baseDir) {
143
184
  const modelRegistry = new ModelRegistry(authStorage);
144
185
  const modelId = (config.model || DEFAULT_CONFIG.model);
145
186
  const model = getModel("anthropic", modelId);
187
+ // Track instinct operations
188
+ const instinctCounts = { created: 0, updated: 0, deleted: 0 };
189
+ const trackedTools = wrapInstinctToolsWithTracking(project.id, project.name, baseDir, instinctCounts);
146
190
  const customTools = [
147
- createInstinctListTool(project.id, baseDir),
148
- createInstinctReadTool(project.id, baseDir),
149
- createInstinctWriteTool(project.id, project.name, baseDir),
150
- createInstinctDeleteTool(project.id, baseDir),
191
+ trackedTools.listTool,
192
+ trackedTools.readTool,
193
+ trackedTools.writeTool,
194
+ trackedTools.deleteTool,
151
195
  ];
152
196
  const loader = new DefaultResourceLoader({
153
197
  systemPromptOverride: () => buildAnalyzerSystemPrompt(),
@@ -167,40 +211,88 @@ async function analyzeProject(project, config, baseDir) {
167
211
  finally {
168
212
  session.dispose();
169
213
  }
214
+ // Collect stats after session completes
215
+ const sessionStats = session.getSessionStats();
216
+ const durationMs = Date.now() - startTime;
217
+ const stats = {
218
+ project_id: project.id,
219
+ project_name: project.name,
220
+ duration_ms: durationMs,
221
+ observations_processed: newObsLines.length,
222
+ observations_total: obsCount,
223
+ instincts_created: instinctCounts.created,
224
+ instincts_updated: instinctCounts.updated,
225
+ instincts_deleted: instinctCounts.deleted,
226
+ tokens_input: sessionStats.tokens.input,
227
+ tokens_output: sessionStats.tokens.output,
228
+ tokens_cache_read: sessionStats.tokens.cacheRead,
229
+ tokens_cache_write: sessionStats.tokens.cacheWrite,
230
+ tokens_total: sessionStats.tokens.total,
231
+ cost_usd: sessionStats.cost,
232
+ model: modelId,
233
+ };
234
+ logger.projectComplete(stats);
170
235
  saveProjectMeta(project.id, { ...meta, last_analyzed_at: new Date().toISOString(), last_observation_line_count: totalLineCount }, baseDir);
171
- console.log(`[analyze] Completed ${project.name}`);
172
- return true;
236
+ return { ran: true, stats };
173
237
  }
174
238
  // ---------------------------------------------------------------------------
175
239
  // Main
176
240
  // ---------------------------------------------------------------------------
177
241
  async function main() {
178
242
  const baseDir = getBaseDir();
243
+ const config = loadConfig();
244
+ const logger = new AnalyzeLogger(config.log_path);
179
245
  if (!acquireLock(baseDir)) {
180
- console.log("[analyze] Another instance is already running. Exiting.");
246
+ logger.info("Another instance is already running, exiting");
181
247
  process.exit(0);
182
248
  }
183
- startGlobalTimeout(DEFAULT_TIMEOUT_MS);
249
+ startGlobalTimeout(DEFAULT_TIMEOUT_MS, logger);
250
+ const runStart = Date.now();
184
251
  try {
185
- const config = loadConfig();
186
252
  const registry = loadProjectsRegistry(baseDir);
187
253
  const projects = Object.values(registry);
188
254
  if (projects.length === 0) {
189
- console.log("[analyze] No projects registered. Use pi with the continuous-learning extension first.");
255
+ logger.info("No projects registered");
190
256
  return;
191
257
  }
258
+ logger.runStart(projects.length);
192
259
  let processed = 0;
260
+ let skipped = 0;
261
+ let errored = 0;
262
+ const allProjectStats = [];
193
263
  for (const project of projects) {
194
264
  try {
195
- const didRun = await analyzeProject(project, config, baseDir);
196
- if (didRun)
265
+ const result = await analyzeProject(project, config, baseDir, logger);
266
+ if (result.ran && result.stats) {
197
267
  processed++;
268
+ allProjectStats.push(result.stats);
269
+ }
270
+ else {
271
+ skipped++;
272
+ if (result.skippedReason) {
273
+ logger.projectSkipped(project.id, project.name, result.skippedReason);
274
+ }
275
+ }
198
276
  }
199
277
  catch (err) {
200
- console.error(`[analyze] Error processing ${project.name}: ${String(err)}`);
278
+ errored++;
279
+ logger.projectError(project.id, project.name, err);
201
280
  }
202
281
  }
203
- console.log(`[analyze] Done. Processed ${processed}/${projects.length} project(s).`);
282
+ const summary = {
283
+ total_duration_ms: Date.now() - runStart,
284
+ projects_processed: processed,
285
+ projects_skipped: skipped,
286
+ projects_errored: errored,
287
+ projects_total: projects.length,
288
+ total_tokens: allProjectStats.reduce((sum, s) => sum + s.tokens_total, 0),
289
+ total_cost_usd: allProjectStats.reduce((sum, s) => sum + s.cost_usd, 0),
290
+ total_instincts_created: allProjectStats.reduce((sum, s) => sum + s.instincts_created, 0),
291
+ total_instincts_updated: allProjectStats.reduce((sum, s) => sum + s.instincts_updated, 0),
292
+ total_instincts_deleted: allProjectStats.reduce((sum, s) => sum + s.instincts_deleted, 0),
293
+ project_stats: allProjectStats,
294
+ };
295
+ logger.runComplete(summary);
204
296
  }
205
297
  finally {
206
298
  releaseLock(baseDir);
@@ -208,7 +300,9 @@ async function main() {
208
300
  }
209
301
  main().catch((err) => {
210
302
  releaseLock(getBaseDir());
211
- console.error(`[analyze] Fatal error: ${String(err)}`);
303
+ // Last-resort logging - config may not have loaded
304
+ const logger = new AnalyzeLogger();
305
+ logger.error("Fatal error", err);
212
306
  process.exit(1);
213
307
  });
214
308
  //# sourceMappingURL=analyze.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/cli/analyze.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/cli/analyze.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAWhD,CAAC;AAEF,eAAO,MAAM,WAAW,QAKvB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,MAW5B,CAAC;AA2BF;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CA2BnC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAWhD,CAAC;AAEF,eAAO,MAAM,WAAW,QAKvB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,MAW5B,CAAC;AA4BF;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CA2BnC"}
package/dist/config.js CHANGED
@@ -53,6 +53,7 @@ const PartialConfigSchema = Type.Partial(Type.Object({
53
53
  active_hours_start: Type.Number(),
54
54
  active_hours_end: Type.Number(),
55
55
  max_idle_seconds: Type.Number(),
56
+ log_path: Type.String(),
56
57
  }));
57
58
  // ---------------------------------------------------------------------------
58
59
  // loadConfig
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -70,5 +70,6 @@ export interface Config {
70
70
  active_hours_start: number;
71
71
  active_hours_end: number;
72
72
  max_idle_seconds: number;
73
+ log_path?: string;
73
74
  }
74
75
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,eAAe,GACf,aAAa,GACb,WAAW,GACX,YAAY,GACZ,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,cAAc,CAAC;AAEnB,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAMD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,CAAC;AACjD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAEtD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2BAA2B,EAAE,MAAM,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,eAAe,GACf,aAAa,GACb,WAAW,GACX,YAAY,GACZ,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,cAAc,CAAC;AAEnB,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAMD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,CAAC;AACjD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAEtD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2BAA2B,EAAE,MAAM,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-continuous-learning",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A Pi extension that observes coding sessions and distills patterns into reusable instincts.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Structured logging for the background analyzer CLI.
3
+ * Writes to a configurable log file (default: ~/.pi/continuous-learning/analyzer.log).
4
+ * Each line is a JSON object for easy parsing and grep-ability.
5
+ *
6
+ * Log levels: info, warn, error
7
+ * Never throws - all I/O failures fall back to stderr.
8
+ */
9
+
10
+ import { appendFileSync, mkdirSync, renameSync, statSync } from "node:fs";
11
+ import { dirname } from "node:path";
12
+ import { join } from "node:path";
13
+ import { getBaseDir } from "../storage.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const DEFAULT_LOG_FILENAME = "analyzer.log";
20
+ const MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB - rotate beyond this
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Types
24
+ // ---------------------------------------------------------------------------
25
+
26
+ type LogLevel = "info" | "warn" | "error";
27
+
28
+ interface LogEntry {
29
+ readonly timestamp: string;
30
+ readonly level: LogLevel;
31
+ readonly message: string;
32
+ readonly [key: string]: unknown;
33
+ }
34
+
35
+ export interface ProjectRunStats {
36
+ readonly project_id: string;
37
+ readonly project_name: string;
38
+ readonly duration_ms: number;
39
+ readonly observations_processed: number;
40
+ readonly observations_total: number;
41
+ readonly instincts_created: number;
42
+ readonly instincts_updated: number;
43
+ readonly instincts_deleted: number;
44
+ readonly tokens_input: number;
45
+ readonly tokens_output: number;
46
+ readonly tokens_cache_read: number;
47
+ readonly tokens_cache_write: number;
48
+ readonly tokens_total: number;
49
+ readonly cost_usd: number;
50
+ readonly model: string;
51
+ readonly skipped_reason?: string;
52
+ }
53
+
54
+ export interface RunSummary {
55
+ readonly total_duration_ms: number;
56
+ readonly projects_processed: number;
57
+ readonly projects_skipped: number;
58
+ readonly projects_errored: number;
59
+ readonly projects_total: number;
60
+ readonly total_tokens: number;
61
+ readonly total_cost_usd: number;
62
+ readonly total_instincts_created: number;
63
+ readonly total_instincts_updated: number;
64
+ readonly total_instincts_deleted: number;
65
+ readonly project_stats: readonly ProjectRunStats[];
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Logger
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export class AnalyzeLogger {
73
+ private readonly logPath: string;
74
+
75
+ constructor(logPath?: string) {
76
+ this.logPath = logPath ?? join(getBaseDir(), DEFAULT_LOG_FILENAME);
77
+ this.ensureLogDir();
78
+ }
79
+
80
+ getLogPath(): string {
81
+ return this.logPath;
82
+ }
83
+
84
+ info(message: string, data?: Record<string, unknown>): void {
85
+ this.write("info", message, data);
86
+ }
87
+
88
+ warn(message: string, data?: Record<string, unknown>): void {
89
+ this.write("warn", message, data);
90
+ }
91
+
92
+ error(message: string, error?: unknown, data?: Record<string, unknown>): void {
93
+ const errorData: Record<string, unknown> = { ...data };
94
+ if (error instanceof Error) {
95
+ errorData.error_message = error.message;
96
+ errorData.error_stack = error.stack;
97
+ } else if (error !== undefined) {
98
+ errorData.error_message = String(error);
99
+ }
100
+ this.write("error", message, errorData);
101
+ }
102
+
103
+ /** Log the start of a full analyzer run */
104
+ runStart(projectCount: number): void {
105
+ this.info("Analyzer run started", {
106
+ event: "run_start",
107
+ project_count: projectCount,
108
+ pid: process.pid,
109
+ });
110
+ }
111
+
112
+ /** Log that a project was skipped (with reason) */
113
+ projectSkipped(projectId: string, projectName: string, reason: string): void {
114
+ this.info(`Skipped ${projectName}`, {
115
+ event: "project_skipped",
116
+ project_id: projectId,
117
+ project_name: projectName,
118
+ reason,
119
+ });
120
+ }
121
+
122
+ /** Log the start of a project analysis */
123
+ projectStart(projectId: string, projectName: string, newObservations: number, totalObservations: number): void {
124
+ this.info(`Processing ${projectName}`, {
125
+ event: "project_start",
126
+ project_id: projectId,
127
+ project_name: projectName,
128
+ new_observations: newObservations,
129
+ total_observations: totalObservations,
130
+ });
131
+ }
132
+
133
+ /** Log per-project results after analysis completes */
134
+ projectComplete(stats: ProjectRunStats): void {
135
+ const durationSec = (stats.duration_ms / 1000).toFixed(1);
136
+ const costFormatted = stats.cost_usd.toFixed(4);
137
+ this.info(
138
+ `Completed ${stats.project_name} in ${durationSec}s - ` +
139
+ `tokens: ${stats.tokens_total}, cost: $${costFormatted}, ` +
140
+ `instincts: +${stats.instincts_created} ~${stats.instincts_updated} -${stats.instincts_deleted}`,
141
+ { event: "project_complete", ...stats }
142
+ );
143
+ }
144
+
145
+ /** Log a project that errored during analysis */
146
+ projectError(projectId: string, projectName: string, error: unknown): void {
147
+ this.error(`Error processing ${projectName}`, error, {
148
+ event: "project_error",
149
+ project_id: projectId,
150
+ project_name: projectName,
151
+ });
152
+ }
153
+
154
+ /** Log the full run summary */
155
+ runComplete(summary: RunSummary): void {
156
+ const durationSec = (summary.total_duration_ms / 1000).toFixed(1);
157
+ const costFormatted = summary.total_cost_usd.toFixed(4);
158
+ this.info(
159
+ `Run complete in ${durationSec}s - ` +
160
+ `${summary.projects_processed}/${summary.projects_total} projects processed, ` +
161
+ `${summary.projects_skipped} skipped, ${summary.projects_errored} errored - ` +
162
+ `tokens: ${summary.total_tokens}, cost: $${costFormatted}, ` +
163
+ `instincts: +${summary.total_instincts_created} ~${summary.total_instincts_updated} -${summary.total_instincts_deleted}`,
164
+ { event: "run_complete", ...summary }
165
+ );
166
+ }
167
+
168
+ // -------------------------------------------------------------------------
169
+ // Internal
170
+ // -------------------------------------------------------------------------
171
+
172
+ private ensureLogDir(): void {
173
+ try {
174
+ mkdirSync(dirname(this.logPath), { recursive: true });
175
+ } catch {
176
+ // Best effort
177
+ }
178
+ }
179
+
180
+ private write(level: LogLevel, message: string, data?: Record<string, unknown>): void {
181
+ const entry: LogEntry = {
182
+ timestamp: new Date().toISOString(),
183
+ level,
184
+ message,
185
+ ...data,
186
+ };
187
+
188
+ const line = JSON.stringify(entry) + "\n";
189
+
190
+ try {
191
+ this.rotateIfNeeded();
192
+ appendFileSync(this.logPath, line, "utf-8");
193
+ } catch {
194
+ // Fall back to stderr - never lose log entries entirely
195
+ process.stderr.write(`[analyze] ${line}`);
196
+ }
197
+ }
198
+
199
+ private rotateIfNeeded(): void {
200
+ try {
201
+ const stat = statSync(this.logPath);
202
+ if (stat.size > MAX_LOG_SIZE_BYTES) {
203
+ renameSync(this.logPath, this.logPath + ".old");
204
+ }
205
+ } catch {
206
+ // File doesn't exist yet or stat failed - fine
207
+ }
208
+ }
209
+ }
@@ -37,13 +37,14 @@ import {
37
37
  import { readAgentsMd } from "../agents-md.js";
38
38
  import { homedir } from "node:os";
39
39
  import type { InstalledSkill } from "../types.js";
40
+ import { AnalyzeLogger, type ProjectRunStats, type RunSummary } from "./analyze-logger.js";
40
41
 
41
42
  // ---------------------------------------------------------------------------
42
- // Lockfile guard ensures only one instance runs at a time
43
+ // Lockfile guard - ensures only one instance runs at a time
43
44
  // ---------------------------------------------------------------------------
44
45
 
45
46
  const LOCKFILE_NAME = "analyze.lock";
46
- const LOCK_STALE_MS = 10 * 60 * 1000; // 10 minutes stale lock threshold
47
+ const LOCK_STALE_MS = 10 * 60 * 1000; // 10 minutes - stale lock threshold
47
48
 
48
49
  function getLockfilePath(baseDir: string): string {
49
50
  return join(baseDir, LOCKFILE_NAME);
@@ -64,12 +65,12 @@ function acquireLock(baseDir: string): boolean {
64
65
  if (age < LOCK_STALE_MS) {
65
66
  return false; // Process alive and lock is fresh
66
67
  }
67
- // Process alive but lock is stale treat as abandoned
68
+ // Process alive but lock is stale - treat as abandoned
68
69
  } catch {
69
- // Process is dead lock is orphaned, safe to take over
70
+ // Process is dead - lock is orphaned, safe to take over
70
71
  }
71
72
  } catch {
72
- // Malformed lockfile remove and proceed
73
+ // Malformed lockfile - remove and proceed
73
74
  }
74
75
  }
75
76
 
@@ -86,7 +87,7 @@ function releaseLock(baseDir: string): void {
86
87
  try {
87
88
  if (existsSync(lockPath)) unlinkSync(lockPath);
88
89
  } catch {
89
- // Best effort don't crash on cleanup
90
+ // Best effort - don't crash on cleanup
90
91
  }
91
92
  }
92
93
 
@@ -96,13 +97,79 @@ function releaseLock(baseDir: string): void {
96
97
 
97
98
  const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total
98
99
 
99
- function startGlobalTimeout(timeoutMs: number): void {
100
+ function startGlobalTimeout(timeoutMs: number, logger: AnalyzeLogger): void {
100
101
  setTimeout(() => {
101
- console.error("[analyze] Global timeout reached. Exiting.");
102
+ logger.error("Global timeout reached, forcing exit");
102
103
  process.exit(2);
103
104
  }, timeoutMs).unref();
104
105
  }
105
106
 
107
+ // ---------------------------------------------------------------------------
108
+ // Instinct operation tracking
109
+ // ---------------------------------------------------------------------------
110
+
111
+ interface InstinctOpCounts {
112
+ created: number;
113
+ updated: number;
114
+ deleted: number;
115
+ }
116
+
117
+ /**
118
+ * Wraps instinct tools to count create/update/delete operations.
119
+ * Returns new tool instances that increment the provided counts.
120
+ */
121
+ function wrapInstinctToolsWithTracking(
122
+ projectId: string,
123
+ projectName: string,
124
+ baseDir: string,
125
+ counts: InstinctOpCounts
126
+ ) {
127
+ const writeTool = createInstinctWriteTool(projectId, projectName, baseDir);
128
+ const deleteTool = createInstinctDeleteTool(projectId, baseDir);
129
+
130
+ const trackedWrite = {
131
+ ...writeTool,
132
+ async execute(
133
+ toolCallId: string,
134
+ params: Parameters<typeof writeTool.execute>[1],
135
+ signal: AbortSignal | undefined,
136
+ onUpdate: unknown,
137
+ ctx: unknown
138
+ ) {
139
+ const result = await writeTool.execute(toolCallId, params, signal, onUpdate, ctx);
140
+ const details = result.details as { action?: string } | undefined;
141
+ if (details?.action === "created") {
142
+ counts.created++;
143
+ } else {
144
+ counts.updated++;
145
+ }
146
+ return result;
147
+ },
148
+ };
149
+
150
+ const trackedDelete = {
151
+ ...deleteTool,
152
+ async execute(
153
+ toolCallId: string,
154
+ params: Parameters<typeof deleteTool.execute>[1],
155
+ signal: AbortSignal | undefined,
156
+ onUpdate: unknown,
157
+ ctx: unknown
158
+ ) {
159
+ const result = await deleteTool.execute(toolCallId, params, signal, onUpdate, ctx);
160
+ counts.deleted++;
161
+ return result;
162
+ },
163
+ };
164
+
165
+ return {
166
+ listTool: createInstinctListTool(projectId, baseDir),
167
+ readTool: createInstinctReadTool(projectId, baseDir),
168
+ writeTool: trackedWrite,
169
+ deleteTool: trackedDelete,
170
+ };
171
+ }
172
+
106
173
  // ---------------------------------------------------------------------------
107
174
  // Per-project analysis
108
175
  // ---------------------------------------------------------------------------
@@ -150,27 +217,39 @@ function hasNewObservations(projectId: string, meta: ProjectMeta, baseDir: strin
150
217
  return true;
151
218
  }
152
219
 
220
+ interface AnalyzeResult {
221
+ readonly ran: boolean;
222
+ readonly stats?: ProjectRunStats;
223
+ readonly skippedReason?: string;
224
+ }
225
+
153
226
  async function analyzeProject(
154
227
  project: ProjectEntry,
155
228
  config: ReturnType<typeof loadConfig>,
156
- baseDir: string
157
- ): Promise<boolean> {
229
+ baseDir: string,
230
+ logger: AnalyzeLogger
231
+ ): Promise<AnalyzeResult> {
158
232
  const meta = loadProjectMeta(project.id, baseDir);
159
233
 
160
- if (!hasNewObservations(project.id, meta, baseDir)) return false;
234
+ if (!hasNewObservations(project.id, meta, baseDir)) {
235
+ return { ran: false, skippedReason: "no new observations" };
236
+ }
161
237
 
162
238
  const obsPath = getObservationsPath(project.id, baseDir);
163
239
  const sinceLineCount = meta.last_observation_line_count ?? 0;
164
240
  const { lines: newObsLines, totalLineCount } = tailObservationsSince(obsPath, sinceLineCount);
165
241
 
166
- if (newObsLines.length === 0) return false;
242
+ if (newObsLines.length === 0) {
243
+ return { ran: false, skippedReason: "no new observation lines" };
244
+ }
167
245
 
168
246
  const obsCount = countObservations(project.id, baseDir);
169
- if (obsCount < config.min_observations_to_analyze) return false;
247
+ if (obsCount < config.min_observations_to_analyze) {
248
+ return { ran: false, skippedReason: `below threshold (${obsCount}/${config.min_observations_to_analyze})` };
249
+ }
170
250
 
171
- console.log(
172
- `[analyze] Processing ${project.name} (${project.id}): ${newObsLines.length} new observations (${obsCount} total)`
173
- );
251
+ const startTime = Date.now();
252
+ logger.projectStart(project.id, project.name, newObsLines.length, obsCount);
174
253
 
175
254
  runDecayPass(project.id, baseDir);
176
255
 
@@ -188,7 +267,7 @@ async function analyzeProject(
188
267
  description: s.description,
189
268
  }));
190
269
  } catch {
191
- // Skills loading is best-effort continue without them
270
+ // Skills loading is best-effort - continue without them
192
271
  }
193
272
 
194
273
  const userPrompt = buildAnalyzerUserPrompt(obsPath, instinctsDir, project, {
@@ -203,11 +282,15 @@ async function analyzeProject(
203
282
  const modelId = (config.model || DEFAULT_CONFIG.model) as Parameters<typeof getModel>[1];
204
283
  const model = getModel("anthropic", modelId);
205
284
 
285
+ // Track instinct operations
286
+ const instinctCounts: InstinctOpCounts = { created: 0, updated: 0, deleted: 0 };
287
+ const trackedTools = wrapInstinctToolsWithTracking(project.id, project.name, baseDir, instinctCounts);
288
+
206
289
  const customTools = [
207
- createInstinctListTool(project.id, baseDir),
208
- createInstinctReadTool(project.id, baseDir),
209
- createInstinctWriteTool(project.id, project.name, baseDir),
210
- createInstinctDeleteTool(project.id, baseDir),
290
+ trackedTools.listTool,
291
+ trackedTools.readTool,
292
+ trackedTools.writeTool,
293
+ trackedTools.deleteTool,
211
294
  ];
212
295
 
213
296
  const loader = new DefaultResourceLoader({
@@ -230,13 +313,37 @@ async function analyzeProject(
230
313
  session.dispose();
231
314
  }
232
315
 
316
+ // Collect stats after session completes
317
+ const sessionStats = session.getSessionStats();
318
+ const durationMs = Date.now() - startTime;
319
+
320
+ const stats: ProjectRunStats = {
321
+ project_id: project.id,
322
+ project_name: project.name,
323
+ duration_ms: durationMs,
324
+ observations_processed: newObsLines.length,
325
+ observations_total: obsCount,
326
+ instincts_created: instinctCounts.created,
327
+ instincts_updated: instinctCounts.updated,
328
+ instincts_deleted: instinctCounts.deleted,
329
+ tokens_input: sessionStats.tokens.input,
330
+ tokens_output: sessionStats.tokens.output,
331
+ tokens_cache_read: sessionStats.tokens.cacheRead,
332
+ tokens_cache_write: sessionStats.tokens.cacheWrite,
333
+ tokens_total: sessionStats.tokens.total,
334
+ cost_usd: sessionStats.cost,
335
+ model: modelId,
336
+ };
337
+
338
+ logger.projectComplete(stats);
339
+
233
340
  saveProjectMeta(
234
341
  project.id,
235
342
  { ...meta, last_analyzed_at: new Date().toISOString(), last_observation_line_count: totalLineCount },
236
343
  baseDir
237
344
  );
238
- console.log(`[analyze] Completed ${project.name}`);
239
- return true;
345
+
346
+ return { ran: true, stats };
240
347
  }
241
348
 
242
349
  // ---------------------------------------------------------------------------
@@ -245,35 +352,67 @@ async function analyzeProject(
245
352
 
246
353
  async function main(): Promise<void> {
247
354
  const baseDir = getBaseDir();
355
+ const config = loadConfig();
356
+ const logger = new AnalyzeLogger(config.log_path);
248
357
 
249
358
  if (!acquireLock(baseDir)) {
250
- console.log("[analyze] Another instance is already running. Exiting.");
359
+ logger.info("Another instance is already running, exiting");
251
360
  process.exit(0);
252
361
  }
253
362
 
254
- startGlobalTimeout(DEFAULT_TIMEOUT_MS);
363
+ startGlobalTimeout(DEFAULT_TIMEOUT_MS, logger);
364
+
365
+ const runStart = Date.now();
255
366
 
256
367
  try {
257
- const config = loadConfig();
258
368
  const registry = loadProjectsRegistry(baseDir);
259
369
  const projects = Object.values(registry);
260
370
 
261
371
  if (projects.length === 0) {
262
- console.log("[analyze] No projects registered. Use pi with the continuous-learning extension first.");
372
+ logger.info("No projects registered");
263
373
  return;
264
374
  }
265
375
 
376
+ logger.runStart(projects.length);
377
+
266
378
  let processed = 0;
379
+ let skipped = 0;
380
+ let errored = 0;
381
+ const allProjectStats: ProjectRunStats[] = [];
382
+
267
383
  for (const project of projects) {
268
384
  try {
269
- const didRun = await analyzeProject(project, config, baseDir);
270
- if (didRun) processed++;
385
+ const result = await analyzeProject(project, config, baseDir, logger);
386
+ if (result.ran && result.stats) {
387
+ processed++;
388
+ allProjectStats.push(result.stats);
389
+ } else {
390
+ skipped++;
391
+ if (result.skippedReason) {
392
+ logger.projectSkipped(project.id, project.name, result.skippedReason);
393
+ }
394
+ }
271
395
  } catch (err) {
272
- console.error(`[analyze] Error processing ${project.name}: ${String(err)}`);
396
+ errored++;
397
+ logger.projectError(project.id, project.name, err);
273
398
  }
274
399
  }
275
400
 
276
- console.log(`[analyze] Done. Processed ${processed}/${projects.length} project(s).`);
401
+ const summary: RunSummary = {
402
+ total_duration_ms: Date.now() - runStart,
403
+ projects_processed: processed,
404
+ projects_skipped: skipped,
405
+ projects_errored: errored,
406
+ projects_total: projects.length,
407
+ total_tokens: allProjectStats.reduce((sum, s) => sum + s.tokens_total, 0),
408
+ total_cost_usd: allProjectStats.reduce((sum, s) => sum + s.cost_usd, 0),
409
+ total_instincts_created: allProjectStats.reduce((sum, s) => sum + s.instincts_created, 0),
410
+ total_instincts_updated: allProjectStats.reduce((sum, s) => sum + s.instincts_updated, 0),
411
+ total_instincts_deleted: allProjectStats.reduce((sum, s) => sum + s.instincts_deleted, 0),
412
+ project_stats: allProjectStats,
413
+ };
414
+
415
+ logger.runComplete(summary);
277
416
  } finally {
278
417
  releaseLock(baseDir);
279
418
  }
@@ -281,6 +420,8 @@ async function main(): Promise<void> {
281
420
 
282
421
  main().catch((err) => {
283
422
  releaseLock(getBaseDir());
284
- console.error(`[analyze] Fatal error: ${String(err)}`);
423
+ // Last-resort logging - config may not have loaded
424
+ const logger = new AnalyzeLogger();
425
+ logger.error("Fatal error", err);
285
426
  process.exit(1);
286
427
  });
package/src/config.ts CHANGED
@@ -67,6 +67,7 @@ const PartialConfigSchema = Type.Partial(
67
67
  active_hours_start: Type.Number(),
68
68
  active_hours_end: Type.Number(),
69
69
  max_idle_seconds: Type.Number(),
70
+ log_path: Type.String(),
70
71
  })
71
72
  );
72
73
 
package/src/types.ts CHANGED
@@ -102,4 +102,5 @@ export interface Config {
102
102
  active_hours_start: number; // 0-23
103
103
  active_hours_end: number; // 0-23
104
104
  max_idle_seconds: number;
105
+ log_path?: string; // Override analyzer log location (default: ~/.pi/continuous-learning/analyzer.log)
105
106
  }