pi-continuous-learning 0.5.1 → 0.7.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 +78 -0
- package/dist/agents-md.d.ts +23 -2
- package/dist/agents-md.d.ts.map +1 -1
- package/dist/agents-md.js +58 -3
- package/dist/agents-md.js.map +1 -1
- package/dist/cli/analyze-single-shot.d.ts +62 -0
- package/dist/cli/analyze-single-shot.d.ts.map +1 -0
- package/dist/cli/analyze-single-shot.js +105 -0
- package/dist/cli/analyze-single-shot.js.map +1 -0
- package/dist/cli/analyze.js +82 -81
- package/dist/cli/analyze.js.map +1 -1
- package/dist/command-scaffold.d.ts +25 -0
- package/dist/command-scaffold.d.ts.map +1 -0
- package/dist/command-scaffold.js +77 -0
- package/dist/command-scaffold.js.map +1 -0
- package/dist/confidence.d.ts.map +1 -1
- package/dist/confidence.js +2 -1
- package/dist/confidence.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -1
- package/dist/graduation.d.ts +63 -0
- package/dist/graduation.d.ts.map +1 -0
- package/dist/graduation.js +155 -0
- package/dist/graduation.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/instinct-cleanup.d.ts +57 -0
- package/dist/instinct-cleanup.d.ts.map +1 -0
- package/dist/instinct-cleanup.js +150 -0
- package/dist/instinct-cleanup.js.map +1 -0
- package/dist/instinct-graduate.d.ts +43 -0
- package/dist/instinct-graduate.d.ts.map +1 -0
- package/dist/instinct-graduate.js +253 -0
- package/dist/instinct-graduate.js.map +1 -0
- package/dist/instinct-parser.d.ts.map +1 -1
- package/dist/instinct-parser.js +12 -0
- package/dist/instinct-parser.js.map +1 -1
- package/dist/instinct-tools.d.ts.map +1 -1
- package/dist/instinct-tools.js +19 -0
- package/dist/instinct-tools.js.map +1 -1
- package/dist/instinct-validator.d.ts +61 -0
- package/dist/instinct-validator.d.ts.map +1 -0
- package/dist/instinct-validator.js +235 -0
- package/dist/instinct-validator.js.map +1 -0
- package/dist/observation-preprocessor.d.ts +26 -0
- package/dist/observation-preprocessor.d.ts.map +1 -0
- package/dist/observation-preprocessor.js +31 -0
- package/dist/observation-preprocessor.js.map +1 -0
- package/dist/prompts/analyzer-system-single-shot.d.ts +6 -0
- package/dist/prompts/analyzer-system-single-shot.d.ts.map +1 -0
- package/dist/prompts/analyzer-system-single-shot.js +164 -0
- package/dist/prompts/analyzer-system-single-shot.js.map +1 -0
- package/dist/prompts/analyzer-user-single-shot.d.ts +22 -0
- package/dist/prompts/analyzer-user-single-shot.d.ts.map +1 -0
- package/dist/prompts/analyzer-user-single-shot.js +53 -0
- package/dist/prompts/analyzer-user-single-shot.js.map +1 -0
- package/dist/prompts/analyzer-user.d.ts +3 -1
- package/dist/prompts/analyzer-user.d.ts.map +1 -1
- package/dist/prompts/analyzer-user.js +20 -7
- package/dist/prompts/analyzer-user.js.map +1 -1
- package/dist/skill-scaffold.d.ts +23 -0
- package/dist/skill-scaffold.d.ts.map +1 -0
- package/dist/skill-scaffold.js +62 -0
- package/dist/skill-scaffold.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents-md.ts +73 -3
- package/src/cli/analyze-single-shot.ts +175 -0
- package/src/cli/analyze.ts +93 -124
- package/src/command-scaffold.ts +105 -0
- package/src/confidence.ts +2 -1
- package/src/config.ts +40 -0
- package/src/graduation.ts +243 -0
- package/src/index.ts +14 -0
- package/src/instinct-cleanup.ts +204 -0
- package/src/instinct-graduate.ts +377 -0
- package/src/instinct-parser.ts +12 -0
- package/src/instinct-tools.ts +26 -0
- package/src/instinct-validator.ts +287 -0
- package/src/observation-preprocessor.ts +48 -0
- package/src/prompts/analyzer-system-single-shot.ts +163 -0
- package/src/prompts/analyzer-user-single-shot.ts +94 -0
- package/src/prompts/analyzer-user.ts +26 -8
- package/src/skill-scaffold.ts +90 -0
- package/src/types.ts +10 -0
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ To analyze observations and create/update instincts, you need to run the analyze
|
|
|
57
57
|
| `/instinct-export` | Export instincts to a JSON file (filterable by scope/domain) |
|
|
58
58
|
| `/instinct-import <path>` | Import instincts from a JSON file |
|
|
59
59
|
| `/instinct-promote [id]` | Promote project instincts to global scope |
|
|
60
|
+
| `/instinct-graduate` | Graduate mature instincts to AGENTS.md, skills, or commands |
|
|
60
61
|
| `/instinct-projects` | List all known projects and their instinct counts |
|
|
61
62
|
|
|
62
63
|
### LLM Tools
|
|
@@ -244,6 +245,46 @@ inactive_count: 12
|
|
|
244
245
|
Always search with grep to find relevant context before editing files.
|
|
245
246
|
```
|
|
246
247
|
|
|
248
|
+
Graduated instincts include additional fields:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
---
|
|
252
|
+
id: grep-before-edit
|
|
253
|
+
# ...other fields...
|
|
254
|
+
graduated_to: agents-md
|
|
255
|
+
graduated_at: "2026-03-27T12:00:00.000Z"
|
|
256
|
+
---
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Instinct Quality Control
|
|
260
|
+
|
|
261
|
+
Every instinct write (from the LLM tools or the background analyzer) is validated and deduplicated before being saved.
|
|
262
|
+
|
|
263
|
+
### Content Validation
|
|
264
|
+
|
|
265
|
+
| Rule | Details |
|
|
266
|
+
|---|---|
|
|
267
|
+
| Non-empty fields | `action` and `trigger` cannot be `undefined`, `null`, `"null"`, `"none"`, or empty |
|
|
268
|
+
| Minimum length | Both fields must be >= 10 characters |
|
|
269
|
+
| Known domain | `domain` must be in the known set: `git`, `testing`, `debugging`, `workflow`, `typescript`, `javascript`, `python`, `go`, `css`, `design`, `security`, `performance`, `documentation`, `react`, `node`, `database`, `api`, `devops`, `architecture`, or `other` |
|
|
270
|
+
| Verb heuristic | `action` should start with an imperative verb - a warning is logged but the write is not rejected |
|
|
271
|
+
|
|
272
|
+
### Semantic Deduplication
|
|
273
|
+
|
|
274
|
+
Before a new instinct is created, a Jaccard similarity check runs against all existing instincts. Tokenize `trigger + action`, compute `|intersection| / |union|`, and block the write if any existing instinct scores >= 0.6.
|
|
275
|
+
|
|
276
|
+
This prevents near-duplicate instincts from accumulating. When a similar instinct exists, the LLM is told to update the existing one instead.
|
|
277
|
+
|
|
278
|
+
### Analyzer Quality Tiers
|
|
279
|
+
|
|
280
|
+
The background analyzer is instructed to classify patterns before recording them:
|
|
281
|
+
|
|
282
|
+
- **Project Conventions** (Tier 1): Project-specific patterns like "use Result<T,E> for errors in this codebase" → record as project-scoped instinct
|
|
283
|
+
- **Workflow Patterns** (Tier 2): Universal multi-step workflows → record as global-scoped instinct
|
|
284
|
+
- **Generic Agent Behavior** (Tier 3): Read-before-edit, clarify-before-implement, check-errors-after-tool-calls → **skip entirely**, these are fundamental behaviors not learned patterns
|
|
285
|
+
|
|
286
|
+
The analyzer also checks AGENTS.md content before creating instincts - if a pattern is already covered by AGENTS.md, it is skipped.
|
|
287
|
+
|
|
247
288
|
## Confidence Scoring
|
|
248
289
|
|
|
249
290
|
Confidence comes from two sources:
|
|
@@ -263,6 +304,39 @@ Confidence comes from two sources:
|
|
|
263
304
|
|
|
264
305
|
This means an instinct observed 20 times but consistently contradicted in practice will lose confidence. Frequency alone doesn't equal correctness.
|
|
265
306
|
|
|
307
|
+
## Instinct Graduation
|
|
308
|
+
|
|
309
|
+
Instincts are designed to be short-lived - they should graduate into permanent knowledge within a few weeks. The graduation pipeline (`/instinct-graduate`) handles this lifecycle:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
Observation -> Instinct (days) -> AGENTS.md / Skill / Command (1-2 weeks)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Graduation Targets
|
|
316
|
+
|
|
317
|
+
| Target | When | What happens |
|
|
318
|
+
|--------|------|--------------|
|
|
319
|
+
| **AGENTS.md** | Single mature instinct | Appended as a guideline entry to your project or global AGENTS.md |
|
|
320
|
+
| **Skill** | 3+ related instincts in the same domain | Scaffolded into a `SKILL.md` file |
|
|
321
|
+
| **Command** | 3+ workflow instincts in the same domain | Scaffolded into a slash command specification |
|
|
322
|
+
|
|
323
|
+
### Maturity Criteria
|
|
324
|
+
|
|
325
|
+
An instinct qualifies for graduation when all of these are met:
|
|
326
|
+
- Age >= 7 days
|
|
327
|
+
- Confidence >= 0.75
|
|
328
|
+
- Confirmed >= 3 times
|
|
329
|
+
- Contradicted <= 1 time
|
|
330
|
+
- Not a duplicate of existing AGENTS.md content
|
|
331
|
+
|
|
332
|
+
### TTL Enforcement
|
|
333
|
+
|
|
334
|
+
Instincts that don't graduate within 28 days are subject to TTL enforcement:
|
|
335
|
+
- **Confidence < 0.3**: Deleted outright
|
|
336
|
+
- **Confidence >= 0.3**: Aggressively decayed (confidence halved, flagged for removal)
|
|
337
|
+
|
|
338
|
+
Graduated instincts are tracked with `graduated_to` and `graduated_at` fields so they aren't left as duplicates of the knowledge they graduated into.
|
|
339
|
+
|
|
266
340
|
## Updating
|
|
267
341
|
|
|
268
342
|
```bash
|
|
@@ -296,12 +370,16 @@ Only include the fields you want to change — missing fields use the defaults a
|
|
|
296
370
|
|
|
297
371
|
| Field | Default | Description |
|
|
298
372
|
|-------|---------|-------------|
|
|
373
|
+
| `run_interval_minutes` | 5 | How often the analyzer is expected to run (informational, used for decay calculations) |
|
|
299
374
|
| `min_observations_to_analyze` | 20 | Minimum observations before analysis triggers |
|
|
300
375
|
| `min_confidence` | 0.5 | Instincts below this are not injected into prompts |
|
|
301
376
|
| `max_instincts` | 20 | Maximum instincts injected per turn |
|
|
302
377
|
| `max_injection_chars` | 4000 | Character budget for the injection block (~1000 tokens) |
|
|
303
378
|
| `model` | `claude-haiku-4-5` | Model for the background analyzer (lightweight models recommended to minimize cost) |
|
|
304
379
|
| `timeout_seconds` | 120 | Per-project timeout for the analyzer LLM session |
|
|
380
|
+
| `active_hours_start` | 8 | Hour (0-23) at which the active observation window starts |
|
|
381
|
+
| `active_hours_end` | 23 | Hour (0-23) at which the active observation window ends |
|
|
382
|
+
| `max_idle_seconds` | 1800 | Seconds of inactivity before a session is considered idle |
|
|
305
383
|
| `log_path` | `~/.pi/continuous-learning/analyzer.log` | Path to the analyzer log file |
|
|
306
384
|
|
|
307
385
|
## Storage
|
package/dist/agents-md.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utility for reading AGENTS.md files.
|
|
3
|
-
* Provides
|
|
2
|
+
* Utility for reading and writing AGENTS.md files.
|
|
3
|
+
* Provides safe wrappers around filesystem access.
|
|
4
4
|
*/
|
|
5
|
+
import type { Instinct } from "./types.js";
|
|
5
6
|
/**
|
|
6
7
|
* Reads an AGENTS.md file and returns its content.
|
|
7
8
|
* Returns null if the file does not exist or cannot be read.
|
|
@@ -9,4 +10,24 @@
|
|
|
9
10
|
* @param filePath - Absolute path to the AGENTS.md file
|
|
10
11
|
*/
|
|
11
12
|
export declare function readAgentsMd(filePath: string): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Formats an instinct as an AGENTS.md section entry.
|
|
15
|
+
* Produces a markdown block with the instinct title as heading and
|
|
16
|
+
* trigger/action as content.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatInstinctAsAgentsMdEntry(instinct: Instinct): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generates a complete AGENTS.md diff showing proposed additions.
|
|
21
|
+
* Returns the full new content that would result from appending the entries.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateAgentsMdDiff(currentContent: string | null, instincts: Instinct[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Appends graduated instinct entries to an AGENTS.md file.
|
|
26
|
+
* Creates the file and parent directories if they don't exist.
|
|
27
|
+
*
|
|
28
|
+
* @param filePath - Absolute path to AGENTS.md
|
|
29
|
+
* @param instincts - Instincts to append as entries
|
|
30
|
+
* @returns The new file content that was written
|
|
31
|
+
*/
|
|
32
|
+
export declare function appendToAgentsMd(filePath: string, instincts: Instinct[]): string;
|
|
12
33
|
//# sourceMappingURL=agents-md.d.ts.map
|
package/dist/agents-md.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../src/agents-md.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D"}
|
|
1
|
+
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../src/agents-md.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAUxE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,SAAS,EAAE,QAAQ,EAAE,GACpB,MAAM,CAmBR;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,QAAQ,EAAE,GACpB,MAAM,CAUR"}
|
package/dist/agents-md.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utility for reading AGENTS.md files.
|
|
3
|
-
* Provides
|
|
2
|
+
* Utility for reading and writing AGENTS.md files.
|
|
3
|
+
* Provides safe wrappers around filesystem access.
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
6
7
|
/**
|
|
7
8
|
* Reads an AGENTS.md file and returns its content.
|
|
8
9
|
* Returns null if the file does not exist or cannot be read.
|
|
@@ -20,4 +21,58 @@ export function readAgentsMd(filePath) {
|
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Formats an instinct as an AGENTS.md section entry.
|
|
26
|
+
* Produces a markdown block with the instinct title as heading and
|
|
27
|
+
* trigger/action as content.
|
|
28
|
+
*/
|
|
29
|
+
export function formatInstinctAsAgentsMdEntry(instinct) {
|
|
30
|
+
const lines = [
|
|
31
|
+
`### ${instinct.title}`,
|
|
32
|
+
"",
|
|
33
|
+
`**When:** ${instinct.trigger}`,
|
|
34
|
+
"",
|
|
35
|
+
instinct.action,
|
|
36
|
+
"",
|
|
37
|
+
];
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generates a complete AGENTS.md diff showing proposed additions.
|
|
42
|
+
* Returns the full new content that would result from appending the entries.
|
|
43
|
+
*/
|
|
44
|
+
export function generateAgentsMdDiff(currentContent, instincts) {
|
|
45
|
+
const entries = instincts.map(formatInstinctAsAgentsMdEntry);
|
|
46
|
+
const graduatedSection = [
|
|
47
|
+
"",
|
|
48
|
+
"## Graduated Instincts",
|
|
49
|
+
"",
|
|
50
|
+
...entries,
|
|
51
|
+
].join("\n");
|
|
52
|
+
if (currentContent === null || currentContent.trim().length === 0) {
|
|
53
|
+
return `# Project Guidelines\n${graduatedSection}\n`;
|
|
54
|
+
}
|
|
55
|
+
// If the section already exists, append to it; otherwise add a new section
|
|
56
|
+
if (currentContent.includes("## Graduated Instincts")) {
|
|
57
|
+
return `${currentContent.trimEnd()}\n\n${entries.join("\n")}\n`;
|
|
58
|
+
}
|
|
59
|
+
return `${currentContent.trimEnd()}\n${graduatedSection}\n`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Appends graduated instinct entries to an AGENTS.md file.
|
|
63
|
+
* Creates the file and parent directories if they don't exist.
|
|
64
|
+
*
|
|
65
|
+
* @param filePath - Absolute path to AGENTS.md
|
|
66
|
+
* @param instincts - Instincts to append as entries
|
|
67
|
+
* @returns The new file content that was written
|
|
68
|
+
*/
|
|
69
|
+
export function appendToAgentsMd(filePath, instincts) {
|
|
70
|
+
if (instincts.length === 0)
|
|
71
|
+
return readAgentsMd(filePath) ?? "";
|
|
72
|
+
const currentContent = readAgentsMd(filePath);
|
|
73
|
+
const newContent = generateAgentsMdDiff(currentContent, instincts);
|
|
74
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
75
|
+
writeFileSync(filePath, newContent, "utf-8");
|
|
76
|
+
return newContent;
|
|
77
|
+
}
|
|
23
78
|
//# sourceMappingURL=agents-md.js.map
|
package/dist/agents-md.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-md.js","sourceRoot":"","sources":["../src/agents-md.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"agents-md.js","sourceRoot":"","sources":["../src/agents-md.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAkB;IAC9D,MAAM,KAAK,GAAG;QACZ,OAAO,QAAQ,CAAC,KAAK,EAAE;QACvB,EAAE;QACF,aAAa,QAAQ,CAAC,OAAO,EAAE;QAC/B,EAAE;QACF,QAAQ,CAAC,MAAM;QACf,EAAE;KACH,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,cAA6B,EAC7B,SAAqB;IAErB,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC7D,MAAM,gBAAgB,GAAG;QACvB,EAAE;QACF,wBAAwB;QACxB,EAAE;QACF,GAAG,OAAO;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,OAAO,yBAAyB,gBAAgB,IAAI,CAAC;IACvD,CAAC;IAED,2EAA2E;IAC3E,IAAI,cAAc,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;QACtD,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAClE,CAAC;IAED,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,gBAAgB,IAAI,CAAC;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,SAAqB;IAErB,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEhE,MAAM,cAAc,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,oBAAoB,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAEnE,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IAE7C,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
export interface InstinctChangePayload {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
trigger: string;
|
|
15
|
+
action: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
domain: string;
|
|
18
|
+
scope: "project" | "global";
|
|
19
|
+
observation_count?: number;
|
|
20
|
+
confirmed_count?: number;
|
|
21
|
+
contradicted_count?: number;
|
|
22
|
+
inactive_count?: number;
|
|
23
|
+
evidence?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface InstinctChange {
|
|
26
|
+
action: "create" | "update" | "delete";
|
|
27
|
+
instinct?: InstinctChangePayload;
|
|
28
|
+
/** For delete: the instinct ID to remove. */
|
|
29
|
+
id?: string;
|
|
30
|
+
/** For delete: the scope to target. */
|
|
31
|
+
scope?: "project" | "global";
|
|
32
|
+
}
|
|
33
|
+
export interface SingleShotResult {
|
|
34
|
+
changes: InstinctChange[];
|
|
35
|
+
message: AssistantMessage;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parses the model's raw text response into an array of InstinctChange.
|
|
39
|
+
* Strips markdown code fences if present. Throws on invalid JSON or schema.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseChanges(raw: string): InstinctChange[];
|
|
42
|
+
/**
|
|
43
|
+
* Builds a full Instinct from a create/update change.
|
|
44
|
+
* Returns null for delete changes, changes with missing instinct data,
|
|
45
|
+
* invalid fields, or semantically duplicate actions.
|
|
46
|
+
*
|
|
47
|
+
* @param change - The change to apply
|
|
48
|
+
* @param existing - The existing instinct with this ID, if any
|
|
49
|
+
* @param projectId - Project ID for scoping
|
|
50
|
+
* @param allInstincts - All current instincts, used for dedup check on creates
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildInstinctFromChange(change: InstinctChange, existing: Instinct | null, projectId: string, allInstincts?: Instinct[]): Instinct | null;
|
|
53
|
+
/**
|
|
54
|
+
* Formats existing instincts as serialized markdown blocks for inline context.
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatInstinctsForPrompt(instincts: Instinct[]): string;
|
|
57
|
+
/**
|
|
58
|
+
* Runs a single complete() call with the provided context.
|
|
59
|
+
* Returns parsed changes and the raw AssistantMessage (for usage stats).
|
|
60
|
+
*/
|
|
61
|
+
export declare function runSingleShot(context: Context, model: Parameters<typeof complete>[0], apiKey: string, signal?: AbortSignal): Promise<SingleShotResult>;
|
|
62
|
+
//# sourceMappingURL=analyze-single-shot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-single-shot.d.ts","sourceRoot":"","sources":["../../src/cli/analyze-single-shot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,MAAM,WAAW,qBAAqB;IACpC,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,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,6CAA6C;IAC7C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CA0B1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,QAAQ,GAAG,IAAI,EACzB,SAAS,EAAE,MAAM,EACjB,YAAY,GAAE,QAAQ,EAAO,GAC5B,QAAQ,GAAG,IAAI,CAgDjB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAKtE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,EACrC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAgB3B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { complete } from "@mariozechner/pi-ai";
|
|
2
|
+
import { serializeInstinct } from "../instinct-parser.js";
|
|
3
|
+
import { validateInstinct, findSimilarInstinct } from "../instinct-validator.js";
|
|
4
|
+
/**
|
|
5
|
+
* Parses the model's raw text response into an array of InstinctChange.
|
|
6
|
+
* Strips markdown code fences if present. Throws on invalid JSON or schema.
|
|
7
|
+
*/
|
|
8
|
+
export function parseChanges(raw) {
|
|
9
|
+
const stripped = raw
|
|
10
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
11
|
+
.replace(/\s*```\s*$/, "")
|
|
12
|
+
.trim();
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = JSON.parse(stripped);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
throw new Error(`Analyzer returned invalid JSON: ${String(e)}\nRaw: ${raw.slice(0, 200)}`);
|
|
19
|
+
}
|
|
20
|
+
if (typeof parsed !== "object" ||
|
|
21
|
+
parsed === null ||
|
|
22
|
+
!Array.isArray(parsed.changes)) {
|
|
23
|
+
throw new Error(`Analyzer response missing 'changes' array. Got: ${JSON.stringify(parsed).slice(0, 200)}`);
|
|
24
|
+
}
|
|
25
|
+
return parsed.changes;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Builds a full Instinct from a create/update change.
|
|
29
|
+
* Returns null for delete changes, changes with missing instinct data,
|
|
30
|
+
* invalid fields, or semantically duplicate actions.
|
|
31
|
+
*
|
|
32
|
+
* @param change - The change to apply
|
|
33
|
+
* @param existing - The existing instinct with this ID, if any
|
|
34
|
+
* @param projectId - Project ID for scoping
|
|
35
|
+
* @param allInstincts - All current instincts, used for dedup check on creates
|
|
36
|
+
*/
|
|
37
|
+
export function buildInstinctFromChange(change, existing, projectId, allInstincts = []) {
|
|
38
|
+
if (change.action === "delete" || !change.instinct) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const payload = change.instinct;
|
|
42
|
+
const validation = validateInstinct({
|
|
43
|
+
action: payload.action,
|
|
44
|
+
trigger: payload.trigger,
|
|
45
|
+
domain: payload.domain,
|
|
46
|
+
});
|
|
47
|
+
if (!validation.valid) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// On create, reject if semantically similar to an existing instinct (skip self on update)
|
|
51
|
+
if (change.action === "create") {
|
|
52
|
+
const similar = findSimilarInstinct({ trigger: payload.trigger, action: payload.action }, allInstincts, payload.id);
|
|
53
|
+
if (similar) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const now = new Date().toISOString();
|
|
58
|
+
return {
|
|
59
|
+
id: payload.id,
|
|
60
|
+
title: payload.title,
|
|
61
|
+
trigger: payload.trigger,
|
|
62
|
+
action: payload.action,
|
|
63
|
+
confidence: Math.max(0.1, Math.min(0.9, payload.confidence)),
|
|
64
|
+
domain: payload.domain,
|
|
65
|
+
scope: payload.scope,
|
|
66
|
+
source: "personal",
|
|
67
|
+
...(payload.scope === "project" ? { project_id: projectId } : {}),
|
|
68
|
+
created_at: existing?.created_at ?? now,
|
|
69
|
+
updated_at: now,
|
|
70
|
+
observation_count: payload.observation_count ?? 1,
|
|
71
|
+
confirmed_count: payload.confirmed_count ?? 0,
|
|
72
|
+
contradicted_count: payload.contradicted_count ?? 0,
|
|
73
|
+
inactive_count: payload.inactive_count ?? 0,
|
|
74
|
+
...(payload.evidence !== undefined ? { evidence: payload.evidence } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Formats existing instincts as serialized markdown blocks for inline context.
|
|
79
|
+
*/
|
|
80
|
+
export function formatInstinctsForPrompt(instincts) {
|
|
81
|
+
if (instincts.length === 0) {
|
|
82
|
+
return "(no existing instincts)";
|
|
83
|
+
}
|
|
84
|
+
return instincts.map((i) => serializeInstinct(i)).join("\n---\n");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Runs a single complete() call with the provided context.
|
|
88
|
+
* Returns parsed changes and the raw AssistantMessage (for usage stats).
|
|
89
|
+
*/
|
|
90
|
+
export async function runSingleShot(context, model, apiKey, signal) {
|
|
91
|
+
const opts = { apiKey };
|
|
92
|
+
if (signal !== undefined)
|
|
93
|
+
opts.signal = signal;
|
|
94
|
+
const message = await complete(model, context, opts);
|
|
95
|
+
const textContent = message.content
|
|
96
|
+
.filter((c) => c.type === "text")
|
|
97
|
+
.map((c) => c.text)
|
|
98
|
+
.join("");
|
|
99
|
+
if (!textContent.trim()) {
|
|
100
|
+
throw new Error("Analyzer returned empty response");
|
|
101
|
+
}
|
|
102
|
+
const changes = parseChanges(textContent);
|
|
103
|
+
return { changes, message };
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=analyze-single-shot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-single-shot.js","sourceRoot":"","sources":["../../src/cli/analyze-single-shot.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/analyze.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync, statSync, writeFileSync, unlinkSync, } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
5
5
|
import { getModel } from "@mariozechner/pi-ai";
|
|
6
6
|
import { loadConfig, DEFAULT_CONFIG } from "../config.js";
|
|
7
|
-
import { getBaseDir, getProjectsRegistryPath, getObservationsPath, getProjectDir, } from "../storage.js";
|
|
7
|
+
import { getBaseDir, getProjectsRegistryPath, getObservationsPath, getProjectDir, getProjectInstinctsDir, getGlobalInstinctsDir, } from "../storage.js";
|
|
8
8
|
import { countObservations } from "../observations.js";
|
|
9
9
|
import { runDecayPass } from "../instinct-decay.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { runCleanupPass } from "../instinct-cleanup.js";
|
|
11
|
+
import { tailObservationsSince } from "../prompts/analyzer-user.js";
|
|
12
|
+
import { buildSingleShotSystemPrompt } from "../prompts/analyzer-system-single-shot.js";
|
|
13
|
+
import { buildSingleShotUserPrompt } from "../prompts/analyzer-user-single-shot.js";
|
|
14
|
+
import { runSingleShot, buildInstinctFromChange, } from "./analyze-single-shot.js";
|
|
15
|
+
import { loadProjectInstincts, loadGlobalInstincts, saveInstinct, } from "../instinct-store.js";
|
|
13
16
|
import { readAgentsMd } from "../agents-md.js";
|
|
14
17
|
import { homedir } from "node:os";
|
|
15
18
|
import { AnalyzeLogger } from "./analyze-logger.js";
|
|
@@ -28,7 +31,6 @@ function acquireLock(baseDir) {
|
|
|
28
31
|
const content = readFileSync(lockPath, "utf-8");
|
|
29
32
|
const lock = JSON.parse(content);
|
|
30
33
|
const age = Date.now() - new Date(lock.started_at).getTime();
|
|
31
|
-
// Check if the owning process is still alive
|
|
32
34
|
try {
|
|
33
35
|
process.kill(lock.pid, 0); // signal 0 = existence check, no actual signal
|
|
34
36
|
if (age < LOCK_STALE_MS) {
|
|
@@ -67,42 +69,6 @@ function startGlobalTimeout(timeoutMs, logger) {
|
|
|
67
69
|
process.exit(2);
|
|
68
70
|
}, timeoutMs).unref();
|
|
69
71
|
}
|
|
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
|
-
}
|
|
106
72
|
function loadProjectsRegistry(baseDir) {
|
|
107
73
|
const path = getProjectsRegistryPath(baseDir);
|
|
108
74
|
if (!existsSync(path))
|
|
@@ -148,18 +114,22 @@ async function analyzeProject(project, config, baseDir, logger) {
|
|
|
148
114
|
}
|
|
149
115
|
const obsPath = getObservationsPath(project.id, baseDir);
|
|
150
116
|
const sinceLineCount = meta.last_observation_line_count ?? 0;
|
|
151
|
-
const { lines: newObsLines, totalLineCount } = tailObservationsSince(obsPath, sinceLineCount);
|
|
117
|
+
const { lines: newObsLines, totalLineCount, rawLineCount } = tailObservationsSince(obsPath, sinceLineCount);
|
|
152
118
|
if (newObsLines.length === 0) {
|
|
153
|
-
return { ran: false, skippedReason: "no new observation lines" };
|
|
119
|
+
return { ran: false, skippedReason: "no new observation lines after preprocessing" };
|
|
154
120
|
}
|
|
155
121
|
const obsCount = countObservations(project.id, baseDir);
|
|
156
122
|
if (obsCount < config.min_observations_to_analyze) {
|
|
157
123
|
return { ran: false, skippedReason: `below threshold (${obsCount}/${config.min_observations_to_analyze})` };
|
|
158
124
|
}
|
|
159
125
|
const startTime = Date.now();
|
|
160
|
-
logger.projectStart(project.id, project.name,
|
|
126
|
+
logger.projectStart(project.id, project.name, rawLineCount, obsCount);
|
|
127
|
+
runCleanupPass(project.id, config, baseDir);
|
|
161
128
|
runDecayPass(project.id, baseDir);
|
|
162
|
-
|
|
129
|
+
// Load current instincts inline - no tool calls needed
|
|
130
|
+
const projectInstincts = loadProjectInstincts(project.id, baseDir);
|
|
131
|
+
const globalInstincts = loadGlobalInstincts(baseDir);
|
|
132
|
+
const allInstincts = [...projectInstincts, ...globalInstincts];
|
|
163
133
|
const agentsMdProject = readAgentsMd(join(project.root, "AGENTS.md"));
|
|
164
134
|
const agentsMdGlobal = readAgentsMd(join(homedir(), ".pi", "agent", "AGENTS.md"));
|
|
165
135
|
let installedSkills = [];
|
|
@@ -174,61 +144,93 @@ async function analyzeProject(project, config, baseDir, logger) {
|
|
|
174
144
|
catch {
|
|
175
145
|
// Skills loading is best-effort - continue without them
|
|
176
146
|
}
|
|
177
|
-
const userPrompt =
|
|
147
|
+
const userPrompt = buildSingleShotUserPrompt(project, allInstincts, newObsLines, {
|
|
178
148
|
agentsMdProject,
|
|
179
149
|
agentsMdGlobal,
|
|
180
150
|
installedSkills,
|
|
181
|
-
observationLines: newObsLines,
|
|
182
151
|
});
|
|
183
152
|
const authStorage = AuthStorage.create();
|
|
184
|
-
const modelRegistry = new ModelRegistry(authStorage);
|
|
185
153
|
const modelId = (config.model || DEFAULT_CONFIG.model);
|
|
186
154
|
const model = getModel("anthropic", modelId);
|
|
187
|
-
|
|
155
|
+
const apiKey = await authStorage.getApiKey("anthropic");
|
|
156
|
+
if (!apiKey) {
|
|
157
|
+
throw new Error("No Anthropic API key configured. Set via auth.json or ANTHROPIC_API_KEY.");
|
|
158
|
+
}
|
|
159
|
+
const context = {
|
|
160
|
+
systemPrompt: buildSingleShotSystemPrompt(),
|
|
161
|
+
messages: [
|
|
162
|
+
{ role: "user", content: userPrompt, timestamp: Date.now() },
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
const timeoutMs = (config.timeout_seconds ?? DEFAULT_CONFIG.timeout_seconds) * 1000;
|
|
166
|
+
const abortController = new AbortController();
|
|
167
|
+
const timeoutHandle = setTimeout(() => abortController.abort(), timeoutMs);
|
|
188
168
|
const instinctCounts = { created: 0, updated: 0, deleted: 0 };
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
trackedTools.readTool,
|
|
193
|
-
trackedTools.writeTool,
|
|
194
|
-
trackedTools.deleteTool,
|
|
195
|
-
];
|
|
196
|
-
const loader = new DefaultResourceLoader({
|
|
197
|
-
systemPromptOverride: () => buildAnalyzerSystemPrompt(),
|
|
198
|
-
});
|
|
199
|
-
await loader.reload();
|
|
200
|
-
const { session } = await createAgentSession({
|
|
201
|
-
model,
|
|
202
|
-
authStorage,
|
|
203
|
-
modelRegistry,
|
|
204
|
-
sessionManager: SessionManager.inMemory(),
|
|
205
|
-
customTools,
|
|
206
|
-
resourceLoader: loader,
|
|
207
|
-
});
|
|
169
|
+
const projectInstinctsDir = getProjectInstinctsDir(project.id, "personal", baseDir);
|
|
170
|
+
const globalInstinctsDir = getGlobalInstinctsDir("personal", baseDir);
|
|
171
|
+
let singleShotMessage;
|
|
208
172
|
try {
|
|
209
|
-
await
|
|
173
|
+
const result = await runSingleShot(context, model, apiKey, abortController.signal);
|
|
174
|
+
singleShotMessage = result.message;
|
|
175
|
+
// Enforce creation rate limit: only the first N create actions per run are applied.
|
|
176
|
+
const maxNewInstincts = config.max_new_instincts_per_run ?? DEFAULT_CONFIG.max_new_instincts_per_run;
|
|
177
|
+
let createsRemaining = maxNewInstincts;
|
|
178
|
+
for (const change of result.changes) {
|
|
179
|
+
if (change.action === "delete") {
|
|
180
|
+
const id = change.id;
|
|
181
|
+
if (!id)
|
|
182
|
+
continue;
|
|
183
|
+
const dir = change.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
184
|
+
const filePath = join(dir, `${id}.md`);
|
|
185
|
+
if (existsSync(filePath)) {
|
|
186
|
+
unlinkSync(filePath);
|
|
187
|
+
instinctCounts.deleted++;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (change.action === "create") {
|
|
191
|
+
if (createsRemaining <= 0)
|
|
192
|
+
continue; // rate limit reached
|
|
193
|
+
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
194
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
195
|
+
if (!instinct)
|
|
196
|
+
continue;
|
|
197
|
+
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
198
|
+
saveInstinct(instinct, dir);
|
|
199
|
+
instinctCounts.created++;
|
|
200
|
+
createsRemaining--;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// update
|
|
204
|
+
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
205
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
206
|
+
if (!instinct)
|
|
207
|
+
continue;
|
|
208
|
+
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
209
|
+
saveInstinct(instinct, dir);
|
|
210
|
+
instinctCounts.updated++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
210
213
|
}
|
|
211
214
|
finally {
|
|
212
|
-
|
|
215
|
+
clearTimeout(timeoutHandle);
|
|
213
216
|
}
|
|
214
|
-
|
|
215
|
-
const sessionStats = session.getSessionStats();
|
|
217
|
+
const usage = singleShotMessage.usage;
|
|
216
218
|
const durationMs = Date.now() - startTime;
|
|
217
219
|
const stats = {
|
|
218
220
|
project_id: project.id,
|
|
219
221
|
project_name: project.name,
|
|
220
222
|
duration_ms: durationMs,
|
|
221
|
-
observations_processed:
|
|
223
|
+
observations_processed: rawLineCount,
|
|
222
224
|
observations_total: obsCount,
|
|
223
225
|
instincts_created: instinctCounts.created,
|
|
224
226
|
instincts_updated: instinctCounts.updated,
|
|
225
227
|
instincts_deleted: instinctCounts.deleted,
|
|
226
|
-
tokens_input:
|
|
227
|
-
tokens_output:
|
|
228
|
-
tokens_cache_read:
|
|
229
|
-
tokens_cache_write:
|
|
230
|
-
tokens_total:
|
|
231
|
-
cost_usd:
|
|
228
|
+
tokens_input: usage.input,
|
|
229
|
+
tokens_output: usage.output,
|
|
230
|
+
tokens_cache_read: usage.cacheRead,
|
|
231
|
+
tokens_cache_write: usage.cacheWrite,
|
|
232
|
+
tokens_total: usage.totalTokens,
|
|
233
|
+
cost_usd: usage.cost.total,
|
|
232
234
|
model: modelId,
|
|
233
235
|
};
|
|
234
236
|
logger.projectComplete(stats);
|
|
@@ -300,7 +302,6 @@ async function main() {
|
|
|
300
302
|
}
|
|
301
303
|
main().catch((err) => {
|
|
302
304
|
releaseLock(getBaseDir());
|
|
303
|
-
// Last-resort logging - config may not have loaded
|
|
304
305
|
const logger = new AnalyzeLogger();
|
|
305
306
|
logger.error("Fatal error", err);
|
|
306
307
|
process.exit(1);
|