pi-continuous-learning 0.5.1 → 0.6.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,22 @@
1
+ /**
2
+ * User prompt builder for the single-shot background analyzer.
3
+ * Includes current instincts inline (no tool calls needed) and filtered observations.
4
+ */
5
+ import type { InstalledSkill, Instinct, ProjectEntry } from "../types.js";
6
+ export interface SingleShotPromptOptions {
7
+ agentsMdProject?: string | null;
8
+ agentsMdGlobal?: string | null;
9
+ installedSkills?: InstalledSkill[];
10
+ }
11
+ /**
12
+ * Builds the user prompt for the single-shot analyzer.
13
+ * Embeds all current instincts inline so the model has full context
14
+ * without making any tool calls.
15
+ *
16
+ * @param project - Project metadata
17
+ * @param existingInstincts - All current instincts (project + global)
18
+ * @param observationLines - Preprocessed observation lines (JSONL strings)
19
+ * @param options - Optional AGENTS.md content and installed skills
20
+ */
21
+ export declare function buildSingleShotUserPrompt(project: ProjectEntry, existingInstincts: Instinct[], observationLines: string[], options?: SingleShotPromptOptions): string;
22
+ //# sourceMappingURL=analyzer-user-single-shot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer-user-single-shot.d.ts","sourceRoot":"","sources":["../../src/prompts/analyzer-user-single-shot.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,YAAY,EACrB,iBAAiB,EAAE,QAAQ,EAAE,EAC7B,gBAAgB,EAAE,MAAM,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CA2DR"}
@@ -0,0 +1,53 @@
1
+ import { formatInstinctsForPrompt } from "../cli/analyze-single-shot.js";
2
+ /**
3
+ * Builds the user prompt for the single-shot analyzer.
4
+ * Embeds all current instincts inline so the model has full context
5
+ * without making any tool calls.
6
+ *
7
+ * @param project - Project metadata
8
+ * @param existingInstincts - All current instincts (project + global)
9
+ * @param observationLines - Preprocessed observation lines (JSONL strings)
10
+ * @param options - Optional AGENTS.md content and installed skills
11
+ */
12
+ export function buildSingleShotUserPrompt(project, existingInstincts, observationLines, options = {}) {
13
+ const { agentsMdProject = null, agentsMdGlobal = null, installedSkills = [] } = options;
14
+ const observationBlock = observationLines.length > 0
15
+ ? observationLines.join("\n")
16
+ : "(no observations recorded yet)";
17
+ const instinctBlock = formatInstinctsForPrompt(existingInstincts);
18
+ const parts = [
19
+ "## Project Context",
20
+ "",
21
+ `project_id: ${project.id}`,
22
+ `project_name: ${project.name}`,
23
+ "",
24
+ "## Existing Instincts",
25
+ "",
26
+ instinctBlock,
27
+ "",
28
+ "## New Observations (preprocessed)",
29
+ "",
30
+ "```",
31
+ observationBlock,
32
+ "```",
33
+ ];
34
+ if (agentsMdProject != null || agentsMdGlobal != null) {
35
+ parts.push("", "## Existing Guidelines", "");
36
+ if (agentsMdProject != null) {
37
+ parts.push("### Project AGENTS.md", "", agentsMdProject, "");
38
+ }
39
+ if (agentsMdGlobal != null) {
40
+ parts.push("### Global AGENTS.md", "", agentsMdGlobal, "");
41
+ }
42
+ }
43
+ if (installedSkills.length > 0) {
44
+ parts.push("", "## Installed Skills", "");
45
+ for (const skill of installedSkills) {
46
+ parts.push(`- **${skill.name}**: ${skill.description}`);
47
+ }
48
+ parts.push("");
49
+ }
50
+ parts.push("", "## Instructions", "", "1. Review the existing instincts above.", "2. Analyze the new observations for patterns per the system prompt rules.", "3. Return a JSON change-set: create new instincts, update existing ones, or delete obsolete ones.", "4. Apply feedback analysis using the active_instincts field in each observation.", "5. Passive confidence decay has already been applied before this analysis.", "", "Return ONLY the JSON object. No prose, no markdown fences.");
51
+ return parts.join("\n");
52
+ }
53
+ //# sourceMappingURL=analyzer-user-single-shot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer-user-single-shot.js","sourceRoot":"","sources":["../../src/prompts/analyzer-user-single-shot.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAQzE;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAAqB,EACrB,iBAA6B,EAC7B,gBAA0B,EAC1B,UAAmC,EAAE;IAErC,MAAM,EAAE,eAAe,GAAG,IAAI,EAAE,cAAc,GAAG,IAAI,EAAE,eAAe,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAExF,MAAM,gBAAgB,GACpB,gBAAgB,CAAC,MAAM,GAAG,CAAC;QACzB,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7B,CAAC,CAAC,gCAAgC,CAAC;IAEvC,MAAM,aAAa,GAAG,wBAAwB,CAAC,iBAAiB,CAAC,CAAC;IAElE,MAAM,KAAK,GAAa;QACtB,oBAAoB;QACpB,EAAE;QACF,eAAe,OAAO,CAAC,EAAE,EAAE;QAC3B,iBAAiB,OAAO,CAAC,IAAI,EAAE;QAC/B,EAAE;QACF,uBAAuB;QACvB,EAAE;QACF,aAAa;QACb,EAAE;QACF,oCAAoC;QACpC,EAAE;QACF,KAAK;QACL,gBAAgB;QAChB,KAAK;KACN,CAAC;IAEF,IAAI,eAAe,IAAI,IAAI,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,wBAAwB,EAAE,EAAE,CAAC,CAAC;QAC7C,IAAI,eAAe,IAAI,IAAI,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,iBAAiB,EACjB,EAAE,EACF,yCAAyC,EACzC,2EAA2E,EAC3E,mGAAmG,EACnG,kFAAkF,EAClF,4EAA4E,EAC5E,EAAE,EACF,4DAA4D,CAC7D,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -15,8 +15,10 @@ export declare function tailObservations(observationsPath: string, maxEntries?:
15
15
  export interface TailSinceResult {
16
16
  lines: string[];
17
17
  totalLineCount: number;
18
+ /** Number of raw new lines before preprocessing. */
19
+ rawLineCount: number;
18
20
  }
