pi-hermes-memory 0.7.6 → 0.7.8
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 +80 -33
- package/docs/PUBLISHING.md +1 -1
- package/docs/ROADMAP.md +1 -1
- package/package.json +5 -4
- package/src/constants.ts +7 -3
- package/src/handlers/auto-consolidate.ts +1 -1
- package/src/handlers/background-review.ts +1 -1
- package/src/handlers/correction-detector.ts +1 -1
- package/src/handlers/index-sessions.ts +1 -1
- package/src/handlers/insights.ts +1 -1
- package/src/handlers/interview.ts +1 -1
- package/src/handlers/learn-memory.ts +4 -4
- package/src/handlers/preview-context.ts +5 -15
- package/src/handlers/session-flush.ts +1 -1
- package/src/handlers/skill-auto-trigger.ts +24 -4
- package/src/handlers/skills-command.ts +749 -23
- package/src/handlers/switch-project.ts +1 -1
- package/src/handlers/sync-markdown-memories.ts +1 -1
- package/src/index.ts +58 -27
- package/src/project.ts +13 -0
- package/src/prompt-context.ts +0 -4
- package/src/skills/procedural-skill-creator/SKILL.md +146 -0
- package/src/store/skill-store.ts +600 -181
- package/src/store/skill-utils.ts +116 -0
- package/src/tools/memory-search-tool.ts +2 -2
- package/src/tools/memory-tool.ts +2 -2
- package/src/tools/session-search-tool.ts +2 -2
- package/src/tools/skill-tool.ts +23 -20
- package/src/types.ts +22 -4
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* for a project they're not currently in.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ExtensionAPI } from "@
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import type { MemoryConfig } from "../types.js";
|
|
12
12
|
import * as fs from "node:fs/promises";
|
|
13
13
|
import * as path from "node:path";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@
|
|
8
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { DatabaseManager } from '../store/db.js';
|
|
10
10
|
import {
|
|
11
11
|
parseMarkdownMemoryEntry,
|
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import * as path from "node:path";
|
|
26
26
|
import * as os from "node:os";
|
|
27
|
-
import type { ExtensionAPI } from "@
|
|
27
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
import { MemoryStore } from "./store/memory-store.js";
|
|
29
29
|
import { SkillStore } from "./store/skill-store.js";
|
|
30
30
|
import { DatabaseManager } from "./store/db.js";
|
|
@@ -48,18 +48,59 @@ import { registerLearnMemoryCommand } from "./handlers/learn-memory.js";
|
|
|
48
48
|
import { registerSyncMarkdownMemoriesCommand, syncMarkdownMemoriesToSqlite } from "./handlers/sync-markdown-memories.js";
|
|
49
49
|
import { registerPreviewContextCommand } from "./handlers/preview-context.js";
|
|
50
50
|
import { loadConfig } from "./config.js";
|
|
51
|
-
import { detectProject } from "./project.js";
|
|
51
|
+
import { detectProject, detectProjectSkills } from "./project.js";
|
|
52
52
|
import { buildPromptContext } from "./prompt-context.js";
|
|
53
53
|
import { migrateLegacyProjectMemoryDirs } from "./project-memory-migration.js";
|
|
54
54
|
|
|
55
|
+
export function resolveProjectSkillDiscovery(
|
|
56
|
+
skillStore: SkillStore,
|
|
57
|
+
projectsMemoryDir: string | undefined,
|
|
58
|
+
cwd?: string,
|
|
59
|
+
): { skillPaths: string[] } | undefined {
|
|
60
|
+
const detected = detectProjectSkills(projectsMemoryDir, cwd);
|
|
61
|
+
skillStore.setProjectContext(detected.name, detected.skillsDir);
|
|
62
|
+
if (!detected.skillsDir) return undefined;
|
|
63
|
+
return {
|
|
64
|
+
skillPaths: [detected.skillsDir],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerProjectSkillDiscoveryHandler(
|
|
69
|
+
pi: Pick<ExtensionAPI, "on">,
|
|
70
|
+
skillStore: SkillStore,
|
|
71
|
+
projectsMemoryDir: string | undefined,
|
|
72
|
+
): void {
|
|
73
|
+
pi.on("resources_discover", async (event, _ctx) => {
|
|
74
|
+
return resolveProjectSkillDiscovery(skillStore, projectsMemoryDir, (event as { cwd?: string }).cwd);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
55
78
|
export default function (pi: ExtensionAPI) {
|
|
56
79
|
const config = loadConfig();
|
|
57
80
|
|
|
58
81
|
const globalDir = config.memoryDir ?? path.join(os.homedir(), ".pi", "agent", "memory");
|
|
82
|
+
const agentRoot = path.join(os.homedir(), ".pi", "agent");
|
|
59
83
|
const store = new MemoryStore(config);
|
|
60
|
-
const
|
|
84
|
+
const project = detectProject(config.projectsMemoryDir);
|
|
85
|
+
const projectName = project.name ?? "";
|
|
86
|
+
const skillStore = new SkillStore({
|
|
87
|
+
globalSkillsDir: path.join(agentRoot, "skills"),
|
|
88
|
+
projectSkillsDir: project.memoryDir ? path.join(project.memoryDir, "skills") : null,
|
|
89
|
+
projectName: project.name,
|
|
90
|
+
legacySkillsDir: path.join(globalDir, "skills"),
|
|
91
|
+
migrationSentinelPath: path.join(globalDir, ".skills-migrated-to-pi-native"),
|
|
92
|
+
});
|
|
61
93
|
const dbManager = new DatabaseManager(globalDir);
|
|
62
94
|
|
|
95
|
+
const refreshSkillProjectContext = (cwd?: string) => {
|
|
96
|
+
const resource = resolveProjectSkillDiscovery(skillStore, config.projectsMemoryDir, cwd);
|
|
97
|
+
return {
|
|
98
|
+
name: skillStore.getProjectName(),
|
|
99
|
+
skillsDir: skillStore.getProjectSkillsDir(),
|
|
100
|
+
resource,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
63
104
|
// Keep project memory available for users upgrading from the old
|
|
64
105
|
// ~/.pi/agent/<project>/ layout. This is non-destructive: legacy folders
|
|
65
106
|
// remain in place while entries are copied/merged into projects-memory/.
|
|
@@ -71,24 +112,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
71
112
|
}
|
|
72
113
|
|
|
73
114
|
// Detect project from cwd using shared helper
|
|
74
|
-
const project = detectProject(config.projectsMemoryDir);
|
|
75
|
-
|
|
76
115
|
// Project-scoped store: ~/.pi/agent/<projectsMemoryDir>/<project_name>/
|
|
77
116
|
const projectConfig = project.memoryDir
|
|
78
117
|
? { ...config, memoryCharLimit: config.projectCharLimit, memoryDir: project.memoryDir }
|
|
79
118
|
: { ...config, memoryDir: undefined };
|
|
80
119
|
const projectStore = project.memoryDir ? new MemoryStore(projectConfig) : null;
|
|
81
|
-
const projectName = project.name ?? "";
|
|
82
120
|
|
|
83
121
|
// ── 1. Load memory from disk on session start ──
|
|
84
|
-
pi.on("session_start", async (
|
|
122
|
+
pi.on("session_start", async (event, _ctx) => {
|
|
123
|
+
refreshSkillProjectContext((event as { cwd?: string }).cwd);
|
|
124
|
+
await skillStore.migrateLegacySkills();
|
|
125
|
+
await skillStore.ensureDiscoveredRoots();
|
|
85
126
|
await store.loadFromDisk();
|
|
86
127
|
if (projectStore) await projectStore.loadFromDisk();
|
|
87
128
|
});
|
|
88
129
|
|
|
130
|
+
registerProjectSkillDiscoveryHandler(pi, skillStore, config.projectsMemoryDir);
|
|
131
|
+
|
|
89
132
|
// ── 2. Inject memory policy by default; legacy mode keeps full frozen memory blocks ──
|
|
90
133
|
pi.on("before_agent_start", async (event, _ctx) => {
|
|
91
|
-
const promptContext = await buildPromptContext(config, store, projectStore,
|
|
134
|
+
const promptContext = await buildPromptContext(config, store, projectStore, projectName);
|
|
92
135
|
|
|
93
136
|
if (promptContext) {
|
|
94
137
|
return {
|
|
@@ -128,7 +171,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
128
171
|
registerSwitchProjectCommand(pi, config);
|
|
129
172
|
registerLearnMemoryCommand(pi);
|
|
130
173
|
registerSyncMarkdownMemoriesCommand(pi, dbManager, globalDir, config.projectsMemoryDir);
|
|
131
|
-
registerPreviewContextCommand(pi, store, projectStore,
|
|
174
|
+
registerPreviewContextCommand(pi, store, projectStore, projectName, config);
|
|
132
175
|
|
|
133
176
|
// ── 11. SQLite session search + extended memory ──
|
|
134
177
|
registerSessionSearchTool(pi, dbManager);
|
|
@@ -136,25 +179,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
136
179
|
registerIndexSessionsCommand(pi);
|
|
137
180
|
|
|
138
181
|
// ── 12. Auto-index session on shutdown ──
|
|
139
|
-
pi.on("session_shutdown", async (_event,
|
|
182
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
140
183
|
try {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (fs.existsSync(sessionDir)) {
|
|
148
|
-
// Find the most recent JSONL file (the one we just finished)
|
|
149
|
-
const files = fs.readdirSync(sessionDir)
|
|
150
|
-
.filter((f: string) => f.endsWith(".jsonl"))
|
|
151
|
-
.sort()
|
|
152
|
-
.reverse();
|
|
153
|
-
if (files.length > 0) {
|
|
154
|
-
const sessionData = parseSessionFile(path.join(sessionDir, files[0]));
|
|
155
|
-
if (sessionData) {
|
|
156
|
-
indexSession(dbManager, sessionData);
|
|
157
|
-
}
|
|
184
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
185
|
+
if (sessionFile && require("node:fs").existsSync(sessionFile)) {
|
|
186
|
+
const sessionData = parseSessionFile(sessionFile);
|
|
187
|
+
if (sessionData) {
|
|
188
|
+
indexSession(dbManager, sessionData);
|
|
158
189
|
}
|
|
159
190
|
}
|
|
160
191
|
} catch {
|
package/src/project.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface ProjectInfo {
|
|
|
13
13
|
memoryDir: string | null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface ProjectSkillInfo extends ProjectInfo {
|
|
17
|
+
/** Path to the project-scoped skills directory, or null. */
|
|
18
|
+
skillsDir: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
/**
|
|
17
22
|
* Detect project from the current working directory.
|
|
18
23
|
*
|
|
@@ -42,3 +47,11 @@ export function detectProject(projectsMemoryDir = "projects-memory", cwd?: strin
|
|
|
42
47
|
memoryDir: path.join(homeDir, ".pi", "agent", projectsMemoryDir, name),
|
|
43
48
|
};
|
|
44
49
|
}
|
|
50
|
+
|
|
51
|
+
export function detectProjectSkills(projectsMemoryDir = "projects-memory", cwd?: string): ProjectSkillInfo {
|
|
52
|
+
const project = detectProject(projectsMemoryDir, cwd);
|
|
53
|
+
return {
|
|
54
|
+
...project,
|
|
55
|
+
skillsDir: project.memoryDir ? path.join(project.memoryDir, "skills") : null,
|
|
56
|
+
};
|
|
57
|
+
}
|
package/src/prompt-context.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { MEMORY_POLICY_PROMPT, MEMORY_POLICY_PROMPT_COMPACT } from "./constants.js";
|
|
2
2
|
import type { MemoryConfig } from "./types.js";
|
|
3
3
|
import type { MemoryStore } from "./store/memory-store.js";
|
|
4
|
-
import type { SkillStore } from "./store/skill-store.js";
|
|
5
4
|
|
|
6
5
|
type MemoryPolicyConfig = Pick<MemoryConfig, "memoryPolicyStyle" | "memoryPolicyCustomText">;
|
|
7
6
|
|
|
@@ -27,7 +26,6 @@ export async function buildPromptContext(
|
|
|
27
26
|
config: Pick<MemoryConfig, "memoryMode" | "memoryPolicyStyle" | "memoryPolicyCustomText">,
|
|
28
27
|
store: MemoryStore,
|
|
29
28
|
projectStore: MemoryStore | null,
|
|
30
|
-
skillStore: SkillStore,
|
|
31
29
|
projectName: string,
|
|
32
30
|
): Promise<string> {
|
|
33
31
|
if (config.memoryMode === "policy-only") {
|
|
@@ -35,13 +33,11 @@ export async function buildPromptContext(
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
const memoryBlock = store.formatForSystemPrompt();
|
|
38
|
-
const skillIndex = await skillStore.formatIndexForSystemPrompt();
|
|
39
36
|
const projectBlock = projectStore ? projectStore.formatProjectBlock(projectName) : "";
|
|
40
37
|
|
|
41
38
|
const parts: string[] = [];
|
|
42
39
|
if (memoryBlock) parts.push(memoryBlock);
|
|
43
40
|
if (projectBlock) parts.push(projectBlock);
|
|
44
|
-
if (skillIndex) parts.push(skillIndex);
|
|
45
41
|
|
|
46
42
|
return parts.join("\n\n");
|
|
47
43
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: procedural-skill-creator
|
|
3
|
+
description: Create, patch, or improve a procedural skill using the extension's skill tool. Use when recent work should be captured as a reusable global or project-scoped procedure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Procedural Skill Creator
|
|
7
|
+
|
|
8
|
+
Turn recent work into a durable procedural skill managed by the extension `skill` tool.
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Capture repeatable **how-to workflows** so future agents can execute them reliably.
|
|
13
|
+
|
|
14
|
+
- `create` when no skill covers the workflow.
|
|
15
|
+
- `patch` when an existing skill should absorb new learning.
|
|
16
|
+
- skip extraction when the work is one-off.
|
|
17
|
+
|
|
18
|
+
## Operating Principle
|
|
19
|
+
|
|
20
|
+
Extract from what already happened first (conversation, commands, edits, failures, fixes).
|
|
21
|
+
Ask follow-up questions only if critical details are missing.
|
|
22
|
+
|
|
23
|
+
## Extraction Gate
|
|
24
|
+
|
|
25
|
+
Save a skill only if all checks pass:
|
|
26
|
+
|
|
27
|
+
1. Multi-step workflow (not a single trivial action)
|
|
28
|
+
2. Likely to recur
|
|
29
|
+
3. Includes non-obvious pitfalls/decision points
|
|
30
|
+
4. Can be verified with concrete pass/fail checks
|
|
31
|
+
|
|
32
|
+
If any check fails, respond exactly:
|
|
33
|
+
|
|
34
|
+
`Nothing to extract.`
|
|
35
|
+
|
|
36
|
+
## Scope Decision
|
|
37
|
+
|
|
38
|
+
- `global`: portable across repos/projects
|
|
39
|
+
- `project`: depends on this repo's paths, scripts, architecture, or conventions
|
|
40
|
+
|
|
41
|
+
Heuristic: if repo-specific paths or commands are required, use `project`.
|
|
42
|
+
|
|
43
|
+
## Global Skill De-duplication Protocol
|
|
44
|
+
|
|
45
|
+
Before creating a **global** skill, prevent overlap with existing global skills.
|
|
46
|
+
|
|
47
|
+
1. List skills with `skill(action="view")`.
|
|
48
|
+
2. Filter to `scope=global`.
|
|
49
|
+
3. Compare candidate against existing skills in three passes:
|
|
50
|
+
- **Name pass**: exact slug match or near-name match (same core verb+noun)
|
|
51
|
+
- **Description pass**: same trigger intent / same expected outcome
|
|
52
|
+
- **Procedure pass**: substantially same step sequence
|
|
53
|
+
4. Decide action:
|
|
54
|
+
- **Exact same name/intent** → do **not** create; use `patch`/`edit`
|
|
55
|
+
- **Different name, same intent** → merge into existing skill (patch existing), avoid duplicate
|
|
56
|
+
- **Adjacent but distinct intent** → create new skill with sharper boundary in `When to Use`
|
|
57
|
+
|
|
58
|
+
If uncertain between two similar skills, run a tie-breaker:
|
|
59
|
+
- ask: "Would both skills trigger for the same user prompt and produce the same outcome?"
|
|
60
|
+
- if yes, merge; if no, keep separate and clarify boundaries.
|
|
61
|
+
|
|
62
|
+
Default bias: **merge over duplicate**.
|
|
63
|
+
|
|
64
|
+
## Action Decision
|
|
65
|
+
|
|
66
|
+
- `create`: no existing skill covers the core job
|
|
67
|
+
- `patch`: existing skill exists; improve only changed section(s)
|
|
68
|
+
|
|
69
|
+
Prefer patching over creating overlapping skills.
|
|
70
|
+
|
|
71
|
+
## Workflow
|
|
72
|
+
|
|
73
|
+
1. **Capture intent**
|
|
74
|
+
- What job should this skill enable?
|
|
75
|
+
- When should it trigger?
|
|
76
|
+
- What outcome should it produce?
|
|
77
|
+
2. **Collect evidence from recent work**
|
|
78
|
+
- successful sequence
|
|
79
|
+
- dead ends and corrections
|
|
80
|
+
- verification signals
|
|
81
|
+
3. **Run de-dup check** (required for global scope)
|
|
82
|
+
4. **Decide** `create` / `patch` / `Nothing to extract.`
|
|
83
|
+
5. **Draft or revise** using required sections:
|
|
84
|
+
- `## When to Use`
|
|
85
|
+
- `## Procedure`
|
|
86
|
+
- `## Pitfalls`
|
|
87
|
+
- `## Verification`
|
|
88
|
+
6. **Run a lightweight eval pass** (before saving):
|
|
89
|
+
- one normal case
|
|
90
|
+
- one edge case
|
|
91
|
+
- one near-miss (should *not* use this skill)
|
|
92
|
+
Refine if ambiguous.
|
|
93
|
+
7. **Persist with `skill` tool**
|
|
94
|
+
- create: `name`, `description`, `scope` (always explicit), full body
|
|
95
|
+
- patch: `skill_id`, `section`, section content
|
|
96
|
+
|
|
97
|
+
## Authoring Standards
|
|
98
|
+
|
|
99
|
+
### Name
|
|
100
|
+
|
|
101
|
+
- short kebab-case (`debug-ci-timeouts`, `backfill-flag-rollout`)
|
|
102
|
+
- name the reusable job, not the incident
|
|
103
|
+
|
|
104
|
+
### Description (Trigger Quality)
|
|
105
|
+
|
|
106
|
+
The description is the main trigger signal.
|
|
107
|
+
Include:
|
|
108
|
+
- what it does
|
|
109
|
+
- when to use it
|
|
110
|
+
- nearby phrasing users might use
|
|
111
|
+
|
|
112
|
+
Be explicit enough to avoid under-triggering, without becoming spammy.
|
|
113
|
+
|
|
114
|
+
### Section Quality
|
|
115
|
+
|
|
116
|
+
- **When to Use**: trigger conditions + boundaries
|
|
117
|
+
- **Procedure**: ordered, actionable steps
|
|
118
|
+
- **Pitfalls**: frequent failure modes + prevention
|
|
119
|
+
- **Verification**: concrete checks (tests, logs, files, outputs)
|
|
120
|
+
|
|
121
|
+
## Generalization Guardrails
|
|
122
|
+
|
|
123
|
+
- Don’t overfit to one transcript.
|
|
124
|
+
- Explain *why* key steps matter when non-obvious.
|
|
125
|
+
- Prefer principles + steps over rigid cargo-cult rules.
|
|
126
|
+
- Keep it lean; remove steps that do not change outcomes.
|
|
127
|
+
|
|
128
|
+
## Patch Guidance
|
|
129
|
+
|
|
130
|
+
When patching:
|
|
131
|
+
|
|
132
|
+
- use existing `skill_id`
|
|
133
|
+
- patch only changed section(s)
|
|
134
|
+
- prioritize `Procedure`, `Pitfalls`, `Verification`
|
|
135
|
+
- avoid rewriting unrelated content
|
|
136
|
+
|
|
137
|
+
## Rules
|
|
138
|
+
|
|
139
|
+
- Use `skill` tool only (no direct file writes).
|
|
140
|
+
- Prefer one strong skill over many near-duplicates.
|
|
141
|
+
- Do not store temporary task state, ticket notes, or one-off results.
|
|
142
|
+
|
|
143
|
+
## Completion Standard
|
|
144
|
+
|
|
145
|
+
A saved skill must allow a future agent to execute the workflow with minimal guesswork and clear verification.
|
|
146
|
+
If not, refine before saving.
|