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.
@@ -7,7 +7,7 @@
7
7
  * for a project they're not currently in.
8
8
  */
9
9
 
10
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
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 "@mariozechner/pi-coding-agent";
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 "@mariozechner/pi-coding-agent";
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 skillStore = new SkillStore(path.join(globalDir, "skills"));
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 (_event, _ctx) => {
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, skillStore, projectName);
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, skillStore, projectName, config);
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, _ctx) => {
182
+ pi.on("session_shutdown", async (_event, ctx) => {
140
183
  try {
141
- const fs = require("node:fs");
142
- const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");
143
- const cwd = process.cwd();
144
- const encodedCwd = cwd.replace(/\//g, "-");
145
- const sessionDir = path.join(sessionsDir, encodedCwd);
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
+ }
@@ -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.