19
- export declare function tailObservationsSince(observationsPath: string, sinceLineCount: number, maxEntries?: number): TailSinceResult;
21
+ export declare function tailObservationsSince(observationsPath: string, sinceLineCount: number, maxEntries?: number, preprocess?: boolean): TailSinceResult;
20
22
  export interface AnalyzerUserPromptOptions {
21
23
  agentsMdProject?: string | null;
22
24
  agentsMdGlobal?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer-user.d.ts","sourceRoot":"","sources":["../../src/prompts/analyzer-user.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,gBAAgB,EAAE,MAAM,EACxB,UAAU,SAAmB,GAC5B,MAAM,EAAE,CAUV;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,UAAU,SAAmB,GAC5B,eAAe,CAoBjB;AAED,MAAM,WAAW,yBAAyB;IACxC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,yBAA8B,GACtC,MAAM,CAmER"}
1
+ {"version":3,"file":"analyzer-user.d.ts","sourceRoot":"","sources":["../../src/prompts/analyzer-user.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AAM7E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,gBAAgB,EAAE,MAAM,EACxB,UAAU,SAAmB,GAC5B,MAAM,EAAE,CAUV;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,UAAU,SAAmB,EAC7B,UAAU,UAAO,GAChB,eAAe,CAkCjB;AAED,MAAM,WAAW,yBAAyB;IACxC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,yBAA8B,GACtC,MAAM,CAmER"}
@@ -4,6 +4,7 @@
4
4
  * to locate observations and instinct files for the current project.
5
5
  */
6
6
  import { existsSync, readFileSync } from "node:fs";
7
+ import { preprocessObservations } from "../observation-preprocessor.js";
7
8
  /** Maximum number of observation lines to include in analysis. */
8
9
  const MAX_TAIL_ENTRIES = 500;
9
10
  /**
@@ -24,9 +25,9 @@ export function tailObservations(observationsPath, maxEntries = MAX_TAIL_ENTRIES
24
25
  .filter((l) => l.length > 0);
25
26
  return lines.slice(-maxEntries);
26
27
  }
27
- export function tailObservationsSince(observationsPath, sinceLineCount, maxEntries = MAX_TAIL_ENTRIES) {
28
+ export function tailObservationsSince(observationsPath, sinceLineCount, maxEntries = MAX_TAIL_ENTRIES, preprocess = true) {
28
29
  if (!existsSync(observationsPath)) {
29
- return { lines: [], totalLineCount: 0 };
30
+ return { lines: [], totalLineCount: 0, rawLineCount: 0 };
30
31
  }
31
32
  const content = readFileSync(observationsPath, "utf-8");
32
33
  const allLines = content
@@ -36,11 +37,23 @@ export function tailObservationsSince(observationsPath, sinceLineCount, maxEntri
36
37
  const totalLineCount = allLines.length;
37
38
  // If file was archived/reset (fewer lines than cursor), treat as fresh
38
39
  const effectiveSince = totalLineCount < sinceLineCount ? 0 : sinceLineCount;
39
- const newLines = allLines.slice(effectiveSince);
40
- return {
41
- lines: newLines.slice(-maxEntries),
42
- totalLineCount,
43
- };
40
+ const newLines = allLines.slice(effectiveSince).slice(-maxEntries);
41
+ const rawLineCount = newLines.length;
42
+ if (!preprocess) {
43
+ return { lines: newLines, totalLineCount, rawLineCount };
44
+ }
45
+ const parsed = [];
46
+ for (const line of newLines) {
47
+ try {
48
+ parsed.push(JSON.parse(line));
49
+ }
50
+ catch {
51
+ // skip malformed lines
52
+ }
53
+ }
54
+ const filtered = preprocessObservations(parsed);
55
+ const lines = filtered.map((obs) => JSON.stringify(obs));
56
+ return { lines, totalLineCount, rawLineCount };
44
57
  }
45
58
  /**
46
59
  * Builds the user prompt for the background Haiku analyzer.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer-user.js","sourceRoot":"","sources":["../../src/prompts/analyzer-user.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGnD,kEAAkE;AAClE,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,gBAAwB,EACxB,UAAU,GAAG,gBAAgB;IAE7B,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,OAAO;SAClB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC;AAOD,MAAM,UAAU,qBAAqB,CACnC,gBAAwB,EACxB,cAAsB,EACtB,UAAU,GAAG,gBAAgB;IAE7B,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO;SACrB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE/B,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEvC,uEAAuE;IACvE,MAAM,cAAc,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAC5E,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAEhD,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC;QAClC,cAAc;KACf,CAAC;AACJ,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CACrC,gBAAwB,EACxB,YAAoB,EACpB,OAAqB,EACrB,UAAqC,EAAE;IAEvC,MAAM,EAAE,eAAe,GAAG,IAAI,EAAE,cAAc,GAAG,IAAI,EAAE,eAAe,GAAG,EAAE,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAE1G,MAAM,WAAW,GAAG,gBAAgB,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAC3E,MAAM,gBAAgB,GACpB,WAAW,CAAC,MAAM,GAAG,CAAC;QACpB,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,CAAC,CAAC,gCAAgC,CAAC;IAEvC,MAAM,YAAY,GAAG,gBAAgB;QACnC,CAAC,CAAC,+CAA+C,gBAAgB,GAAG;QACpE,CAAC,CAAC,8BAA8B,gBAAgB,GAAG,CAAC;IAEtD,MAAM,KAAK,GAAa;QACtB,kBAAkB;QAClB,EAAE;QACF,uFAAuF;QACvF,EAAE;QACF,oBAAoB;QACpB,EAAE;QACF,eAAe,OAAO,CAAC,EAAE,EAAE;QAC3B,iBAAiB,OAAO,CAAC,IAAI,EAAE;QAC/B,EAAE;QACF,eAAe;QACf,EAAE;QACF,sBAAsB,gBAAgB,EAAE;QACxC,wBAAwB,YAAY,EAAE;QACtC,EAAE;QACF,kCAAkC,YAAY,GAAG;QACjD,EAAE;QACF,KAAK;QACL,gBAAgB;QAChB,KAAK;KACN,CAAC;IAEF,IAAI,eAAe,IAAI,IAAI,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,wBAAwB,EAAE,EAAE,CAAC,CAAC;QAC7C,IAAI,eAAe,IAAI,IAAI,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,iBAAiB,EACjB,EAAE,EACF,+DAA+D,EAC/D,mFAAmF,EACnF,kFAAkF,EAClF,kFAAkF,EAClF,8DAA8D,EAC9D,EAAE,EACF,qGAAqG,CACtG,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"analyzer-user.js","sourceRoot":"","sources":["../../src/prompts/analyzer-user.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-continuous-learning",
3
- "version": "0.5.1",
3
+ "version": "0.6.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,145 @@
1
+ /**
2
+ * Single-shot (non-agentic) analyzer core.
3
+ *
4
+ * Replaces the multi-turn agentic session with a single complete() call.
5
+ * The model receives all current instincts inline and returns a JSON change-set.
6
+ * Changes are applied client-side, eliminating the ~16x cache-read multiplier.
7
+ */
8
+ import type { AssistantMessage, Context } from "@mariozechner/pi-ai";
9
+ import { complete } from "@mariozechner/pi-ai";
10
+ import type { Instinct } from "../types.js";
11
+ import { serializeInstinct } from "../instinct-parser.js";
12
+
13
+ export interface InstinctChangePayload {
14
+ id: string;
15
+ title: string;
16
+ trigger: string;
17
+ action: string;
18
+ confidence: number;
19
+ domain: string;
20
+ scope: "project" | "global";
21
+ observation_count?: number;
22
+ confirmed_count?: number;
23
+ contradicted_count?: number;
24
+ inactive_count?: number;
25
+ evidence?: string[];
26
+ }
27
+
28
+ export interface InstinctChange {
29
+ action: "create" | "update" | "delete";
30
+ instinct?: InstinctChangePayload;
31
+ /** For delete: the instinct ID to remove. */
32
+ id?: string;
33
+ /** For delete: the scope to target. */
34
+ scope?: "project" | "global";
35
+ }
36
+
37
+ export interface SingleShotResult {
38
+ changes: InstinctChange[];
39
+ message: AssistantMessage;
40
+ }
41
+
42
+ /**
43
+ * Parses the model's raw text response into an array of InstinctChange.
44
+ * Strips markdown code fences if present. Throws on invalid JSON or schema.
45
+ */
46
+ export function parseChanges(raw: string): InstinctChange[] {
47
+ const stripped = raw
48
+ .replace(/^```(?:json)?\s*/i, "")
49
+ .replace(/\s*```\s*$/, "")
50
+ .trim();
51
+
52
+ let parsed: unknown;
53
+ try {
54
+ parsed = JSON.parse(stripped);
55
+ } catch (e) {
56
+ throw new Error(
57
+ `Analyzer returned invalid JSON: ${String(e)}\nRaw: ${raw.slice(0, 200)}`
58
+ );
59
+ }
60
+
61
+ if (
62
+ typeof parsed !== "object" ||
63
+ parsed === null ||
64
+ !Array.isArray((parsed as { changes?: unknown }).changes)
65
+ ) {
66
+ throw new Error(
67
+ `Analyzer response missing 'changes' array. Got: ${JSON.stringify(parsed).slice(0, 200)}`
68
+ );
69
+ }
70
+
71
+ return (parsed as { changes: InstinctChange[] }).changes;
72
+ }
73
+
74
+ /**
75
+ * Builds a full Instinct from a create/update change.
76
+ * Returns null for delete changes or changes with missing instinct data.
77
+ */
78
+ export function buildInstinctFromChange(
79
+ change: InstinctChange,
80
+ existing: Instinct | null,
81
+ projectId: string
82
+ ): Instinct | null {
83
+ if (change.action === "delete" || !change.instinct) {
84
+ return null;
85
+ }
86
+
87
+ const now = new Date().toISOString();
88
+ const payload = change.instinct;
89
+
90
+ return {
91
+ id: payload.id,
92
+ title: payload.title,
93
+ trigger: payload.trigger,
94
+ action: payload.action,
95
+ confidence: Math.max(0.1, Math.min(0.9, payload.confidence)),
96
+ domain: payload.domain,
97
+ scope: payload.scope,
98
+ source: "personal",
99
+ ...(payload.scope === "project" ? { project_id: projectId } : {}),
100
+ created_at: existing?.created_at ?? now,
101
+ updated_at: now,
102
+ observation_count: payload.observation_count ?? 1,
103
+ confirmed_count: payload.confirmed_count ?? 0,
104
+ contradicted_count: payload.contradicted_count ?? 0,
105
+ inactive_count: payload.inactive_count ?? 0,
106
+ ...(payload.evidence !== undefined ? { evidence: payload.evidence } : {}),
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Formats existing instincts as serialized markdown blocks for inline context.
112
+ */
113
+ export function formatInstinctsForPrompt(instincts: Instinct[]): string {
114
+ if (instincts.length === 0) {
115
+ return "(no existing instincts)";
116
+ }
117
+ return instincts.map((i) => serializeInstinct(i)).join("\n---\n");
118
+ }
119
+
120
+ /**
121
+ * Runs a single complete() call with the provided context.
122
+ * Returns parsed changes and the raw AssistantMessage (for usage stats).
123
+ */
124
+ export async function runSingleShot(
125
+ context: Context,
126
+ model: Parameters<typeof complete>[0],
127
+ apiKey: string,
128
+ signal?: AbortSignal
129
+ ): Promise<SingleShotResult> {
130
+ const opts: Parameters<typeof complete>[2] = { apiKey };
131
+ if (signal !== undefined) opts.signal = signal;
132
+ const message = await complete(model, context, opts);
133
+
134
+ const textContent = message.content
135
+ .filter((c) => c.type === "text")
136
+ .map((c) => (c as { type: "text"; text: string }).text)
137
+ .join("");
138
+
139
+ if (!textContent.trim()) {
140
+ throw new Error("Analyzer returned empty response");
141
+ }
142
+
143
+ const changes = parseChanges(textContent);
144
+ return { changes, message };
145
+ }
@@ -7,36 +7,35 @@ import {
7
7
  unlinkSync,
8
8
  } from "node:fs";
9
9
  import { join } from "node:path";
10
- import {
11
- createAgentSession,
12
- SessionManager,
13
- AuthStorage,
14
- ModelRegistry,
15
- DefaultResourceLoader,
16
- } from "@mariozechner/pi-coding-agent";
10
+ import { AuthStorage } from "@mariozechner/pi-coding-agent";
17
11
  import { getModel } from "@mariozechner/pi-ai";
18
12
 
19
13
  import { loadConfig, DEFAULT_CONFIG } from "../config.js";
20
- import type { ProjectEntry } from "../types.js";
14
+ import type { InstalledSkill, ProjectEntry } from "../types.js";
21
15
  import {
22
16
  getBaseDir,
23
17
  getProjectsRegistryPath,
24
18
  getObservationsPath,
25
19
  getProjectDir,
20
+ getProjectInstinctsDir,
21
+ getGlobalInstinctsDir,
26
22
  } from "../storage.js";
27
23
  import { countObservations } from "../observations.js";
28
24
  import { runDecayPass } from "../instinct-decay.js";
29
- import { buildAnalyzerUserPrompt, tailObservationsSince } from "../prompts/analyzer-user.js";
30
- import { buildAnalyzerSystemPrompt } from "./analyze-prompt.js";
25
+ import { tailObservationsSince } from "../prompts/analyzer-user.js";
26
+ import { buildSingleShotSystemPrompt } from "../prompts/analyzer-system-single-shot.js";
27
+ import { buildSingleShotUserPrompt } from "../prompts/analyzer-user-single-shot.js";
28
+ import {
29
+ runSingleShot,
30
+ buildInstinctFromChange,
31
+ } from "./analyze-single-shot.js";
31
32
  import {
32
- createInstinctListTool,
33
- createInstinctReadTool,
34
- createInstinctWriteTool,
35
- createInstinctDeleteTool,
36
- } from "../instinct-tools.js";
33
+ loadProjectInstincts,
34
+ loadGlobalInstincts,
35
+ saveInstinct,
36
+ } from "../instinct-store.js";
37
37
  import { readAgentsMd } from "../agents-md.js";
38
38
  import { homedir } from "node:os";
39
- import type { InstalledSkill } from "../types.js";
40
39
  import { AnalyzeLogger, type ProjectRunStats, type RunSummary } from "./analyze-logger.js";
41
40
 
42
41
  // ---------------------------------------------------------------------------
@@ -59,7 +58,6 @@ function acquireLock(baseDir: string): boolean {
59
58
  const lock = JSON.parse(content) as { pid: number; started_at: string };
60
59
  const age = Date.now() - new Date(lock.started_at).getTime();
61
60
 
62
- // Check if the owning process is still alive
63
61
  try {
64
62
  process.kill(lock.pid, 0); // signal 0 = existence check, no actual signal
65
63
  if (age < LOCK_STALE_MS) {
@@ -104,72 +102,6 @@ function startGlobalTimeout(timeoutMs: number, logger: AnalyzeLogger): void {
104
102
  }, timeoutMs).unref();
105
103
  }
106
104
 
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
-
173
105
  // ---------------------------------------------------------------------------
174
106
  // Per-project analysis
175
107
  // ---------------------------------------------------------------------------
@@ -237,10 +169,13 @@ async function analyzeProject(
237
169
 
238
170
  const obsPath = getObservationsPath(project.id, baseDir);
239
171
  const sinceLineCount = meta.last_observation_line_count ?? 0;
240
- const { lines: newObsLines, totalLineCount } = tailObservationsSince(obsPath, sinceLineCount);
172
+ const { lines: newObsLines, totalLineCount, rawLineCount } = tailObservationsSince(
173
+ obsPath,
174
+ sinceLineCount
175
+ );
241
176
 
242
177
  if (newObsLines.length === 0) {
243
- return { ran: false, skippedReason: "no new observation lines" };
178
+ return { ran: false, skippedReason: "no new observation lines after preprocessing" };
244
179
  }
245
180
 
246
181
  const obsCount = countObservations(project.id, baseDir);
@@ -249,11 +184,14 @@ async function analyzeProject(
249
184
  }
250
185
 
251
186
  const startTime = Date.now();
252
- logger.projectStart(project.id, project.name, newObsLines.length, obsCount);
187
+ logger.projectStart(project.id, project.name, rawLineCount, obsCount);
253
188
 
254
189
  runDecayPass(project.id, baseDir);
255
190
 
256
- const instinctsDir = join(getProjectDir(project.id, baseDir), "instincts", "personal");
191
+ // Load current instincts inline - no tool calls needed
192
+ const projectInstincts = loadProjectInstincts(project.id, baseDir);
193
+ const globalInstincts = loadGlobalInstincts(baseDir);
194
+ const allInstincts = [...projectInstincts, ...globalInstincts];
257
195
 
258
196
  const agentsMdProject = readAgentsMd(join(project.root, "AGENTS.md"));
259
197
  const agentsMdGlobal = readAgentsMd(join(homedir(), ".pi", "agent", "AGENTS.md"));
@@ -270,68 +208,89 @@ async function analyzeProject(
270
208
  // Skills loading is best-effort - continue without them
271
209
  }
272
210
 
273
- const userPrompt = buildAnalyzerUserPrompt(obsPath, instinctsDir, project, {
211
+ const userPrompt = buildSingleShotUserPrompt(project, allInstincts, newObsLines, {
274
212
  agentsMdProject,
275
213
  agentsMdGlobal,
276
214
  installedSkills,
277
- observationLines: newObsLines,
278
215
  });
279
216
 
280
217
  const authStorage = AuthStorage.create();
281
- const modelRegistry = new ModelRegistry(authStorage);
282
218
  const modelId = (config.model || DEFAULT_CONFIG.model) as Parameters<typeof getModel>[1];
283
219
  const model = getModel("anthropic", modelId);
220
+ const apiKey = await authStorage.getApiKey("anthropic");
221
+
222
+ if (!apiKey) {
223
+ throw new Error("No Anthropic API key configured. Set via auth.json or ANTHROPIC_API_KEY.");
224
+ }
284
225
 
285
- // Track instinct operations
286
- const instinctCounts: InstinctOpCounts = { created: 0, updated: 0, deleted: 0 };
287
- const trackedTools = wrapInstinctToolsWithTracking(project.id, project.name, baseDir, instinctCounts);
226
+ const context = {
227
+ systemPrompt: buildSingleShotSystemPrompt(),
228
+ messages: [
229
+ { role: "user" as const, content: userPrompt, timestamp: Date.now() },
230
+ ],
231
+ };
288
232
 
289
- const customTools = [
290
- trackedTools.listTool,
291
- trackedTools.readTool,
292
- trackedTools.writeTool,
293
- trackedTools.deleteTool,
294
- ];
233
+ const timeoutMs = (config.timeout_seconds ?? DEFAULT_CONFIG.timeout_seconds) * 1000;
234
+ const abortController = new AbortController();
235
+ const timeoutHandle = setTimeout(() => abortController.abort(), timeoutMs);
295
236
 
296
- const loader = new DefaultResourceLoader({
297
- systemPromptOverride: () => buildAnalyzerSystemPrompt(),
298
- });
299
- await loader.reload();
300
-
301
- const { session } = await createAgentSession({
302
- model,
303
- authStorage,
304
- modelRegistry,
305
- sessionManager: SessionManager.inMemory(),
306
- customTools,
307
- resourceLoader: loader,
308
- });
237
+ const instinctCounts = { created: 0, updated: 0, deleted: 0 };
238
+ const projectInstinctsDir = getProjectInstinctsDir(project.id, "personal", baseDir);
239
+ const globalInstinctsDir = getGlobalInstinctsDir("personal", baseDir);
309
240
 
241
+ let singleShotMessage;
310
242
  try {
311
- await session.prompt(userPrompt);
243
+ const result = await runSingleShot(context, model, apiKey, abortController.signal);
244
+ singleShotMessage = result.message;
245
+
246
+ for (const change of result.changes) {
247
+ if (change.action === "delete") {
248
+ const id = change.id;
249
+ if (!id) continue;
250
+ const dir = change.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
251
+ const filePath = join(dir, `${id}.md`);
252
+ if (existsSync(filePath)) {
253
+ unlinkSync(filePath);
254
+ instinctCounts.deleted++;
255
+ }
256
+ } else {
257
+ // create or update
258
+ const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
259
+ const instinct = buildInstinctFromChange(change, existing, project.id);
260
+ if (!instinct) continue;
261
+
262
+ const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
263
+ saveInstinct(instinct, dir);
264
+
265
+ if (change.action === "create") {
266
+ instinctCounts.created++;
267
+ } else {
268
+ instinctCounts.updated++;
269
+ }
270
+ }
271
+ }
312
272
  } finally {
313
- session.dispose();
273
+ clearTimeout(timeoutHandle);
314
274
  }
315
275
 
316
- // Collect stats after session completes
317
- const sessionStats = session.getSessionStats();
276
+ const usage = singleShotMessage!.usage;
318
277
  const durationMs = Date.now() - startTime;
319
278
 
320
279
  const stats: ProjectRunStats = {
321
280
  project_id: project.id,
322
281
  project_name: project.name,
323
282
  duration_ms: durationMs,
324
- observations_processed: newObsLines.length,
283
+ observations_processed: rawLineCount,
325
284
  observations_total: obsCount,
326
285
  instincts_created: instinctCounts.created,
327
286
  instincts_updated: instinctCounts.updated,
328
287
  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,
288
+ tokens_input: usage.input,
289
+ tokens_output: usage.output,
290
+ tokens_cache_read: usage.cacheRead,
291
+ tokens_cache_write: usage.cacheWrite,
292
+ tokens_total: usage.totalTokens,
293
+ cost_usd: usage.cost.total,
335
294
  model: modelId,
336
295
  };
337
296
 
@@ -420,7 +379,6 @@ async function main(): Promise<void> {
420
379
 
421
380
  main().catch((err) => {
422
381
  releaseLock(getBaseDir());
423
- // Last-resort logging - config may not have loaded
424
382
  const logger = new AnalyzeLogger();
425
383
  logger.error("Fatal error", err);
426
384
  process.exit(1);